diff --git a/cmd/micromdm/serve.go b/cmd/micromdm/serve.go index d68fb307..1e1fbb16 100644 --- a/cmd/micromdm/serve.go +++ b/cmd/micromdm/serve.go @@ -16,6 +16,7 @@ import ( "github.com/micromdm/micromdm/mdm" "github.com/micromdm/micromdm/mdm/enroll" + "github.com/micromdm/micromdm/pkg/crypto" httputil2 "github.com/micromdm/micromdm/pkg/httputil" "github.com/micromdm/micromdm/platform/apns" "github.com/micromdm/micromdm/platform/appstore" @@ -127,6 +128,7 @@ func serve(args []string) error { flQueue = flagset.String("queue", env.String("MICROMDM_QUEUE", "builtin"), "command queue type") flDMURL = flagset.String("dm", env.String("DM", ""), "URL to send Declarative Management requests to") flLogTime = flagset.Bool("log-time", false, "Include timestamp in log messages") + flP7Skew = flagset.Int("device-signature-skew", env.Int("MICROMDM_DEVICE_SIGNATURE_SKEW", 0), "Sets the allowable clock skew (in seconds) when verifying device signatures") ) flagset.Usage = usageFor(flagset, "micromdm serve [flags]") if err := flagset.Parse(args); err != nil { @@ -250,7 +252,9 @@ func serve(args []string) error { scepEndpoints.PostEndpoint = scep.EndpointLoggingMiddleware(scepComponentLogger)(scepEndpoints.PostEndpoint) scepHandler := scep.MakeHTTPHandler(scepEndpoints, sm.SCEPService, scepComponentLogger) - enrollHandlers := enroll.MakeHTTPHandlers(ctx, enroll.MakeServerEndpoints(sm.EnrollService, sm.SCEPDepot), httptransport.ServerErrorLogger(httpLogger)) + pkcs7Verifier := &crypto.PKCS7Verifier{MaxSkew: time.Duration(*flP7Skew) * time.Second} + + enrollHandlers := enroll.MakeHTTPHandlers(ctx, enroll.MakeServerEndpoints(sm.EnrollService, sm.SCEPDepot), pkcs7Verifier, httptransport.ServerErrorLogger(httpLogger)) r, options := httputil2.NewRouter(logger) @@ -266,7 +270,7 @@ func serve(args []string) error { } mdmEndpoints := mdm.MakeServerEndpoints(sm.MDMService) - mdm.RegisterHTTPHandlers(r, mdmEndpoints, logger) + mdm.RegisterHTTPHandlers(r, mdmEndpoints, pkcs7Verifier, logger) // API commands. Only handled if the user provides an api key. if *flAPIKey != "" { diff --git a/mdm/enroll/transport_http.go b/mdm/enroll/transport_http.go index 5a7a0263..2bbc2d22 100644 --- a/mdm/enroll/transport_http.go +++ b/mdm/enroll/transport_http.go @@ -22,11 +22,12 @@ type HTTPHandlers struct { OTAPhase2Phase3Handler http.Handler } -func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { +func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, v *crypto.PKCS7Verifier, opts ...httptransport.ServerOption) HTTPHandlers { + ver := verifier{PKCS7Verifier: v} h := HTTPHandlers{ EnrollHandler: httptransport.NewServer( endpoints.GetEnrollEndpoint, - decodeMDMEnrollRequest, + ver.decodeMDMEnrollRequest, encodeMobileconfigResponse, opts..., ), @@ -38,7 +39,7 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran ), OTAPhase2Phase3Handler: httptransport.NewServer( endpoints.OTAPhase2Phase3Endpoint, - decodeOTAPhase2Phase3Request, + ver.decodeOTAPhase2Phase3Request, encodeMobileconfigResponse, opts..., ), @@ -50,7 +51,11 @@ func decodeEmptyRequest(_ context.Context, _ *http.Request) (interface{}, error) return nil, nil } -func decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, error) { +type verifier struct { + *crypto.PKCS7Verifier +} + +func (v verifier) decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, error) { switch r.Method { case "GET": return mdmEnrollRequest{}, nil @@ -63,7 +68,7 @@ func decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, er if err != nil { return nil, err } - err = p7.Verify() + err = v.Verify(p7) if err != nil { return nil, err } @@ -93,7 +98,7 @@ func encodeMobileconfigResponse(ctx context.Context, w http.ResponseWriter, resp return err } -func decodeOTAPhase2Phase3Request(_ context.Context, r *http.Request) (interface{}, error) { +func (v verifier) decodeOTAPhase2Phase3Request(_ context.Context, r *http.Request) (interface{}, error) { data, err := ioutil.ReadAll(r.Body) if err != nil { return nil, err @@ -102,7 +107,7 @@ func decodeOTAPhase2Phase3Request(_ context.Context, r *http.Request) (interface if err != nil { return nil, err } - err = p7.Verify() + err = v.Verify(p7) if err != nil { return nil, err } diff --git a/mdm/server.go b/mdm/server.go index 28459efd..fc571b7f 100644 --- a/mdm/server.go +++ b/mdm/server.go @@ -13,6 +13,7 @@ import ( httptransport "github.com/go-kit/kit/transport/http" "github.com/gorilla/mux" "github.com/groob/plist" + "github.com/micromdm/micromdm/pkg/crypto" "github.com/pkg/errors" "go.mozilla.org/pkcs7" ) @@ -29,12 +30,12 @@ func MakeServerEndpoints(s Service) Endpoints { } } -func RegisterHTTPHandlers(r *mux.Router, e Endpoints, logger log.Logger) { +func RegisterHTTPHandlers(r *mux.Router, e Endpoints, v *crypto.PKCS7Verifier, logger log.Logger) { options := []httptransport.ServerOption{ httptransport.ServerErrorEncoder(encodeError), httptransport.ServerErrorLogger(logger), httptransport.ServerBefore(httptransport.PopulateRequestContext), - httptransport.ServerBefore(populateDeviceCertificateFromSignRequestHeader), + httptransport.ServerBefore((verifier{PKCS7Verifier: v}).populateDeviceCertificateFromSignRequestHeader), } r.Methods(http.MethodPut).Path("/mdm/checkin").Handler(httptransport.NewServer( @@ -65,7 +66,11 @@ func DeviceCertificateFromContext(ctx context.Context) (*x509.Certificate, error return cert, err } -func populateDeviceCertificateFromSignRequestHeader(ctx context.Context, r *http.Request) context.Context { +type verifier struct { + *crypto.PKCS7Verifier +} + +func (v verifier) populateDeviceCertificateFromSignRequestHeader(ctx context.Context, r *http.Request) context.Context { bodyReader := r.Body defer bodyReader.Close() @@ -76,7 +81,7 @@ func populateDeviceCertificateFromSignRequestHeader(ctx context.Context, r *http // Replace our body object with a fully buffered response r.Body = ioutil.NopCloser(bytes.NewBuffer(body)) - cert, err := verifySignature(r.Header.Get("Mdm-Signature"), body) + cert, err := v.verifySignature(r.Header.Get("Mdm-Signature"), body) ctx = context.WithValue(ctx, ContextKeyDeviceCertificate, cert) ctx = context.WithValue(ctx, ContextKeyDeviceCertificateVerifyError, err) @@ -107,7 +112,7 @@ func mdmRequestBody(r *http.Request, s interface{}) ([]byte, error) { } // Verify MDM header signature. Note: does NOT verify device certificate -func verifySignature(header string, body []byte) (*x509.Certificate, error) { +func (v verifier) verifySignature(header string, body []byte) (*x509.Certificate, error) { if header == "" { return nil, errors.New("signature missing") } @@ -120,7 +125,7 @@ func verifySignature(header string, body []byte) (*x509.Certificate, error) { return nil, errors.Wrap(err, "CMS parse decoded MDM SignMessage signature") } p7.Content = body - if err := p7.Verify(); err != nil { + if err := v.Verify(p7); err != nil { return nil, errors.Wrap(err, "CMS verify MDM Signed Message") } cert := p7.GetOnlySigner() diff --git a/mdm/server_test.go b/mdm/server_test.go index 36ab8a12..a637bd32 100644 --- a/mdm/server_test.go +++ b/mdm/server_test.go @@ -45,7 +45,7 @@ func Test_mdmMdmSignatureHeader(t *testing.T) { req.Header.Set("Mdm-Signature", b64sig) ctx := context.Background() - ctx = populateDeviceCertificateFromSignRequestHeader(ctx, req) + ctx = (verifier{PKCS7Verifier: &crypto.PKCS7Verifier{}}).populateDeviceCertificateFromSignRequestHeader(ctx, req) reqcert, err := DeviceCertificateFromContext(ctx) if err != nil { diff --git a/pkg/crypto/helpers.go b/pkg/crypto/helpers.go index 02978186..28ea321a 100644 --- a/pkg/crypto/helpers.go +++ b/pkg/crypto/helpers.go @@ -14,6 +14,8 @@ import ( "os" "strings" "time" + + "go.mozilla.org/pkcs7" ) func GenerateRandomCertificateSerialNumber() (*big.Int, error) { @@ -192,3 +194,21 @@ func TopicFromCert(cert *x509.Certificate) (string, error) { return "", errors.New("could not find Push Topic (UserID OID) in certificate") } + +// PKCS7Verifier verifies PKCS7 objects with a configurable clock skew +type PKCS7Verifier struct { + // MaxSkew is the maximum amount of clock skew permitted between the the server time and the pkcs7 signature validity + MaxSkew time.Duration +} + +// Verify checks the signatures of a PKCS7 object +func (v *PKCS7Verifier) Verify(p7 *pkcs7.PKCS7) error { + // verify with skew added to beginning of validity window + err := p7.VerifyWithChainAtTime(nil, time.Now().Add(v.MaxSkew)) + // if verification fails due to missing the validity window, try verifying with the skew added to the end of the validity window + // the pkcs7 lib doesn't return a concrete error, so check against the error string + if err != nil && strings.Contains(err.Error(), "is outside of certificate validity") { + return p7.VerifyWithChainAtTime(nil, time.Now().Add(-v.MaxSkew)) + } + return err +}