Skip to content

Commit 215e2ab

Browse files
authored
Add HashNonce flag to Attest and VerifyAttestation (#585)
Currently, key.Attest takes a slice of data for the attestation nonce. This gets passed directly to the TPM. However, verifiers that are not aware of the TPM's max size for extraData can inadvertently send a challenge that is too large. This change introduces a HashNonce flag that uses the key's signing scheme hash algorithm to first hash the AttestOpts nonce. The server package also introduces the same logic to hash the nonce based on the Attestation message's AkPub signing scheme. This allows verifiers to send an arbitrary amount of entropy without knowing the max hash size of the TPM.
1 parent d2379fa commit 215e2ab

File tree

4 files changed

+109
-21
lines changed

4 files changed

+109
-21
lines changed

client/attest.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ type AttestOpts struct {
5959
// depending on the technology's size. Leaving this nil is not recommended. If
6060
// nil, then TEEDevice must be nil.
6161
TEENonce []byte
62+
// HashNonce will apply the attestation key's signing scheme hash algorithm
63+
// to the input Nonce field and use the resulting digest in place of the
64+
// original Nonce.
65+
// Nonce must still be unique and application-specific.
66+
HashNonce bool
6267
}
6368

6469
// SevSnpQuoteProvider encapsulates the SEV-SNP attestation device to add its attestation report
@@ -286,8 +291,16 @@ func (k *Key) Attest(opts AttestOpts) (*pb.Attestation, error) {
286291
return nil, fmt.Errorf("failed to encode public area: %w", err)
287292
}
288293
attestation.AkCert = k.CertDERBytes()
294+
extraData := opts.Nonce
295+
if opts.HashNonce {
296+
var err error
297+
extraData, err = internal.HashNonce(k.PublicArea(), extraData)
298+
if err != nil {
299+
return nil, fmt.Errorf("failed to hash the input nonce: %w", err)
300+
}
301+
}
289302
for _, sel := range sels {
290-
quote, err := k.Quote(sel, opts.Nonce)
303+
quote, err := k.Quote(sel, extraData)
291304
if err != nil {
292305
return nil, err
293306
}

internal/quote.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,20 @@ func validatePCRDigest(quoteInfo *tpm2.QuoteInfo, pcrs *pb.PCRs, hash crypto.Has
133133
}
134134
return nil
135135
}
136+
137+
// HashNonce takes an arbitrary-sized nonce and ensures it can fit in the TPM's
138+
// extraData field by applying the given key's signing hash algorithm to the
139+
// nonce.
140+
func HashNonce(pubArea tpm2.Public, nonce []byte) ([]byte, error) {
141+
sigHash, err := GetSigningHashAlg(pubArea)
142+
if err != nil {
143+
return nil, err
144+
}
145+
chash, err := sigHash.Hash()
146+
if err != nil {
147+
return nil, err
148+
}
149+
hasher := chash.New()
150+
hasher.Write(nonce)
151+
return hasher.Sum(nil), nil
152+
}

server/verify.go

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ type VerifyOpts struct {
6666
// "Calling EFI Application from Boot Option". This option is useful when
6767
// the host platform loads EFI Applications unrelated to OS boot.
6868
AllowEFIAppBeforeCallingEvent bool
69+
// HashNonce will apply the attestation key's signing scheme hash algorithm
70+
// to the input Nonce field and use the resulting digest in place of the
71+
// original Nonce.
72+
HashNonce bool
6973
}
7074

7175
// Bootloader refers to the second-stage bootloader that loads and transfers
@@ -114,16 +118,24 @@ func VerifyAttestation(attestation *pb.Attestation, opts VerifyOpts) (*pb.Machin
114118
return nil, fmt.Errorf("bad options: %w", err)
115119
}
116120

117-
machineState, akPubKey, err := validateAK(attestation, opts)
121+
machineState, akPub, akPubKey, err := validateAK(attestation, opts)
118122
if err != nil {
119123
return nil, fmt.Errorf("failed to parse and validate AK: %w", err)
120124
}
125+
extraData := opts.Nonce
126+
if opts.HashNonce {
127+
var err error
128+
extraData, err = internal.HashNonce(akPub, extraData)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to hash the input nonce: %w", err)
131+
}
132+
}
121133

122134
// Attempt to replay the log against our PCRs in order of hash preference
123135
var lastErr error
124136
for _, quote := range supportedQuotes(attestation.GetQuotes()) {
125137
// Verify the Quote
126-
if err := internal.VerifyQuote(quote, akPubKey, opts.Nonce); err != nil {
138+
if err := internal.VerifyQuote(quote, akPubKey, extraData); err != nil {
127139
lastErr = fmt.Errorf("failed to verify quote: %w", err)
128140
continue
129141
}
@@ -158,44 +170,48 @@ func VerifyAttestation(attestation *pb.Attestation, opts VerifyOpts) (*pb.Machin
158170

159171
// validateAK validates AK cert in the attestation, and returns AK cert (if exists) and public key.
160172
// It also pulls out the GCE Instance Info if it exists.
161-
func validateAK(attestation *pb.Attestation, opts VerifyOpts) (*pb.MachineState, crypto.PublicKey, error) {
173+
func validateAK(attestation *pb.Attestation, opts VerifyOpts) (*pb.MachineState, tpm2.Public, crypto.PublicKey, error) {
174+
// If the AK Cert is not in the attestation, use the AK Public Area.
175+
akPubArea, err := tpm2.DecodePublic(attestation.GetAkPub())
176+
if err != nil {
177+
return nil, tpm2.Public{}, nil, fmt.Errorf("failed to decode AK public area: %w", err)
178+
}
179+
akPubKey, err := akPubArea.Key()
180+
if err != nil {
181+
return nil, tpm2.Public{}, nil, fmt.Errorf("failed to get AK public key: %w", err)
182+
}
162183
if len(attestation.GetAkCert()) == 0 || len(opts.TrustedRootCerts) == 0 {
163-
// If the AK Cert is not in the attestation, use the AK Public Area.
164-
akPubArea, err := tpm2.DecodePublic(attestation.GetAkPub())
165-
if err != nil {
166-
return nil, nil, fmt.Errorf("failed to decode AK public area: %w", err)
167-
}
168-
akPubKey, err := akPubArea.Key()
169-
if err != nil {
170-
return nil, nil, fmt.Errorf("failed to get AK public key: %w", err)
171-
}
172184
if err := validateAKPub(akPubKey, opts); err != nil {
173-
return nil, nil, fmt.Errorf("failed to validate AK public key: %w", err)
185+
return nil, tpm2.Public{}, nil, fmt.Errorf("failed to validate AK public key: %w", err)
174186
}
175-
return &pb.MachineState{}, akPubKey, nil
187+
return &pb.MachineState{}, akPubArea, akPubKey, nil
176188
}
177189

178190
// If AK Cert is presented, ignore the AK Public Area.
179191
akCert, err := x509.ParseCertificate(attestation.GetAkCert())
192+
certPubKey := akCert.PublicKey.(crypto.PublicKey) // This cast cannot fail
193+
if !internal.PubKeysEqual(certPubKey, akPubKey) {
194+
return nil, tpm2.Public{}, nil, errors.New("AK certificate does not match key")
195+
}
180196
if err != nil {
181-
return nil, nil, fmt.Errorf("failed to parse AK certificate: %w", err)
197+
return nil, tpm2.Public{}, nil, fmt.Errorf("failed to parse AK certificate: %w", err)
182198
}
183199
// Use intermediate certs from the attestation if they exist.
184200
certs, err := parseCerts(attestation.IntermediateCerts)
185201
if err != nil {
186-
return nil, nil, fmt.Errorf("attestation intermediates: %w", err)
202+
return nil, tpm2.Public{}, nil, fmt.Errorf("attestation intermediates: %w", err)
187203
}
188204
opts.IntermediateCerts = append(opts.IntermediateCerts, certs...)
189205

190206
if err := VerifyAKCert(akCert, opts.TrustedRootCerts, opts.IntermediateCerts); err != nil {
191-
return nil, nil, fmt.Errorf("failed to validate AK certificate: %w", err)
207+
return nil, tpm2.Public{}, nil, fmt.Errorf("failed to validate AK certificate: %w", err)
192208
}
193209
instanceInfo, err := getInstanceInfoFromExtensions(akCert.Extensions)
194210
if err != nil {
195-
return nil, nil, fmt.Errorf("error getting instance info: %v", err)
211+
return nil, tpm2.Public{}, nil, fmt.Errorf("error getting instance info: %v", err)
196212
}
197213

198-
return &pb.MachineState{Platform: &pb.PlatformState{InstanceInfo: instanceInfo}}, akCert.PublicKey, nil
214+
return &pb.MachineState{Platform: &pb.PlatformState{InstanceInfo: instanceInfo}}, akPubArea, akCert.PublicKey, nil
199215
}
200216

201217
// GetGCEInstanceInfo takes a GCE-issued x509 EK/AK certificate and tries to

server/verify_test.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"fmt"
1414
"io"
1515
"os"
16+
"strconv"
1617
"testing"
1718

1819
"github.com/google/go-tpm-tools/client"
@@ -220,6 +221,47 @@ func TestVerifyWithTrustedAK(t *testing.T) {
220221
}
221222
}
222223

224+
func TestVerifyHashNonce(t *testing.T) {
225+
rwc := test.GetTPM(t)
226+
defer client.CheckedClose(t, rwc)
227+
228+
ak, err := client.AttestationKeyRSA(rwc)
229+
if err != nil {
230+
t.Fatalf("failed to generate AK: %v", err)
231+
}
232+
defer ak.Close()
233+
tests := []struct {
234+
attHash bool
235+
verHash bool
236+
wantErr bool
237+
}{
238+
{true, true, false},
239+
{false, false, false},
240+
{true, false, true},
241+
{false, true, true},
242+
}
243+
nonce := []byte("super secret nonce")
244+
245+
for _, test := range tests {
246+
t.Run("attest hash "+strconv.FormatBool(test.attHash)+" verify hash "+strconv.FormatBool(test.verHash), func(t *testing.T) {
247+
attestation, err := ak.Attest(client.AttestOpts{Nonce: nonce, HashNonce: test.attHash})
248+
if err != nil {
249+
t.Fatalf("failed to attest: %v", err)
250+
}
251+
252+
opts := VerifyOpts{
253+
Nonce: nonce,
254+
TrustedAKs: []crypto.PublicKey{ak.PublicKey()},
255+
HashNonce: test.verHash,
256+
}
257+
_, err = VerifyAttestation(attestation, opts)
258+
if test.wantErr != (err != nil) {
259+
t.Errorf("Attest(HashNonce %v), Verify(HashNonce %v): got %v wantErr %v", test.attHash, test.verHash, err, test.wantErr)
260+
}
261+
})
262+
}
263+
}
264+
223265
func TestVerifySHA1Attestation(t *testing.T) {
224266
rwc := test.GetTPM(t)
225267
defer client.CheckedClose(t, rwc)
@@ -439,7 +481,7 @@ func TestValidateAK(t *testing.T) {
439481

440482
for _, tc := range testCases {
441483
t.Run(tc.name, func(t *testing.T) {
442-
_, _, err := validateAK(tc.att(), tc.opts)
484+
_, _, _, err := validateAK(tc.att(), tc.opts)
443485
if gotPass := (err == nil); gotPass != tc.wantPass {
444486
t.Errorf("ValidateAK failed, got pass %v, but want %v", gotPass, tc.wantPass)
445487
}

0 commit comments

Comments
 (0)