forked from cloudflare/tls-tris
-
Notifications
You must be signed in to change notification settings - Fork 2
/
subcerts.go
392 lines (335 loc) · 12.6 KB
/
subcerts.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
385
386
387
388
389
390
391
392
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
// Delegated credentials for TLS
// (https://tools.ietf.org/html/draft-ietf-tls-subcerts-02) is an IETF Internet
// draft and proposed TLS extension. This allows a backend server to delegate
// TLS termination to a trusted frontend. If the client supports this extension,
// then the frontend may use a "delegated credential" as the signing key in the
// handshake. A delegated credential is a short lived key pair delegated to the
// server by an entity trusted by the client. Once issued, credentials can't be
// revoked; in order to mitigate risk in case the frontend is compromised, the
// credential is only valid for a short time (days, hours, or even minutes).
//
// This implements draft 02. This draft doesn't specify an object identifier for
// the X.509 extension; we use one assigned by Cloudflare. In addition, IANA has
// not assigned an extension ID for this extension; we picked up one that's not
// yet taken.
//
// TODO(cjpatton) Only ECDSA is supported with delegated credentials for now;
// we'd like to suppoort for EcDSA signatures once these have better support
// upstream.
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/asn1"
"encoding/binary"
"errors"
"fmt"
"time"
)
const (
// length of the public key field
dcPubKeyFieldLen = 3
dcMaxTTLSeconds = 60 * 60 * 24 * 7 // 7 days
dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second)
dcMaxPublicKeyLen = 1 << 24 // Bytes
dcMaxSignatureLen = 1 << 16 // Bytes
)
var errNoDelegationUsage = errors.New("certificate not authorized for delegation")
// delegationUsageId is the DelegationUsage X.509 extension OID
//
// NOTE(cjpatton) This OID is a child of Cloudflare's IANA-assigned OID.
var delegationUsageId = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 44}
// canDelegate returns true if a certificate can be used for delegated
// credentials.
func canDelegate(cert *x509.Certificate) bool {
// Check that the digitalSignature key usage is set.
if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 {
return false
}
// Check that the certificate has the DelegationUsage extension and that
// it's non-critical (per the spec).
for _, extension := range cert.Extensions {
if extension.Id.Equal(delegationUsageId) {
return true
}
}
return false
}
// credential stores the public components of a credential.
type credential struct {
// The serialized form of the credential.
raw []byte
// The amount of time for which the credential is valid. Specifically, the
// the credential expires `ValidTime` seconds after the `notBefore` of the
// delegation certificate. The delegator shall not issue delegated
// credentials that are valid for more than 7 days from the current time.
//
// When this data structure is serialized, this value is converted to a
// uint32 representing the duration in seconds.
validTime time.Duration
// The signature scheme associated with the delegated credential public key.
expectedCertVerifyAlgorithm SignatureScheme
// The version of TLS in which the credential will be used.
expectedVersion uint16
// The credential public key.
publicKey crypto.PublicKey
}
// isExpired returns true if the credential has expired. The end of the validity
// interval is defined as the delegator certificate's notBefore field (`start`)
// plus ValidTime seconds. This function simply checks that the current time
// (`now`) is before the end of the valdity interval.
func (cred *credential) isExpired(start, now time.Time) bool {
end := start.Add(cred.validTime)
return !now.Before(end)
}
// invalidTTL returns true if the credential's validity period is longer than the
// maximum permitted. This is defined by the certificate's notBefore field
// (`start`) plus the ValidTime, minus the current time (`now`).
func (cred *credential) invalidTTL(start, now time.Time) bool {
return cred.validTime > (now.Sub(start) + dcMaxTTL).Round(time.Second)
}
// marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure
// (as defined in the X.509 standard) for the credential.
func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) {
switch cred.expectedCertVerifyAlgorithm {
case ECDSAWithP256AndSHA256,
ECDSAWithP384AndSHA384,
ECDSAWithP521AndSHA512:
serializedPublicKey, err := x509.MarshalPKIXPublicKey(cred.publicKey)
if err != nil {
return nil, err
}
return serializedPublicKey, nil
default:
return nil, fmt.Errorf("unsupported signature scheme: 0x%04x", cred.expectedCertVerifyAlgorithm)
}
}
// marshal encodes a credential in the wire format specified in
// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02.
func (cred *credential) marshal() ([]byte, error) {
// The number of bytes comprising the DC parameters, which includes the
// validity time (4 bytes), the signature scheme of the public key (2 bytes), and
// the protocol version (2 bytes).
paramsLen := 8
// The first 4 bytes are the valid_time, scheme, and version fields.
serialized := make([]byte, paramsLen+dcPubKeyFieldLen)
binary.BigEndian.PutUint32(serialized, uint32(cred.validTime/time.Second))
binary.BigEndian.PutUint16(serialized[4:], uint16(cred.expectedCertVerifyAlgorithm))
binary.BigEndian.PutUint16(serialized[6:], cred.expectedVersion)
// Encode the public key and assert that the encoding is no longer than 2^16
// bytes (per the spec).
serializedPublicKey, err := cred.marshalSubjectPublicKeyInfo()
if err != nil {
return nil, err
}
if len(serializedPublicKey) > dcMaxPublicKeyLen {
return nil, errors.New("public key is too long")
}
// The next 3 bytes are the length of the public key field, which may be up
// to 2^24 bytes long.
putUint24(serialized[paramsLen:], len(serializedPublicKey))
// The remaining bytes are the public key itself.
serialized = append(serialized, serializedPublicKey...)
cred.raw = serialized
return serialized, nil
}
// unmarshalCredential decodes a credential and returns it.
func unmarshalCredential(serialized []byte) (*credential, error) {
// The number of bytes comprising the DC parameters.
paramsLen := 8
if len(serialized) < paramsLen+dcPubKeyFieldLen {
return nil, errors.New("credential is too short")
}
// Parse the valid_time, scheme, and version fields.
validTime := time.Duration(binary.BigEndian.Uint32(serialized)) * time.Second
scheme := SignatureScheme(binary.BigEndian.Uint16(serialized[4:]))
version := binary.BigEndian.Uint16(serialized[6:])
// Parse the SubjectPublicKeyInfo.
pk, err := x509.ParsePKIXPublicKey(serialized[paramsLen+dcPubKeyFieldLen:])
if err != nil {
return nil, err
}
if _, ok := pk.(*ecdsa.PublicKey); !ok {
return nil, fmt.Errorf("unsupported delegation key type: %T", pk)
}
return &credential{
raw: serialized,
validTime: validTime,
expectedCertVerifyAlgorithm: scheme,
expectedVersion: version,
publicKey: pk,
}, nil
}
// getCredentialLen returns the number of bytes comprising the serialized
// credential that starts at the beginning of the input slice. It returns an
// error if the input is too short to contain a credential.
func getCredentialLen(serialized []byte) (int, error) {
paramsLen := 8
if len(serialized) < paramsLen+dcPubKeyFieldLen {
return 0, errors.New("credential is too short")
}
// First several bytes are the valid_time, scheme, and version fields.
serialized = serialized[paramsLen:]
// The next 3 bytes are the length of the serialized public key, which may
// be up to 2^24 bytes in length.
serializedPublicKeyLen := getUint24(serialized)
serialized = serialized[dcPubKeyFieldLen:]
if len(serialized) < serializedPublicKeyLen {
return 0, errors.New("public key of credential is too short")
}
return paramsLen + dcPubKeyFieldLen + serializedPublicKeyLen, nil
}
// delegatedCredential stores a credential and its delegation.
type delegatedCredential struct {
raw []byte
// The credential, which contains a public and its validity time.
cred *credential
// The signature scheme used to sign the credential.
algorithm SignatureScheme
// The credential's delegation.
signature []byte
}
// ensureCertificateHasLeaf parses the leaf certificate if needed.
func ensureCertificateHasLeaf(cert *Certificate) error {
var err error
if cert.Leaf == nil {
if len(cert.Certificate[0]) == 0 {
return errors.New("missing leaf certificate")
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return err
}
}
return nil
}
// validate checks that that the signature is valid, that the credential hasn't
// expired, and that the TTL is valid. It also checks that certificate can be
// used for delegation.
func (dc *delegatedCredential) validate(cert *x509.Certificate, now time.Time) (bool, error) {
// Check that the cert can delegate.
if !canDelegate(cert) {
return false, errNoDelegationUsage
}
if dc.cred.isExpired(cert.NotBefore, now) {
return false, errors.New("credential has expired")
}
if dc.cred.invalidTTL(cert.NotBefore, now) {
return false, errors.New("credential TTL is invalid")
}
// Prepare the credential for verification.
rawCred, err := dc.cred.marshal()
if err != nil {
return false, err
}
hash := getHash(dc.algorithm)
in := prepareDelegation(hash, rawCred, cert.Raw, dc.algorithm)
// TODO(any) This code overlaps significantly with verifyHandshakeSignature()
// in ../auth.go. This should be refactored.
switch dc.algorithm {
case ECDSAWithP256AndSHA256,
ECDSAWithP384AndSHA384,
ECDSAWithP521AndSHA512:
pk, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return false, errors.New("expected ECDSA public key")
}
sig := new(ecdsaSignature)
if _, err = asn1.Unmarshal(dc.signature, sig); err != nil {
return false, err
}
return ecdsa.Verify(pk, in, sig.R, sig.S), nil
default:
return false, fmt.Errorf(
"unsupported signature scheme: 0x%04x", dc.algorithm)
}
}
// unmarshalDelegatedCredential decodes a DelegatedCredential structure.
func unmarshalDelegatedCredential(serialized []byte) (*delegatedCredential, error) {
// Get the length of the serialized credential that begins at the start of
// the input slice.
serializedCredentialLen, err := getCredentialLen(serialized)
if err != nil {
return nil, err
}
// Parse the credential.
cred, err := unmarshalCredential(serialized[:serializedCredentialLen])
if err != nil {
return nil, err
}
// Parse the signature scheme.
serialized = serialized[serializedCredentialLen:]
if len(serialized) < 4 {
return nil, errors.New("delegated credential is too short")
}
scheme := SignatureScheme(binary.BigEndian.Uint16(serialized))
// Parse the signature length.
serialized = serialized[2:]
serializedSignatureLen := binary.BigEndian.Uint16(serialized)
// Prase the signature.
serialized = serialized[2:]
if len(serialized) < int(serializedSignatureLen) {
return nil, errors.New("signature of delegated credential is too short")
}
sig := serialized[:serializedSignatureLen]
return &delegatedCredential{
raw: serialized,
cred: cred,
algorithm: scheme,
signature: sig,
}, nil
}
// getCurve maps the SignatureScheme to its corresponding elliptic.Curve.
func getCurve(scheme SignatureScheme) elliptic.Curve {
switch scheme {
case ECDSAWithP256AndSHA256:
return elliptic.P256()
case ECDSAWithP384AndSHA384:
return elliptic.P384()
case ECDSAWithP521AndSHA512:
return elliptic.P521()
default:
return nil
}
}
// getHash maps the SignatureScheme to its corresponding hash function.
//
// TODO(any) This function overlaps with hashForSignatureScheme in 13.go.
func getHash(scheme SignatureScheme) crypto.Hash {
switch scheme {
case ECDSAWithP256AndSHA256:
return crypto.SHA256
case ECDSAWithP384AndSHA384:
return crypto.SHA384
case ECDSAWithP521AndSHA512:
return crypto.SHA512
default:
return 0 // Unknown hash function
}
}
// prepareDelegation returns a hash of the message that the delegator is to
// sign. The inputs are the credential (`cred`), the DER-encoded delegator
// certificate (`delegatorCert`) and the signature scheme of the delegator
// (`delegatorAlgorithm`).
func prepareDelegation(hash crypto.Hash, cred, delegatorCert []byte, delegatorAlgorithm SignatureScheme) []byte {
h := hash.New()
// The header.
h.Write(bytes.Repeat([]byte{0x20}, 64))
h.Write([]byte("TLS, server delegated credentials"))
h.Write([]byte{0x00})
// The delegation certificate.
h.Write(delegatorCert)
// The credential.
h.Write(cred)
// The delegator signature scheme.
var serializedScheme [2]byte
binary.BigEndian.PutUint16(serializedScheme[:], uint16(delegatorAlgorithm))
h.Write(serializedScheme[:])
return h.Sum(nil)
}