mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-11 16:09:07 +02:00
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.
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
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])
|
|
}
|