/
issuer.go
384 lines (335 loc) · 12.9 KB
/
issuer.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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package issuance
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"strings"
"github.com/jmhodges/clock"
"golang.org/x/crypto/ocsp"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/linter"
"github.com/letsencrypt/boulder/privatekey"
"github.com/letsencrypt/pkcs11key/v4"
)
// ----- Name ID -----
// NameID is a statistically-unique small ID which can be computed from
// both CA and end-entity certs to link them together into a validation chain.
// It is computed as a truncated hash over the issuer Subject Name bytes, or
// over the end-entity's Issuer Name bytes, which are required to be equal.
type NameID int64
// SubjectNameID returns the NameID (a truncated hash over the raw bytes of a
// Distinguished Name) of this issuer certificate's Subject. Useful for storing
// as a lookup key in contexts that don't expect hash collisions.
func SubjectNameID(ic *Certificate) NameID {
return truncatedHash(ic.RawSubject)
}
// IssuerNameID returns the IssuerNameID (a truncated hash over the raw bytes
// of the Issuer Distinguished Name) of the given end-entity certificate.
// Useful for performing lookups in contexts that don't expect hash collisions.
func IssuerNameID(ee *x509.Certificate) NameID {
return truncatedHash(ee.RawIssuer)
}
// ResponderNameID returns the NameID (a truncated hash over the raw
// bytes of the Responder Distinguished Name) of the given OCSP Response.
// As per the OCSP spec, it is technically possible for this field to not be
// populated: the OCSP Response can instead contain a SHA-1 hash of the Issuer
// Public Key as the Responder ID. However, all OCSP responses that we produce
// contain it, because the Go stdlib always includes it.
func ResponderNameID(resp *ocsp.Response) NameID {
return truncatedHash(resp.RawResponderName)
}
// truncatedHash computes a truncated SHA1 hash across arbitrary bytes. Uses
// SHA1 because that is the algorithm most commonly used in OCSP requests.
// PURPOSEFULLY NOT EXPORTED. Exists only to ensure that the implementations of
// SubjectNameID(), IssuerNameID(), and ResponderNameID never diverge. Use those
// instead.
func truncatedHash(name []byte) NameID {
h := crypto.SHA1.New()
h.Write(name)
s := h.Sum(nil)
return NameID(big.NewInt(0).SetBytes(s[:7]).Int64())
}
// ----- Issuer Certificates -----
// Certificate embeds an *x509.Certificate and represents the added semantics
// that this certificate is a CA certificate.
type Certificate struct {
*x509.Certificate
// nameID is stored here simply for the sake of precomputation.
nameID NameID
}
// NameID is equivalent to SubjectNameID(ic), but faster because it is
// precomputed.
func (ic *Certificate) NameID() NameID {
return ic.nameID
}
// NewCertificate wraps an in-memory cert in an issuance.Certificate, marking it
// as an issuer cert. It may fail if the certificate does not contain the
// attributes expected of an issuer certificate.
func NewCertificate(ic *x509.Certificate) (*Certificate, error) {
if !ic.IsCA {
return nil, errors.New("certificate is not a CA certificate")
}
res := Certificate{ic, 0}
res.nameID = SubjectNameID(&res)
return &res, nil
}
func LoadCertificate(path string) (*Certificate, error) {
cert, err := core.LoadCert(path)
if err != nil {
return nil, fmt.Errorf("loading issuer certificate: %w", err)
}
return NewCertificate(cert)
}
// LoadChain takes a list of filenames containing pem-formatted certificates,
// and returns a chain representing all of those certificates in order. It
// ensures that the resulting chain is valid. The final file is expected to be
// a root certificate, which the chain will be verified against, but which will
// not be included in the resulting chain.
func LoadChain(certFiles []string) ([]*Certificate, error) {
if len(certFiles) < 2 {
return nil, errors.New(
"each chain must have at least two certificates: an intermediate and a root")
}
// Pre-load all the certificates to make validation easier.
certs := make([]*Certificate, len(certFiles))
var err error
for i := range len(certFiles) {
certs[i], err = LoadCertificate(certFiles[i])
if err != nil {
return nil, fmt.Errorf("failed to load certificate %q: %w", certFiles[i], err)
}
}
// Iterate over all certs except for the last, checking that their signature
// comes from the next cert in the list.
chain := make([]*Certificate, len(certFiles)-1)
for i := range len(certs) - 1 {
err = certs[i].CheckSignatureFrom(certs[i+1].Certificate)
if err != nil {
return nil, fmt.Errorf("failed to verify signature from %q to %q (%q to %q): %w",
certs[i+1].Subject, certs[i].Subject, certFiles[i+1], certFiles[i], err)
}
chain[i] = certs[i]
}
// Verify that the last cert is self-signed.
lastCert := certs[len(certs)-1]
err = lastCert.CheckSignatureFrom(lastCert.Certificate)
if err != nil {
return nil, fmt.Errorf(
"final cert in chain (%q; %q) must be self-signed (used only for validation): %w",
lastCert.Subject, certFiles[len(certFiles)-1], err)
}
return chain, nil
}
// ----- Issuers with Signers -----
// IssuerConfig describes the constraints on and URLs used by a single issuer.
type IssuerConfig struct {
// Active determines if the issuer can be used to sign precertificates. All
// issuers, regardless of this field, can be used to sign final certificates
// (for which an issuance token is presented), OCSP responses, and CRLs.
// All Active issuers of a given key type (RSA or ECDSA) are part of a pool
// and each precertificate will be issued randomly from a selected pool.
// The selection of which pool depends on the precertificate's key algorithm,
// the ECDSAForAll feature flag, and the ECDSAAllowListFilename config field.
Active bool
// UseForRSALeaves is a synonym for Active. Note that, despite the name,
// setting this field to true cannot add an issuer to a pool different than
// its key type. An active issuer will always be part of a pool based on its
// key type.
//
// Deprecated: use Active instead.
UseForRSALeaves bool
// UseForECDSALeaves is a synonym for Active. Note that, despite the name,
// setting this field to true cannot add an issuer to a pool different than
// its key type. An active issuer will always be part of a pool based on its
// key type.
//
// Deprecated: use Active instead.
UseForECDSALeaves bool
IssuerURL string `validate:"required,url"`
OCSPURL string `validate:"required,url"`
CRLURLBase string `validate:"omitempty,url,startswith=http://,endswith=/"`
Location IssuerLoc
}
// IssuerLoc describes the on-disk location and parameters that an issuer
// should use to retrieve its certificate and private key.
// Only one of File, ConfigFile, or PKCS11 should be set.
type IssuerLoc struct {
// A file from which a private key will be read and parsed.
File string `validate:"required_without_all=ConfigFile PKCS11"`
// A file from which a pkcs11key.Config will be read and parsed, if File is not set.
ConfigFile string `validate:"required_without_all=PKCS11 File"`
// An in-memory pkcs11key.Config, which will be used if ConfigFile is not set.
PKCS11 *pkcs11key.Config `validate:"required_without_all=ConfigFile File"`
// A file from which a certificate will be read and parsed.
CertFile string `validate:"required"`
// Number of sessions to open with the HSM. For maximum performance,
// this should be equal to the number of cores in the HSM. Defaults to 1.
NumSessions int
}
// Issuer is capable of issuing new certificates.
type Issuer struct {
// TODO(#7159): make Cert, Signer, and Linter private when all signing ops
// are handled through this package (e.g. the CA doesn't need direct access
// while signing CRLs anymore).
Cert *Certificate
Signer crypto.Signer
Linter *linter.Linter
keyAlg x509.PublicKeyAlgorithm
sigAlg x509.SignatureAlgorithm
active bool
// Used to set the Authority Information Access caIssuers URL in issued
// certificates.
issuerURL string
// Used to set the Authority Information Access ocsp URL in issued
// certificates.
ocspURL string
// Used to set the Issuing Distribution Point extension in issued CRLs
// *and* (eventually) the CRL Distribution Point extension in issued certs.
crlURLBase string
clk clock.Clock
}
// newIssuer constructs a new Issuer from the in-memory certificate and signer.
// It exists as a helper for LoadIssuer to make testing simpler.
func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk clock.Clock) (*Issuer, error) {
var keyAlg x509.PublicKeyAlgorithm
var sigAlg x509.SignatureAlgorithm
switch k := cert.PublicKey.(type) {
case *rsa.PublicKey:
keyAlg = x509.RSA
sigAlg = x509.SHA256WithRSA
case *ecdsa.PublicKey:
keyAlg = x509.ECDSA
switch k.Curve {
case elliptic.P256():
sigAlg = x509.ECDSAWithSHA256
case elliptic.P384():
sigAlg = x509.ECDSAWithSHA384
default:
return nil, fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
}
default:
return nil, errors.New("unsupported issuer key type")
}
if config.IssuerURL == "" {
return nil, errors.New("Issuer URL is required")
}
if config.OCSPURL == "" {
return nil, errors.New("OCSP URL is required")
}
if config.CRLURLBase != "" {
if !strings.HasPrefix(config.CRLURLBase, "http://") {
return nil, fmt.Errorf("crlURLBase must use HTTP scheme, got %q", config.CRLURLBase)
}
if !strings.HasSuffix(config.CRLURLBase, "/") {
return nil, fmt.Errorf("crlURLBase must end with exactly one forward slash, got %q", config.CRLURLBase)
}
}
// We require that all of our issuers be capable of both issuing certs and
// providing revocation information.
if cert.KeyUsage&x509.KeyUsageCertSign == 0 {
return nil, errors.New("end-entity signing cert does not have keyUsage certSign")
}
if cert.KeyUsage&x509.KeyUsageCRLSign == 0 {
return nil, errors.New("end-entity signing cert does not have keyUsage crlSign")
}
if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
return nil, errors.New("end-entity signing cert does not have keyUsage digitalSignature")
}
lintSigner, err := linter.New(cert.Certificate, signer)
if err != nil {
return nil, fmt.Errorf("creating fake lint signer: %w", err)
}
i := &Issuer{
Cert: cert,
Signer: signer,
Linter: lintSigner,
keyAlg: keyAlg,
sigAlg: sigAlg,
active: config.Active || config.UseForRSALeaves || config.UseForECDSALeaves,
issuerURL: config.IssuerURL,
ocspURL: config.OCSPURL,
crlURLBase: config.CRLURLBase,
clk: clk,
}
return i, nil
}
// KeyType returns either x509.RSA or x509.ECDSA, depending on whether the
// issuer has an RSA or ECDSA keypair. This is useful for determining which
// issuance requests should be routed to this issuer.
func (i *Issuer) KeyType() x509.PublicKeyAlgorithm {
return i.keyAlg
}
// IsActive is true if the issuer is willing to issue precertificates, and false
// if the issuer is only willing to issue final certificates, OCSP, and CRLs.
func (i *Issuer) IsActive() bool {
return i.active
}
// Name provides the Common Name specified in the issuer's certificate.
func (i *Issuer) Name() string {
return i.Cert.Subject.CommonName
}
// NameID provides the NameID of the issuer's certificate.
func (i *Issuer) NameID() NameID {
return i.Cert.NameID()
}
// LoadIssuer constructs a new Issuer, loading its certificate from disk and its
// private key material from the indicated location. It also verifies that the
// issuer metadata (such as AIA URLs) is well-formed.
func LoadIssuer(config IssuerConfig, clk clock.Clock) (*Issuer, error) {
issuerCert, err := LoadCertificate(config.Location.CertFile)
if err != nil {
return nil, err
}
signer, err := loadSigner(config.Location, issuerCert.PublicKey)
if err != nil {
return nil, err
}
if !core.KeyDigestEquals(signer.Public(), issuerCert.PublicKey) {
return nil, fmt.Errorf("issuer key did not match issuer cert %q", config.Location.CertFile)
}
return newIssuer(config, issuerCert, signer, clk)
}
func loadSigner(location IssuerLoc, pubkey crypto.PublicKey) (crypto.Signer, error) {
if location.File == "" && location.ConfigFile == "" && location.PKCS11 == nil {
return nil, errors.New("must supply File, ConfigFile, or PKCS11")
}
if location.File != "" {
signer, _, err := privatekey.Load(location.File)
if err != nil {
return nil, err
}
return signer, nil
}
var pkcs11Config *pkcs11key.Config
if location.ConfigFile != "" {
contents, err := os.ReadFile(location.ConfigFile)
if err != nil {
return nil, err
}
pkcs11Config = new(pkcs11key.Config)
err = json.Unmarshal(contents, pkcs11Config)
if err != nil {
return nil, err
}
} else {
pkcs11Config = location.PKCS11
}
if pkcs11Config.Module == "" ||
pkcs11Config.TokenLabel == "" ||
pkcs11Config.PIN == "" {
return nil, fmt.Errorf("missing a field in pkcs11Config %#v", pkcs11Config)
}
numSessions := location.NumSessions
if numSessions <= 0 {
numSessions = 1
}
return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
pkcs11Config.TokenLabel, pkcs11Config.PIN, pubkey)
}