diff --git a/attest/application_key.go b/attest/application_key.go index 71163b8..2e0ba6d 100644 --- a/attest/application_key.go +++ b/attest/application_key.go @@ -76,6 +76,10 @@ type KeyConfig struct { // If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed. // Supported only by TPM 2.0 on Linux. Parent *ParentKeyConfig + // QualifyingData is an optional data that will be included into + // a TPM-generated signature of the minted key. + // It may contain any data chosen by the caller. + QualifyingData []byte } // defaultConfig is used when no other configuration is specified. diff --git a/attest/application_key_test.go b/attest/application_key_test.go index 466bd3a..18416a1 100644 --- a/attest/application_key_test.go +++ b/attest/application_key_test.go @@ -100,6 +100,22 @@ func testKeyCreateAndLoad(t *testing.T, tpm *TPM) { Size: 2048, }, }, + { + name: "QualifyingData-RSA", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + QualifyingData: []byte("qualifying data"), + }, + }, + { + name: "QualifyingData-ECDSA", + opts: &KeyConfig{ + Algorithm: ECDSA, + Size: 256, + QualifyingData: []byte("qualifying data"), + }, + }, } { t.Run(test.name, func(t *testing.T) { sk, err := tpm.NewKey(ak, test.opts) diff --git a/attest/attest.go b/attest/attest.go index 2584819..4e3aded 100644 --- a/attest/attest.go +++ b/attest/attest.go @@ -117,7 +117,7 @@ type ak interface { activateCredential(tpm tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) attestationParameters() AttestationParameters - certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) + certify(tb tpmBase, handle interface{}, opts CertifyOpts) (*CertificationParameters, error) } // AK represents a key which can be used for attestation. @@ -185,7 +185,7 @@ func (k *AK) AttestationParameters() AttestationParameters { // key. Depending on the actual instantiation it can accept different handle // types (e.g., tpmutil.Handle on Linux or uintptr on Windows). func (k *AK) Certify(tpm *TPM, handle interface{}) (*CertificationParameters, error) { - return k.ak.certify(tpm.tpm, handle) + return k.ak.certify(tpm.tpm, handle, CertifyOpts{}) } // AKConfig encapsulates parameters for minting keys. diff --git a/attest/certification.go b/attest/certification.go index c30d3e3..9f330b7 100644 --- a/attest/certification.go +++ b/attest/certification.go @@ -83,6 +83,12 @@ type ActivateOpts struct { VerifierKeyNameDigest *tpm2.HashValue } +// CertifyOpts specifies options for the key's certification. +type CertifyOpts struct { + // QualifyingData is the user provided qualifying data. + QualifyingData []byte +} + // NewActivateOpts creates options for use in generating an activation challenge for a certified key. // The computed hash is the name digest of the public key used to verify the certification of our key. func NewActivateOpts(verifierPubKey tpm2.Public, ek crypto.PublicKey) (*ActivateOpts, error) { @@ -241,9 +247,9 @@ func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, }, nil } -// certify uses AK's handle and the passed signature scheme to certify the key -// with the `hnd` handle. -func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigScheme) (*CertificationParameters, error) { +// certify uses AK's handle, the passed user qualifying data, and the passed +// signature scheme to certify the key with the `hnd` handle. +func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, qualifyingData []byte, scheme tpm2.SigScheme) (*CertificationParameters, error) { pub, _, _, err := tpm2.ReadPublic(tpm, hnd) if err != nil { return nil, fmt.Errorf("tpm2.ReadPublic() failed: %v", err) @@ -252,7 +258,7 @@ func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigS if err != nil { return nil, fmt.Errorf("could not encode public key: %v", err) } - att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, nil, scheme) + att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, qualifyingData, scheme) if err != nil { return nil, fmt.Errorf("tpm2.Certify() failed: %v", err) } diff --git a/attest/certification_test.go b/attest/certification_test.go index df84155..f585c7e 100644 --- a/attest/certification_test.go +++ b/attest/certification_test.go @@ -24,6 +24,7 @@ import ( "crypto" "crypto/rand" "crypto/rsa" + "slices" "testing" "github.com/google/go-cmp/cmp" @@ -224,6 +225,15 @@ func TestTPM20KeyCertificationECC(t *testing.T) { testKeyCertification(t, tpm, ECDSA) } +func extraData(t *testing.T, p CertificationParameters) []byte { + t.Helper() + ad, err := tpm2.DecodeAttestationData(p.CreateAttestation) + if err != nil { + t.Fatalf("failed to decode attestation data: %v", err) + } + return ad.ExtraData +} + func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) { ak, err := tpm.NewAK(&AKConfig{Algorithm: akAlg}) if err != nil { @@ -247,9 +257,10 @@ func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) { Hash: hash, } for _, test := range []struct { - name string - opts *KeyConfig - err error + name string + opts *KeyConfig + wantExtraData []byte + err error }{ { name: "default", @@ -296,6 +307,26 @@ func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) { }, err: nil, }, + { + name: "QualifyingData-RSA", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + QualifyingData: []byte("qualifying data"), + }, + wantExtraData: []byte("qualifying data"), + err: nil, + }, + { + name: "QualifyingData-ECDSA", + opts: &KeyConfig{ + Algorithm: ECDSA, + Size: 384, + QualifyingData: []byte("qualifying data"), + }, + wantExtraData: []byte("qualifying data"), + err: nil, + }, } { t.Run(test.name, func(t *testing.T) { sk, err := tpm.NewKey(ak, test.opts) @@ -304,6 +335,9 @@ func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) { } defer sk.Close() p := sk.CertificationParameters() + if gotExtraData, wantExtraData := extraData(t, p), test.wantExtraData; !slices.Equal(gotExtraData, wantExtraData) { + t.Errorf("ExtraData got = %v, want = %v", gotExtraData, wantExtraData) + } err = p.Verify(verifyOpts) if test.err == nil && err == nil { return diff --git a/attest/key_linux.go b/attest/key_linux.go index 0afd58f..0a79f6e 100644 --- a/attest/key_linux.go +++ b/attest/key_linux.go @@ -96,6 +96,6 @@ func (k *trousersKey12) attestationParameters() AttestationParameters { } } -func (k *trousersKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *trousersKey12) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) { return nil, fmt.Errorf("not implemented") } diff --git a/attest/key_windows.go b/attest/key_windows.go index 831b9e6..bc0639f 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -107,7 +107,7 @@ func (k *windowsKey12) attestationParameters() AttestationParameters { Public: k.public, } } -func (k *windowsKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *windowsKey12) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) { return nil, fmt.Errorf("not implemented") } @@ -185,7 +185,7 @@ func (k *windowsKey20) attestationParameters() AttestationParameters { } } -func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *windowsKey20) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) { t, ok := tb.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) @@ -210,5 +210,5 @@ func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*CertificationPa Alg: tpm2.AlgRSASSA, Hash: tpm2.AlgSHA1, // PCP-created AK uses SHA1 } - return certify(tpm, hnd, akHnd, scheme) + return certify(tpm, hnd, akHnd, nil, scheme) } diff --git a/attest/wrapped_tpm20.go b/attest/wrapped_tpm20.go index 19ca9fa..a1cd9fc 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -302,7 +302,8 @@ func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) { }() // Certify application key by AK - cp, err := k.certify(t, keyHandle) + certifyOpts := CertifyOpts{QualifyingData: opts.QualifyingData} + cp, err := k.certify(t, keyHandle, certifyOpts) if err != nil { return nil, fmt.Errorf("ak.Certify() failed: %v", err) } @@ -587,7 +588,7 @@ func sigSchemeFromPublicKey(pub []byte) (tpm2.SigScheme, error) { } } -func (k *wrappedKey20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *wrappedKey20) certify(tb tpmBase, handle interface{}, opts CertifyOpts) (*CertificationParameters, error) { t, ok := tb.(*wrappedTPM20) if !ok { return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) @@ -600,7 +601,7 @@ func (k *wrappedKey20) certify(tb tpmBase, handle interface{}) (*CertificationPa if err != nil { return nil, fmt.Errorf("get signature scheme: %v", err) } - return certify(t.rwc, hnd, k.hnd, scheme) + return certify(t.rwc, hnd, k.hnd, opts.QualifyingData, scheme) } func (k *wrappedKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {