From 0b7298fb18b67601b5270858e7bc9c77217d0a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sza=C5=82achowski?= Date: Thu, 20 May 2021 11:15:09 -0700 Subject: [PATCH] Support RSA application keys (#218) --- attest/application_key.go | 4 +- attest/application_key_test.go | 172 +++++++++++++++++++++++++++++---- attest/certification_test.go | 127 +++++++++++++++++++++++- attest/tpm.go | 8 ++ attest/wrapped_tpm20.go | 61 +++++++++--- 5 files changed, 336 insertions(+), 36 deletions(-) diff --git a/attest/application_key.go b/attest/application_key.go index 519b33f..1c66dc9 100644 --- a/attest/application_key.go +++ b/attest/application_key.go @@ -26,7 +26,7 @@ type key interface { close(tpmBase) error marshal() ([]byte, error) certificationParameters() CertificationParameters - sign(tpmBase, []byte) ([]byte, error) + sign(tpmBase, []byte, crypto.PublicKey, crypto.SignerOpts) ([]byte, error) decrypt(tpmBase, []byte) ([]byte, error) blobs() ([]byte, []byte, error) } @@ -48,7 +48,7 @@ type signer struct { // Sign signs digest with the TPM-stored private signing key. func (s *signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return s.key.sign(s.tpm, digest) + return s.key.sign(s.tpm, digest, s.pub, opts) } // Public returns the public key corresponding to the private signing key. diff --git a/attest/application_key_test.go b/attest/application_key_test.go index 35c4d70..08db88b 100644 --- a/attest/application_key_test.go +++ b/attest/application_key_test.go @@ -84,9 +84,23 @@ func testKeyCreateAndLoad(t *testing.T, tpm *TPM) { Size: 521, }, }, + { + name: "RSA-1024", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 1024, + }, + }, + { + name: "RSA-2048", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + }, } { t.Run(test.name, func(t *testing.T) { - sk, err := tpm.NewKey(ak, nil) + sk, err := tpm.NewKey(ak, test.opts) if err != nil { t.Fatalf("NewKey() failed: %v", err) } @@ -164,48 +178,131 @@ func TestTPM20KeySign(t *testing.T) { testKeySign(t, tpm) } +type simpleOpts struct { + Hash crypto.Hash +} + +func (o *simpleOpts) HashFunc() crypto.Hash { + return o.Hash +} + func testKeySign(t *testing.T, tpm *TPM) { ak, err := tpm.NewAK(nil) if err != nil { t.Fatalf("NewAK() failed: %v", err) } + for _, test := range []struct { - name string - opts *KeyConfig - digest []byte + name string + keyOpts *KeyConfig + signOpts crypto.SignerOpts + digest []byte }{ { - name: "default", - opts: nil, - digest: []byte("12345678901234567890123456789012"), + name: "default", + keyOpts: nil, + signOpts: nil, + digest: []byte("12345678901234567890123456789012"), }, { name: "ECDSAP256-SHA256", - opts: &KeyConfig{ + keyOpts: &KeyConfig{ Algorithm: ECDSA, Size: 256, }, + signOpts: nil, + digest: []byte("12345678901234567890123456789012"), + }, + { + name: "ECDSAP384-SHA384", + keyOpts: &KeyConfig{ + Algorithm: ECDSA, + Size: 384, + }, + signOpts: nil, + digest: []byte("123456789012345678901234567890121234567890123456"), + }, + { + name: "ECDSAP521-SHA512", + keyOpts: &KeyConfig{ + Algorithm: ECDSA, + Size: 521, + }, + signOpts: nil, + digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"), + }, + { + name: "RSA2048-PKCS1v15-SHA256", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &simpleOpts{ + Hash: crypto.SHA256, + }, digest: []byte("12345678901234567890123456789012"), }, { - name: "ECDSAP384-SHA384", - opts: &KeyConfig{ - Algorithm: ECDSA, - Size: 384, + name: "RSA2048-PKCS1v15-SHA384", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &simpleOpts{ + Hash: crypto.SHA384, }, digest: []byte("123456789012345678901234567890121234567890123456"), }, { - name: "ECDSAP521-SHA512", - opts: &KeyConfig{ - Algorithm: ECDSA, - Size: 521, + name: "RSA2048-PKCS1v15-SHA512", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &simpleOpts{ + Hash: crypto.SHA512, + }, + digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"), + }, + { + name: "RSA2048-PSS-SHA256", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA256, + }, + digest: []byte("12345678901234567890123456789012"), + }, + { + name: "RSA2048-PSS-SHA384", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA384, + }, + digest: []byte("123456789012345678901234567890121234567890123456"), + }, + { + name: "RSA2048-PSS-SHA512", + keyOpts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + signOpts: &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA512, }, digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"), }, } { t.Run(test.name, func(t *testing.T) { - sk, err := tpm.NewKey(ak, test.opts) + sk, err := tpm.NewKey(ak, test.keyOpts) if err != nil { t.Fatalf("NewKey() failed: %v", err) } @@ -220,12 +317,16 @@ func testKeySign(t *testing.T, tpm *TPM) { if !ok { t.Fatalf("want crypto.Signer, got %T", priv) } - sig, err := signer.Sign(rand.Reader, test.digest, nil) + sig, err := signer.Sign(rand.Reader, test.digest, test.signOpts) if err != nil { t.Fatalf("signer.Sign() failed: %v", err) } - verifyECDSA(t, pub, test.digest, sig) + if test.keyOpts == nil || test.keyOpts.Algorithm == ECDSA { + verifyECDSA(t, pub, test.digest, sig) + } else { + verifyRSA(t, pub, test.digest, sig, test.signOpts) + } }) } } @@ -246,6 +347,23 @@ func verifyECDSA(t *testing.T, pub crypto.PublicKey, digest, sig []byte) { } } +func verifyRSA(t *testing.T, pub crypto.PublicKey, digest, sig []byte, opts crypto.SignerOpts) { + t.Helper() + pubRSA, ok := pub.(*rsa.PublicKey) + if !ok { + t.Fatalf("want *rsa.PublicKey, got %T", pub) + } + if pss, ok := opts.(*rsa.PSSOptions); ok { + if err := rsa.VerifyPSS(pubRSA, opts.HashFunc(), digest, sig, pss); err != nil { + t.Fatalf("rsa.VerifyPSS(): %v", err) + } + } else { + if err := rsa.VerifyPKCS1v15(pubRSA, opts.HashFunc(), digest, sig); err != nil { + t.Fatalf("signature verification failed: %v", err) + } + } +} + func TestSimTPM20KeyOpts(t *testing.T) { sim, tpm := setupSimulatedTPM(t) defer sim.Close() @@ -318,6 +436,22 @@ func testKeyOpts(t *testing.T, tpm *TPM) { }, err: false, }, + { + name: "RSA-1024", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 1024, + }, + err: false, + }, + { + name: "RSA-2048", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + err: false, + }, } { t.Run(test.name, func(t *testing.T) { sk, err := tpm.NewKey(ak, test.opts) diff --git a/attest/certification_test.go b/attest/certification_test.go index ee38fcd..ef277d0 100644 --- a/attest/certification_test.go +++ b/attest/certification_test.go @@ -25,10 +25,25 @@ import ( "github.com/google/go-tpm/tpm2" ) -func TestCertificationParametersTPM20(t *testing.T) { - s, tpm := setupSimulatedTPM(t) - defer s.Close() +func TestSimTPM20CertificationParameters(t *testing.T) { + sim, tpm := setupSimulatedTPM(t) + defer sim.Close() + testCertificationParameters(t, tpm) +} +func TestTPM20CertificationParameters(t *testing.T) { + if !*testLocal { + t.SkipNow() + } + tpm, err := OpenTPM(nil) + if err != nil { + t.Fatalf("OpenTPM() failed: %v", err) + } + defer tpm.Close() + testCertificationParameters(t, tpm) +} + +func testCertificationParameters(t *testing.T, tpm *TPM) { ak, err := tpm.NewAK(nil) if err != nil { t.Fatal(err) @@ -148,3 +163,109 @@ func TestCertificationParametersTPM20(t *testing.T) { }) } } + +func TestSimTPM20KeyCertification(t *testing.T) { + sim, tpm := setupSimulatedTPM(t) + defer sim.Close() + testKeyCertification(t, tpm) +} + +func TestTPM20KeyCertification(t *testing.T) { + if !*testLocal { + t.SkipNow() + } + tpm, err := OpenTPM(nil) + if err != nil { + t.Fatalf("OpenTPM() failed: %v", err) + } + defer tpm.Close() + testKeyCertification(t, tpm) +} + +func testKeyCertification(t *testing.T, tpm *TPM) { + ak, err := tpm.NewAK(nil) + if err != nil { + t.Fatalf("NewAK() failed: %v", err) + } + akAttestParams := ak.AttestationParameters() + pub, err := tpm2.DecodePublic(akAttestParams.Public) + if err != nil { + t.Fatalf("DecodePublic() failed: %v", err) + } + pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()} + hash, err := pub.RSAParameters.Sign.Hash.Hash() + if err != nil { + t.Fatalf("cannot access AK's hash function: %v", err) + } + verifyOpts := VerifyOpts{ + Public: pk, + Hash: hash, + } + for _, test := range []struct { + name string + opts *KeyConfig + err error + }{ + { + name: "default", + opts: nil, + err: nil, + }, + { + name: "ECDSAP256-SHA256", + opts: &KeyConfig{ + Algorithm: ECDSA, + Size: 256, + }, + err: nil, + }, + { + name: "ECDSAP384-SHA384", + opts: &KeyConfig{ + Algorithm: ECDSA, + Size: 384, + }, + err: nil, + }, + { + name: "ECDSAP521-SHA512", + opts: &KeyConfig{ + Algorithm: ECDSA, + Size: 521, + }, + err: nil, + }, + { + name: "RSA-1024, key too short", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 1024, + }, + err: cmpopts.AnyError, + }, + { + name: "RSA-2048", + opts: &KeyConfig{ + Algorithm: RSA, + Size: 2048, + }, + err: nil, + }, + } { + t.Run(test.name, func(t *testing.T) { + sk, err := tpm.NewKey(ak, test.opts) + if err != nil { + t.Fatalf("NewKey() failed: %v", err) + } + defer sk.Close() + p := sk.CertificationParameters() + err = p.Verify(verifyOpts) + if test.err == nil && err == nil { + return + } + if got, want := err, test.err; !cmp.Equal(got, want, cmpopts.EquateErrors()) { + t.Errorf("p.Verify() err = %v, want = %v", got, want) + } + }) + } +} diff --git a/attest/tpm.go b/attest/tpm.go index 8bef96d..706a17a 100644 --- a/attest/tpm.go +++ b/attest/tpm.go @@ -107,6 +107,14 @@ var ( }, }, } + // Basic template for an RSA key signing outside-TPM objects. Other + // fields are populated depending on the key creation options. + rsaKeyTemplate = tpm2.Public{ + Type: tpm2.AlgRSA, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, + RSAParameters: &tpm2.RSAParams{}, + } ) type tpm20Info struct { diff --git a/attest/wrapped_tpm20.go b/attest/wrapped_tpm20.go index b51f17d..b6e5ba6 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -17,9 +17,11 @@ package attest import ( "bytes" "crypto" + "crypto/ecdsa" "crypto/rsa" "errors" "fmt" + "io" "math/big" "github.com/google/certificate-transparency-go/asn1" @@ -224,7 +226,11 @@ func templateFromConfig(opts *KeyConfig) (tpm2.Public, error) { var tmpl tpm2.Public switch opts.Algorithm { case RSA: - return tmpl, fmt.Errorf("RSA keys are not implemented") + tmpl = rsaKeyTemplate + if opts.Size < 0 || opts.Size > 65535 { // basic sanity check + return tmpl, fmt.Errorf("incorrect size parameter") + } + tmpl.RSAParameters.KeyBits = uint16(opts.Size) case ECDSA: tmpl = ecdsaKeyTemplate @@ -468,25 +474,56 @@ func (k *wrappedKey20) certificationParameters() CertificationParameters { } } -func (k *wrappedKey20) sign(tb tpmBase, digest []byte) ([]byte, error) { +func (k *wrappedKey20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) { t, ok := tb.(*wrappedTPM20) if !ok { return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) } - sig, err := tpm2.Sign(t.rwc, k.hnd, "", digest, nil, nil) + switch pub.(type) { + case *ecdsa.PublicKey: + return signECDSA(t.rwc, k.hnd, digest) + case *rsa.PublicKey: + return signRSA(t.rwc, k.hnd, digest, opts) + } + return nil, fmt.Errorf("unsupported signing key type: %T", pub) +} + +func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte) ([]byte, error) { + sig, err := tpm2.Sign(rw, key, "", digest, nil, nil) if err != nil { - return nil, fmt.Errorf("signing data: %v", err) + return nil, fmt.Errorf("cannot sign: %v", err) } - if sig.RSA != nil { - return sig.RSA.Signature, nil + if sig.ECC == nil { + return nil, fmt.Errorf("expected ECDSA signature, got: %v", sig.Alg) } - if sig.ECC != nil { - return asn1.Marshal(struct { - R *big.Int - S *big.Int - }{sig.ECC.R, sig.ECC.S}) + return asn1.Marshal(struct { + R *big.Int + S *big.Int + }{sig.ECC.R, sig.ECC.S}) +} + +func signRSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + h, err := tpm2.HashToAlgorithm(opts.HashFunc()) + if err != nil { + return nil, fmt.Errorf("incorrect hash algorithm: %v", err) } - return nil, fmt.Errorf("unsupported signature type: %v", sig.Alg) + + scheme := &tpm2.SigScheme{ + Alg: tpm2.AlgRSASSA, + Hash: h, + } + if _, ok := opts.(*rsa.PSSOptions); ok { + scheme.Alg = tpm2.AlgRSAPSS + } + + sig, err := tpm2.Sign(rw, key, "", digest, nil, scheme) + if err != nil { + return nil, fmt.Errorf("cannot sign: %v", err) + } + if sig.RSA == nil { + return nil, fmt.Errorf("expected RSA signature, got: %v", sig.Alg) + } + return sig.RSA.Signature, nil } func (k *wrappedKey20) decrypt(tb tpmBase, ctxt []byte) ([]byte, error) {