From 5df948af3b39519b52a422c68d85eb2b13bd34dc Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Tue, 6 Feb 2024 17:26:09 -0600 Subject: [PATCH 1/2] Update attestation for a key Currently a new key has to be created every time a new certificate is authorized with the acme device attestation protocol. This allows a new attestation to be generated with fresh qualifying data from the CA. --- kms/tpmkms/tpmkms.go | 21 ++++--- tpm/key.go | 139 +++++++++++++++++++++++++++++++++++++------ tpm/key_test.go | 1 - tpm/storage/types.go | 40 ++++++++----- 4 files changed, 159 insertions(+), 42 deletions(-) diff --git a/kms/tpmkms/tpmkms.go b/kms/tpmkms/tpmkms.go index b2692fc3..8709b1f3 100644 --- a/kms/tpmkms/tpmkms.go +++ b/kms/tpmkms/tpmkms.go @@ -281,9 +281,14 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons key, err = k.tpm.AttestKey(ctx, properties.attestBy, properties.name, config) if err != nil { if errors.Is(err, tpm.ErrExists) { - return nil, apiv1.AlreadyExistsError{Message: err.Error()} + key, err = k.tpm.CertifyKey(ctx, properties.attestBy, properties.name, config) + // return nil, apiv1.AlreadyExistsError{Message: err.Error()} + if err != nil { + return nil, fmt.Errorf("failed certifying existing attested key: %w", err) + } + } else { + return nil, fmt.Errorf("failed creating attested key: %w", err) } - return nil, fmt.Errorf("failed creating attested key: %w", err) } } else { config := tpm.CreateKeyConfig{ @@ -858,8 +863,10 @@ func parseTSS2(pemBytes []byte) (*tss2.TPMKey, error) { return nil, fmt.Errorf("failed parsing TSS2 PEM: block not found") } -var _ apiv1.KeyManager = (*TPMKMS)(nil) -var _ apiv1.Attester = (*TPMKMS)(nil) -var _ apiv1.CertificateManager = (*TPMKMS)(nil) -var _ apiv1.CertificateChainManager = (*TPMKMS)(nil) -var _ apiv1.AttestationClient = (*attestationClient)(nil) +var ( + _ apiv1.KeyManager = (*TPMKMS)(nil) + _ apiv1.Attester = (*TPMKMS)(nil) + _ apiv1.CertificateManager = (*TPMKMS)(nil) + _ apiv1.CertificateChainManager = (*TPMKMS)(nil) + _ apiv1.AttestationClient = (*attestationClient)(nil) +) diff --git a/tpm/key.go b/tpm/key.go index cb1b08c3..c6323b88 100644 --- a/tpm/key.go +++ b/tpm/key.go @@ -9,6 +9,8 @@ import ( "fmt" "time" + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" "github.com/smallstep/go-attestation/attest" internalkey "go.step.sm/crypto/tpm/internal/key" "go.step.sm/crypto/tpm/storage" @@ -19,13 +21,15 @@ import ( // attested by an AK, to be able to prove that it // was created by a specific TPM. type Key struct { - name string - data []byte - attestedBy string - chain []*x509.Certificate - createdAt time.Time - blobs *Blobs - tpm *TPM + name string + data []byte + attestedBy string + chain []*x509.Certificate + createdAt time.Time + blobs *Blobs + tpm *TPM + latestAttestation []byte + latestAttestationSig []byte } // Name returns the Key name. The name uniquely @@ -204,6 +208,97 @@ func (w attestValidationWrapper) Validate() error { return nil } +// CertifyKey certifies a pre-existing key. It refreshes the CreateAttestation and CreateSignature +// CertificationParameters of the stored key. +func (t *TPM) CertifyKey(ctx context.Context, akName, name string, config AttestKeyConfig) (*Key, error) { + var paths []string + if t.deviceName != "" { + paths = append(paths, t.deviceName) + } + thetpm, err := transport.OpenTPM(paths...) + if err != nil { + return nil, fmt.Errorf("failed opening TPM: %w", err) + } + + k, err := t.store.GetKey(name) + if err != nil { + return nil, fmt.Errorf("failed creating key %q: %w", name, err) + } + + ak, err := t.store.GetAK(akName) + if err != nil { + return nil, fmt.Errorf("failed getting AK %q: %w", akName, err) + } + + // serializedKey represents a loadable, TPM-backed key. + type serializedKey struct { + // Public represents the public key, in a TPM-specific format. This + // field is populated on all platforms and TPM versions. + Public []byte + // Blob represents the key material for KeyEncodingEncrypted keys. This + // is only used on Linux. + Blob []byte `json:"KeyBlob"` + } + + var sKey serializedKey + if err := json.Unmarshal(k.Data, &sKey); err != nil { + return nil, fmt.Errorf("failed deserializing key %q: %w", name, err) + } + parent := tpm2.NamedHandle{ + Handle: tpm2.TPMHandle(commonSrkEquivalentHandle), + } + keyLoad := tpm2.Load{ + ParentHandle: parent, + InPrivate: tpm2.TPM2BPrivate{Buffer: sKey.Blob}, + InPublic: tpm2.BytesAs2B[tpm2.TPMTPublic, *tpm2.TPMTPublic](sKey.Public), + } + loadedKey, err := keyLoad.Execute(thetpm) + if err != nil { + return nil, fmt.Errorf("failed to load key %q: %w", name, err) + } + + var attSKey serializedKey + if err := json.Unmarshal(ak.Data, &attSKey); err != nil { + return nil, fmt.Errorf("failed deserializing AK %q: %w", akName, err) + } + akLoad := tpm2.Load{ + ParentHandle: parent, + InPrivate: tpm2.TPM2BPrivate{Buffer: attSKey.Blob}, + InPublic: tpm2.BytesAs2B[tpm2.TPMTPublic, *tpm2.TPMTPublic](attSKey.Public), + } + loadedAK, err := akLoad.Execute(thetpm) + if err != nil { + return nil, fmt.Errorf("failed to load AK %q: %w", akName, err) + } + + keyHandle := tpm2.NamedHandle{ + Handle: loadedKey.ObjectHandle, + } + akHandle := tpm2.NamedHandle{ + Handle: loadedAK.ObjectHandle, + } + certify := tpm2.Certify{ + ObjectHandle: keyHandle, + SignHandle: akHandle, + QualifyingData: tpm2.TPM2BData{ + Buffer: config.QualifyingData, + }, + } + certified, err := certify.Execute(thetpm) + if err != nil { + return nil, fmt.Errorf("failed to certify key %q: %w", name, err) + } + + k.LatestAttestation = certified.CertifyInfo.Bytes() + k.LatestAttestationSig = tpm2.Marshal(certified.Signature) + + if err := t.store.UpdateKey(k); err != nil { + return nil, fmt.Errorf("failed to update stored key %q with new attestation: %w", name, err) + } + + return keyFromStorage(k, t), nil +} + // AttestKey creates a new Key identified by `name` and attested by the AK // identified by `akName`. If no name is provided, a random 10 character // name is generated. If a Key with the same name exists, `ErrExists` is @@ -387,6 +482,10 @@ func (k *Key) CertificationParameters(ctx context.Context) (params attest.Certif defer loadedKey.Close() params = loadedKey.CertificationParameters() + if len(k.latestAttestation) > 0 { + params.CreateAttestation = k.latestAttestation + params.CreateSignature = k.latestAttestationSig + } return } @@ -463,11 +562,13 @@ func (k *Key) SetCertificateChain(ctx context.Context, chain []*x509.Certificate // persisting Keys. func (k *Key) toStorage() *storage.Key { return &storage.Key{ - Name: k.name, - Data: k.data, - AttestedBy: k.attestedBy, - Chain: k.chain, - CreatedAt: k.createdAt.UTC(), + Name: k.name, + Data: k.data, + AttestedBy: k.attestedBy, + Chain: k.chain, + CreatedAt: k.createdAt.UTC(), + LatestAttestation: k.latestAttestation, + LatestAttestationSig: k.latestAttestationSig, } } @@ -475,11 +576,13 @@ func (k *Key) toStorage() *storage.Key { // persisting Keys. func keyFromStorage(sk *storage.Key, t *TPM) *Key { return &Key{ - name: sk.Name, - data: sk.Data, - attestedBy: sk.AttestedBy, - chain: sk.Chain, - createdAt: sk.CreatedAt.Local(), - tpm: t, + name: sk.Name, + data: sk.Data, + attestedBy: sk.AttestedBy, + chain: sk.Chain, + createdAt: sk.CreatedAt.Local(), + tpm: t, + latestAttestation: sk.LatestAttestation, + latestAttestationSig: sk.LatestAttestationSig, } } diff --git a/tpm/key_test.go b/tpm/key_test.go index c9e861d7..24203af7 100644 --- a/tpm/key_test.go +++ b/tpm/key_test.go @@ -15,7 +15,6 @@ import ( ) func TestKey_MarshalJSON(t *testing.T) { - ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { diff --git a/tpm/storage/types.go b/tpm/storage/types.go index b3fd5c5c..5b059080 100644 --- a/tpm/storage/types.go +++ b/tpm/storage/types.go @@ -68,11 +68,13 @@ func (ak *AK) UnmarshalJSON(data []byte) error { // Key is the type used to store Keys. type Key struct { - Name string - Data []byte - AttestedBy string - Chain []*x509.Certificate - CreatedAt time.Time + Name string + Data []byte + AttestedBy string + Chain []*x509.Certificate + CreatedAt time.Time + LatestAttestation []byte + LatestAttestationSig []byte } // MarshalJSON marshals the Key into JSON. @@ -83,11 +85,13 @@ func (key *Key) MarshalJSON() ([]byte, error) { } sk := serializedKey{ - Name: key.Name, - Type: typeKey, - Data: key.Data, - AttestedBy: key.AttestedBy, - CreatedAt: key.CreatedAt, + Name: key.Name, + Type: typeKey, + Data: key.Data, + AttestedBy: key.AttestedBy, + CreatedAt: key.CreatedAt, + LatestAttestation: key.LatestAttestation, + LatestAttestationSig: key.LatestAttestationSig, } if len(chain) > 0 { @@ -112,6 +116,8 @@ func (key *Key) UnmarshalJSON(data []byte) error { key.Data = sk.Data key.AttestedBy = sk.AttestedBy key.CreatedAt = sk.CreatedAt + key.LatestAttestation = sk.LatestAttestation + key.LatestAttestationSig = sk.LatestAttestationSig if len(sk.Chain) > 0 { chain := make([]*x509.Certificate, len(sk.Chain)) @@ -153,12 +159,14 @@ type serializedAK struct { // serializedKey is the struct used when marshaling // a storage Key to JSON. type serializedKey struct { - Name string `json:"name"` - Type tpmObjectType `json:"type"` - Data []byte `json:"data"` - AttestedBy string `json:"attestedBy"` - Chain [][]byte `json:"chain"` - CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + Type tpmObjectType `json:"type"` + Data []byte `json:"data"` + AttestedBy string `json:"attestedBy"` + Chain [][]byte `json:"chain"` + CreatedAt time.Time `json:"createdAt"` + LatestAttestation []byte `json:"latestAttestation"` + LatestAttestationSig []byte `json:"latestAttestationSig"` } // keyForAK returns the key to use when storing an AK. From c8d8967bde590d7d5a94d8cadf91efb5d91c99bf Mon Sep 17 00:00:00 2001 From: Andrew Reed Date: Tue, 6 Feb 2024 17:51:07 -0600 Subject: [PATCH 2/2] Close tpm --- tpm/key.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tpm/key.go b/tpm/key.go index c6323b88..97b592f3 100644 --- a/tpm/key.go +++ b/tpm/key.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "time" "github.com/google/go-tpm/tpm2" @@ -210,7 +211,7 @@ func (w attestValidationWrapper) Validate() error { // CertifyKey certifies a pre-existing key. It refreshes the CreateAttestation and CreateSignature // CertificationParameters of the stored key. -func (t *TPM) CertifyKey(ctx context.Context, akName, name string, config AttestKeyConfig) (*Key, error) { +func (t *TPM) CertifyKey(_ context.Context, akName, name string, config AttestKeyConfig) (*Key, error) { var paths []string if t.deviceName != "" { paths = append(paths, t.deviceName) @@ -219,6 +220,11 @@ func (t *TPM) CertifyKey(ctx context.Context, akName, name string, config Attest if err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } + defer func() { + if err := thetpm.Close(); err != nil { + log.Printf("Failed to close tpm: %v", err) + } + }() k, err := t.store.GetKey(name) if err != nil {