Skip to content

Commit

Permalink
Add support for TSS2 files on tpmkms
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Nov 3, 2023
1 parent 06b9ab4 commit eb54143
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 44 deletions.
8 changes: 8 additions & 0 deletions kms/tpmkms/testdata/ec-tss2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN TSS2 PRIVATE KEY-----
MIHyBgZngQUKAQOgAwEB/wIEQAAAAQRaAFgAIwALAAQAcgAAABAAGAALAAMAEAAg
ebLnGlDAN5aHdkffRTqBdsQNnO60aY+Xvg5u80sIauMAIM0E3zndp5392TPBroKz
PLHEwLiUVeBmOhBG3kss/uICBIGAAH4AIIMO322TFYndManhpvTgLMiFd6Vs3HVs
OrHb15qLZTDQABBMcF+L3C2322FU060DHXv56Uq87uMu/qWE3HU+r5856+70P94I
0z3Plxwln2iGhhbKZ8gQQNKhiOdE4MYDPnN1uqvcwJwd7NZ1fqnwBKks6E1vgSne
tYeNooQ=
-----END TSS2 PRIVATE KEY-----
170 changes: 134 additions & 36 deletions kms/tpmkms/tpmkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/url"
"os"
"time"

"go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/uri"
"go.step.sm/crypto/tpm"
"go.step.sm/crypto/tpm/attestation"
"go.step.sm/crypto/tpm/storage"
"go.step.sm/crypto/tpm/tss2"
)

func init() {
Expand Down Expand Up @@ -188,6 +191,7 @@ func New(_ context.Context, opts apiv1.Options) (kms *TPMKMS, err error) {
//
// - name=<name>: specify the name to identify the key with
// - ak=true: if set to true, an Attestation Key (AK) will be created instead of an application key
// - tss2=true: is set to true, the PrivateKey response will contain a [tss2.TPMKey].
// - attest-by=<akName>: attest an application key at creation time with the AK identified by `akName`
// - qualifying-data=<random>: hexadecimal coded binary data that can be used to guarantee freshness when attesting creation of a key
//
Expand Down Expand Up @@ -240,6 +244,7 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}

ctx := context.Background()

if properties.ak {
ak, err := k.tpm.CreateAK(ctx, properties.name) // NOTE: size is never passed for AKs; it's hardcoded to 2048 in lower levels.
if err != nil {
Expand All @@ -248,10 +253,19 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}
return nil, fmt.Errorf("failed creating AK: %w", err)
}

var tpmKey *tss2.TPMKey
if properties.tss2 {
if tpmKey, err = ak.ToTSS2(ctx); err != nil {
return nil, fmt.Errorf("failed exporting AK to TSS2: %w", err)
}

Check warning on line 261 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L260-L261

Added lines #L260 - L261 were not covered by tests
}

createdAKURI := fmt.Sprintf("tpmkms:name=%s;ak=true", ak.Name())
return &apiv1.CreateKeyResponse{
Name: createdAKURI,
PublicKey: ak.Public(),
Name: createdAKURI,
PublicKey: ak.Public(),
PrivateKey: tpmKey,
}, nil
}

Expand Down Expand Up @@ -283,6 +297,13 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}
}

var tpmKey *tss2.TPMKey
if properties.tss2 {
if tpmKey, err = key.ToTSS2(ctx); err != nil {
return nil, fmt.Errorf("failed exporting key to TSS2: %w", err)
}

Check warning on line 304 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L303-L304

Added lines #L303 - L304 were not covered by tests
}

signer, err := key.Signer(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting signer for key: %w", err)
Expand All @@ -294,8 +315,9 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}

return &apiv1.CreateKeyResponse{
Name: createdKeyURI,
PublicKey: signer.Public(),
Name: createdKeyURI,
PublicKey: signer.Public(),
PrivateKey: tpmKey,
CreateSignerRequest: apiv1.CreateSignerRequest{
SigningKey: createdKeyURI,
Signer: signer,
Expand All @@ -304,39 +326,76 @@ func (k *TPMKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}

// CreateSigner creates a signer using a key present in the TPM KMS.
//
// The `signingKey` in the [apiv1.CreateSignerRequest] can be used to specify
// some key properties. These are as follows:
//
// - name=<name>: specify the name to identify the key with
// - path=<file>: specify the TSS2 PEM file to use
func (k *TPMKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
if req.Signer != nil {
return req.Signer, nil
}

if req.SigningKey == "" {
return nil, errors.New("createSignerRequest 'signingKey' cannot be empty")
}
var pemBytes []byte

properties, err := parseNameURI(req.SigningKey)
if err != nil {
return nil, fmt.Errorf("failed parsing %q: %w", req.SigningKey, err)
}
switch {
case req.SigningKey != "":
properties, err := parseNameURI(req.SigningKey)
if err != nil {
return nil, fmt.Errorf("failed parsing %q: %w", req.SigningKey, err)
}
if properties.ak {
return nil, fmt.Errorf("signing with an AK currently not supported")
}

if properties.ak {
return nil, fmt.Errorf("signing with an AK currently not supported")
switch {
case properties.name != "":
ctx := context.Background()
key, err := k.tpm.GetKey(ctx, properties.name)
if err != nil {
return nil, err
}
signer, err := key.Signer(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err)
}

Check warning on line 362 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L361-L362

Added lines #L361 - L362 were not covered by tests
return signer, nil
case properties.path != "":
if pemBytes, err = os.ReadFile(properties.path); err != nil {
return nil, fmt.Errorf("failed reading key from %q: %w", properties.path, err)
}
default:
return nil, fmt.Errorf("failed parsing %q: name and path cannot be empty", req.SigningKey)
}
case len(req.SigningKeyPEM) > 0:
pemBytes = req.SigningKeyPEM
default:
return nil, errors.New("createSignerRequest 'signingKey' and 'signingKeyPEM' cannot be empty")
}

ctx := context.Background()
key, err := k.tpm.GetKey(ctx, properties.name)
// Create a signer from a TSS2 PEM block
key, err := parseTSS2(pemBytes)
if err != nil {
return nil, err
}

signer, err := key.Signer(ctx)
ctx := context.Background()
signer, err := k.tpm.GetTSS2Signer(ctx, key)
if err != nil {
return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err)
return nil, fmt.Errorf("failed getting signer for TSS2 PEM: %w", err)

Check warning on line 386 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L386

Added line #L386 was not covered by tests
}

return signer, nil
}

// GetPublicKey returns the public key ....
// GetPublicKey returns the public key present in the TPM KMS.
//
// The `name` in the [apiv1.GetPublicKeyRequest] can be used to specify some key
// properties. These are as follows:
//
// - name=<name>: specify the name to identify the key with
// - ak=true: if set to true, an Attestation Key (AK) will be read instead of an application key
// - path=<file>: specify the TSS2 PEM file to read from
func (k *TPMKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
if req.Name == "" {
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
Expand All @@ -348,29 +407,48 @@ func (k *TPMKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey,
}

ctx := context.Background()
if properties.ak {
ak, err := k.tpm.GetAK(ctx, properties.name)
switch {
case properties.name != "":
if properties.ak {
ak, err := k.tpm.GetAK(ctx, properties.name)
if err != nil {
return nil, err
}
akPub := ak.Public()
if akPub == nil {
return nil, errors.New("failed getting AK public key")
}

Check warning on line 420 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L419-L420

Added lines #L419 - L420 were not covered by tests
return akPub, nil
}

key, err := k.tpm.GetKey(ctx, properties.name)
if err != nil {
return nil, err
}
akPub := ak.Public()
if akPub == nil {
return nil, errors.New("failed getting AK public key")
}
return akPub, nil
}

key, err := k.tpm.GetKey(ctx, properties.name)
if err != nil {
return nil, err
}
signer, err := key.Signer(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err)
}

Check warning on line 432 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L431-L432

Added lines #L431 - L432 were not covered by tests

signer, err := key.Signer(ctx)
if err != nil {
return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err)
return signer.Public(), nil
case properties.path != "":
pemBytes, err := os.ReadFile(properties.path)
if err != nil {
return nil, fmt.Errorf("failed reading key from %q: %w", properties.path, err)
}
key, err := parseTSS2(pemBytes)
if err != nil {
return nil, err
}

Check warning on line 443 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L442-L443

Added lines #L442 - L443 were not covered by tests
pub, err := key.Public()
if err != nil {
return nil, fmt.Errorf("error decoding public key from %q: %w", properties.path, err)
}

Check warning on line 447 in kms/tpmkms/tpmkms.go

View check run for this annotation

Codecov / codecov/patch

kms/tpmkms/tpmkms.go#L446-L447

Added lines #L446 - L447 were not covered by tests
return pub, nil
default:
return nil, fmt.Errorf("failed parsing %q: name and path cannot be empty", req.Name)
}

return signer.Public(), nil
}

// LoadCertificate loads the certificate for the key identified by name from the TPMKMS.
Expand Down Expand Up @@ -757,6 +835,26 @@ func ekURL(keyID []byte) *url.URL {
}
}

func parseTSS2(pemBytes []byte) (*tss2.TPMKey, error) {
var block *pem.Block
for len(pemBytes) > 0 {
block, pemBytes = pem.Decode(pemBytes)
if block == nil {
break
}
if block.Type != "TSS2 PRIVATE KEY" {
continue
}

key, err := tss2.ParsePrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed parsing TSS2 PEM: %w", err)
}
return key, nil
}
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)
Expand Down
Loading

0 comments on commit eb54143

Please sign in to comment.