fleet/server/mdm/android/onc.go
Victor Lyuboslavsky 36ad83f611
Android Wi-Fi profile withheld until cert installed on device (#42877)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42405

Demo video: https://www.youtube.com/watch?v=F3nfFvwdj-c

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Android Wi‑Fi configuration profiles that reference client
certificates are withheld until the certificate is installed or reaches
a terminal state.
* Host OS settings now show the specific pending reason in the detail
column when Android profiles are waiting on certificate installation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-07 16:26:09 -05:00

81 lines
2.7 KiB
Go

package android
import "encoding/json"
// ONC structs. Minimal types for extracting certificate alias references from
// Android's openNetworkConfiguration policy field (Chrome OS ONC spec).
type oncConfig struct {
NetworkConfigurations []oncNetworkConfiguration `json:"NetworkConfigurations"`
}
type oncNetworkConfiguration struct {
WiFi *oncEAPWrapper `json:"WiFi,omitempty"`
Ethernet *oncEAPWrapper `json:"Ethernet,omitempty"`
VPN *oncCertKeyHolder `json:"VPN,omitempty"`
}
// oncEAPWrapper is shared by WiFi and Ethernet, which nest the cert alias inside an EAP sub-object.
type oncEAPWrapper struct {
EAP *oncCertKeyHolder `json:"EAP,omitempty"`
}
// oncCertKeyHolder holds certificate client auth fields. ClientCertKeyPairAlias is only
// meaningful when ClientCertType is "KeyPairAlias" per the ONC spec; otherwise it is ignored.
type oncCertKeyHolder struct {
ClientCertType string `json:"ClientCertType,omitempty"`
ClientCertKeyPairAlias string `json:"ClientCertKeyPairAlias,omitempty"`
}
// extractAlias returns the ClientCertKeyPairAlias only when ClientCertType is "KeyPairAlias".
// Per the ONC spec, the alias field is ignored for all other ClientCertType values.
func extractAlias(h *oncCertKeyHolder) string {
if h.ClientCertType == "KeyPairAlias" && h.ClientCertKeyPairAlias != "" {
return h.ClientCertKeyPairAlias
}
return ""
}
// ExtractCertAliasesFromONC parses an openNetworkConfiguration JSON blob
// and returns all ClientCertKeyPairAlias values found.
func ExtractCertAliasesFromONC(oncJSON json.RawMessage) ([]string, error) {
var onc oncConfig
if err := json.Unmarshal(oncJSON, &onc); err != nil {
return nil, err
}
var aliases []string
for _, nc := range onc.NetworkConfigurations {
if nc.WiFi != nil && nc.WiFi.EAP != nil {
if a := extractAlias(nc.WiFi.EAP); a != "" {
aliases = append(aliases, a)
}
}
if nc.Ethernet != nil && nc.Ethernet.EAP != nil {
if a := extractAlias(nc.Ethernet.EAP); a != "" {
aliases = append(aliases, a)
}
}
if nc.VPN != nil {
if a := extractAlias(nc.VPN); a != "" {
aliases = append(aliases, a)
}
}
}
return aliases, nil
}
// ExtractCertAliasesFromProfileJSON parses an Android profile's raw JSON
// and returns any ClientCertKeyPairAlias values found in its
// openNetworkConfiguration field. Returns nil if no ONC field exists.
func ExtractCertAliasesFromProfileJSON(profileJSON json.RawMessage) ([]string, error) {
var fields map[string]json.RawMessage
if err := json.Unmarshal(profileJSON, &fields); err != nil {
return nil, err
}
oncJSON, hasONC := fields["openNetworkConfiguration"]
if !hasONC {
return nil, nil
}
return ExtractCertAliasesFromONC(oncJSON)
}