|
| 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 sctfe |
| 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 | +var ( |
| 29 | + ErrNoRFCCompliantPathFound = errors.New("no RFC compliant path to root found when trying to validate chain") |
| 30 | +) |
| 31 | + |
| 32 | +// isPrecertificate tests if a certificate is a pre-certificate as defined in CT. |
| 33 | +// An error is returned if the CT extension is present but is not ASN.1 NULL as defined |
| 34 | +// by the spec. |
| 35 | +func isPrecertificate(cert *x509.Certificate) (bool, error) { |
| 36 | + for _, ext := range cert.Extensions { |
| 37 | + if x509.OIDExtensionCTPoison.Equal(ext.Id) { |
| 38 | + if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) { |
| 39 | + return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext) |
| 40 | + } |
| 41 | + |
| 42 | + return true, nil |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + return false, nil |
| 47 | +} |
| 48 | + |
| 49 | +// validateChain takes the certificate chain as it was parsed from a JSON request. Ensures all |
| 50 | +// elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the |
| 51 | +// end entity certificate in the chain to a trusted root cert, possibly using the intermediates |
| 52 | +// supplied in the chain. Then applies the RFC requirement that the path must involve all |
| 53 | +// the submitted chain in the order of submission. |
| 54 | +func validateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) { |
| 55 | + // First make sure the certs parse as X.509 |
| 56 | + chain := make([]*x509.Certificate, 0, len(rawChain)) |
| 57 | + intermediatePool := x509util.NewPEMCertPool() |
| 58 | + |
| 59 | + for i, certBytes := range rawChain { |
| 60 | + cert, err := x509.ParseCertificate(certBytes) |
| 61 | + if x509.IsFatal(err) { |
| 62 | + return nil, err |
| 63 | + } |
| 64 | + |
| 65 | + chain = append(chain, cert) |
| 66 | + |
| 67 | + // All but the first cert form part of the intermediate pool |
| 68 | + if i > 0 { |
| 69 | + intermediatePool.AddCert(cert) |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + naStart := validationOpts.notAfterStart |
| 74 | + naLimit := validationOpts.notAfterLimit |
| 75 | + cert := chain[0] |
| 76 | + |
| 77 | + // Check whether the expiry date of the cert is within the acceptable range. |
| 78 | + if naStart != nil && cert.NotAfter.Before(*naStart) { |
| 79 | + return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart) |
| 80 | + } |
| 81 | + if naLimit != nil && !cert.NotAfter.Before(*naLimit) { |
| 82 | + return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit) |
| 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, ErrNoRFCCompliantPathFound |
| 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 | +} |
0 commit comments