// Copyright 2016 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctfe import ( "bytes" "errors" "fmt" "time" "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" ) // IsPrecertificate tests if a certificate is a pre-certificate as defined in CT. // An error is returned if the CT extension is present but is not ASN.1 NULL as defined // by the spec. func IsPrecertificate(cert *x509.Certificate) (bool, error) { for _, ext := range cert.Extensions { if x509.OIDExtensionCTPoison.Equal(ext.Id) { if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) { return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext) } return true, nil } } return false, nil } // ValidateChain takes the certificate chain as it was parsed from a JSON request. Ensures all // elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the // end entity certificate in the chain to a trusted root cert, possibly using the intermediates // supplied in the chain. Then applies the RFC requirement that the path must involve all // the submitted chain in the order of submission. func ValidateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) { // First make sure the certs parse as X.509 chain := make([]*x509.Certificate, 0, len(rawChain)) intermediatePool := x509util.NewPEMCertPool() for i, certBytes := range rawChain { cert, err := x509.ParseCertificate(certBytes) if x509.IsFatal(err) { return nil, err } chain = append(chain, cert) // All but the first cert form part of the intermediate pool if i > 0 { intermediatePool.AddCert(cert) } } naStart := validationOpts.notAfterStart naLimit := validationOpts.notAfterLimit cert := chain[0] // Check whether the expiry date of the cert is within the acceptable range. if naStart != nil && cert.NotAfter.Before(*naStart) { return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart) } if naLimit != nil && !cert.NotAfter.Before(*naLimit) { return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit) } if validationOpts.acceptOnlyCA && !cert.IsCA { return nil, errors.New("only certificates with CA bit set are accepted") } now := validationOpts.currentTime if now.IsZero() { now = time.Now() } expired := now.After(cert.NotAfter) if validationOpts.rejectExpired && expired { return nil, errors.New("rejecting expired certificate") } if validationOpts.rejectUnexpired && !expired { return nil, errors.New("rejecting unexpired certificate") } // Check for unwanted extension types, if required. // TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and // pre-calc this in there if len(validationOpts.rejectExtIds) != 0 { badIDs := make(map[string]bool) for _, id := range validationOpts.rejectExtIds { badIDs[id.String()] = true } for idx, ext := range cert.Extensions { extOid := ext.Id.String() if _, ok := badIDs[extOid]; ok { return nil, fmt.Errorf("rejecting certificate containing extension %v at index %d", extOid, idx) } } } // TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and // pre-calc this in there too. if len(validationOpts.extKeyUsages) > 0 { acceptEKUs := make(map[x509.ExtKeyUsage]bool) for _, eku := range validationOpts.extKeyUsages { acceptEKUs[eku] = true } good := false for _, certEKU := range cert.ExtKeyUsage { if _, ok := acceptEKUs[certEKU]; ok { good = true break } } if !good { return nil, fmt.Errorf("rejecting certificate without EKU in %v", validationOpts.extKeyUsages) } } // We can now do the verification. Use fairly lax options for verification, as // CT is intended to observe certificates rather than police them. verifyOpts := x509.VerifyOptions{ Roots: validationOpts.trustedRoots.CertPool(), CurrentTime: now, Intermediates: intermediatePool.CertPool(), DisableTimeChecks: true, // Precertificates have the poison extension; also the Go library code does not // support the standard PolicyConstraints extension (which is required to be marked // critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions. DisableCriticalExtensionChecks: true, // Pre-issued precertificates have the Certificate Transparency EKU; also some // leaves have unknown EKUs that should not be bounced just because the intermediate // does not also have them (cf. https://github.com/golang/go/issues/24590) so // disable EKU checks inside the x509 library, but we've already done our own check // on the leaf above. DisableEKUChecks: true, // Path length checks get confused by the presence of an additional // pre-issuer intermediate, so disable them. DisablePathLenChecks: true, DisableNameConstraintChecks: true, DisableNameChecks: false, KeyUsages: validationOpts.extKeyUsages, } verifiedChains, err := cert.Verify(verifyOpts) if err != nil { return nil, err } if len(verifiedChains) == 0 { return nil, errors.New("no path to root found when trying to validate chains") } // Verify might have found multiple paths to roots. Now we check that we have a path that // uses all the certs in the order they were submitted so as to comply with RFC 6962 // requirements detailed in Section 3.1. for _, verifiedChain := range verifiedChains { if chainsEquivalent(chain, verifiedChain) { return verifiedChain, nil } } return nil, errors.New("no RFC compliant path to root found when trying to validate chain") } func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool { // The verified chain includes a root, but the input chain may or may not include a // root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate // or a certificate that chains to a known root certificate"). if len(inChain) != len(verifiedChain) && len(inChain) != (len(verifiedChain)-1) { return false } for i, certInChain := range inChain { if !certInChain.Equal(verifiedChain[i]) { return false } } return true }