-
Notifications
You must be signed in to change notification settings - Fork 28
/
cert_validations.go
306 lines (279 loc) · 10.8 KB
/
cert_validations.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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// Copyright The Notary Project Authors.
// 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 x509
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"strings"
"time"
)
// ValidateCodeSigningCertChain takes an ordered code-signing certificate chain
// and validates issuance from leaf to root
// Validates certificates according to this spec:
// https://github.com/notaryproject/notaryproject/blob/main/specs/signature-specification.md#certificate-requirements
func ValidateCodeSigningCertChain(certChain []*x509.Certificate, signingTime *time.Time) error {
return validateCertChain(certChain, 0, signingTime)
}
// ValidateTimeStampingCertChain takes an ordered time-stamping certificate
// chain and validates issuance from leaf to root
// Validates certificates according to this spec:
// https://github.com/notaryproject/notaryproject/blob/main/specs/signature-specification.md#certificate-requirements
func ValidateTimeStampingCertChain(certChain []*x509.Certificate, signingTime *time.Time) error {
return validateCertChain(certChain, x509.ExtKeyUsageTimeStamping, signingTime)
}
func validateCertChain(certChain []*x509.Certificate, expectedLeafEku x509.ExtKeyUsage, signingTime *time.Time) error {
if len(certChain) < 1 {
return errors.New("certificate chain must contain at least one certificate")
}
// For self-signed signing certificate (not a CA)
if len(certChain) == 1 {
cert := certChain[0]
if signedTimeError := validateSigningTime(cert, signingTime); signedTimeError != nil {
return signedTimeError
}
if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
return fmt.Errorf("invalid self-signed certificate. subject: %q. Error: %w", cert.Subject, err)
}
if err := validateLeafCertificate(cert, expectedLeafEku); err != nil {
return fmt.Errorf("invalid self-signed certificate. Error: %w", err)
}
return nil
}
for i, cert := range certChain {
if signedTimeError := validateSigningTime(cert, signingTime); signedTimeError != nil {
return signedTimeError
}
if i == len(certChain)-1 {
selfSigned, selfSignedError := isSelfSigned(cert)
if selfSignedError != nil {
return fmt.Errorf("root certificate with subject %q is invalid or not self-signed. Certificate chain must end with a valid self-signed root certificate. Error: %v", cert.Subject, selfSignedError)
}
if !selfSigned {
return fmt.Errorf("root certificate with subject %q is not self-signed. Certificate chain must end with a valid self-signed root certificate", cert.Subject)
}
} else {
// This is to avoid extra/redundant multiple root cert at the end
// of certificate-chain
selfSigned, selfSignedError := isSelfSigned(cert)
// not checking selfSignedError != nil here because we expect
// a non-nil err. For a non-root certificate, it shouldn't be
// self-signed, hence CheckSignatureFrom would return a non-nil
// error.
if selfSignedError == nil && selfSigned {
if i == 0 {
return fmt.Errorf("leaf certificate with subject %q is self-signed. Certificate chain must not contain self-signed leaf certificate", cert.Subject)
}
return fmt.Errorf("intermediate certificate with subject %q is self-signed. Certificate chain must not contain self-signed intermediate certificate", cert.Subject)
}
parentCert := certChain[i+1]
issuedBy, issuedByError := isIssuedBy(cert, parentCert)
if issuedByError != nil {
return fmt.Errorf("invalid certificates or certificate with subject %q is not issued by %q. Error: %v", cert.Subject, parentCert.Subject, issuedByError)
}
if !issuedBy {
return fmt.Errorf("certificate with subject %q is not issued by %q", cert.Subject, parentCert.Subject)
}
}
if i == 0 {
if err := validateLeafCertificate(cert, expectedLeafEku); err != nil {
return err
}
} else {
if err := validateCACertificate(cert, i-1); err != nil {
return err
}
}
}
return nil
}
func isSelfSigned(cert *x509.Certificate) (bool, error) {
return isIssuedBy(cert, cert)
}
func isIssuedBy(subject *x509.Certificate, issuer *x509.Certificate) (bool, error) {
if err := subject.CheckSignatureFrom(issuer); err != nil {
return false, err
}
return bytes.Equal(issuer.RawSubject, subject.RawIssuer), nil
}
func validateSigningTime(cert *x509.Certificate, signingTime *time.Time) error {
if signingTime != nil && (signingTime.Before(cert.NotBefore) || signingTime.After(cert.NotAfter)) {
return fmt.Errorf("certificate with subject %q was invalid at signing time of %s. Certificate is valid from [%s] to [%s]",
cert.Subject, signingTime.UTC(), cert.NotBefore.UTC(), cert.NotAfter.UTC())
}
return nil
}
func validateCACertificate(cert *x509.Certificate, expectedPathLen int) error {
if err := validateCABasicConstraints(cert, expectedPathLen); err != nil {
return err
}
return validateCAKeyUsage(cert)
}
func validateLeafCertificate(cert *x509.Certificate, expectedEku x509.ExtKeyUsage) error {
if err := validateLeafBasicConstraints(cert); err != nil {
return err
}
if err := validateLeafKeyUsage(cert); err != nil {
return err
}
if err := validateExtendedKeyUsage(cert, expectedEku); err != nil {
return err
}
return validateKeyLength(cert)
}
func validateCABasicConstraints(cert *x509.Certificate, expectedPathLen int) error {
if !cert.BasicConstraintsValid || !cert.IsCA {
return fmt.Errorf("certificate with subject %q: ca field in basic constraints must be present, critical, and set to true", cert.Subject)
}
maxPathLen := cert.MaxPathLen
isMaxPathLenPresent := maxPathLen > 0 || (maxPathLen == 0 && cert.MaxPathLenZero)
if isMaxPathLenPresent && maxPathLen < expectedPathLen {
return fmt.Errorf("certificate with subject %q: expected path length of %d but certificate has path length %d instead", cert.Subject, expectedPathLen, maxPathLen)
}
return nil
}
func validateLeafBasicConstraints(cert *x509.Certificate) error {
if cert.BasicConstraintsValid && cert.IsCA {
return fmt.Errorf("certificate with subject %q: if the basic constraints extension is present, the ca field must be set to false", cert.Subject)
}
return nil
}
func validateCAKeyUsage(cert *x509.Certificate) error {
if err := validateKeyUsagePresent(cert); err != nil {
return err
}
if cert.KeyUsage&x509.KeyUsageCertSign == 0 {
return fmt.Errorf("certificate with subject %q: key usage must have the bit positions for key cert sign set", cert.Subject)
}
return nil
}
func validateLeafKeyUsage(cert *x509.Certificate) error {
if err := validateKeyUsagePresent(cert); err != nil {
return err
}
if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
return fmt.Errorf("The certificate with subject %q is invalid. The key usage must have the bit positions for \"Digital Signature\" set", cert.Subject)
}
var invalidKeyUsages []string
if cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"KeyEncipherment"`)
}
if cert.KeyUsage&x509.KeyUsageDataEncipherment != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"DataEncipherment"`)
}
if cert.KeyUsage&x509.KeyUsageKeyAgreement != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"KeyAgreement"`)
}
if cert.KeyUsage&x509.KeyUsageCertSign != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"CertSign"`)
}
if cert.KeyUsage&x509.KeyUsageCRLSign != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"CRLSign"`)
}
if cert.KeyUsage&x509.KeyUsageEncipherOnly != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"EncipherOnly"`)
}
if cert.KeyUsage&x509.KeyUsageDecipherOnly != 0 {
invalidKeyUsages = append(invalidKeyUsages, `"DecipherOnly"`)
}
if len(invalidKeyUsages) > 0 {
return fmt.Errorf("The certificate with subject %q is invalid. The key usage must be \"Digital Signature\" only, but found %s", cert.Subject, strings.Join(invalidKeyUsages, ", "))
}
return nil
}
func validateKeyUsagePresent(cert *x509.Certificate) error {
keyUsageExtensionOid := []int{2, 5, 29, 15}
var hasKeyUsageExtension bool
for _, ext := range cert.Extensions {
if ext.Id.Equal(keyUsageExtensionOid) {
if !ext.Critical {
return fmt.Errorf("certificate with subject %q: key usage extension must be marked critical", cert.Subject)
}
hasKeyUsageExtension = true
break
}
}
if !hasKeyUsageExtension {
return fmt.Errorf("certificate with subject %q: key usage extension must be present", cert.Subject)
}
return nil
}
func validateExtendedKeyUsage(cert *x509.Certificate, expectedEku x509.ExtKeyUsage) error {
if len(cert.ExtKeyUsage) <= 0 {
return nil
}
excludedEkus := []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageEmailProtection,
x509.ExtKeyUsageOCSPSigning,
}
if expectedEku == 0 {
excludedEkus = append(excludedEkus, x509.ExtKeyUsageTimeStamping)
} else if expectedEku == x509.ExtKeyUsageTimeStamping {
excludedEkus = append(excludedEkus, x509.ExtKeyUsageCodeSigning)
}
var hasExpectedEku bool
for _, certEku := range cert.ExtKeyUsage {
if certEku == expectedEku {
hasExpectedEku = true
continue
}
for _, excludedEku := range excludedEkus {
if certEku == excludedEku {
return fmt.Errorf("certificate with subject %q: extended key usage must not contain %s eku", cert.Subject, ekuToString(excludedEku))
}
}
}
if expectedEku != 0 && !hasExpectedEku {
return fmt.Errorf("certificate with subject %q: extended key usage must contain %s eku", cert.Subject, ekuToString(expectedEku))
}
return nil
}
func validateKeyLength(cert *x509.Certificate) error {
switch key := cert.PublicKey.(type) {
case *rsa.PublicKey:
if key.N.BitLen() < 2048 {
return fmt.Errorf("certificate with subject %q: rsa public key length must be 2048 bits or higher", cert.Subject)
}
case *ecdsa.PublicKey:
if key.Params().N.BitLen() < 256 {
return fmt.Errorf("certificate with subject %q: ecdsa public key length must be 256 bits or higher", cert.Subject)
}
}
return nil
}
func ekuToString(eku x509.ExtKeyUsage) string {
switch eku {
case x509.ExtKeyUsageAny:
return "Any"
case x509.ExtKeyUsageServerAuth:
return "ServerAuth"
case x509.ExtKeyUsageClientAuth:
return "ClientAuth"
case x509.ExtKeyUsageOCSPSigning:
return "OCSPSigning"
case x509.ExtKeyUsageEmailProtection:
return "EmailProtection"
case x509.ExtKeyUsageCodeSigning:
return "CodeSigning"
case x509.ExtKeyUsageTimeStamping:
return "TimeStamping"
default:
return fmt.Sprintf("%d", int(eku))
}
}