forked from google/certificate-transparency-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cert_checker.go
195 lines (167 loc) · 6.79 KB
/
cert_checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Copyright 2016 Google Inc. 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"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509"
)
// OID of the non-critical extension used to mark pre-certificates, defined in RFC 6962
var ctPoisonExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
// Byte representation of ASN.1 NULL.
var asn1NullBytes = []byte{0x05, 0x00}
// 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 ctPoisonExtensionOID.Equal(ext.Id) {
if !ext.Critical || !bytes.Equal(asn1NullBytes, 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 := NewPEMCertPool()
for i, certBytes := range rawChain {
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
_, ok := err.(x509.NonFatalErrors)
if !ok {
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
// Check whether the expiry date of this certificate is within the acceptable
// range.
if naStart != nil && chain[0].NotAfter.Before(*naStart) {
return nil, fmt.Errorf("certificate NotAfter (%v) < %v", chain[0].NotAfter, *naStart)
}
if naLimit != nil && !chain[0].NotAfter.Before(*naLimit) {
return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", chain[0].NotAfter, *naLimit)
}
if validationOpts.acceptOnlyCA && !chain[0].IsCA {
return nil, errors.New("only certificates with CA bit set are accepted")
}
// We can now do the verification
verifyOpts := x509.VerifyOptions{
Roots: validationOpts.trustedRoots.CertPool(),
Intermediates: intermediatePool.CertPool(),
DisableTimeChecks: !validationOpts.rejectExpired,
KeyUsages: validationOpts.extKeyUsages,
}
// We don't want failures from Verify due to unknown critical extensions in the leaf,
// so clear them out.
chain[0].UnhandledCriticalExtensions = nil
for i := 1; i < len(chain); i++ {
// The PolicyConstraints extension is required to be marked critical
// (RFC 5280 s4.2.1.11), but is not parsed by the Go x509 library.
// To allow validation of chains where an intermediate has this extension,
// remove it from the unknown critical extensions slice.
for j, extOID := range chain[i].UnhandledCriticalExtensions {
if extOID.Equal(x509.OIDExtensionPolicyConstraints) {
chain[i].UnhandledCriticalExtensions = append(chain[i].UnhandledCriticalExtensions[:j], chain[i].UnhandledCriticalExtensions[j+1:]...)
break
}
}
}
// If the first intermediate has the CertificateTransparency EKU, remove it
// so that it doesn't affect EKU validity calculations. In particular, if
// the pre-issuer has just the CT EKU, then it should act as if it has an
// empty set of EKUs (and so allow any usage in the leaf).
havePreissuer := false
var originalEKUs []x509.ExtKeyUsage
if len(chain) > 1 {
for i, eku := range chain[1].ExtKeyUsage {
if eku == x509.ExtKeyUsageCertificateTransparency {
originalEKUs = chain[1].ExtKeyUsage
chain[1].ExtKeyUsage = append(chain[1].ExtKeyUsage[:i], chain[1].ExtKeyUsage[i+1:]...)
havePreissuer = true
break
}
}
}
if havePreissuer {
// Any MaxPathLen constraints on CA certificates may not allow for the presence
// of an extra pre-issuer intermediate CA cert. Allow for this by adding one.
for i := 2; i < len(chain); i++ {
if chain[i].MaxPathLen > 0 || (chain[i].MaxPathLen == 0 && chain[i].MaxPathLenZero) {
chain[i].MaxPathLen++
}
}
}
verifiedChains, err := chain[0].Verify(verifyOpts)
if err != nil {
return nil, err
}
if havePreissuer {
// Restore the preissuer EKUs so the returned chain looks like the submission
// (and so can be fed to ct.MerkleTreeLeafFromChain(), which also looks for the
// pre-issuer EKU in chain[1]).
chain[1].ExtKeyUsage = originalEKUs
// Although it shouldn't affect any serialization/verification, restore any
// MaxPathLen values we have modified so the parsed certs stay in sync with
// the associated DER data.
for i := 2; i < len(chain); i++ {
if chain[i].MaxPathLen > 0 {
chain[i].MaxPathLen--
}
}
}
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
}