diff --git a/crypto/apple.go b/crypto/apple.go new file mode 100644 index 00000000..c602728c --- /dev/null +++ b/crypto/apple.go @@ -0,0 +1,109 @@ +package crypto + +import ( + "crypto/x509" + "encoding/pem" + "fmt" +) + +/* +These certificates are not currently used but they represent the chain +of certificates to verify a device's certificate in DEP & OTA requests. + +const appleRootCAPEM = `-----BEGIN CERTIFICATE----- +MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET +MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0 +MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw +bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+ ++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1 +XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w +tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW +q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM +aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3 +R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE +ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93 +d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl +IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0 +YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj +b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp +Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc +NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP +y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7 +R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg +xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP +IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX +UKqK1drk/NAJBzewdXUh +-----END CERTIFICATE----- +` + +const appleiPhoneCertificateAuthorityPEM = `-----BEGIN CERTIFICATE----- +MIID8zCCAtugAwIBAgIBFzANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET +MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDcwNDEyMTc0 +MzI4WhcNMjIwNDEyMTc0MzI4WjB5MQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw +bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +LTArBgNVBAMTJEFwcGxlIGlQaG9uZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMevvBHwLSeEFtGpLghuE+G +IXAoRWBcHMPICmRjiPv8ae74VPzpW7cGTgQvw2szr0RM6kuACbSH9lu0/WTds3Lg +E7P9F9m856jtwoxhwir57M6lXtZp62QLjQiPuKBQRgncGeTlsJRtu/eZmMTom0FO +1PFl4xtSetzoA9luHdoQVYakKVhJDOpH1xU0M/bAoERKcL4stSowN4wuFevR5GyX +OFVWsTUrWOpEoyaF7shmSuTPifA9Y60p3q26WrPcpaOapwlOgBY1ZaSFDWN7PmOK +2n1KRuyjORg0ucYoZRi8E2Ccf1esFMmJ7aG2h2hStoROuMiD7PmeGauzwQuGx58C +AwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU5zQqLiLeOWBrtJTOd4NhLzGgfDUwHwYDVR0jBBgwFoAUK9BpR5R2Cf70 +a40uQKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5hcHBsZS5j +b20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAHdHVe910TtcX +/IItDJmbXkJy8mnc1WteDQxrSz57FCXes5TooPoPgInyFz0AAqKRkb50V9yvmp+h +Cn0wvgAqzCFZ6/1JrG51GeiaegPRhvbn9rAOS0n6o7dButfR41ahfYOrl674UUom +wYVCEyaNA1RmEF5ghAUSMStrVMCgyEG8VB7nVK0TANJKx7vBiq+BCI7wRgq/J6a+ +3M85OoBwGSMyo2tmXZ5NqEdJsntFtVEzp3RnCU62bG9I9yy5MwVEa0W+dEtvsoaR +tD4lKCWes8JRhvxP5a87qrtELAFJ4nSzNPpE7xTCEfItGRpRidMISkFsWFbemzrh +BVflYs/SDw== +-----END CERTIFICATE----- +` +*/ + +// TODO: This certificate expired 2014, but is required. +const appleiPhoneDeviceCAPEM = `-----BEGIN CERTIFICATE----- +MIIDaTCCAlGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB5MQswCQYDVQQGEwJVUzET +MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxLTArBgNVBAMTJEFwcGxlIGlQaG9uZSBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wNzA0MTYyMjU0NDZaFw0xNDA0MTYyMjU0NDZaMFoxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMRUwEwYDVQQLEwxBcHBsZSBp +UGhvbmUxHzAdBgNVBAMTFkFwcGxlIGlQaG9uZSBEZXZpY2UgQ0EwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBAPGUSsnquloYYK3Lok1NTlQZaRdZB2bLl+hmmkdf +Rq5nerVKc1SxywT2vTa4DFU4ioSDMVJl+TPhl3ecK0wmsCU/6TKqewh0lOzBSzgd +Z04IUpRai1mjXNeT9KD+VYW7TEaXXm6yd0UvZ1y8Cxi/WblshvcqdXbSGXH0KWO5 +JQuvAgMBAAGjgZ4wgZswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLL+ISNEhpVqedWBJo5zENinTI50MB8GA1UdIwQYMBaAFOc0Ki4i +3jlga7SUzneDYS8xoHw1MDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly93d3cuYXBw +bGUuY29tL2FwcGxlY2EvaXBob25lLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAd13P +Z3pMViukVHe9WUg8Hum+0I/0kHKvjhwVd/IMwGlXyU7DhUYWdja2X/zqj7W24Aq5 +7dEKm3fqqxK5XCFVGY5HI0cRsdENyTP7lxSiiTRYj2mlPedheCn+k6T5y0U4Xr40 +FXwWb2nWqCF1AgIudhgvVbxlvqcxUm8Zz7yDeJ0JFovXQhyO5fLUHRLCQFssAbf8 +B4i8rYYsBUhYTspVJcxVpIIltkYpdIRSIARA49HNvKK4hzjzMS/OhKQpVKw+OCEZ +xptCVeN2pjbdt9uzi175oVo/u6B2ArKAW17u6XEHIdDMOe7cb33peVI6TD15W4MI +pyQPbp8orlXe+tA8JA== +-----END CERTIFICATE----- +` + +// VerifyFromAppleDeviceCA verifies a certificate was signed by Apple's iPhone Device CA. +// TODO: We want to have more intensive verification (like the whole provided chain). +// TODO: Implement some sort of cache so we don't need to parse PEM & DER every invocation. +func VerifyFromAppleDeviceCA(c *x509.Certificate) error { + block, _ := pem.Decode([]byte(appleiPhoneDeviceCAPEM)) + if block == nil || block.Type != "CERTIFICATE" { + panic("appleiPhoneDeviceCAPEM: invalid PEM block") + } + parent, err := x509.ParseCertificate(block.Bytes) + if err != nil { + panic(fmt.Sprintf("appleiPhoneDeviceCAPEM: err parsing: %s", err)) + } + // Note we CheckSignatureFrom() as we cannot Verify the certificate chain + // (known expired intermediate) + return c.CheckSignatureFrom(parent) +} diff --git a/enroll/endpoint.go b/enroll/endpoint.go index 5b08e61d..760d1ee8 100644 --- a/enroll/endpoint.go +++ b/enroll/endpoint.go @@ -5,18 +5,43 @@ import ( "errors" "fmt" + "github.com/micromdm/micromdm/crypto" + boltdepot "github.com/micromdm/scep/depot/bolt" + + "github.com/fullsailor/pkcs7" "github.com/go-kit/kit/endpoint" "github.com/micromdm/mdm" ) type Endpoints struct { - GetEnrollEndpoint endpoint.Endpoint + GetEnrollEndpoint endpoint.Endpoint + OTAEnrollEndpoint endpoint.Endpoint + OTAPhase2Phase3Endpoint endpoint.Endpoint } type depEnrollmentRequest struct { mdm.DEPEnrollmentRequest } +// TODO: may overlap at some point with mdm.DEPEnrollmentRequest +type otaEnrollmentRequest struct { + Challenge string `plist:"CHALLENGE"` + Product string `plist:"PRODUCT"` + Serial string `plist:"SERIAL"` + UDID string `plist:"UDID"` + Version string `plist:"VERSION"` // build no. + IMSI string `plist:"IMSI"` + IMEI string `plist:"IMEI,omitempty"` + MEID string `plist:"MEID,omitempty"` + ICCID string `plist:"ICCID"` + MACAddressEN0 string `plist:"MAC_ADDRESS_EN0"` + DeviceName string `plist:"DEVICE_NAME"` + NotOnConsole bool + UserID string // GUID of User + UserLongName string + UserShortName string +} + type mdmEnrollRequest struct{} type mdmEnrollResponse struct { @@ -24,9 +49,21 @@ type mdmEnrollResponse struct { Err error `plist:"error,omitempty"` } -func MakeServerEndpoints(s Service) Endpoints { +type mdmOTAPhase2Phase3Request struct { + otaEnrollmentRequest otaEnrollmentRequest + p7 *pkcs7.PKCS7 +} + +type mdmOTAEnrollResponse struct { + Payload + Err error `plist:"error,omitempty"` +} + +func MakeServerEndpoints(s Service, scepDepot *boltdepot.Depot) Endpoints { return Endpoints{ - GetEnrollEndpoint: MakeGetEnrollEndpoint(s), + GetEnrollEndpoint: MakeGetEnrollEndpoint(s), + OTAEnrollEndpoint: MakeOTAEnrollEndpoint(s), + OTAPhase2Phase3Endpoint: MakeOTAPhase2Phase3Endpoint(s, scepDepot), } } @@ -45,3 +82,61 @@ func MakeGetEnrollEndpoint(s Service) endpoint.Endpoint { } } } + +func MakeOTAEnrollEndpoint(s Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + payload, err := s.OTAEnroll(ctx) + return mdmOTAEnrollResponse{payload, err}, nil + } +} + +func MakeOTAPhase2Phase3Endpoint(s Service, scepDepot *boltdepot.Depot) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(mdmOTAPhase2Phase3Request) + + if req.p7 == nil || req.p7.GetOnlySigner() == nil { + return nil, errors.New("invalid signer/signer not provided") + } + + // TODO: currently only verifying the signing certificate but ought to + // verify the whole provided chain. Note this will be difficult to do + // given the inconsist certificate chain returned by macOS in OTA mode, + // macOS in DEP mode, and iOS in either mode. See: + // https://openradar.appspot.com/radar?id=4957320861712384 + if err := crypto.VerifyFromAppleDeviceCA(req.p7.GetOnlySigner()); err == nil { + // signing certificate is signed by the Apple Device CA. this means + // we don't yet have a SCEP identity and thus are in Phase 2 of the + // OTA enrollment + profile, err := s.OTAPhase2(ctx) + return mdmEnrollResponse{profile, err}, nil + } + + caChain, _, err := scepDepot.CA(nil) + if err != nil { + return nil, err + } + + if len(caChain) < 1 { + return nil, errors.New("invalid SCEP CA chain") + } + + if req.p7.GetOnlySigner().CheckSignatureFrom(caChain[0]) == nil { + // signing certificate is signed by our SCEP CA. this means we + // we are in Phase 3 of OTA enrollment (as we already have a + // identified certificate) + + // TODO: possibly deliver a different enrollment profile based + // on device certificates + // TODO: we can encrypt the enrollment (or any profile) at this + // point: we have a device identity that we can encrypt to that + // device's private key that it can decrypt + // TODO: the SCEP CA checking ought to be more robust + // see: https://github.com/micromdm/scep/issues/32 + + profile, err := s.Enroll(ctx) + // profile, err := s.OTAPhase3(ctx) + return mdmEnrollResponse{profile, err}, nil + } + return mdmEnrollResponse{Profile{}, errors.New("unauthorized client")}, nil + } +} diff --git a/enroll/profile.go b/enroll/profile.go index 93100057..df95d49e 100644 --- a/enroll/profile.go +++ b/enroll/profile.go @@ -77,3 +77,9 @@ type MDMPayloadContent struct { ServerURL string Topic string } + +type ProfileServicePayload struct { + URL string + Challenge string `plist:",omitempty"` + DeviceAttributes []string +} diff --git a/enroll/service.go b/enroll/service.go index adc6508a..99f93d30 100644 --- a/enroll/service.go +++ b/enroll/service.go @@ -1,6 +1,7 @@ package enroll import ( + "crypto/x509" "golang.org/x/net/context" "io/ioutil" "strings" @@ -8,6 +9,9 @@ import ( type Service interface { Enroll(ctx context.Context) (Profile, error) + OTAEnroll(ctx context.Context) (Payload, error) + OTAPhase2(ctx context.Context) (Profile, error) + OTAPhase3(ctx context.Context) (Profile, error) } func NewService(pushTopic, caCertPath, scepURL, scepChallenge, url, tlsCertPath, scepSubject string) (Service, error) { @@ -145,3 +149,64 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) { return *profile, nil } + +// OTAEnroll returns an Over-the-Air "Profile Service" Payload for enrollment. +func (svc service) OTAEnroll(ctx context.Context) (Payload, error) { + payload := NewPayload("Profile Service") + payload.PayloadIdentifier = "com.github.micromdm.ota.profile-service" + 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"}, + } + + // yes, this is a bare Payload, not a Profile + return *payload, nil +} + +// OTAPhase2 returns a SCEP Profile for use in phase 2 of Over-the-Air enrollment. +func (svc service) OTAPhase2(ctx context.Context) (Profile, error) { + profile := NewProfile() + profile.PayloadIdentifier = "com.github.micromdm.micromdm.ota-scep" + profile.PayloadOrganization = "MicroMDM" + profile.PayloadDisplayName = "Enrollment Profile" + profile.PayloadDescription = "The server may alter your settings" + profile.PayloadScope = "System" + + scepContent := SCEPPayloadContent{ + URL: svc.SCEPURL, + Keysize: 1024, + KeyType: "RSA", + KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), + Name: "Device Management Identity Certificate", + Subject: svc.SCEPSubject, + } + + if svc.SCEPChallenge != "" { + scepContent.Challenge = svc.SCEPChallenge + } + + scepPayload := NewPayload("com.apple.security.scep") + scepPayload.PayloadDescription = "Configures SCEP" + scepPayload.PayloadDisplayName = "SCEP" + scepPayload.PayloadIdentifier = "com.github.micromdm.scep" + scepPayload.PayloadOrganization = "MicroMDM" + scepPayload.PayloadContent = scepContent + scepPayload.PayloadScope = "System" + + profile.PayloadContent = append(profile.PayloadContent, *scepPayload) + + return *profile, nil +} + +// OTAPhase3 returns a Profile for use in phase 3 of Over-the-Air profile enrollment. +// This would typically be the final or end profile of the Over-the-Air +// enrollment process. In our case this would probably be a device-specifc +// MDM enrollment payload. +// TODO: Not implemented. +func (svc service) OTAPhase3(ctx context.Context) (Profile, error) { + return Profile{}, nil +} diff --git a/enroll/transport.go b/enroll/transport.go deleted file mode 100644 index 9a174b64..00000000 --- a/enroll/transport.go +++ /dev/null @@ -1,68 +0,0 @@ -package enroll - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - - "github.com/fullsailor/pkcs7" - "github.com/go-kit/kit/log" - httptransport "github.com/go-kit/kit/transport/http" - "github.com/gorilla/mux" - "github.com/groob/plist" -) - -// ServiceHandler returns an HTTP Handler for the enroll service -func ServiceHandler(ctx context.Context, svc Service, logger log.Logger) http.Handler { - r := mux.NewRouter() - e := MakeServerEndpoints(svc) - opts := []httptransport.ServerOption{ - httptransport.ServerErrorLogger(logger), - } - r.Methods("GET", "POST").Path("/mdm/enroll").Handler(httptransport.NewServer( - e.GetEnrollEndpoint, - decodeMDMEnrollRequest, - encodeResponse, - opts..., - )) - - return r -} - -func decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, error) { - switch r.Method { - case "GET": - return mdmEnrollRequest{}, nil - case "POST": - data, err := ioutil.ReadAll(r.Body) - if err != nil { - return nil, err - } - p7, err := pkcs7.Parse(data) - if err != nil { - return nil, err - } - // TODO: We should verify but not currently possible. Apple - // does no provide a cert for the CA. - var request depEnrollmentRequest - if err := plist.Unmarshal(p7.Content, &request); err != nil { - return nil, err - } - return request, nil - default: - return nil, errors.New("unknown enrollment method") - } -} - -func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { - resp := response.(mdmEnrollResponse) - - w.Header().Set("Content-Type", "application/x-apple-aspen-config") - - if err := plist.NewEncoder(w).Encode(resp); err != nil { - return err - } - - return nil -} diff --git a/enroll/transport_http.go b/enroll/transport_http.go new file mode 100644 index 00000000..79d42ad1 --- /dev/null +++ b/enroll/transport_http.go @@ -0,0 +1,107 @@ +package enroll + +import ( + "context" + "errors" + "io/ioutil" + "net/http" + + "github.com/fullsailor/pkcs7" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/groob/plist" +) + +type HTTPHandlers struct { + EnrollHandler http.Handler + OTAEnrollHandler http.Handler + + // In Apple's Over-the-Air design Phases 2 and 3 happen over the same URL. + // The differentiator is which certificate signed the CMS POST body. + OTAPhase2Phase3Handler http.Handler +} + +func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { + h := HTTPHandlers{ + EnrollHandler: httptransport.NewServer( + endpoints.GetEnrollEndpoint, + decodeMDMEnrollRequest, + encodeResponse, + opts..., + ), + OTAEnrollHandler: httptransport.NewServer( + endpoints.OTAEnrollEndpoint, + nilRequest, + encodeResponse, + opts..., + ), + OTAPhase2Phase3Handler: httptransport.NewServer( + endpoints.OTAPhase2Phase3Endpoint, + decodeOTAPhase2Phase3Request, + encodeResponse, + opts..., + ), + } + return h +} + +func nilRequest(_ context.Context, _ *http.Request) (interface{}, error) { + return nil, nil +} + +func decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, error) { + switch r.Method { + case "GET": + return mdmEnrollRequest{}, nil + case "POST": + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + p7, err := pkcs7.Parse(data) + if err != nil { + return nil, err + } + // TODO: We should verify but not currently possible. Apple + // does no provide a cert for the CA. + var request depEnrollmentRequest + if err := plist.Unmarshal(p7.Content, &request); err != nil { + return nil, err + } + return request, nil + default: + return nil, errors.New("unknown enrollment method") + } +} + +func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { + w.Header().Set("Content-Type", "application/x-apple-aspen-config") + + enc := plist.NewEncoder(w) + enc.Indent(" ") + if err := enc.Encode(response); err != nil { + return err + } + + return nil +} + +func decodeOTAPhase2Phase3Request(_ context.Context, r *http.Request) (interface{}, error) { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + p7, err := pkcs7.Parse(data) + if err != nil { + return nil, err + } + err = p7.Verify() + if err != nil { + return nil, err + } + var request otaEnrollmentRequest + err = plist.Unmarshal(p7.Content, &request) + if err != nil { + return nil, err + } + return mdmOTAPhase2Phase3Request{request, p7}, nil +} diff --git a/serve.go b/serve.go index 0a1a515a..3a34028a 100644 --- a/serve.go +++ b/serve.go @@ -226,11 +226,13 @@ func serve(args []string) error { pushHandlers := nanopush.MakeHTTPHandlers(ctx, pushEndpoints, checkinOpts...) scepHandler := scep.ServiceHandler(ctx, sm.scepService, httpLogger) - enrollHandler := enroll.ServiceHandler(ctx, sm.enrollService, httpLogger) + enrollHandlers := enroll.MakeHTTPHandlers(ctx, enroll.MakeServerEndpoints(sm.enrollService, sm.scepDepot), httptransport.ServerErrorLogger(httpLogger)) r := mux.NewRouter() r.Handle("/mdm/checkin", mdmAuthSignMessageMiddleware(sm.scepDepot, checkinHandlers.CheckinHandler)).Methods("PUT") r.Handle("/mdm/connect", mdmAuthSignMessageMiddleware(sm.scepDepot, connectHandlers.ConnectHandler)).Methods("PUT") - r.Handle("/mdm/enroll", enrollHandler).Methods("GET", "POST") + r.Handle("/mdm/enroll", enrollHandlers.EnrollHandler).Methods("GET", "POST") + r.Handle("/ota/enroll", enrollHandlers.OTAEnrollHandler) + r.Handle("/ota/phase23", enrollHandlers.OTAPhase2Phase3Handler).Methods("POST") r.Handle("/scep", scepHandler) r.Handle("/push/{udid}", pushHandlers.PushHandler) r.Handle("/v1/commands", commandHandlers.NewCommandHandler).Methods("POST")