forked from adamlaska/boulder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinter.go
More file actions
189 lines (177 loc) · 7.37 KB
/
linter.go
File metadata and controls
189 lines (177 loc) · 7.37 KB
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
package linter
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"strings"
zlintx509 "github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
_ "github.com/letsencrypt/boulder/linter/lints/all"
_ "github.com/letsencrypt/boulder/linter/lints/intermediate"
_ "github.com/letsencrypt/boulder/linter/lints/root"
_ "github.com/letsencrypt/boulder/linter/lints/subscriber"
)
// Check accomplishes the entire process of linting: it generates a throwaway
// signing key, uses that to create a throwaway cert, and runs a default set
// of lints (everything except for the ETSI and EV lints) against it. This is
// the primary public interface of this package, but it can be inefficient;
// creating a new signer and a new lint registry are expensive operations which
// performance-sensitive clients may want to cache.
func Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) error {
linter, err := New(realIssuer, realSigner, skipLints)
if err != nil {
return err
}
return linter.Check(tbs, subjectPubKey)
}
// Linter is capable of linting a to-be-signed (TBS) certificate. It does so by
// signing that certificate with a throwaway private key and a fake issuer whose
// public key matches the throwaway private key, and then running the resulting
// throwaway certificate through a registry of zlint lints.
type Linter struct {
issuer *x509.Certificate
signer crypto.Signer
registry lint.Registry
}
// New constructs a Linter. It uses the provided real certificate and signer
// (private key) to generate a matching fake keypair and issuer cert that will
// be used to sign the lint certificate. It uses the provided list of lint names
// to skip to filter the zlint global registry to only those lints which should
// be run.
func New(realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) (*Linter, error) {
lintSigner, err := makeSigner(realSigner)
if err != nil {
return nil, err
}
lintIssuer, err := makeIssuer(realIssuer, lintSigner)
if err != nil {
return nil, err
}
reg, err := makeRegistry(skipLints)
if err != nil {
return nil, err
}
return &Linter{lintIssuer, lintSigner, reg}, nil
}
// Check signs the given TBS certificate using the Linter's fake issuer cert and
// private key, then runs the resulting certificate through all non-filtered
// lints. It returns an error if any lint fails.
func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) error {
cert, err := makeLintCert(tbs, subjectPubKey, l.issuer, l.signer)
if err != nil {
return err
}
return check(cert, l.registry)
}
func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
var lintSigner crypto.Signer
var err error
switch k := realSigner.Public().(type) {
case *rsa.PublicKey:
lintSigner, err = rsa.GenerateKey(rand.Reader, k.Size()*8)
if err != nil {
return nil, fmt.Errorf("failed to create RSA lint signer: %w", err)
}
case *ecdsa.PublicKey:
lintSigner, err = ecdsa.GenerateKey(k.Curve, rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to create ECDSA lint signer: %w", err)
}
default:
return nil, fmt.Errorf("unsupported lint signer type: %T", k)
}
return lintSigner, nil
}
func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.Certificate, error) {
lintIssuerTBS := &x509.Certificate{
// This is the full list of attributes that x509.CreateCertificate() says it
// carries over from the template. Constructing this TBS certificate in
// this way ensures that the resulting lint issuer is as identical to the
// real issuer as we can get, without sharing a public key.
AuthorityKeyId: realIssuer.AuthorityKeyId,
BasicConstraintsValid: realIssuer.BasicConstraintsValid,
CRLDistributionPoints: realIssuer.CRLDistributionPoints,
DNSNames: realIssuer.DNSNames,
EmailAddresses: realIssuer.EmailAddresses,
ExcludedDNSDomains: realIssuer.ExcludedDNSDomains,
ExcludedEmailAddresses: realIssuer.ExcludedEmailAddresses,
ExcludedIPRanges: realIssuer.ExcludedIPRanges,
ExcludedURIDomains: realIssuer.ExcludedURIDomains,
ExtKeyUsage: realIssuer.ExtKeyUsage,
ExtraExtensions: realIssuer.ExtraExtensions,
IPAddresses: realIssuer.IPAddresses,
IsCA: realIssuer.IsCA,
IssuingCertificateURL: realIssuer.IssuingCertificateURL,
KeyUsage: realIssuer.KeyUsage,
MaxPathLen: realIssuer.MaxPathLen,
MaxPathLenZero: realIssuer.MaxPathLenZero,
NotAfter: realIssuer.NotAfter,
NotBefore: realIssuer.NotBefore,
OCSPServer: realIssuer.OCSPServer,
PermittedDNSDomains: realIssuer.PermittedDNSDomains,
PermittedDNSDomainsCritical: realIssuer.PermittedDNSDomainsCritical,
PermittedEmailAddresses: realIssuer.PermittedEmailAddresses,
PermittedIPRanges: realIssuer.PermittedIPRanges,
PermittedURIDomains: realIssuer.PermittedURIDomains,
PolicyIdentifiers: realIssuer.PolicyIdentifiers,
SerialNumber: realIssuer.SerialNumber,
SignatureAlgorithm: realIssuer.SignatureAlgorithm,
Subject: realIssuer.Subject,
SubjectKeyId: realIssuer.SubjectKeyId,
URIs: realIssuer.URIs,
UnknownExtKeyUsage: realIssuer.UnknownExtKeyUsage,
}
lintIssuerBytes, err := x509.CreateCertificate(rand.Reader, lintIssuerTBS, lintIssuerTBS, lintSigner.Public(), lintSigner)
if err != nil {
return nil, fmt.Errorf("failed to create lint issuer: %w", err)
}
lintIssuer, err := x509.ParseCertificate(lintIssuerBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse lint issuer: %w", err)
}
return lintIssuer, nil
}
func makeRegistry(skipLints []string) (lint.Registry, error) {
reg, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
ExcludeNames: skipLints,
ExcludeSources: []lint.LintSource{
// Excluded because Boulder does not issue EV certs.
lint.CABFEVGuidelines,
// Excluded because Boulder does not use the
// ETSI EN 319 412-5 qcStatements extension.
lint.EtsiEsi,
},
})
if err != nil {
return nil, fmt.Errorf("failed to create lint registry: %w", err)
}
return reg, nil
}
func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer *x509.Certificate, signer crypto.Signer) (*zlintx509.Certificate, error) {
lintCertBytes, err := x509.CreateCertificate(rand.Reader, tbs, issuer, subjectPubKey, signer)
if err != nil {
return nil, fmt.Errorf("failed to create lint certificate: %w", err)
}
lintCert, err := zlintx509.ParseCertificate(lintCertBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse lint certificate: %w", err)
}
return lintCert, nil
}
func check(lintCert *zlintx509.Certificate, lints lint.Registry) error {
lintRes := zlint.LintCertificateEx(lintCert, lints)
if lintRes.NoticesPresent || lintRes.WarningsPresent || lintRes.ErrorsPresent || lintRes.FatalsPresent {
var failedLints []string
for lintName, result := range lintRes.Results {
if result.Status > lint.Pass {
failedLints = append(failedLints, lintName)
}
}
return fmt.Errorf("failed lints: %s", strings.Join(failedLints, ", "))
}
return nil
}