-
Notifications
You must be signed in to change notification settings - Fork 71
/
keys.go
522 lines (469 loc) · 18.3 KB
/
keys.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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
// Package client contains some high-level TPM 2.0 functions.
package client
import (
"bytes"
"crypto"
"crypto/subtle"
"crypto/x509"
"errors"
"fmt"
"io"
"github.com/google/go-tpm-tools/internal"
pb "github.com/google/go-tpm-tools/proto/tpm"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// Key wraps an active asymmetric TPM2 key. This can either be a signing key or
// an encryption key. Users of Key should be sure to call Close() when the Key
// is no longer needed, so that the underlying TPM handle can be freed.
// Concurrent accesses on Key are not safe, with the exception of the
// Sign method called on the crypto.Signer returned by Key.GetSigner.
type Key struct {
rw io.ReadWriter
handle tpmutil.Handle
pubArea tpm2.Public
pubKey crypto.PublicKey
name tpm2.Name
session Session
cert *x509.Certificate
}
// EndorsementKeyRSA generates and loads a key from DefaultEKTemplateRSA.
func EndorsementKeyRSA(rw io.ReadWriter) (*Key, error) {
ekRsa, err := NewCachedKey(rw, tpm2.HandleEndorsement, DefaultEKTemplateRSA(), EKReservedHandle)
if err != nil {
return nil, err
}
if err := ekRsa.trySetCertificateFromNvram(EKCertNVIndexRSA); err != nil {
ekRsa.Close()
return nil, err
}
return ekRsa, nil
}
// EndorsementKeyECC generates and loads a key from DefaultEKTemplateECC.
func EndorsementKeyECC(rw io.ReadWriter) (*Key, error) {
ekEcc, err := NewCachedKey(rw, tpm2.HandleEndorsement, DefaultEKTemplateECC(), EKECCReservedHandle)
if err != nil {
return nil, err
}
if err := ekEcc.trySetCertificateFromNvram(EKCertNVIndexECC); err != nil {
ekEcc.Close()
return nil, err
}
return ekEcc, nil
}
// StorageRootKeyRSA generates and loads a key from SRKTemplateRSA.
func StorageRootKeyRSA(rw io.ReadWriter) (*Key, error) {
return NewCachedKey(rw, tpm2.HandleOwner, SRKTemplateRSA(), SRKReservedHandle)
}
// StorageRootKeyECC generates and loads a key from SRKTemplateECC.
func StorageRootKeyECC(rw io.ReadWriter) (*Key, error) {
return NewCachedKey(rw, tpm2.HandleOwner, SRKTemplateECC(), SRKECCReservedHandle)
}
// AttestationKeyRSA generates and loads a key from AKTemplateRSA in the Owner hierarchy.
func AttestationKeyRSA(rw io.ReadWriter) (*Key, error) {
return NewCachedKey(rw, tpm2.HandleOwner, AKTemplateRSA(), DefaultAKRSAHandle)
}
// AttestationKeyECC generates and loads a key from AKTemplateECC in the Owner hierarchy.
func AttestationKeyECC(rw io.ReadWriter) (*Key, error) {
return NewCachedKey(rw, tpm2.HandleOwner, AKTemplateECC(), DefaultAKECCHandle)
}
// EndorsementKeyFromNvIndex generates and loads an endorsement key using the
// template stored at the provided nvdata index. This is useful for TPMs which
// have a preinstalled AK template.
func EndorsementKeyFromNvIndex(rw io.ReadWriter, idx uint32) (*Key, error) {
return KeyFromNvIndex(rw, tpm2.HandleEndorsement, idx)
}
// GceAttestationKeyRSA generates and loads the GCE RSA AK. Note that this
// function will only work on a GCE VM. Unlike AttestationKeyRSA, this key uses
// the Endorsement Hierarchy and its template loaded from GceAKTemplateNVIndexRSA.
func GceAttestationKeyRSA(rw io.ReadWriter) (*Key, error) {
akRsa, err := EndorsementKeyFromNvIndex(rw, GceAKTemplateNVIndexRSA)
if err != nil {
return nil, err
}
if err := akRsa.trySetCertificateFromNvram(GceAKCertNVIndexRSA); err != nil {
akRsa.Close()
return nil, err
}
return akRsa, nil
}
// GceAttestationKeyECC generates and loads the GCE ECC AK. Note that this
// function will only work on a GCE VM. Unlike AttestationKeyECC, this key uses
// the Endorsement Hierarchy and its template loaded from GceAKTemplateNVIndexECC.
func GceAttestationKeyECC(rw io.ReadWriter) (*Key, error) {
akEcc, err := EndorsementKeyFromNvIndex(rw, GceAKTemplateNVIndexECC)
if err != nil {
return nil, err
}
if err := akEcc.trySetCertificateFromNvram(GceAKCertNVIndexECC); err != nil {
akEcc.Close()
return nil, err
}
return akEcc, nil
}
// LoadCachedKey loads a key from cachedHandle.
// If the key is not found, an error is returned.
// This function will not overwrite an existing key, unlike NewCachedKey.
func LoadCachedKey(rw io.ReadWriter, cachedHandle tpmutil.Handle, keySession Session) (k *Key, err error) {
cachedPub, _, _, err := tpm2.ReadPublic(rw, cachedHandle)
if err != nil {
return nil, fmt.Errorf("failed to read public area of cached key: %w", err)
}
k = &Key{rw: rw, handle: cachedHandle, pubArea: cachedPub, session: keySession}
return k, k.finish()
}
// KeyFromNvIndex generates and loads a key under the provided parent
// (possibly a hierarchy root tpm2.Handle{Owner|Endorsement|Platform|Null})
// using the template stored at the provided nvdata index.
func KeyFromNvIndex(rw io.ReadWriter, parent tpmutil.Handle, idx uint32) (*Key, error) {
data, err := tpm2.NVReadEx(rw, tpmutil.Handle(idx), tpm2.HandleOwner, "", 0)
if err != nil {
return nil, fmt.Errorf("read error at index %d: %w", idx, err)
}
template, err := tpm2.DecodePublic(data)
if err != nil {
return nil, fmt.Errorf("index %d data was not a TPM key template: %w", idx, err)
}
return NewKey(rw, parent, template)
}
// NewCachedKey is almost identical to NewKey, except that it initially tries to
// see if the a key matching the provided template is at cachedHandle. If so,
// that key is returned. If not, the key is created as in NewKey, and that key
// is persisted to the cachedHandle, overwriting any existing key there.
func NewCachedKey(rw io.ReadWriter, parent tpmutil.Handle, template tpm2.Public, cachedHandle tpmutil.Handle) (k *Key, err error) {
owner := tpm2.HandleOwner
if parent == tpm2.HandlePlatform {
owner = tpm2.HandlePlatform
} else if parent == tpm2.HandleNull {
return nil, fmt.Errorf("cannot cache objects in the null hierarchy")
}
cachedPub, _, _, err := tpm2.ReadPublic(rw, cachedHandle)
if err == nil {
if cachedPub.MatchesTemplate(template) {
k = &Key{rw: rw, handle: cachedHandle, pubArea: cachedPub}
return k, k.finish()
}
// Kick out old cached key if it does not match
if err = tpm2.EvictControl(rw, "", owner, cachedHandle, cachedHandle); err != nil {
return nil, err
}
}
k, err = NewKey(rw, parent, template)
if err != nil {
return nil, err
}
defer tpm2.FlushContext(rw, k.handle)
if err = tpm2.EvictControl(rw, "", owner, k.handle, cachedHandle); err != nil {
return nil, err
}
k.handle = cachedHandle
return k, nil
}
// NewKey generates a key from the template and loads that key into the TPM
// under the specified parent. NewKey can call many different TPM commands:
// - If parent is tpm2.Handle{Owner|Endorsement|Platform|Null} a primary key
// is created in the specified hierarchy (using CreatePrimary).
// - If parent is a valid key handle, a normal key object is created under
// that parent (using Create and Load). NOTE: Not yet supported.
//
// This function also assumes that the desired key:
// - Does not have its usage locked to specific PCR values
// - Usable with empty authorization sessions (i.e. doesn't need a password)
func NewKey(rw io.ReadWriter, parent tpmutil.Handle, template tpm2.Public) (k *Key, err error) {
if !isHierarchy(parent) {
// TODO add support for normal objects with Create() and Load()
return nil, fmt.Errorf("unsupported parent handle: %x", parent)
}
handle, pubArea, _, _, _, _, err := tpm2.CreatePrimaryEx(rw, parent, tpm2.PCRSelection{}, "", "", template)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tpm2.FlushContext(rw, handle)
}
}()
k = &Key{rw: rw, handle: handle}
if k.pubArea, err = tpm2.DecodePublic(pubArea); err != nil {
return
}
return k, k.finish()
}
func (k *Key) finish() error {
var err error
if k.pubKey, err = k.pubArea.Key(); err != nil {
return err
}
if k.name, err = k.pubArea.Name(); err != nil {
return err
}
// We determine the right type of session based on the auth policy
if k.session == nil {
if bytes.Equal(k.pubArea.AuthPolicy, defaultEKAuthPolicy()) {
if k.session, err = NewEKSession(k.rw); err != nil {
return err
}
} else if len(k.pubArea.AuthPolicy) == 0 {
k.session = NullSession{}
} else {
return fmt.Errorf("unknown auth policy when creating key")
}
}
return nil
}
// Handle allows this key to be used directly with other go-tpm commands.
func (k *Key) Handle() tpmutil.Handle {
return k.handle
}
// Name is hash of this key's public area. Only the Digest field will ever be
// populated. It is useful for various TPM commands related to authorization.
// This is equivalent to k.PublicArea.Name(), except that is cannot fail.
func (k *Key) Name() tpm2.Name {
return k.name
}
// PublicArea exposes the key's entire public area. This is useful for
// determining additional properties of the underlying TPM key.
func (k *Key) PublicArea() tpm2.Public {
return k.pubArea
}
// PublicKey provides a go interface to the loaded key's public area.
func (k *Key) PublicKey() crypto.PublicKey {
return k.pubKey
}
// Close should be called when the key is no longer needed. This is important to
// do as most TPMs can only have a small number of key simultaneously loaded.
func (k *Key) Close() {
if k.session != nil {
k.session.Close()
}
tpm2.FlushContext(k.rw, k.handle)
}
// Seal seals the sensitive byte buffer to a key. This key must be an SRK (we
// currently do not support sealing to EKs). Optionally, the SealOpts struct can
// be modified to provide sealed-to PCRs. In this case, the sensitive data can
// only be unsealed if the seal-time PCRs are in the SealOpts-specified state.
// There must not be overlap in PCRs between SealOpts' Current and Target.
// During the sealing process, certification data will be created allowing
// Unseal() to validate the state of the TPM during the sealing process.
func (k *Key) Seal(sensitive []byte, opts SealOpts) (*pb.SealedBytes, error) {
var pcrs *pb.PCRs
var err error
var auth []byte
pcrs, err = mergePCRSelAndProto(k.rw, opts.Current, opts.Target)
if err != nil {
return nil, fmt.Errorf("invalid SealOpts: %v", err)
}
if len(pcrs.GetPcrs()) > 0 {
auth = internal.PCRSessionAuth(pcrs, SessionHashAlg)
}
certifySel := FullPcrSel(CertifyHashAlgTpm)
sb, err := sealHelper(k.rw, k.Handle(), auth, sensitive, certifySel)
if err != nil {
return nil, err
}
for pcrNum := range pcrs.GetPcrs() {
sb.Pcrs = append(sb.Pcrs, pcrNum)
}
sb.Hash = pcrs.GetHash()
sb.Srk = pb.ObjectType(k.pubArea.Type)
return sb, nil
}
func sealHelper(rw io.ReadWriter, parentHandle tpmutil.Handle, auth []byte, sensitive []byte, certifyPCRsSel tpm2.PCRSelection) (*pb.SealedBytes, error) {
inPublic := tpm2.Public{
Type: tpm2.AlgKeyedHash,
NameAlg: SessionHashAlgTpm,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent,
AuthPolicy: auth,
}
if auth == nil {
inPublic.Attributes |= tpm2.FlagUserWithAuth
} else {
inPublic.Attributes |= tpm2.FlagAdminWithPolicy
}
priv, pub, creationData, _, ticket, err := tpm2.CreateKeyWithSensitive(rw, parentHandle, certifyPCRsSel, "", "", inPublic, sensitive)
if err != nil {
return nil, fmt.Errorf("failed to create key: %w", err)
}
certifiedPcr, err := ReadPCRs(rw, certifyPCRsSel)
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %w", err)
}
computedDigest := internal.PCRDigest(certifiedPcr, SessionHashAlg)
decodedCreationData, err := tpm2.DecodeCreationData(creationData)
if err != nil {
return nil, fmt.Errorf("failed to decode creation data: %w", err)
}
// make sure PCRs haven't being altered after sealing
if subtle.ConstantTimeCompare(computedDigest, decodedCreationData.PCRDigest) == 0 {
return nil, fmt.Errorf("PCRs have been modified after sealing")
}
sb := &pb.SealedBytes{}
sb.CertifiedPcrs = certifiedPcr
sb.Priv = priv
sb.Pub = pub
sb.CreationData = creationData
if sb.Ticket, err = tpmutil.Pack(ticket); err != nil {
return nil, err
}
return sb, nil
}
// Unseal attempts to reverse the process of Seal(), using the PCRs, public, and
// private data in proto.SealedBytes. Optionally, the UnsealOpts parameter can
// be used to verify the state of the TPM when the data was sealed. The
// zero-value UnsealOpts can be passed to skip certification.
func (k *Key) Unseal(in *pb.SealedBytes, opts UnsealOpts) ([]byte, error) {
if in.Srk != pb.ObjectType(k.pubArea.Type) {
return nil, fmt.Errorf("expected key of type %v, got %v", in.Srk, k.pubArea.Type)
}
sealed, _, err := tpm2.Load(
k.rw,
k.Handle(),
/*parentPassword=*/ "",
in.GetPub(),
in.GetPriv())
if err != nil {
return nil, fmt.Errorf("failed to load sealed object: %w", err)
}
defer tpm2.FlushContext(k.rw, sealed)
pcrs, err := mergePCRSelAndProto(k.rw, opts.CertifyCurrent, opts.CertifyExpected)
if err != nil {
return nil, fmt.Errorf("invalid UnsealOpts: %v", err)
}
if len(pcrs.GetPcrs()) > 0 {
if err := internal.CheckSubset(pcrs, in.GetCertifiedPcrs()); err != nil {
return nil, fmt.Errorf("failed to certify PCRs: %w", err)
}
var ticket tpm2.Ticket
if _, err = tpmutil.Unpack(in.GetTicket(), &ticket); err != nil {
return nil, fmt.Errorf("ticket unpack failed: %w", err)
}
creationHash := SessionHashAlg.New()
creationHash.Write(in.GetCreationData())
_, _, certErr := tpm2.CertifyCreation(k.rw, "", sealed, tpm2.HandleNull, nil, creationHash.Sum(nil), tpm2.SigScheme{}, ticket)
// There is a bug in some older TPMs, where they are unable to
// CertifyCreation when using a Null signing handle (despite this
// being allowed by all versions of the TPM spec). To work around
// this bug, we use a temporary signing key and ignore the signed
// result. To reduce the cost of this workaround, we use a cached
// ECC signing key.
// We can detect this bug, as it triggers a RCInsufficient
// Unmarshaling error.
if paramErr, ok := certErr.(tpm2.ParameterError); ok && paramErr.Code == tpm2.RCInsufficient {
signer, err := AttestationKeyECC(k.rw)
if err != nil {
return nil, fmt.Errorf("failed to create fallback signing key: %w", err)
}
defer signer.Close()
_, _, certErr = tpm2.CertifyCreation(k.rw, "", sealed, signer.Handle(), nil, creationHash.Sum(nil), tpm2.SigScheme{}, ticket)
}
if certErr != nil {
return nil, fmt.Errorf("failed to certify creation: %w", certErr)
}
// verify certify PCRs haven't been modified
decodedCreationData, err := tpm2.DecodeCreationData(in.GetCreationData())
if err != nil {
return nil, fmt.Errorf("failed to decode creation data: %w", err)
}
if !internal.SamePCRSelection(in.GetCertifiedPcrs(), decodedCreationData.PCRSelection) {
return nil, fmt.Errorf("certify PCRs does not match the PCR selection in the creation data")
}
expectedDigest := internal.PCRDigest(in.GetCertifiedPcrs(), SessionHashAlg)
if subtle.ConstantTimeCompare(decodedCreationData.PCRDigest, expectedDigest) == 0 {
return nil, fmt.Errorf("certify PCRs digest does not match the digest in the creation data")
}
}
sel := tpm2.PCRSelection{Hash: tpm2.Algorithm(in.GetHash())}
for _, pcr := range in.GetPcrs() {
sel.PCRs = append(sel.PCRs, int(pcr))
}
session, err := NewPCRSession(k.rw, sel)
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
defer session.Close()
auth, err := session.Auth()
if err != nil {
return nil, err
}
return tpm2.UnsealWithSession(k.rw, auth.Session, sealed, "")
}
// Quote will tell TPM to compute a hash of a set of given PCR selection, together with
// some extra data (typically a nonce), sign it with the given signing key, and return
// the signature and the attestation data. This function will return an error if
// the key is not a restricted signing key.
func (k *Key) Quote(selpcr tpm2.PCRSelection, extraData []byte) (*pb.Quote, error) {
// Make sure that we have a valid signing key before trying quote
var err error
if _, err = internal.GetSigningHashAlg(k.pubArea); err != nil {
return nil, err
}
if !k.hasAttribute(tpm2.FlagRestricted) {
return nil, fmt.Errorf("unrestricted keys are insecure to use with Quote")
}
quote := &pb.Quote{}
quote.Quote, quote.RawSig, err = tpm2.QuoteRaw(k.rw, k.Handle(), "", "", extraData, selpcr, tpm2.AlgNull)
if err != nil {
return nil, fmt.Errorf("failed to quote: %w", err)
}
quote.Pcrs, err = ReadPCRs(k.rw, selpcr)
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %w", err)
}
// Verify the quote client-side to make sure we didn't mess things up.
// NOTE: the quote still must be verified server-side as well.
if err := internal.VerifyQuote(quote, k.PublicKey(), extraData); err != nil {
return nil, fmt.Errorf("failed to verify quote: %w", err)
}
return quote, nil
}
// Reseal is a shortcut to call Unseal() followed by Seal().
// CertifyOpt(nillable) will be used in Unseal(), and SealOpt(nillable)
// will be used in Seal()
func (k *Key) Reseal(in *pb.SealedBytes, uOpts UnsealOpts, sOpts SealOpts) (*pb.SealedBytes, error) {
sensitive, err := k.Unseal(in, uOpts)
if err != nil {
return nil, fmt.Errorf("failed to unseal: %w", err)
}
return k.Seal(sensitive, sOpts)
}
func (k *Key) hasAttribute(attr tpm2.KeyProp) bool {
return k.pubArea.Attributes&attr != 0
}
// Cert returns the parsed certificate (or nil) for the given key.
func (k *Key) Cert() *x509.Certificate {
return k.cert
}
// CertDERBytes provides the ASN.1 DER content of the key's certificate. If the
// key does not have a certficate, returns nil.
func (k *Key) CertDERBytes() []byte {
if k.cert == nil {
return nil
}
return k.cert.Raw
}
// SetCert assigns the provided certificate to the key after verifying it matches the key.
func (k *Key) SetCert(cert *x509.Certificate) error {
certPubKey := cert.PublicKey.(crypto.PublicKey) // This cast cannot fail
if !internal.PubKeysEqual(certPubKey, k.pubKey) {
return errors.New("certificate does not match key")
}
k.cert = cert
return nil
}
// Attempt to fetch a key's certificate from NVRAM. If the certificate is simply
// missing, this function succeeds (and no certificate is set). This is to allow
// for AKs and EKs that simply don't have a certificate. However, if the
// certificate read from NVRAM is either malformed or does not match the key, we
// return an error.
func (k *Key) trySetCertificateFromNvram(index uint32) error {
certASN1, err := tpm2.NVReadEx(k.rw, tpmutil.Handle(index), tpm2.HandleOwner, "", 0)
if err != nil {
// Either the cert data is missing, or we are not allowed to read it
return nil
}
x509Cert, err := x509.ParseCertificate(certASN1)
if err != nil {
return fmt.Errorf("failed to parse certificate from NV memory: %w", err)
}
return k.SetCert(x509Cert)
}