diff --git a/go.mod b/go.mod index 61c4fa73..299770ce 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ require ( github.com/boltdb/bolt v1.3.1 github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 github.com/go-kit/kit v0.7.0 - github.com/google/uuid v1.1.1 + github.com/google/uuid v1.1.2 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.6.2 github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5 + github.com/jessepeterson/cfgprofiles v0.2.0 github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0 github.com/korylprince/go-macos-pkg v1.3.5 diff --git a/go.sum b/go.sum index ec91e129..85337a78 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -36,20 +36,17 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= -github.com/groob/plist v0.0.0-20180203051248-dd56909aee38 h1:afbUddvIjPRC7XHHgeSTRfzZtIxEsSl4VCxumLBGDJU= -github.com/groob/plist v0.0.0-20180203051248-dd56909aee38/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc= +github.com/groob/plist v0.0.0-20190114192801-a99fbe489d03/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc= github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5 h1:saaSiB25B1wgaxrshQhurfPKUGJ4It3OxNJUy0rdOjU= github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw= +github.com/jessepeterson/cfgprofiles v0.2.0 h1:yuAWfY5X0MZfWA1yGppnqIJr5zXziHSlutDKv5ST/wg= +github.com/jessepeterson/cfgprofiles v0.2.0/go.mod h1:bdPSaKnOBjiToJhSVaF2+0N3iSMvugICiC7kguXkD14= github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE= github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0 h1:8UB0NJBoB/lN2O9zG9+U2klyYE0UhvN2Zrn7HBjhwNY= github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0/go.mod h1:N3Yv8okDVC/5qZhPA9uxVYRfkp4mD2vrlQiSCWlNCpg= -github.com/korylprince/go-cpio-odc v0.9.3 h1:MGpo1PcJN2ajysEoAhF34zR/Peevo4N+T+OmhDLIUvk= -github.com/korylprince/go-cpio-odc v0.9.3/go.mod h1:1iHsjUXO64Hui0YsubGp0Tm/Uf04Iow2iy+1LJIarMw= github.com/korylprince/go-cpio-odc v0.9.4 h1:N0Afrp7Z5qCZF8cbpzF5CvyiC/KHK5IcLJwqm5MWpNU= github.com/korylprince/go-cpio-odc v0.9.4/go.mod h1:1iHsjUXO64Hui0YsubGp0Tm/Uf04Iow2iy+1LJIarMw= -github.com/korylprince/go-macos-pkg v1.3.4 h1:6McQ9bj3p/k/FeqFBHhdvXVvi+ftNcWXj4EWAEuEiz0= -github.com/korylprince/go-macos-pkg v1.3.4/go.mod h1:eLXotCZDK3vBt6AfDg62Fl20lCjrIm5e8V/8Q9YLHIo= github.com/korylprince/go-macos-pkg v1.3.5 h1:jwwefLR1yRKQPNu51QTwk2B4abIjjISj/BHipcvKKGs= github.com/korylprince/go-macos-pkg v1.3.5/go.mod h1:jlPGliXXjjCMpISR30/fL60hXKrCJ2LNQSptdx+WQkc= github.com/korylprince/goxar v0.0.0-20211111233330-e9f257bcdf25 h1:XVf+U+WekiGPPD27w7qI+0Pe8NehaTWPqRuO3BddefY= diff --git a/mdm/enroll/profile.go b/mdm/enroll/profile.go deleted file mode 100644 index 09632f05..00000000 --- a/mdm/enroll/profile.go +++ /dev/null @@ -1,149 +0,0 @@ -package enroll - -import ( - "time" - - "github.com/google/uuid" -) - -type Payload struct { - PayloadType string `json:"type" db:"type"` - PayloadVersion int `json:"version" db:"version"` - PayloadIdentifier string `json:"identifier" db:"identifier"` - PayloadUUID string `json:"uuid" db:"uuid"` - PayloadDisplayName string `json:"displayname" db:"displayname"` - PayloadDescription string `json:"description,omitempty" db:"description"` - PayloadOrganization string `json:"organization,omitempty" db:"organization"` - PayloadScope string `json:"scope" db:"scope" plist:",omitempty"` - PayloadContent interface{} `json:"content,omitempty" plist:"PayloadContent,omitempty"` -} - -type Profile struct { - PayloadContent []interface{} `json:"content,omitempty" db:"content"` - PayloadDescription string `json:"description,omitempty" db:"description"` - PayloadDisplayName string `json:"displayname,omitempty" db:"displayname"` - PayloadExpirationDate *time.Time `json:"expiration_date,omitempty" db:"expiration_date" plist:",omitempty"` - PayloadIdentifier string `json:"identifier" db:"identifier"` - PayloadOrganization string `json:"organization,omitempty" db:"organization"` - PayloadUUID string `json:"uuid" db:"uuid"` - PayloadRemovalDisallowed bool `json:"removal_disallowed" db:"removal_disallowed" plist:",omitempty"` - PayloadType string `json:"type" db:"type"` - PayloadVersion int `json:"version" db:"version"` - PayloadScope string `json:"scope" db:"scope" plist:",omitempty"` - RemovalDate *time.Time `json:"removal_date" db:"removal_date" plist:"-" plist:",omitempty"` - DurationUntilRemoval float32 `json:"duration_until_removal" db:"duration_until_removal" plist:",omitempty"` - ConsentText map[string]string `json:"consent_text" db:"consent_text" plist:",omitempty"` -} - -func NewProfile() *Profile { - payloadUuid := uuid.New() - - return &Profile{ - PayloadVersion: 1, - PayloadType: "Configuration", - PayloadUUID: payloadUuid.String(), - } -} - -func NewPayload(payloadType string) *Payload { - payloadUuid := uuid.New() - - return &Payload{ - PayloadVersion: 1, - PayloadType: payloadType, - PayloadUUID: payloadUuid.String(), - } -} - -type SCEPPayloadContent struct { - CAFingerprint []byte `plist:"CAFingerprint,omitempty"` // NSData - Challenge string `plist:"Challenge,omitempty"` - Keysize int - KeyType string `plist:"Key Type"` - KeyUsage int `plist:"Key Usage"` - Name string - Subject [][][]string `plist:"Subject,omitempty"` - URL string -} - -// AccessRights define the management rights of the MDM server over the device. -// May not be zero. If 2 is specified, 1 must also be specified. If 128 is specified, 64 must also be specified. -type AccessRights int - -const ( - // Allow inspection of installed configuration profiles. - ProfileInspection AccessRights = 1 << iota - - // Allow installation and removal of configuration profiles. - ProfileInstallAndRemoval - - // Allow device lock and passcode removal. - DeviceLock - - // Allow device erase. - DeviceErase - - // Allow query of Device Information (device capacity, serial number). - DeviceInformationQuery - - // Allow query of Network Information (phone/SIM numbers, MAC addresses). - NetworkInformationQuery - - // Allow inspection of installed provisioning profiles. - ProvisioningProfileInspection - - // Allow installation and removal of provisioning profiles. - ProvisioningProfileInstallAndRemoval - - // Allow inspection of installed applications. - ApplicationInspection - - // Allow restriction-related queries. - RestrictionQuery - - // Allow security-related queries. - SecurityQuery - - // Allow manipulation of settings. - // Availability: Available in iOS 5.0 and later. Available in macOS 10.9 for certain commands. - SettingsManipulation - - // Allow app management. - // Availability: Available in iOS 5.0 and later. Available in macOS 10.9 for certain commands. - AppManagement -) - -func allRights() AccessRights { - return ProfileInspection | - ProfileInstallAndRemoval | - DeviceLock | - DeviceErase | - DeviceInformationQuery | - NetworkInformationQuery | - ProvisioningProfileInspection | - ProvisioningProfileInstallAndRemoval | - ApplicationInspection | - RestrictionQuery | - SecurityQuery | - SettingsManipulation | - AppManagement -} - -// TODO: Actually this is one of those non-nested payloads that doesnt respect the PayloadContent key. -type MDMPayloadContent struct { - Payload - AccessRights AccessRights - CheckInURL string - CheckOutWhenRemoved bool - IdentityCertificateUUID string - ServerCapabilities []string `plist:"ServerCapabilities,omitempty"` - SignMessage bool `plist:"SignMessage,omitempty"` - ServerURL string - Topic string -} - -type ProfileServicePayload struct { - URL string - Challenge string `plist:",omitempty"` - DeviceAttributes []string -} diff --git a/mdm/enroll/profile_test.go b/mdm/enroll/profile_test.go index b6c9db11..6d234cee 100644 --- a/mdm/enroll/profile_test.go +++ b/mdm/enroll/profile_test.go @@ -11,14 +11,13 @@ func TestEnrollProfile(t *testing.T) { t.Fatal(err) } - var payloadContent MDMPayloadContent - for _, payload := range profile.PayloadContent { - if c, ok := payload.(MDMPayloadContent); ok { - payloadContent = c - } + mdmPayloads := profile.MDMPayloads() + if len(mdmPayloads) != 1 { + t.Fatal("number of MDM payloads is not 1") } + payloadContent := mdmPayloads[0] - if have, want := payloadContent.AccessRights, AccessRights(8191); have != want { + if have, want := payloadContent.AccessRights, 8191; have != want { t.Errorf("have %d, want %d", have, want) } diff --git a/mdm/enroll/service.go b/mdm/enroll/service.go index 6273eecd..96f9e6cc 100644 --- a/mdm/enroll/service.go +++ b/mdm/enroll/service.go @@ -14,6 +14,7 @@ import ( "github.com/micromdm/scep/v2/challenge" "github.com/groob/plist" + "github.com/jessepeterson/cfgprofiles" "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -21,6 +22,17 @@ import ( const ( EnrollmentProfileId string = "com.github.micromdm.micromdm.enroll" OTAProfileId string = "com.github.micromdm.micromdm.ota" + + profilePayloadOrganization = "MicroMDM" + profilePayloadDisplayName = "Enrollment Profile" + profilePayloadDescription = "The server may alter your settings" + + mdmPayloadDescription = "Enrolls with the MDM server" + mdmPayloadServerEndpoint = "/mdm/connect" + mdmPayloadCheckInEndpoint = "/mdm/checkin" + + scepPayloadDescription = "Configures SCEP" + scepPayloadDisplayName = "SCEP" ) type Service interface { @@ -126,17 +138,20 @@ type TopicProvider interface { } func profileOrPayloadFromFunc(f interface{}) (interface{}, error) { - fPayload, ok := f.(func() (Payload, error)) + fProfile, ok := f.(func() (*cfgprofiles.Profile, error)) if !ok { - fProfile := f.(func() (Profile, error)) - return fProfile() + fPayload := f.(func() (*ProfileServicePayload, error)) + return fPayload() } - return fPayload() + return fProfile() } func profileOrPayloadToMobileconfig(in interface{}) (profile.Mobileconfig, error) { - if _, ok := in.(Payload); !ok { - _ = in.(Profile) + switch in.(type) { + case *ProfileServicePayload, *cfgprofiles.Profile: + break + default: + return nil, errors.New("invalid profile type") } buf := new(bytes.Buffer) enc := plist.NewEncoder(buf) @@ -164,88 +179,78 @@ func (svc *service) Enroll(ctx context.Context) (profile.Mobileconfig, error) { return svc.findOrMakeMobileconfig(ctx, EnrollmentProfileId, svc.MakeEnrollmentProfile) } +func (svc *service) scepChallenge() (challenge string, err error) { + if svc.SCEPChallengeStore != nil { + challenge, err = svc.SCEPChallengeStore.SCEPChallenge() + } else if svc.SCEPChallenge != "" { + challenge = svc.SCEPChallenge + } + return +} + const perUserConnections = "com.apple.mdm.per-user-connections" const bootstrapToken = "com.apple.mdm.bootstraptoken" -func (svc *service) MakeEnrollmentProfile() (Profile, error) { - profile := NewProfile() - profile.PayloadIdentifier = EnrollmentProfileId - profile.PayloadOrganization = "MicroMDM" - profile.PayloadDisplayName = "Enrollment Profile" - profile.PayloadDescription = "The server may alter your settings" +func (svc *service) MakeEnrollmentProfile() (*cfgprofiles.Profile, error) { + profile := cfgprofiles.NewProfile(EnrollmentProfileId) profile.PayloadScope = "System" + profile.PayloadOrganization = profilePayloadOrganization + profile.PayloadDisplayName = profilePayloadDisplayName + profile.PayloadDescription = profilePayloadDescription - mdmPayload := NewPayload("com.apple.mdm") - mdmPayload.PayloadDescription = "Enrolls with the MDM server" - mdmPayload.PayloadOrganization = "MicroMDM" - mdmPayload.PayloadIdentifier = EnrollmentProfileId + ".mdm" - mdmPayload.PayloadScope = "System" + mdmPayload := cfgprofiles.NewMDMPayload(EnrollmentProfileId + ".mdm") + mdmPayload.PayloadOrganization = profilePayloadOrganization + mdmPayload.PayloadDescription = mdmPayloadDescription + + mdmPayload.ServerURL = svc.URL + mdmPayloadServerEndpoint + mdmPayload.CheckInURL = svc.URL + mdmPayloadCheckInEndpoint + mdmPayload.CheckOutWhenRemoved = true + mdmPayload.AccessRights = 8191 svc.mu.Lock() - topic := svc.Topic + mdmPayload.Topic = svc.Topic svc.mu.Unlock() - mdmPayloadContent := MDMPayloadContent{ - Payload: *mdmPayload, - AccessRights: allRights(), - CheckInURL: svc.URL + "/mdm/checkin", - CheckOutWhenRemoved: true, - ServerURL: svc.URL + "/mdm/connect", - Topic: topic, - SignMessage: true, - ServerCapabilities: []string{perUserConnections, bootstrapToken}, - } - - payloadContent := []interface{}{} + mdmPayload.SignMessage = true + mdmPayload.ServerCapabilities = []string{perUserConnections, bootstrapToken} if svc.SCEPURL != "" { - scepContent := SCEPPayloadContent{ + scepPayload := cfgprofiles.NewSCEPPayload(EnrollmentProfileId + ".scep") + scepPayload.PayloadDescription = scepPayloadDescription + scepPayload.PayloadDisplayName = scepPayloadDisplayName + scepPayload.PayloadOrganization = profilePayloadOrganization + + scepPayload.PayloadContent = cfgprofiles.SCEPPayloadContent{ URL: svc.SCEPURL, - Keysize: 2048, + KeySize: 2048, KeyType: "RSA", KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), Name: "Device Management Identity Certificate", Subject: svc.SCEPSubject, } - if svc.SCEPChallengeStore != nil { - challenge, err := svc.SCEPChallengeStore.SCEPChallenge() - if err != nil { - return *profile, err - } - scepContent.Challenge = challenge - } else if svc.SCEPChallenge != "" { - scepContent.Challenge = svc.SCEPChallenge + var err error + scepPayload.PayloadContent.Challenge, err = svc.scepChallenge() + if err != nil { + return nil, err } - scepPayload := NewPayload("com.apple.security.scep") - scepPayload.PayloadDescription = "Configures SCEP" - scepPayload.PayloadDisplayName = "SCEP" - scepPayload.PayloadIdentifier = EnrollmentProfileId + ".scep" - scepPayload.PayloadOrganization = "MicroMDM" - scepPayload.PayloadContent = scepContent - scepPayload.PayloadScope = "System" - - payloadContent = append(payloadContent, *scepPayload) - mdmPayloadContent.IdentityCertificateUUID = scepPayload.PayloadUUID + profile.AddPayload(scepPayload) + mdmPayload.IdentityCertificateUUID = scepPayload.PayloadUUID } - payloadContent = append(payloadContent, mdmPayloadContent) + profile.AddPayload(mdmPayload) // Client needs to trust us at this point if we are using a self signed certificate. if len(svc.TLSCert) > 0 { - tlsPayload := NewPayload("com.apple.security.pem") + tlsPayload := cfgprofiles.NewCertificatePKCS1Payload(EnrollmentProfileId + ".cert.selfsigned") tlsPayload.PayloadDisplayName = "Self-signed TLS certificate for MicroMDM" tlsPayload.PayloadDescription = "Installs the TLS certificate for MicroMDM" - tlsPayload.PayloadIdentifier = EnrollmentProfileId + ".cert.selfsigned" tlsPayload.PayloadContent = svc.TLSCert - - payloadContent = append(payloadContent, *tlsPayload) + profile.AddPayload(tlsPayload) } - profile.PayloadContent = payloadContent - - return *profile, nil + return profile, nil } // OTAEnroll returns an Over-the-Air "Profile Service" Payload for enrollment. @@ -253,20 +258,32 @@ func (svc *service) OTAEnroll(ctx context.Context) (profile.Mobileconfig, error) return svc.findOrMakeMobileconfig(ctx, OTAProfileId, svc.MakeOTAEnrollPayload) } -func (svc *service) MakeOTAEnrollPayload() (Payload, error) { - payload := NewPayload("Profile Service") - payload.PayloadIdentifier = OTAProfileId - payload.PayloadDisplayName = "MicroMDM Profile Service" - payload.PayloadDescription = "Profile Service enrollment" - payload.PayloadOrganization = "MicroMDM" - payload.PayloadContent = ProfileServicePayload{ - URL: svc.URL + "/ota/phase23", - Challenge: "", - DeviceAttributes: []string{"UDID", "VERSION", "PRODUCT", "SERIAL", "MEID", "IMEI"}, +type ProfileServicePayloadContent struct { + URL string + Challenge string `plist:",omitempty"` + DeviceAttributes []string +} + +type ProfileServicePayload struct { + *cfgprofiles.Payload + PayloadContent ProfileServicePayloadContent +} + +func (svc *service) MakeOTAEnrollPayload() (*ProfileServicePayload, error) { + payload := &ProfileServicePayload{ + Payload: cfgprofiles.NewPayload("Profile Service", OTAProfileId), + PayloadContent: ProfileServicePayloadContent{ + URL: svc.URL + "/ota/phase23", + Challenge: "", + DeviceAttributes: []string{"UDID", "VERSION", "PRODUCT", "SERIAL", "MEID", "IMEI"}, + }, } + payload.PayloadOrganization = profilePayloadOrganization + payload.PayloadDescription = "Profile Service enrollment" + payload.PayloadDisplayName = "MicroMDM Profile Service" // yes, this is a bare Payload, not a Profile - return *payload, nil + return payload, nil } // OTAPhase2 returns a SCEP Profile for use in phase 2 of Over-the-Air enrollment. @@ -274,38 +291,36 @@ func (svc *service) OTAPhase2(ctx context.Context) (profile.Mobileconfig, error) return svc.findOrMakeMobileconfig(ctx, OTAProfileId+".phase2", svc.MakeOTAPhase2Profile) } -func (svc *service) MakeOTAPhase2Profile() (Profile, error) { - profile := NewProfile() - profile.PayloadIdentifier = OTAProfileId + ".phase2" - profile.PayloadOrganization = "MicroMDM" +func (svc *service) MakeOTAPhase2Profile() (*cfgprofiles.Profile, error) { + profile := cfgprofiles.NewProfile(OTAProfileId + ".phase2") + profile.PayloadOrganization = profilePayloadOrganization profile.PayloadDisplayName = "OTA Phase 2" - profile.PayloadDescription = "The server may alter your settings" + profile.PayloadDescription = profilePayloadDescription profile.PayloadScope = "System" - scepContent := SCEPPayloadContent{ + scepPayload := cfgprofiles.NewSCEPPayload(OTAProfileId + ".phase2.scep") + scepPayload.PayloadDescription = scepPayloadDescription + scepPayload.PayloadDisplayName = scepPayloadDisplayName + scepPayload.PayloadOrganization = profilePayloadOrganization + + scepPayload.PayloadContent = cfgprofiles.SCEPPayloadContent{ URL: svc.SCEPURL, - Keysize: 2048, // NOTE: OTA docs recommend 1024 + KeySize: 2048, // NOTE: OTA docs recommend 1024 KeyType: "RSA", KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), Name: "OTA Phase 2 Certificate", Subject: svc.SCEPSubject, } - if svc.SCEPChallenge != "" { - scepContent.Challenge = svc.SCEPChallenge + var err error + scepPayload.PayloadContent.Challenge, err = svc.scepChallenge() + if err != nil { + return profile, err } - scepPayload := NewPayload("com.apple.security.scep") - scepPayload.PayloadDescription = "Configures SCEP" - scepPayload.PayloadDisplayName = "SCEP" - scepPayload.PayloadIdentifier = OTAProfileId + ".phase2.scep" - scepPayload.PayloadOrganization = "MicroMDM" - scepPayload.PayloadContent = scepContent - scepPayload.PayloadScope = "System" + profile.AddPayload(scepPayload) - profile.PayloadContent = append(profile.PayloadContent, *scepPayload) - - return *profile, nil + return profile, nil } // OTAPhase3 returns a Profile for use in phase 3 of Over-the-Air profile enrollment.