Skip to content

Update attestation for a key #419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions kms/tpmkms/tpmkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
)
145 changes: 127 additions & 18 deletions tpm/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"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"
Expand All @@ -19,13 +22,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
Expand Down Expand Up @@ -204,6 +209,102 @@ 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(_ 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)
}
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 {
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
Expand Down Expand Up @@ -387,6 +488,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
}
Expand Down Expand Up @@ -463,23 +568,27 @@ 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,
}
}

// keyFromStorage recreates a Key from the struct used for
// 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,
}
}
1 change: 0 additions & 1 deletion tpm/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
)

func TestKey_MarshalJSON(t *testing.T) {

ca, err := minica.New(
minica.WithGetSignerFunc(
func() (crypto.Signer, error) {
Expand Down
40 changes: 24 additions & 16 deletions tpm/storage/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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.
Expand Down