mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-10 23:49:09 +02:00
[client] Read TSS2 key files on go-tpm, checked against the library it replaces
The TSS2 parser was the only reason this repository depended on a crypto suite whose own build tooling it inherits. The replacement sits on go-tpm, which was already a direct dependency and is in fact what that suite calls underneath, so this removes a wrapper rather than porting onto a different library: the load, the derived storage root key and the signing commands are the same calls. Swapping a parser on the one path a customer actually runs is not something to assert, so the two are held side by side for this commit. One test feeds the replacement bytes the old library wrote and requires the same key type, empty auth flag, parent handle, blobs and decoded public key; the other feeds both the fixtures the tests are built on, so those are the shape the format calls for and not merely the shape the new parser reads. The scaffolding goes away with the dependency in the commit that follows. The encoder behind the fixtures is written out separately from the parser under test, so an encoder bug and a decoder bug cannot cancel each other out.
This commit is contained in:
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/tpm/tss2"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/tpm"
|
||||
"github.com/netbirdio/netbird/client/internal/tpm/tpmtest"
|
||||
@@ -34,9 +33,8 @@ func TestFileStore_TPMKeyFile(t *testing.T) {
|
||||
|
||||
// A key that needs a password can never be used silently, so its certificate is skipped.
|
||||
locked := certtest.ECDSAKey(t)
|
||||
withAuth := func(k *tss2.TPMKey) { k.EmptyAuth = false }
|
||||
writeFile(t, dir, "locked.pem", certtest.CertPEM(ca.Issue(t, locked, "locked")))
|
||||
writeFile(t, dir, "locked.key", tpmtest.KeyPEM(t, locked.Public().(*ecdsa.PublicKey), withAuth))
|
||||
writeFile(t, dir, "locked.key", tpmtest.KeyPEM(t, locked.Public().(*ecdsa.PublicKey), tpmtest.WithAuth()))
|
||||
|
||||
candidates, err := NewFileStore(dir).Candidates(context.Background())
|
||||
require.NoError(t, err)
|
||||
@@ -88,7 +86,7 @@ func createTPMKey(t *testing.T) (public, private []byte) {
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = rwc.Close() }()
|
||||
|
||||
parent, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", tss2.ECCSRKTemplate)
|
||||
parent, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", tpmtest.ECCSRKTemplate)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = tpm2.FlushContext(rwc, parent) }()
|
||||
|
||||
|
||||
+118
-16
@@ -2,11 +2,17 @@ package tpm
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"go.step.sm/crypto/tpm/tss2"
|
||||
legacy "github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
)
|
||||
|
||||
// KeyPEMType is the PEM block type of a TPM 2.0 key file as defined by
|
||||
@@ -15,29 +21,46 @@ const KeyPEMType = "TSS2 PRIVATE KEY"
|
||||
|
||||
var ErrKeyNeedsAuth = errors.New("TPM key requires an authorization value")
|
||||
|
||||
// eccSRKTemplate is the TCG reference ECC-P256 storage root key. A key whose parent is
|
||||
// a hierarchy rather than a persistent handle was wrapped by the primary this template
|
||||
// derives, and tpm2-openssl and tpm2-tss-engine derive the same one, so the TPM
|
||||
// reproduces the identical parent from the hierarchy seed without anything being stored.
|
||||
var eccSRKTemplate = legacy.Public{
|
||||
Type: legacy.AlgECC,
|
||||
NameAlg: legacy.AlgSHA256,
|
||||
Attributes: legacy.FlagStorageDefault | legacy.FlagNoDA,
|
||||
ECCParameters: &legacy.ECCParams{
|
||||
Symmetric: &legacy.SymScheme{Alg: legacy.AlgAES, KeyBits: 128, Mode: legacy.AlgCFB},
|
||||
Sign: &legacy.SigScheme{Alg: legacy.AlgNull},
|
||||
CurveID: legacy.CurveNISTP256,
|
||||
},
|
||||
}
|
||||
|
||||
// ParseKey reads a TSS2 key file and returns a signer that produces every signature
|
||||
// inside the TPM; only the digest goes in and only the signature comes out. A key with
|
||||
// a persistent parent is loaded under it, a key whose parent is a hierarchy under the
|
||||
// TCG default ECC primary that tpm2-openssl and tpm2-tss-engine derive as well. Keys
|
||||
// guarded by an authorization value are rejected, since nothing can supply it without
|
||||
// prompting.
|
||||
// inside the TPM; only the digest goes in and only the signature comes out. Keys guarded
|
||||
// by an authorization value are rejected, since nothing can supply it without prompting.
|
||||
func ParseKey(der []byte) (crypto.Signer, error) {
|
||||
key, err := tss2.ParsePrivateKey(der)
|
||||
key, err := parseTSS2(der)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse TSS2 key: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
if !key.EmptyAuth {
|
||||
return nil, ErrKeyNeedsAuth
|
||||
}
|
||||
public, err := key.Public()
|
||||
|
||||
public, err := legacy.DecodePublic(key.PublicKey[2:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode TSS2 public area: %w", err)
|
||||
}
|
||||
pub, err := public.Key()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode TSS2 public key: %w", err)
|
||||
}
|
||||
return &keySigner{key: key, public: public}, nil
|
||||
return &keySigner{key: key, public: pub}, nil
|
||||
}
|
||||
|
||||
type keySigner struct {
|
||||
key *tss2.TPMKey
|
||||
key *tss2Key
|
||||
public crypto.PublicKey
|
||||
}
|
||||
|
||||
@@ -45,17 +68,96 @@ func (s *keySigner) Public() crypto.PublicKey {
|
||||
return s.public
|
||||
}
|
||||
|
||||
func (s *keySigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
// Sign loads the key under its parent, signs, and releases both handles. The TPM is
|
||||
// opened per signature so no handle outlives the call, which matters on a device whose
|
||||
// transient object slots are few and shared with everything else on the host.
|
||||
func (s *keySigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
rwc, err := Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = rwc.Close() }()
|
||||
|
||||
signer, err := tss2.CreateSigner(rwc, s.key)
|
||||
parent := tpmutil.Handle(uint32(s.key.Parent)) //nolint:gosec // validParent bounds it
|
||||
if !persistentHandle(s.key.Parent) {
|
||||
parent, _, err = legacy.CreatePrimary(rwc, parent, legacy.PCRSelection{}, "", "", eccSRKTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create TPM primary: %w", err)
|
||||
}
|
||||
defer func() { _ = legacy.FlushContext(rwc, parent) }()
|
||||
}
|
||||
|
||||
public, private := s.key.blobs()
|
||||
handle, _, err := legacy.Load(rwc, parent, "", public, private)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load TSS2 key: %w", err)
|
||||
return nil, fmt.Errorf("load TPM key: %w", err)
|
||||
}
|
||||
defer func() { _ = legacy.FlushContext(rwc, handle) }()
|
||||
|
||||
switch pub := s.public.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
return signECDSA(rwc, handle, digest, pub.Curve)
|
||||
case *rsa.PublicKey:
|
||||
return signRSA(rwc, handle, digest, opts)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM key type %T", s.public)
|
||||
}
|
||||
}
|
||||
|
||||
// signECDSA returns the signature as the ASN.1 sequence crypto.Signer is defined to
|
||||
// return; the TPM hands back the two integers on their own.
|
||||
func signECDSA(rw io.ReadWriter, handle tpmutil.Handle, digest []byte, curve elliptic.Curve) ([]byte, error) {
|
||||
hash, err := eccHash(curve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := legacy.Sign(rw, handle, "", digest, nil, &legacy.SigScheme{Alg: legacy.AlgECDSA, Hash: hash})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TPM ECDSA signature: %w", err)
|
||||
}
|
||||
if sig.ECC == nil {
|
||||
return nil, fmt.Errorf("TPM returned a %v signature for an ECDSA key", sig.Alg)
|
||||
}
|
||||
return asn1.Marshal(struct{ R, S *big.Int }{sig.ECC.R, sig.ECC.S})
|
||||
}
|
||||
|
||||
func signRSA(rw io.ReadWriter, handle tpmutil.Handle, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
hash, err := legacy.HashToAlgorithm(opts.HashFunc())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TPM hash algorithm: %w", err)
|
||||
}
|
||||
|
||||
scheme := &legacy.SigScheme{Alg: legacy.AlgRSASSA, Hash: hash}
|
||||
if pss, ok := opts.(*rsa.PSSOptions); ok {
|
||||
// The TPM always salts to the hash length, so a caller asking for anything
|
||||
// else would get a signature it did not ask for.
|
||||
if pss.SaltLength != rsa.PSSSaltLengthAuto &&
|
||||
pss.SaltLength != rsa.PSSSaltLengthEqualsHash &&
|
||||
pss.SaltLength != len(digest) {
|
||||
return nil, fmt.Errorf("TPM cannot produce a PSS signature with salt length %d", pss.SaltLength)
|
||||
}
|
||||
scheme.Alg = legacy.AlgRSAPSS
|
||||
}
|
||||
|
||||
sig, err := legacy.Sign(rw, handle, "", digest, nil, scheme)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TPM RSA signature: %w", err)
|
||||
}
|
||||
if sig.RSA == nil {
|
||||
return nil, fmt.Errorf("TPM returned a %v signature for an RSA key", sig.Alg)
|
||||
}
|
||||
return sig.RSA.Signature, nil
|
||||
}
|
||||
|
||||
func eccHash(curve elliptic.Curve) (legacy.Algorithm, error) {
|
||||
switch curve {
|
||||
case elliptic.P256():
|
||||
return legacy.AlgSHA256, nil
|
||||
case elliptic.P384():
|
||||
return legacy.AlgSHA384, nil
|
||||
case elliptic.P521():
|
||||
return legacy.AlgSHA512, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported curve %s", curve.Params().Name)
|
||||
}
|
||||
signer.SetSRKTemplate(tss2.ECCSRKTemplate)
|
||||
return signer.Sign(rand, digest, opts)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/tpm/tss2"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/tpm/tpmtest"
|
||||
)
|
||||
@@ -27,9 +26,7 @@ func TestParseKey_ReportsPublicKeyWithoutTouchingTPM(t *testing.T) {
|
||||
|
||||
func TestParseKey_RejectsKeyWithAuthorization(t *testing.T) {
|
||||
key := newP256Key(t)
|
||||
withAuth := func(k *tss2.TPMKey) { k.EmptyAuth = false }
|
||||
|
||||
_, err := ParseKey(decodePEM(t, tpmtest.KeyPEM(t, &key.PublicKey, withAuth)))
|
||||
_, err := ParseKey(decodePEM(t, tpmtest.KeyPEM(t, &key.PublicKey, tpmtest.WithAuth())))
|
||||
assert.ErrorIs(t, err, ErrKeyNeedsAuth)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,49 @@ package tpmtest
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/tpm/tss2"
|
||||
)
|
||||
|
||||
const p256Bytes = 32
|
||||
const (
|
||||
p256Bytes = 32
|
||||
|
||||
// KeyPEMType is the PEM block type of a TPM 2.0 key file.
|
||||
KeyPEMType = "TSS2 PRIVATE KEY"
|
||||
)
|
||||
|
||||
// oidLoadableKey is the key type of draft-bottomley-tpm2-keys that a parent wraps.
|
||||
var oidLoadableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3}
|
||||
|
||||
// tss2KeyDER is the ASN.1 container, written out independently of the parser under test
|
||||
// so that an encoder bug and a decoder bug cannot cancel out. Policy, secret and auth
|
||||
// policy are left out entirely: they are optional, and a key that carries them is
|
||||
// refused anyway.
|
||||
type tss2KeyDER struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
EmptyAuth bool `asn1:"optional,explicit,tag:0"`
|
||||
Parent int
|
||||
PublicKey []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
// Option adjusts a key before it is encoded.
|
||||
type Option func(*tss2KeyDER)
|
||||
|
||||
// WithParent names the handle the key is wrapped by, instead of the owner hierarchy.
|
||||
func WithParent(handle int) Option {
|
||||
return func(k *tss2KeyDER) { k.Parent = handle }
|
||||
}
|
||||
|
||||
// WithAuth marks the key as guarded by an authorization value, which this client refuses
|
||||
// because nothing can supply one without prompting.
|
||||
func WithAuth() Option {
|
||||
return func(k *tss2KeyDER) { k.EmptyAuth = false }
|
||||
}
|
||||
|
||||
// SigningTemplate is the public area of an unrestricted P-256 signing key with no fixed
|
||||
// scheme, the shape tpm2-openssl creates certificate keys in.
|
||||
@@ -26,7 +61,7 @@ func SigningTemplate() tpm2.Public {
|
||||
|
||||
// KeyPEM encodes pub as a TSS2 PRIVATE KEY over a placeholder private blob: it parses
|
||||
// and reports pub, but no TPM can load it.
|
||||
func KeyPEM(t *testing.T, pub *ecdsa.PublicKey, opts ...tss2.TPMOption) string {
|
||||
func KeyPEM(t *testing.T, pub *ecdsa.PublicKey, opts ...Option) string {
|
||||
t.Helper()
|
||||
require.Equal(t, elliptic.P256(), pub.Curve, "fixture keys must be P-256")
|
||||
area := SigningTemplate()
|
||||
@@ -39,10 +74,43 @@ func KeyPEM(t *testing.T, pub *ecdsa.PublicKey, opts ...tss2.TPMOption) string {
|
||||
return EncodePEM(t, encoded, []byte("placeholder"), opts...)
|
||||
}
|
||||
|
||||
// EncodePEM wraps the public and private blobs TPM2_Create returned into a TSS2 PRIVATE KEY.
|
||||
func EncodePEM(t *testing.T, public, private []byte, opts ...tss2.TPMOption) string {
|
||||
// EncodePEM wraps the public and private blobs TPM2_Create returned into a TSS2 PRIVATE
|
||||
// KEY, giving each the TPM2B length prefix the format carries them with.
|
||||
func EncodePEM(t *testing.T, public, private []byte, opts ...Option) string {
|
||||
t.Helper()
|
||||
pemBytes, err := tss2.New(public, private, opts...).EncodeToMemory()
|
||||
|
||||
key := tss2KeyDER{
|
||||
Type: oidLoadableKey,
|
||||
EmptyAuth: true,
|
||||
Parent: int(tpm2.HandleOwner),
|
||||
PublicKey: prefixTPM2B(public),
|
||||
PrivateKey: prefixTPM2B(private),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&key)
|
||||
}
|
||||
|
||||
der, err := asn1.Marshal(key)
|
||||
require.NoError(t, err)
|
||||
return string(pemBytes)
|
||||
return string(pem.EncodeToMemory(&pem.Block{Type: KeyPEMType, Bytes: der}))
|
||||
}
|
||||
|
||||
func prefixTPM2B(b []byte) []byte {
|
||||
out := make([]byte, 0, len(b)+2)
|
||||
out = append(out, byte(len(b)>>8), byte(len(b)))
|
||||
return append(out, b...)
|
||||
}
|
||||
|
||||
// ECCSRKTemplate is the TCG reference ECC-P256 storage root key, the parent a key under
|
||||
// a hierarchy is wrapped by. Tests that create a key in a real TPM have to use the same
|
||||
// template the signer re-derives it with.
|
||||
var ECCSRKTemplate = tpm2.Public{
|
||||
Type: tpm2.AlgECC,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
|
||||
ECCParameters: &tpm2.ECCParams{
|
||||
Symmetric: &tpm2.SymScheme{Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB},
|
||||
Sign: &tpm2.SigScheme{Alg: tpm2.AlgNull},
|
||||
CurveID: tpm2.CurveNISTP256,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package tpm
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
legacy "github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
// oidLoadableKey marks a key wrapped by a parent inside the TPM, which is the only kind
|
||||
// enrollment tooling writes for a signing key and the only kind that can be loaded under
|
||||
// an SRK. The sealed and importable variants carry different material and are refused.
|
||||
var oidLoadableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3}
|
||||
|
||||
var (
|
||||
errNotLoadable = errors.New("TSS2 key is not a loadable key")
|
||||
errKeyHasPolicy = errors.New("TSS2 key carries a policy, which is not supported")
|
||||
errKeyHasSecret = errors.New("TSS2 key carries a secret, which is not supported")
|
||||
errBadParent = errors.New("TSS2 key names a parent that is neither persistent nor a hierarchy")
|
||||
errBadBlob = errors.New("TSS2 key blob is malformed")
|
||||
)
|
||||
|
||||
// tss2Key is the ASN.1 container of draft-bottomley-tpm2-keys, the format tpm2-openssl,
|
||||
// tpm2-tss-engine and tpm2_encodeobject write. PublicKey and PrivateKey hold TPM2B
|
||||
// structures, so each is its own two-byte length followed by that many bytes.
|
||||
type tss2Key struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
EmptyAuth bool `asn1:"optional,explicit,tag:0"`
|
||||
Policy []asn1.RawValue `asn1:"optional,explicit,tag:1"`
|
||||
Secret []byte `asn1:"optional,explicit,tag:2"`
|
||||
AuthPolicy []asn1.RawValue `asn1:"optional,explicit,tag:3"`
|
||||
Parent int
|
||||
PublicKey []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
// parseTSS2 decodes a TSS2 key file and rejects everything this client cannot honour,
|
||||
// so a key that parses here is one the TPM can be asked to load.
|
||||
func parseTSS2(der []byte) (*tss2Key, error) {
|
||||
key := new(tss2Key)
|
||||
rest, err := asn1.Unmarshal(der, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse TSS2 key: %w", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
return nil, fmt.Errorf("parse TSS2 key: %d trailing bytes", len(rest))
|
||||
}
|
||||
|
||||
switch {
|
||||
case !key.Type.Equal(oidLoadableKey):
|
||||
return nil, fmt.Errorf("%w: %s", errNotLoadable, key.Type)
|
||||
case len(key.Policy) > 0 || len(key.AuthPolicy) > 0:
|
||||
return nil, errKeyHasPolicy
|
||||
case len(key.Secret) > 0:
|
||||
return nil, errKeyHasSecret
|
||||
case !validParent(key.Parent):
|
||||
return nil, fmt.Errorf("%w: %d", errBadParent, key.Parent)
|
||||
case !validTPM2B(key.PublicKey) || !validTPM2B(key.PrivateKey):
|
||||
return nil, errBadBlob
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// blobs returns the public and private areas with their TPM2B length prefix removed,
|
||||
// which is the form the load command takes them in.
|
||||
func (k *tss2Key) blobs() (public, private []byte) {
|
||||
return k.PublicKey[2:], k.PrivateKey[2:]
|
||||
}
|
||||
|
||||
// validParent accepts a persistent handle, under which the key was wrapped directly, or
|
||||
// one of the four hierarchies, under which the key is wrapped by a primary the TPM
|
||||
// re-derives from the hierarchy seed.
|
||||
func validParent(parent int) bool {
|
||||
return persistentHandle(parent) ||
|
||||
parent == int(legacy.HandleOwner) ||
|
||||
parent == int(legacy.HandleNull) ||
|
||||
parent == int(legacy.HandleEndorsement) ||
|
||||
parent == int(legacy.HandlePlatform)
|
||||
}
|
||||
|
||||
func persistentHandle(h int) bool {
|
||||
return h>>24 == int(legacy.HandleTypePersistent)
|
||||
}
|
||||
|
||||
// validTPM2B reports whether b is a TPM2B structure: a two-byte big-endian length
|
||||
// followed by exactly that many bytes.
|
||||
func validTPM2B(b []byte) bool {
|
||||
return len(b) >= 2 && len(b)-2 == int(b[0])<<8+int(b[1])
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package tpm
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
legacy "github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/tpm/tss2"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/tpm/tpmtest"
|
||||
)
|
||||
|
||||
// publicArea builds the public area of a P-256 signing key holding pub.
|
||||
func publicArea(t *testing.T, pub *ecdsa.PublicKey) []byte {
|
||||
t.Helper()
|
||||
area := tpmtest.SigningTemplate()
|
||||
area.ECCParameters.Point = legacy.ECPoint{
|
||||
XRaw: pub.X.FillBytes(make([]byte, 32)),
|
||||
YRaw: pub.Y.FillBytes(make([]byte, 32)),
|
||||
}
|
||||
encoded, err := area.Encode()
|
||||
require.NoError(t, err)
|
||||
return encoded
|
||||
}
|
||||
|
||||
// TestParseTSS2_AgreesWithStep is scaffolding for one commit: it holds the replacement
|
||||
// parser against the library it replaces, over bytes that library itself wrote, so the
|
||||
// swap is checked rather than asserted. It goes away with the dependency.
|
||||
func TestParseTSS2_AgreesWithStep(t *testing.T) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
public := publicArea(t, &key.PublicKey)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
opts []tss2.TPMOption
|
||||
}{
|
||||
{name: "owner hierarchy parent"},
|
||||
{name: "persistent parent", opts: []tss2.TPMOption{tss2.WithParent(0x81000001)}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
encoded, err := tss2.New(public, []byte("placeholder"), tc.opts...).EncodeToMemory()
|
||||
require.NoError(t, err)
|
||||
block, _ := pem.Decode(encoded)
|
||||
require.NotNil(t, block)
|
||||
|
||||
want, err := tss2.ParsePrivateKey(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
got, err := parseTSS2(block.Bytes)
|
||||
require.NoError(t, err, "the replacement must accept what the library writes")
|
||||
|
||||
assert.Equal(t, want.Type, got.Type, "key type")
|
||||
assert.Equal(t, want.EmptyAuth, got.EmptyAuth, "emptyAuth")
|
||||
assert.Equal(t, want.Parent, got.Parent, "parent handle")
|
||||
assert.Equal(t, want.PublicKey, got.PublicKey, "public area")
|
||||
assert.Equal(t, want.PrivateKey, got.PrivateKey, "private area")
|
||||
|
||||
wantPub, err := want.Public()
|
||||
require.NoError(t, err)
|
||||
signer, err := ParseKey(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, wantPub, signer.Public(), "the decoded public key must be identical")
|
||||
assert.True(t, key.PublicKey.Equal(signer.Public()), "and must be the key the fixture was built from")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEncodeTSS2_ReadableByStep checks the other direction: the fixtures the tests are
|
||||
// built on are the shape the format calls for, not merely the shape this package reads.
|
||||
func TestEncodeTSS2_ReadableByStep(t *testing.T) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
opts []tpmtest.Option
|
||||
}{
|
||||
{name: "owner hierarchy parent"},
|
||||
{name: "persistent parent", opts: []tpmtest.Option{tpmtest.WithParent(0x81000001)}},
|
||||
{name: "needs authorization", opts: []tpmtest.Option{tpmtest.WithAuth()}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
block, _ := pem.Decode([]byte(tpmtest.KeyPEM(t, &key.PublicKey, tc.opts...)))
|
||||
require.NotNil(t, block)
|
||||
|
||||
want, err := tss2.ParsePrivateKey(block.Bytes)
|
||||
require.NoError(t, err, "the library under replacement must accept our fixtures")
|
||||
got, err := parseTSS2(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, want.EmptyAuth, got.EmptyAuth, "emptyAuth")
|
||||
assert.Equal(t, want.Parent, got.Parent, "parent handle")
|
||||
assert.Equal(t, want.PublicKey, got.PublicKey, "public area")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user