...

Source file src/github.com/google/certificate-transparency-go/trillian/ctfe/cert_checker.go

Documentation: github.com/google/certificate-transparency-go/trillian/ctfe

     1  // Copyright 2016 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ctfe
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/google/certificate-transparency-go/asn1"
    24  	"github.com/google/certificate-transparency-go/x509"
    25  	"github.com/google/certificate-transparency-go/x509util"
    26  )
    27  
    28  // IsPrecertificate tests if a certificate is a pre-certificate as defined in CT.
    29  // An error is returned if the CT extension is present but is not ASN.1 NULL as defined
    30  // by the spec.
    31  func IsPrecertificate(cert *x509.Certificate) (bool, error) {
    32  	for _, ext := range cert.Extensions {
    33  		if x509.OIDExtensionCTPoison.Equal(ext.Id) {
    34  			if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) {
    35  				return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext)
    36  			}
    37  
    38  			return true, nil
    39  		}
    40  	}
    41  
    42  	return false, nil
    43  }
    44  
    45  // ValidateChain takes the certificate chain as it was parsed from a JSON request. Ensures all
    46  // elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the
    47  // end entity certificate in the chain to a trusted root cert, possibly using the intermediates
    48  // supplied in the chain. Then applies the RFC requirement that the path must involve all
    49  // the submitted chain in the order of submission.
    50  func ValidateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) {
    51  	// First make sure the certs parse as X.509
    52  	chain := make([]*x509.Certificate, 0, len(rawChain))
    53  	intermediatePool := x509util.NewPEMCertPool()
    54  
    55  	for i, certBytes := range rawChain {
    56  		cert, err := x509.ParseCertificate(certBytes)
    57  		if x509.IsFatal(err) {
    58  			return nil, err
    59  		}
    60  
    61  		chain = append(chain, cert)
    62  
    63  		// All but the first cert form part of the intermediate pool
    64  		if i > 0 {
    65  			intermediatePool.AddCert(cert)
    66  		}
    67  	}
    68  
    69  	naStart := validationOpts.notAfterStart
    70  	naLimit := validationOpts.notAfterLimit
    71  	cert := chain[0]
    72  
    73  	// Check whether the expiry date of the cert is within the acceptable range.
    74  	if naStart != nil && cert.NotAfter.Before(*naStart) {
    75  		return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart)
    76  	}
    77  	if naLimit != nil && !cert.NotAfter.Before(*naLimit) {
    78  		return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit)
    79  	}
    80  
    81  	if validationOpts.acceptOnlyCA && !cert.IsCA {
    82  		return nil, errors.New("only certificates with CA bit set are accepted")
    83  	}
    84  
    85  	now := validationOpts.currentTime
    86  	if now.IsZero() {
    87  		now = time.Now()
    88  	}
    89  	expired := now.After(cert.NotAfter)
    90  	if validationOpts.rejectExpired && expired {
    91  		return nil, errors.New("rejecting expired certificate")
    92  	}
    93  	if validationOpts.rejectUnexpired && !expired {
    94  		return nil, errors.New("rejecting unexpired certificate")
    95  	}
    96  
    97  	// Check for unwanted extension types, if required.
    98  	// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
    99  	// pre-calc this in there
   100  	if len(validationOpts.rejectExtIds) != 0 {
   101  		badIDs := make(map[string]bool)
   102  		for _, id := range validationOpts.rejectExtIds {
   103  			badIDs[id.String()] = true
   104  		}
   105  		for idx, ext := range cert.Extensions {
   106  			extOid := ext.Id.String()
   107  			if _, ok := badIDs[extOid]; ok {
   108  				return nil, fmt.Errorf("rejecting certificate containing extension %v at index %d", extOid, idx)
   109  			}
   110  		}
   111  	}
   112  
   113  	// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
   114  	// pre-calc this in there too.
   115  	if len(validationOpts.extKeyUsages) > 0 {
   116  		acceptEKUs := make(map[x509.ExtKeyUsage]bool)
   117  		for _, eku := range validationOpts.extKeyUsages {
   118  			acceptEKUs[eku] = true
   119  		}
   120  		good := false
   121  		for _, certEKU := range cert.ExtKeyUsage {
   122  			if _, ok := acceptEKUs[certEKU]; ok {
   123  				good = true
   124  				break
   125  			}
   126  		}
   127  		if !good {
   128  			return nil, fmt.Errorf("rejecting certificate without EKU in %v", validationOpts.extKeyUsages)
   129  		}
   130  	}
   131  
   132  	// We can now do the verification.  Use fairly lax options for verification, as
   133  	// CT is intended to observe certificates rather than police them.
   134  	verifyOpts := x509.VerifyOptions{
   135  		Roots:             validationOpts.trustedRoots.CertPool(),
   136  		CurrentTime:       now,
   137  		Intermediates:     intermediatePool.CertPool(),
   138  		DisableTimeChecks: true,
   139  		// Precertificates have the poison extension; also the Go library code does not
   140  		// support the standard PolicyConstraints extension (which is required to be marked
   141  		// critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions.
   142  		DisableCriticalExtensionChecks: true,
   143  		// Pre-issued precertificates have the Certificate Transparency EKU; also some
   144  		// leaves have unknown EKUs that should not be bounced just because the intermediate
   145  		// does not also have them (cf. https://github.com/golang/go/issues/24590) so
   146  		// disable EKU checks inside the x509 library, but we've already done our own check
   147  		// on the leaf above.
   148  		DisableEKUChecks: true,
   149  		// Path length checks get confused by the presence of an additional
   150  		// pre-issuer intermediate, so disable them.
   151  		DisablePathLenChecks:        true,
   152  		DisableNameConstraintChecks: true,
   153  		DisableNameChecks:           false,
   154  		KeyUsages:                   validationOpts.extKeyUsages,
   155  	}
   156  
   157  	verifiedChains, err := cert.Verify(verifyOpts)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	if len(verifiedChains) == 0 {
   163  		return nil, errors.New("no path to root found when trying to validate chains")
   164  	}
   165  
   166  	// Verify might have found multiple paths to roots. Now we check that we have a path that
   167  	// uses all the certs in the order they were submitted so as to comply with RFC 6962
   168  	// requirements detailed in Section 3.1.
   169  	for _, verifiedChain := range verifiedChains {
   170  		if chainsEquivalent(chain, verifiedChain) {
   171  			return verifiedChain, nil
   172  		}
   173  	}
   174  
   175  	return nil, errors.New("no RFC compliant path to root found when trying to validate chain")
   176  }
   177  
   178  func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool {
   179  	// The verified chain includes a root, but the input chain may or may not include a
   180  	// root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate
   181  	// or a certificate that chains to a known root certificate").
   182  	if len(inChain) != len(verifiedChain) && len(inChain) != (len(verifiedChain)-1) {
   183  		return false
   184  	}
   185  
   186  	for i, certInChain := range inChain {
   187  		if !certInChain.Equal(verifiedChain[i]) {
   188  			return false
   189  		}
   190  	}
   191  	return true
   192  }
   193  

View as plain text