Documentation
¶
Overview ¶
Package enroll builds MDM enrollment profiles and serves the over-the-air Profile Service protocol.
Design ¶
The builder combines MDM settings, a SCEP, ACME or pre-issued PKCS #12 identity, and optional trust anchors, then validates against generated schema types. Profile composition/signing uses mdmprotocol/profile and certificate issuance uses the PKI packages.
OTAService distinguishes device-signed and enrollment-identity-signed protocol phases and calls injected authorization and profile callbacks. Automated Device Enrollment and account-driven enrollment have separate handler packages. Callers provide admission policy, trust roots and stable profile identifiers.
References ¶
- Decision record 0009: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0009-enrollment-profiles.md
- Decision record 0010: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/research/decisions/0010-ota-profile-service.md
- Threat model: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/security/threat-model.md (OTA profile-service and Enrollment profile rows)
- End-to-end scenarios: https://github.com/deploymenttheory/go-apple-dm/blob/main/docs/testing/e2e-scenarios.md (E2E-006, E2E-016)
- Apple: https://developer.apple.com/documentation/devicemanagement/mdm
- Apple: https://developer.apple.com/documentation/devicemanagement/scep
- Apple: https://developer.apple.com/documentation/devicemanagement/deploying-device-management-enrollment-profiles
- Apple: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/
- Schema: third_party/device-management/mdm/profiles/TopLevel.yaml, CommonPayloadKeys.yaml
- Schema: third_party/device-management/mdm/profiles/com.apple.mdm.yaml, com.apple.security.scep.yaml, com.apple.security.root.yaml
Index ¶
Constants ¶
const ( CapabilityPerUserConnections = "com.apple.mdm.per-user-connections" CapabilityBootstrapToken = "com.apple.mdm.bootstraptoken" // #nosec G101 -- capability identifier CapabilityToken = "com.apple.mdm.token" // #nosec G101 -- capability identifier )
ServerCapabilities values: Apple's capability identifiers for the enrollment profile, not credentials.
const ( // KeyTypeEC is Apple's name for an elliptic curve key on a NIST prime // curve. It is the only type a hardware bound key may have. KeyTypeEC = "ECSECPrimeRandom" // KeyTypeRSA can only be used for a key that is not hardware bound, and // so cannot be attested. KeyTypeRSA = "RSA" )
PKCS12 describes a pre-issued identity. Key types Apple's ACME payload accepts.
const ( AttrUDID = "UDID" AttrVersion = "VERSION" AttrProduct = "PRODUCT" AttrSerial = "SERIAL" AttrIMEI = "IMEI" AttrMEID = "MEID" AttrICCID = "ICCID" )
Device attribute names a Profile Service profile may request.
const AttrChallenge = "CHALLENGE"
AttrChallenge is the key the device echoes the profile's Challenge back under. It is not a device attribute a profile requests, so it is not part of DefaultDeviceAttributes, but it is read from the same dictionary.
const ContentTypeProfile = "application/x-apple-aspen-config"
ContentTypeProfile is the response content type for a profile.
const PayloadTypeProfileService = "Profile Service"
PayloadTypeProfileService is the payload type of the initial OTA profile.
Variables ¶
var DefaultDeviceAttributes = []string{AttrUDID, AttrVersion, AttrProduct, AttrSerial}
DefaultDeviceAttributes is a sensible request list.
var ErrOTA = errors.New("enroll: ota")
ErrOTA is returned by the OTA service.
var ErrProfile = errors.New("enroll: profile")
ErrProfile is returned for an enrollment profile that cannot be built or read.
Functions ¶
func NameFromSubject ¶
NameFromSubject is the inverse of SubjectFromName.
func SubjectFromName ¶
SubjectFromName renders a name in the SCEP payload's array-of-arrays form: [[["CN","value"]], [["O","value"]], ...].
Types ¶
type ACME ¶
type ACME struct {
// DirectoryURL is the ACME directory, which must use https.
DirectoryURL string
// ClientIdentifier is what the device orders with, as the
// permanent-identifier. Apple treats it as an anti-replay code, so it
// should be unguessable and issued for one device.
ClientIdentifier string
// KeyType is KeyTypeEC or KeyTypeRSA, and KeySize is the curve size or
// the modulus size. Apple requires both.
KeyType string
KeySize int64
// HardwareBound generates the key in the Secure Enclave, where it
// cannot be exported. Required for Attest.
HardwareBound bool
// Attest asks the device for an attestation of the key and of the
// hardware, which the ACME server verifies. Requires HardwareBound.
Attest bool
// Subject, SubjectAltName, UsageFlags and ExtendedKeyUsage express the device's
// requested certificate properties. Apple permits the server to override them;
// the reference server selects the subject.
Subject pkix.Name
SubjectAltName *profiles.ACMECertificateSubjectAltName
UsageFlags *int64
ExtendedKeyUsage []string
// KeyIsExtractable and AllowAllAppsAccess are macOS only.
KeyIsExtractable *bool
AllowAllAppsAccess *bool
}
ACME configures the com.apple.security.acme identity payload. The device generates a key and requests a certificate from the selected ACME server. HardwareBound and Attest select Secure Enclave key generation and Managed Device Attestation when supported.
The client identifier authorizes an issuance attempt and must be protected. An attested key and verified device properties still require the server's admission policy.
type AccessRights ¶
type AccessRights int64
AccessRights is the MDM payload AccessRights bit mask.
const ( RightInspectProfiles AccessRights = 1 RightInstallProfiles AccessRights = 2 RightLockAndPasscode AccessRights = 4 RightErase AccessRights = 8 RightQueryDeviceInfo AccessRights = 16 RightQueryNetworkInfo AccessRights = 32 RightInspectProvisioning AccessRights = 64 RightInstallProvisioning AccessRights = 128 RightInspectApps AccessRights = 256 RightQueryRestrictions AccessRights = 512 RightQuerySecurity AccessRights = 1024 RightManipulateSettings AccessRights = 2048 RightManageApps AccessRights = 4096 AccessRightsAll AccessRights = 8191 )
AccessRights bits, from the MDM payload documentation.
func (AccessRights) Has ¶
func (a AccessRights) Has(r AccessRights) bool
Has reports whether every bit in r is set.
type DeviceAttributes ¶
type DeviceAttributes struct {
UDID, Version, Product, Serial, IMEI, MEID, ICCID string
Challenge string
// Raw keeps every key received.
Raw map[string]any
}
DeviceAttributes is what the device signs and sends.
type OTAProfile ¶
type OTAProfile struct {
Identifier string
DisplayName string
Organization string
Description string
UUID string
PayloadUUID string
// URL the device POSTs its attributes to.
URL string
// Challenge is echoed back by the device in phase 1.
Challenge string
DeviceAttributes []string
}
OTAProfile builds the Profile Service profile the device installs first.
type OTARequest ¶
type OTARequest struct {
Phase Phase
Attributes DeviceAttributes
Signer *x509.Certificate
}
OTARequest is a verified request.
type OTAService ¶
type OTAService struct {
// DeviceRoots verify phase 1 (the Apple iPhone Device CA chain).
DeviceRoots *x509.CertPool
// IdentityRoots verify phase 2 (the CA behind the SCEP endpoint).
IdentityRoots *x509.CertPool
// ClockSkew for signing-time checks; default 5 minutes.
ClockSkew time.Duration
// Now for verification; default time.Now.
Now func() time.Time
// Authorize vets a verified request: challenge, allow-lists. Nil allows.
Authorize func(ctx context.Context, r *OTARequest) error
// Profile returns the profile bytes for the phase: the SCEP-bearing
// profile for phase 1, the MDM enrollment profile for phase 2.
Profile func(ctx context.Context, r *OTARequest) ([]byte, error)
// Logger defaults to slog.Default.
Logger *slog.Logger
// MaxBytes bounds the request body; default 64 KiB.
MaxBytes int64
}
OTAService serves the profile-service URL.
func (*OTAService) Handler ¶
func (s *OTAService) Handler() http.Handler
Handler serves POST requests from devices.
func (*OTAService) Verify ¶
func (s *OTAService) Verify(body []byte) (*OTARequest, error)
Verify checks the signed body and classifies the phase.
type Phase ¶
type Phase int
Phase of the OTA flow, derived from which CA the request signature chains to.
type Profile ¶
type Profile struct {
Identifier string // top-level PayloadIdentifier, e.g. com.example.mdm
DisplayName string
Description string
Organization string
Topic string
ServerURL string
CheckInURL string
// Exactly one identity source.
SCEP *SCEP
ACME *ACME
PKCS12 *PKCS12
// Roots are installed as com.apple.security.root payloads so the device
// trusts the MDM server and SCEP CA.
Roots []*x509.Certificate
AccessRights AccessRights // default AccessRightsAll
ServerCapabilities []string
// SharedIPad marks a profile for Shared iPad (DEP is_multi_user):
// Apple requires com.apple.mdm.per-user-connections in
// ServerCapabilities, which Build adds when absent (decision record 0029).
SignMessage *bool // default true
CheckOutWhenRemoved bool
UseDevelopmentAPNS bool
// Account-driven and user-enrollment keys.
AssignedManagedAppleID string
EnrollmentMode string
// UUIDs make the profile stable across rebuilds; empty values are
// generated and reported back through Built.
UUID string
MDMUUID string
IdentityUUID string
RootUUIDs []string
// Target for schema validation; the zero value skips OS checks.
Target support.Target
}
Profile is the input to Build.
func Parse ¶
func Parse(data []byte, o profile.ParseOptions) (*Profile, error)
Parse reads an enrollment profile (signed or not) back into Profile so a client can follow it.
type SCEP ¶
type SCEP struct {
KeyIsExtractable *bool
AllowAllAppsAccess *bool
URL string
Name string
Challenge string
Subject pkix.Name
// KeySize default 2048; KeyUsage default 5 (signing and encryption).
KeySize int64
KeyUsage int64
CAFingerprint []byte
Retries int64
RetryDelay int64
}
SCEP describes the SCEP identity payload.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package accountdriven implements account-driven Device Enrollment and account-driven User Enrollment authentication.
|
Package accountdriven implements account-driven Device Enrollment and account-driven User Enrollment authentication. |
|
Package ade serves Automated Device Enrollment profiles after parsing and verifying signed MachineInfo.
|
Package ade serves Automated Device Enrollment profiles after parsing and verifying signed MachineInfo. |
|
Package adetest constructs signed MachineInfo and its request carriers for Automated Device Enrollment tests.
|
Package adetest constructs signed MachineInfo and its request carriers for Automated Device Enrollment tests. |
|
Package discovery serves account-driven enrollment service discovery at /.well-known/com.apple.remotemanagement.
|
Package discovery serves account-driven enrollment service discovery at /.well-known/com.apple.remotemanagement. |
|
Package webauth implements an OpenID Connect relying party for enrollment browser authentication.
|
Package webauth implements an OpenID Connect relying party for enrollment browser authentication. |
|
webauthtest
Package webauthtest supplies an OpenID Connect provider and browser-flow helpers for enrollment tests.
|
Package webauthtest supplies an OpenID Connect provider and browser-flow helpers for enrollment tests. |