diff --git a/cmd/nanomdm/main.go b/cmd/nanomdm/main.go index 49657ac..3aa7020 100644 --- a/cmd/nanomdm/main.go +++ b/cmd/nanomdm/main.go @@ -14,6 +14,7 @@ import ( "github.com/micromdm/nanomdm/certverify" "github.com/micromdm/nanomdm/cli" + "github.com/micromdm/nanomdm/cryptoutil" mdmhttp "github.com/micromdm/nanomdm/http" httpapi "github.com/micromdm/nanomdm/http/api" "github.com/micromdm/nanomdm/http/authproxy" @@ -161,7 +162,7 @@ func main() { if *flDebug { opts = append(opts, httpmdm.SigLogWithLogErrors(true)) } - h = httpmdm.CertExtractMdmSignatureMiddleware(h, opts...) + h = httpmdm.CertExtractMdmSignatureMiddleware(h, cryptoutil.MdmSignatureVerifierFunc(cryptoutil.VerifyMdmSignature), opts...) } return h } diff --git a/cryptoutil/cryptoutil.go b/cryptoutil/cryptoutil.go index ad62b45..487a161 100644 --- a/cryptoutil/cryptoutil.go +++ b/cryptoutil/cryptoutil.go @@ -40,9 +40,15 @@ func TopicFromPEMCert(pemCert []byte) (string, error) { return TopicFromCert(cert) } -// VerifyMdmSignature verifies an Apple MDM "Mdm-Signature" header and -// returns the signing certificate. -// +// MdmSignatureVerifierFunc is an adapter for verifying Apple MDM "Mdm-Signature" headers. +type MdmSignatureVerifierFunc func(header string, body []byte) (*x509.Certificate, error) + +// VerifyMdmSignature calls v with header and body. +func (v MdmSignatureVerifierFunc) VerifyMdmSignature(header string, body []byte) (*x509.Certificate, error) { + return v(header, body) +} + +// VerifyMdmSignature verifies an Apple MDM "Mdm-Signature" header and returns the signing certificate. // See https://developer.apple.com/documentation/devicemanagement/implementing_device_management/managing_certificates_for_mdm_servers_and_devices // section "Pass an Identity Certificate Through a Proxy." func VerifyMdmSignature(header string, body []byte) (*x509.Certificate, error) { diff --git a/cryptoutil/cryptoutil_test.go b/cryptoutil/cryptoutil_test.go index 5b52917..e428fe9 100644 --- a/cryptoutil/cryptoutil_test.go +++ b/cryptoutil/cryptoutil_test.go @@ -7,10 +7,13 @@ import ( "github.com/smallstep/pkcs7" ) +const mdmSignatureHeader1 = "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIDIzCCAx8wggIHoAMCAQICAQQwDQYJKoZIhvcNAQELBQAwPTETMBEGCgmSJomT8ixkARkWA2NvbTEVMBMGCgmSJomT8ixkARkWBUd1c3RvMQ8wDQYDVQQDDAZNRE0gQ0EwHhcNMjEwOTE4MTg0NTA1WhcNMjIwOTE4MTg0NTA1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8Weag+4AQFkLrgm2/lZCdjGj5KC2rbIKdBdfExxaFWmvTtNCdXWyd5eROboRuEG/D1Zun0WKaKc1/emikBhnXP4qzEnNobx1OOfzeR1ZiazwftgAKrDZK6e4IJo15x8juRZvbjfKAQV+fw6TIGe4COUKpBtJo1idxJzI6OO2pQ6tvfzxhvbeD8VtYoHFgTXmBDHqUjmixdM+RIDUqReemaTeK5ybWTw3ZrydR7lM+I92Y9x/sRSxTODjgcczmprMVFl7a/d7biuqJtxg/RRVA85LWE3Gl+3BaVi9TC8xzaVioC++RmbXe3Z5qHmm+fkhfIzHksBW0Yn0DmWZRoWpgwIDAQABo2cwZTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFCxqnx50ZpbKaED6AAsxSScMguy6MB8GA1UdIwQYMBaAFBoyVn803d9H43znmXRJGmE066VrMA0GCSqGSIb3DQEBCwUAA4IBAQAU0jY/wjNth2fJsp49hbhEUFFPJIvM9lS5cWmSX2Xg7cK1pzDZJktA5MAZaLxbYCqpM9HegE3WhpyzaFRcIpBWV6T4R70gWbKcwn7WzAII0TBbDD4nZz2tO0kdLXA4LPyPjm/tJxzNvLfYmVNF61oImU2KXT/zp7rXOLU3KhkA4cWN9TApClTIZqlzr64T07HUA94S2ee9ia8/U2ITOswtYrGNYmky1PA9/GlcGaxm5LkthmIq4qh5/e8J8rfSXvz7GVuVqoZOBPVTQkBChG6ANCtTr8nniRIv+3L3042XjclVFj5mcLsXO5EN/v0i11ICcLs2SRJAF058CPLS7azgMYIBaTCCAWUCAQEwQjA9MRMwEQYKCZImiZPyLGQBGRYDY29tMRUwEwYKCZImiZPyLGQBGRYFR3VzdG8xDzANBgNVBAMMBk1ETSBDQQIBBDAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBBQUABIIBAABiveq4A69qvK2FjCMdhm6o9aBPfTw8WiJU9I6UppTbvw1+o2OBVLAOCXw46v1SIbj7Lhq5EDm3qXLD2xkF9zd5W43PvNZFleL735De+I1IeyXOvkmElOioipDNwrRpsET6vL2zwYlE0JZuGVhr2EU8ra3czy4eAbJwvV2xHLjpvqQJZh0LNvBc10sp7Q/99qpVdCXagUPJTh68Pcua51JiUWn0tDn0eaj083Yyx+I1XNR9opYuBEVz/LwFSsUGiB9zV7KbsLikajD2+Jmues5vS2jOrmCpV+yMN3uMa4lmOlgrQoi4l62edTo45zgnEZOUle0zT2pInMgML8KiWt8AAAAAAAA=" +const mdmSignatureHeader2 = "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIDITCCAx0wggIFoAMCAQICASIwDQYJKoZIhvcNAQELBQAwOzELMAkGA1UEBhMCVVMxDTALBgNVBAoTBHNjZXAxEDAOBgNVBAsTB1NDRVAgQ0ExCzAJBgNVBAMTAmNhMB4XDTIxMDUyODA1MDkyM1oXDTMxMDUyNjA1MDkyM1owADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO1oBJs+BT6daIs7bUWib7Ji1XGpDJ9e5uPtBJ3SVj2eIStDcl9bJj5JPle8HF+WI1HQ7yIT15zu3GSN6xO6gATAk1j/vv0JFimKXa9Q017mvg5ACUcRScY1mnya7pYL9b5C/b02ubOglEMCLnekhDKJhlVTwbdC0n0ZFr6aDWZlIxHFIym28Et9F+JeRJSRCAR6jybr+nRPavxPhGnbrImBM0z6HkzbjlHDlc5g3TOrj67bjz2nT+i6isEyj1stsOeDXH4Ij3z6A3jVW12/XAsHVoaC1KqmzXV2SWuOT9oXnlxs5NxoYJYQC+XN1x604cKauLNZFHjCFOFeeubVHucCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBQpqTV2kEzUfYz+4Mws1S6/D2hGnzAfBgNVHSMEGDAWgBT5TUXoM3msHybCjct8RyHuOpPr6DANBgkqhkiG9w0BAQsFAAOCAQEAMdbAM0+753sYs9erWV+QW8PTYHHTlrGaOU9VB3QaY0TG6iy/k6+l/6rYWQGW+RyElxju6xof9BVYgKb17EB0vm635PSFrAviyp495vLSFesvUUkUr7ALKC7f6H8ud3JIRhycyyih30YSlZrCwduMNjocYXPZN36hsyLeG3c977RGdHLrXFAMCvOSqHLktj/pYChR95XwrKVBaX2jtsk9B9UcDmubccPjROUKWHn3k5f2R+EJv9WVmFqNt7doG4nAAZx0ktDdgzVO7eWz/RwfFZlA9Qtm4+gG5eQd8e8HVefcCNOT/HcjfBblsOM7jzQlIvxvNss8haQ6TC//Fk/0rTGCAWcwggFjAgEBMEAwOzELMAkGA1UEBhMCVVMxDTALBgNVBAoTBHNjZXAxEDAOBgNVBAsTB1NDRVAgQ0ExCzAJBgNVBAMTAmNhAgEiMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEFBQAEggEA5h26uR27aLsghfAuDGe0mefTcxnPbqpiOFzh8t7WbK69giGQIIfs/NVqdFvYn210l2y5cAyHFVU81w6MmvqjHQJqVLptyYVDXgYy0/tsRc6Tbc+5kKD3GEkP8Z4uMCn75/8QlXFCj5DTVNWTZ8Vn/Itns51z+JYccOcsj9TnLX+0aaLz3I8X+r31Ftlx3UP6keW7HDo8m6JeGN4P3t51Dqjt08zyu72yR/gxaS91SHROypPAwMbiKqaxGiAPChgACmZDf0c/+g5T6Q0qc++cEAP9n+ViSjXyoDtS3YbfWxiiJaiieXLAiPjU9eVcXajEAwJUOJtTfECrNd5pgNpLBgAAAAAAAA==" +const mdmSignatureBody2 = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+U3RhdHVzPC9rZXk+Cgk8c3RyaW5nPklkbGU8L3N0cmluZz4KCTxrZXk+VURJRDwva2V5PgoJPHN0cmluZz5ENEQ1NjA4My1CRkIwLTUwNDctOEJERi0yNzI3QkFDREM1OUI8L3N0cmluZz4KPC9kaWN0Pgo8L3BsaXN0Pgo=" + func TestPKCS7ParseTagLengthError(t *testing.T) { // Regression test: Older versions of the library might return this BER tag length error. - mdmSignatureHeader := "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIDIzCCAx8wggIHoAMCAQICAQQwDQYJKoZIhvcNAQELBQAwPTETMBEGCgmSJomT8ixkARkWA2NvbTEVMBMGCgmSJomT8ixkARkWBUd1c3RvMQ8wDQYDVQQDDAZNRE0gQ0EwHhcNMjEwOTE4MTg0NTA1WhcNMjIwOTE4MTg0NTA1WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8Weag+4AQFkLrgm2/lZCdjGj5KC2rbIKdBdfExxaFWmvTtNCdXWyd5eROboRuEG/D1Zun0WKaKc1/emikBhnXP4qzEnNobx1OOfzeR1ZiazwftgAKrDZK6e4IJo15x8juRZvbjfKAQV+fw6TIGe4COUKpBtJo1idxJzI6OO2pQ6tvfzxhvbeD8VtYoHFgTXmBDHqUjmixdM+RIDUqReemaTeK5ybWTw3ZrydR7lM+I92Y9x/sRSxTODjgcczmprMVFl7a/d7biuqJtxg/RRVA85LWE3Gl+3BaVi9TC8xzaVioC++RmbXe3Z5qHmm+fkhfIzHksBW0Yn0DmWZRoWpgwIDAQABo2cwZTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFCxqnx50ZpbKaED6AAsxSScMguy6MB8GA1UdIwQYMBaAFBoyVn803d9H43znmXRJGmE066VrMA0GCSqGSIb3DQEBCwUAA4IBAQAU0jY/wjNth2fJsp49hbhEUFFPJIvM9lS5cWmSX2Xg7cK1pzDZJktA5MAZaLxbYCqpM9HegE3WhpyzaFRcIpBWV6T4R70gWbKcwn7WzAII0TBbDD4nZz2tO0kdLXA4LPyPjm/tJxzNvLfYmVNF61oImU2KXT/zp7rXOLU3KhkA4cWN9TApClTIZqlzr64T07HUA94S2ee9ia8/U2ITOswtYrGNYmky1PA9/GlcGaxm5LkthmIq4qh5/e8J8rfSXvz7GVuVqoZOBPVTQkBChG6ANCtTr8nniRIv+3L3042XjclVFj5mcLsXO5EN/v0i11ICcLs2SRJAF058CPLS7azgMYIBaTCCAWUCAQEwQjA9MRMwEQYKCZImiZPyLGQBGRYDY29tMRUwEwYKCZImiZPyLGQBGRYFR3VzdG8xDzANBgNVBAMMBk1ETSBDQQIBBDAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBBQUABIIBAABiveq4A69qvK2FjCMdhm6o9aBPfTw8WiJU9I6UppTbvw1+o2OBVLAOCXw46v1SIbj7Lhq5EDm3qXLD2xkF9zd5W43PvNZFleL735De+I1IeyXOvkmElOioipDNwrRpsET6vL2zwYlE0JZuGVhr2EU8ra3czy4eAbJwvV2xHLjpvqQJZh0LNvBc10sp7Q/99qpVdCXagUPJTh68Pcua51JiUWn0tDn0eaj083Yyx+I1XNR9opYuBEVz/LwFSsUGiB9zV7KbsLikajD2+Jmues5vS2jOrmCpV+yMN3uMa4lmOlgrQoi4l62edTo45zgnEZOUle0zT2pInMgML8KiWt8AAAAAAAA=" - p7signature, _ := base64.StdEncoding.DecodeString(mdmSignatureHeader) + p7signature, _ := base64.StdEncoding.DecodeString(mdmSignatureHeader1) _, err := pkcs7.Parse(p7signature) @@ -18,3 +21,15 @@ func TestPKCS7ParseTagLengthError(t *testing.T) { t.Error("pkcs7.Parse() failed with error:", err) } } + +func TestMdmVerifierFunc(t *testing.T) { + body, err := base64.StdEncoding.DecodeString(mdmSignatureBody2) + if err != nil { + t.Error(err) + } + verifier := MdmSignatureVerifierFunc(VerifyMdmSignature) + _, err = verifier.VerifyMdmSignature(mdmSignatureHeader2, body) + if err != nil { + t.Error(err) + } +} diff --git a/http/mdm/mdm_cert.go b/http/mdm/mdm_cert.go index 7363062..5e58f11 100644 --- a/http/mdm/mdm_cert.go +++ b/http/mdm/mdm_cert.go @@ -100,6 +100,14 @@ func SigLogWithLogErrors(errors bool) SigLogOption { } } +// MdmSignatureVerifier verifies Apple Mdm-Signature headers and extracts certificates. +type MdmSignatureVerifier interface { + // VerifyMdmSignature verifies an Apple MDM "Mdm-Signature" header and returns the signing certificate. + // See https://developer.apple.com/documentation/devicemanagement/implementing_device_management/managing_certificates_for_mdm_servers_and_devices + // section "Pass an Identity Certificate Through a Proxy." + VerifyMdmSignature(header string, body []byte) (*x509.Certificate, error) +} + // CertExtractMdmSignatureMiddleware extracts the MDM enrollment // identity certificate from the request into the HTTP request context. // It tries to verify the Mdm-Signature header on the request. @@ -107,7 +115,10 @@ func SigLogWithLogErrors(errors bool) SigLogOption { // This middleware does not error if a certificate is not found. It // will, however, error with an HTTP 400 status if the signature // verification fails. -func CertExtractMdmSignatureMiddleware(next http.Handler, opts ...SigLogOption) http.HandlerFunc { +func CertExtractMdmSignatureMiddleware(next http.Handler, verifier MdmSignatureVerifier, opts ...SigLogOption) http.HandlerFunc { + if verifier == nil { + panic("nil verifier") + } config := &sigLogConfig{logger: log.NopLogger} for _, opt := range opts { opt(config) @@ -129,7 +140,7 @@ func CertExtractMdmSignatureMiddleware(next http.Handler, opts ...SigLogOption) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - cert, err := cryptoutil.VerifyMdmSignature(mdmSig, b) + cert, err := verifier.VerifyMdmSignature(mdmSig, b) if err != nil { logger.Info("msg", "verifying Mdm-Signature header", "err", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)