From 6419a80d269c5f7f7bb52a7c2e31c86a26683eff Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 30 Oct 2023 16:22:46 -0700 Subject: [PATCH] Fix parsing of secret, policy and authPolicy --- tpm/tss2/tss2.go | 118 +++++++++++++++++++------------- tpm/tss2/tss2_test.go | 154 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 210 insertions(+), 62 deletions(-) diff --git a/tpm/tss2/tss2.go b/tpm/tss2/tss2.go index a71693cf..06969d0f 100644 --- a/tpm/tss2/tss2.go +++ b/tpm/tss2/tss2.go @@ -8,6 +8,12 @@ import ( cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) +var ( + oidLoadableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3} + oidImportableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 4} + oidSealedKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 5} +) + // TPMKey is defined in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html#section-3.1: // // TPMKey ::= SEQUENCE { @@ -23,9 +29,9 @@ import ( type TPMKey struct { Type asn1.ObjectIdentifier EmptyAuth bool `asn1:"optional,explicit,tag:0"` - Policy []TPMPolicy `asn1:"optional,explicit,tag:0"` - Secret []byte `asn1:"optional,explicit,tag:0"` - AuthPolicy []TPMAuthPolicy `asn1:"optional,explicit,tag:0"` + Policy []TPMPolicy `asn1:"optional,explicit,tag:1"` + Secret []byte `asn1:"optional,explicit,tag:2"` + AuthPolicy []TPMAuthPolicy `asn1:"optional,explicit,tag:3"` Parent int Pubkey []byte Privkey []byte @@ -53,7 +59,10 @@ type TPMAuthPolicy struct { Policy []TPMPolicy `asn1:"explicit,tag:1"` } -func ParseTSS2PrivateKey(derBytes []byte) (*TPMKey, error) { +// ParsePrivateKey parses a single TPM key from the given ASN.1 DER data. +func ParsePrivateKey(derBytes []byte) (*TPMKey, error) { + var err error + input := cryptobyte.String(derBytes) if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 key") @@ -70,57 +79,29 @@ func ParseTSS2PrivateKey(derBytes []byte) (*TPMKey, error) { } } - var err error - var isPresent bool - // TODO(mariano): generate key with policy if tag, ok := readOptionalTag(&input, 1); ok { var policy cryptobyte.String - if !tag.ReadOptionalASN1(&policy, &isPresent, cryptobyte_asn1.SEQUENCE) { + if !tag.ReadASN1(&policy, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 policy") } - if isPresent { - key.Policy, err = readTPMPolicySequence(&policy) - if err != nil { - return nil, err - } + key.Policy, err = readTPMPolicySequence(&policy) + if err != nil { + return nil, err } } // TODO(mariano): generate key with secret if tag, ok := readOptionalTag(&input, 2); ok { - if !tag.ReadOptionalASN1OctetString(&key.Secret, &isPresent, cryptobyte_asn1.OCTET_STRING) { + if key.Secret, ok = readOctetString(&tag); !ok { return nil, errors.New("malformed TSS2 secret") } } // TODO(mariano): generate key with authPolicy if tag, ok := readOptionalTag(&input, 3); ok { - var authPolicy cryptobyte.String - if !tag.ReadOptionalASN1(&authPolicy, &isPresent, cryptobyte_asn1.SEQUENCE) { - return nil, errors.New("malformed TSS2 authPolicy") - } - if isPresent { - var policy cryptobyte.String - for !authPolicy.Empty() { - var ap TPMAuthPolicy - var seq cryptobyte.String - if !policy.ReadASN1Element(&seq, cryptobyte_asn1.SEQUENCE) { - return nil, errors.New("malformed TSS2 authPolicy") - } - var name cryptobyte.String - if !seq.ReadOptionalASN1(&name, &isPresent, cryptobyte_asn1.UTF8String) { - return nil, errors.New("malformed TSS2 authPolicy name") - } - var policySeq cryptobyte.String - if !seq.ReadASN1Element(&policySeq, cryptobyte_asn1.SEQUENCE) { - return nil, errors.New("malformed TSS2 authPolicy policy") - } - if ap.Policy, err = readTPMPolicySequence(&policySeq); err != nil { - return nil, errors.New("malformed TSS2 authPolicy policy") - } - key.AuthPolicy = append(key.AuthPolicy, ap) - } + if key.AuthPolicy, err = readTPMAuthPolicy(&tag); err != nil { + return nil, err } } @@ -140,7 +121,8 @@ func ParseTSS2PrivateKey(derBytes []byte) (*TPMKey, error) { return key, nil } -func MarshalTSS2PrivateKey(key TPMKey) ([]byte, error) { +// MarshalPrivateKey converts the give key to a TSS2 ASN.1 DER form. +func MarshalPrivateKey(key TPMKey) ([]byte, error) { return asn1.Marshal(key) } @@ -183,23 +165,65 @@ func readASN1Boolean(input *cryptobyte.String, out *bool) bool { } func readTPMPolicySequence(input *cryptobyte.String) ([]TPMPolicy, error) { - var ( - ok bool - policies []TPMPolicy - ) + var policies []TPMPolicy for !input.Empty() { var p TPMPolicy var seq cryptobyte.String - if !input.ReadASN1Element(&seq, cryptobyte_asn1.SEQUENCE) { + if !input.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 policy") } - if !seq.ReadASN1Integer(&p.CommandCode) { + tag, ok := readOptionalTag(&seq, 0) + if !ok || !tag.ReadASN1Integer(&p.CommandCode) { return nil, errors.New("malformed TSS2 policy commandCode") } - if p.CommandPolicy, ok = readOctetString(&seq); ok { + tag, ok = readOptionalTag(&seq, 1) + if !ok { + return nil, errors.New("malformed TSS2 policy commandPolicy") + } + if p.CommandPolicy, ok = readOctetString(&tag); !ok { return nil, errors.New("malformed TSS2 policy commandPolicy") } policies = append(policies, p) } return policies, nil } + +func readTPMAuthPolicy(input *cryptobyte.String) ([]TPMAuthPolicy, error) { + var ( + err error + authPolicy cryptobyte.String + authPolicies []TPMAuthPolicy + ) + if !input.ReadASN1(&authPolicy, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("malformed TSS2 authPolicy") + } + + for !authPolicy.Empty() { + var ap TPMAuthPolicy + var seq cryptobyte.String + if !authPolicy.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("malformed TSS2 authPolicy") + } + + var name cryptobyte.String + if tag, ok := readOptionalTag(&seq, 0); ok { + if !tag.ReadASN1(&name, cryptobyte_asn1.UTF8String) { + return nil, errors.New("malformed TSS2 authPolicy name") + } + ap.Name = string(name) + } + + var policySeq cryptobyte.String + if tag, ok := readOptionalTag(&seq, 1); ok { + if !tag.ReadASN1(&policySeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("malformed TSS2 authPolicy policy") + } + if ap.Policy, err = readTPMPolicySequence(&policySeq); err != nil { + return nil, errors.New("malformed TSS2 authPolicy policy") + } + } + authPolicies = append(authPolicies, ap) + } + + return authPolicies, nil +} diff --git a/tpm/tss2/tss2_test.go b/tpm/tss2/tss2_test.go index 89e43e83..cbfc9955 100644 --- a/tpm/tss2/tss2_test.go +++ b/tpm/tss2/tss2_test.go @@ -2,8 +2,10 @@ package tss2 import ( "encoding/pem" - "reflect" + "fmt" "testing" + + "github.com/stretchr/testify/assert" ) // key generated using: @@ -36,7 +38,21 @@ W6fVV4B/ZtnADx9/YGB9FBY8Bu07W7m+PorVTCbXFfOAFmSUg3eB0bgb2TRtFevZ izcX -----END TSS2 PRIVATE KEY-----` -func TestParseTSS2PrivateKey(t *testing.T) { +// key generated using: +// +// tpm2_createprimary -c primary.ctx +// tpm2_create -C primary.ctx -G ecc -u obj.pub -r obj.priv +// tpm2_encodeobject -C primary.ctx -u obj.pub -r obj.priv -o obj.pem +var p256EmptyAuthFalse = `-----BEGIN TSS2 PRIVATE KEY----- +MIHwBgZngQUKAQOgAwEBAAIEQAAAAQRYAFYAIwALAAYAcgAAABAAEAADABAAIIgq +1VllQRCT45GbLlp1Wud0jiSfojBwp1MYljWMw1T7ACAblgTFkwvSMnzpArA8GjVP +ULHy7pJubvS2W7TxmzclRQSBgAB+ACDVXoK8RpE5XxjBcfeHpip9Dz2j7AUj0oE1 +RKlDg/+dYgAQd9c9mgioJc8wFL1zaU4viH1fq3fObbfZF/L8oLrLv6u3Pg8qeGzf +ePVypgEUeJGw68er7UZb4ZSVfoGId6KLX9JE7IwyBkRWLhBU3sLANdgjTqlXUhAD +mnYo +-----END TSS2 PRIVATE KEY-----` + +func TestParsePrivateKey(t *testing.T) { parsePEM := func(s string) []byte { block, _ := pem.Decode([]byte(s)) return block.Bytes @@ -46,13 +62,13 @@ func TestParseTSS2PrivateKey(t *testing.T) { derBytes []byte } tests := []struct { - name string - args args - want *TPMKey - wantErr bool + name string + args args + want *TPMKey + assertion assert.ErrorAssertionFunc }{ {"ok rsa", args{parsePEM(rsaTSS2PEM)}, &TPMKey{ - Type: []int{2, 23, 133, 10, 1, 3}, + Type: oidLoadableKey, EmptyAuth: true, Parent: 1073741825, Pubkey: []byte{ @@ -91,9 +107,9 @@ func TestParseTSS2PrivateKey(t *testing.T) { 0x70, 0xbe, 0x9b, 0x37, 0xbc, 0xea, 0x7d, 0xf6, 0x4e, 0x43, 0xb4, 0xae, 0x14, 0x22, 0x03, 0x29, 0xfc, 0x8d, 0x2e, 0xad, 0x6d, 0x47, 0xba, 0x6f, 0x78, 0xd6, 0xc7, 0xb6, 0x1f, 0x28, 0xfe, 0xa0, }, - }, false}, + }, assert.NoError}, {"ok ec", args{parsePEM(p256TSS2PEM)}, &TPMKey{ - Type: []int{2, 23, 133, 10, 1, 3}, + Type: oidLoadableKey, EmptyAuth: true, Parent: 1073741825, Pubkey: []byte{ @@ -114,17 +130,125 @@ func TestParseTSS2PrivateKey(t *testing.T) { 0xed, 0x3b, 0x5b, 0xb9, 0xbe, 0x3e, 0x8a, 0xd5, 0x4c, 0x26, 0xd7, 0x15, 0xf3, 0x80, 0x16, 0x64, 0x94, 0x83, 0x77, 0x81, 0xd1, 0xb8, 0x1b, 0xd9, 0x34, 0x6d, 0x15, 0xeb, 0xd9, 0x8b, 0x37, 0x17, }, - }, false}, + }, assert.NoError}, + {"ok emptyAuth false", args{parsePEM(p256EmptyAuthFalse)}, &TPMKey{ + Type: oidLoadableKey, + EmptyAuth: false, + Parent: 1073741825, + Pubkey: []byte{ + 0x00, 0x56, 0x00, 0x23, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, + 0x00, 0x03, 0x00, 0x10, 0x00, 0x20, 0x88, 0x2a, 0xd5, 0x59, 0x65, 0x41, 0x10, 0x93, 0xe3, 0x91, + 0x9b, 0x2e, 0x5a, 0x75, 0x5a, 0xe7, 0x74, 0x8e, 0x24, 0x9f, 0xa2, 0x30, 0x70, 0xa7, 0x53, 0x18, + 0x96, 0x35, 0x8c, 0xc3, 0x54, 0xfb, 0x00, 0x20, 0x1b, 0x96, 0x04, 0xc5, 0x93, 0x0b, 0xd2, 0x32, + 0x7c, 0xe9, 0x02, 0xb0, 0x3c, 0x1a, 0x35, 0x4f, 0x50, 0xb1, 0xf2, 0xee, 0x92, 0x6e, 0x6e, 0xf4, + 0xb6, 0x5b, 0xb4, 0xf1, 0x9b, 0x37, 0x25, 0x45, + }, + Privkey: []byte{ + 0x00, 0x7e, 0x00, 0x20, 0xd5, 0x5e, 0x82, 0xbc, 0x46, 0x91, 0x39, 0x5f, 0x18, 0xc1, 0x71, 0xf7, + 0x87, 0xa6, 0x2a, 0x7d, 0x0f, 0x3d, 0xa3, 0xec, 0x05, 0x23, 0xd2, 0x81, 0x35, 0x44, 0xa9, 0x43, + 0x83, 0xff, 0x9d, 0x62, 0x00, 0x10, 0x77, 0xd7, 0x3d, 0x9a, 0x08, 0xa8, 0x25, 0xcf, 0x30, 0x14, + 0xbd, 0x73, 0x69, 0x4e, 0x2f, 0x88, 0x7d, 0x5f, 0xab, 0x77, 0xce, 0x6d, 0xb7, 0xd9, 0x17, 0xf2, + 0xfc, 0xa0, 0xba, 0xcb, 0xbf, 0xab, 0xb7, 0x3e, 0x0f, 0x2a, 0x78, 0x6c, 0xdf, 0x78, 0xf5, 0x72, + 0xa6, 0x01, 0x14, 0x78, 0x91, 0xb0, 0xeb, 0xc7, 0xab, 0xed, 0x46, 0x5b, 0xe1, 0x94, 0x95, 0x7e, + 0x81, 0x88, 0x77, 0xa2, 0x8b, 0x5f, 0xd2, 0x44, 0xec, 0x8c, 0x32, 0x06, 0x44, 0x56, 0x2e, 0x10, + 0x54, 0xde, 0xc2, 0xc0, 0x35, 0xd8, 0x23, 0x4e, 0xa9, 0x57, 0x52, 0x10, 0x03, 0x9a, 0x76, 0x28, + }, + }, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParsePrivateKey(tt.args.derBytes) + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMarshalParse(t *testing.T) { + modKey := func(fn func(key *TPMKey)) TPMKey { + fakeKey := TPMKey{ + Type: oidLoadableKey, + EmptyAuth: true, + Parent: 1234, + Pubkey: []byte("pubkey"), + Privkey: []byte("privkey"), + } + fn(&fakeKey) + return fakeKey + } + + fakePolicy1 := TPMPolicy{CommandCode: 1, CommandPolicy: []byte("fake-policy-1")} + fakePolicy2 := TPMPolicy{CommandCode: 2, CommandPolicy: []byte("fake-policy-2")} + + type args struct { + key TPMKey + } + tests := []struct { + name string + args args + assertion assert.ErrorAssertionFunc + }{ + {"ok", args{TPMKey{ + Type: oidLoadableKey, + EmptyAuth: true, + Parent: 1234, + Pubkey: []byte("pubkey"), + Privkey: []byte("privkey"), + }}, assert.NoError}, + {"ok importable key", args{modKey(func(key *TPMKey) { + key.Type = oidImportableKey + })}, assert.NoError}, + {"ok sealed key", args{modKey(func(key *TPMKey) { + key.Type = oidSealedKey + })}, assert.NoError}, + {"ok emptyAuth false", args{modKey(func(key *TPMKey) { + key.EmptyAuth = false + })}, assert.NoError}, + {"ok policy", args{modKey(func(key *TPMKey) { + key.Policy = []TPMPolicy{fakePolicy1} + })}, assert.NoError}, + {"ok policies", args{modKey(func(key *TPMKey) { + key.Policy = []TPMPolicy{fakePolicy1, fakePolicy2} + })}, assert.NoError}, + {"ok secret", args{modKey(func(key *TPMKey) { + key.Secret = []byte("secret") + })}, assert.NoError}, + {"ok authPolicy", args{modKey(func(key *TPMKey) { + key.AuthPolicy = []TPMAuthPolicy{ + {Name: "auth", Policy: []TPMPolicy{fakePolicy1}}, + } + })}, assert.NoError}, + {"ok authPolicies", args{modKey(func(key *TPMKey) { + key.AuthPolicy = []TPMAuthPolicy{ + {Name: "auth-1", Policy: []TPMPolicy{fakePolicy1}}, + {Name: "auth-2", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, + } + })}, assert.NoError}, + {"ok all", args{TPMKey{ + Type: oidLoadableKey, + EmptyAuth: true, + Policy: []TPMPolicy{fakePolicy1, fakePolicy2}, + Secret: []byte("secret"), + AuthPolicy: []TPMAuthPolicy{ + {Name: "auth-1", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, + {Name: "auth-2", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, + }, + Parent: 1234, + Pubkey: []byte("pubkey"), + Privkey: []byte("privkey"), + }}, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ParseTSS2PrivateKey(tt.args.derBytes) - if (err != nil) != tt.wantErr { - t.Errorf("ParseTSS2PrivateKey() error = %v, wantErr %v", err, tt.wantErr) + derBytes, err := MarshalPrivateKey(tt.args.key) + if err != nil { + t.Errorf("MarshalPrivateKey() error = %v", err) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseTSS2PrivateKey() = %v, want %v", got, tt.want) + fmt.Printf("%x\n", derBytes) + got, err := ParsePrivateKey(derBytes) + if tt.assertion(t, err) { + assert.Equal(t, tt.args.key, *got) } }) }