From bfcbe8f1e24deddbc54dc6aa041de71dfd1aa96c Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Wed, 21 Aug 2019 10:26:55 -0700 Subject: [PATCH] attest: re-work EK API (#79) This PR adds: * Renames 'PlatformEK' to 'EK' * More consistant support of EKs without certificates * Removes HTTP GET to Intel EK certificate service * Always populates EK.Public --- attest/attest-tool/attest-tool.go | 41 ++++++++++++++--- attest/attest.go | 16 +++++-- attest/attest_simulated_tpm20_test.go | 23 +--------- attest/attest_test.go | 13 ++---- attest/attest_tpm12_test.go | 8 ++-- attest/pcp_windows.go | 2 +- attest/tpm.go | 21 ++++++++- attest/tpm_linux.go | 62 +++++++++++++------------- attest/tpm_test.go | 54 +++++++++++++++++++++++ attest/tpm_windows.go | 63 +++++++++------------------ 10 files changed, 181 insertions(+), 122 deletions(-) create mode 100644 attest/tpm_test.go diff --git a/attest/attest-tool/attest-tool.go b/attest/attest-tool/attest-tool.go index aad3327..5a3e430 100644 --- a/attest/attest-tool/attest-tool.go +++ b/attest/attest-tool/attest-tool.go @@ -3,6 +3,7 @@ package main import ( "bytes" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" "encoding/hex" @@ -107,9 +108,11 @@ func runCommand(tpm *attest.TPM) error { return fmt.Errorf("failed to read EKs: %v", err) } for _, ek := range eks { - if ek.Cert != nil { - fmt.Printf("EK certificate: %x\n", ek.Cert.Raw) + data, err := encodeEK(ek) + if err != nil { + return fmt.Errorf("encoding ek: %v", err) } + fmt.Printf("%s\n", data) } case "list-pcrs": @@ -145,6 +148,33 @@ func runCommand(tpm *attest.TPM) error { return nil } +func encodeEK(ek attest.EK) ([]byte, error) { + if ek.Certificate != nil { + return pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: ek.Certificate.Raw, + }), nil + } + switch pub := ek.Public.(type) { + case *ecdsa.PublicKey: + data, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, fmt.Errorf("marshaling ec public key: %v", err) + } + return pem.EncodeToMemory(&pem.Block{ + Type: "EC PUBLIC KEY", + Bytes: data, + }), nil + case *rsa.PublicKey: + return pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: x509.MarshalPKCS1PublicKey(pub), + }), nil + default: + return nil, fmt.Errorf("unsupported public key type %T", pub) + } +} + func runDump(tpm *attest.TPM) (*internal.Dump, error) { var ( out internal.Dump @@ -201,11 +231,8 @@ func rsaEKPEM(tpm *attest.TPM) ([]byte, error) { buf bytes.Buffer ) for _, ek := range eks { - if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA { - pk = ek.Cert.PublicKey.(*rsa.PublicKey) - break - } else if ek.Public != nil { - pk = ek.Public.(*rsa.PublicKey) + if pub, ok := ek.Public.(*rsa.PublicKey); ok { + pk = pub break } } diff --git a/attest/attest.go b/attest/attest.go index ebde9c8..07bc493 100644 --- a/attest/attest.go +++ b/attest/attest.go @@ -156,11 +156,19 @@ type PCR struct { DigestAlg crypto.Hash } -// PlatformEK represents a burned-in Endorsement Key, and its -// corrresponding EKCert (where present). -type PlatformEK struct { - Cert *x509.Certificate +// EK is a burned-in endorcement key bound to a TPM. This optionally contains +// a certificate that can chain to the TPM manufacturer. +type EK struct { + // Public key of the EK. Public crypto.PublicKey + + // Certificate is the EK certificate for TPMs that provide it. + Certificate *x509.Certificate + + // For Intel TPMs, Intel hosts certificates at a public URL derived from the + // Public key. Clients or servers can perform an HTTP GET to this URL, and + // use ParseEKCertificate on the response body. + CertificateURL string } // AttestationParameters describes information about a key which is necessary diff --git a/attest/attest_simulated_tpm20_test.go b/attest/attest_simulated_tpm20_test.go index 459f0a8..9e5fa38 100644 --- a/attest/attest_simulated_tpm20_test.go +++ b/attest/attest_simulated_tpm20_test.go @@ -19,11 +19,8 @@ package attest import ( "bytes" "crypto" - "crypto/rsa" "testing" - "github.com/google/certificate-transparency-go/x509" - "github.com/google/go-tpm-tools/simulator" ) @@ -49,7 +46,7 @@ func TestSimTPM20EK(t *testing.T) { if err != nil { t.Errorf("EKs() failed: %v", err) } - if len(eks) == 0 || (eks[0].Cert == nil && eks[0].Public == nil) { + if len(eks) == 0 || (eks[0].Public == nil) { t.Errorf("EKs() = %v, want at least 1 EK with populated fields", eks) } } @@ -99,22 +96,6 @@ func TestSimTPM20AIKCreateAndLoad(t *testing.T) { } } -// chooseEKPub selects the EK public which will be activated against. -func chooseEKPub(t *testing.T, eks []PlatformEK) crypto.PublicKey { - t.Helper() - - for _, ek := range eks { - if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA { - return ek.Cert.PublicKey.(*rsa.PublicKey) - } else if ek.Public != nil { - return ek.Public - } - } - - t.Skip("No suitable RSA EK found") - return nil -} - func TestSimTPM20ActivateCredential(t *testing.T) { sim, tpm := setupSimulatedTPM(t) defer sim.Close() @@ -129,7 +110,7 @@ func TestSimTPM20ActivateCredential(t *testing.T) { if err != nil { t.Fatalf("EKs() failed: %v", err) } - ek := chooseEKPub(t, EKs) + ek := chooseEK(t, EKs) ap := ActivationParameters{ TPMVersion: TPMVersion20, diff --git a/attest/attest_test.go b/attest/attest_test.go index c7d0e7d..fff43bc 100644 --- a/attest/attest_test.go +++ b/attest/attest_test.go @@ -17,11 +17,8 @@ package attest import ( "bytes" "crypto" - "crypto/rsa" "flag" "testing" - - "github.com/google/certificate-transparency-go/x509" ) var ( @@ -121,18 +118,14 @@ func TestAIKCreateAndLoad(t *testing.T) { } // chooseEK selects the EK public which will be activated against. -func chooseEK(t *testing.T, eks []PlatformEK) crypto.PublicKey { +func chooseEK(t *testing.T, eks []EK) crypto.PublicKey { t.Helper() for _, ek := range eks { - if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA { - return ek.Cert.PublicKey.(*rsa.PublicKey) - } else if ek.Public != nil { - return ek.Public - } + return ek.Public } - t.Skip("No suitable RSA EK found") + t.Fatalf("No suitable EK found") return nil } diff --git a/attest/attest_tpm12_test.go b/attest/attest_tpm12_test.go index 6a4be84..ada4eaf 100644 --- a/attest/attest_tpm12_test.go +++ b/attest/attest_tpm12_test.go @@ -77,16 +77,14 @@ func TestTPM12EKs(t *testing.T) { tpm := openTPM12(t) defer tpm.Close() - EKs, err := tpm.EKs() + eks, err := tpm.EKs() if err != nil { t.Fatalf("Failed to get EKs: %v", err) } - if len(EKs) == 0 { + if len(eks) == 0 { t.Fatalf("EKs returned nothing") } - - t.Logf("EKCert Raw: %x\n", EKs[0].Cert.Raw) } func TestMintAIK(t *testing.T) { @@ -151,7 +149,7 @@ func TestTPMActivateCredential(t *testing.T) { if err != nil { t.Fatalf("failed to read EKs: %v", err) } - ek := chooseEKPub(t, EKs) + ek := chooseEK(t, EKs) ap := ActivationParameters{ TPMVersion: TPMVersion12, diff --git a/attest/pcp_windows.go b/attest/pcp_windows.go index 380c8a7..d3a73ed 100644 --- a/attest/pcp_windows.go +++ b/attest/pcp_windows.go @@ -404,7 +404,7 @@ func (h *winPCP) EKCerts() ([]*x509.Certificate, error) { var out []*x509.Certificate for _, der := range c { - cert, err := parseCert(der) + cert, err := ParseEKCertificate(der) if err != nil { return nil, err } diff --git a/attest/tpm.go b/attest/tpm.go index c0d52fa..196e48c 100644 --- a/attest/tpm.go +++ b/attest/tpm.go @@ -16,6 +16,9 @@ package attest import ( "bytes" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" "encoding/binary" "fmt" "io" @@ -147,7 +150,8 @@ func readTPM2VendorAttributes(tpm io.ReadWriter) (tpm20Info, error) { }, nil } -func parseCert(ekCert []byte) (*x509.Certificate, error) { +// ParseEKCertificate parses a raw DER encoded EK certificate blob. +func ParseEKCertificate(ekCert []byte) (*x509.Certificate, error) { var wasWrapped bool // TCG PC Specific Implementation section 7.3.2 specifies @@ -184,12 +188,25 @@ func parseCert(ekCert []byte) (*x509.Certificate, error) { return c, nil } +const ( + manufacturerIntel = "Intel" + intelEKCertServiceURL = "https://ekop.intel.com/ekcertservice/" +) + +func intelEKURL(ekPub *rsa.PublicKey) string { + pubHash := sha256.New() + pubHash.Write(ekPub.N.Bytes()) + pubHash.Write([]byte{0x1, 0x00, 0x01}) + + return intelEKCertServiceURL + base64.URLEncoding.EncodeToString(pubHash.Sum(nil)) +} + func readEKCertFromNVRAM20(tpm io.ReadWriter) (*x509.Certificate, error) { ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0) if err != nil { return nil, fmt.Errorf("reading EK cert: %v", err) } - return parseCert(ekCert) + return ParseEKCertificate(ekCert) } func quote20(tpm io.ReadWriter, aikHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte) (*Quote, error) { diff --git a/attest/tpm_linux.go b/attest/tpm_linux.go index 55abed9..ce027c1 100644 --- a/attest/tpm_linux.go +++ b/attest/tpm_linux.go @@ -17,6 +17,7 @@ package attest import ( + "crypto" "crypto/rsa" "encoding/binary" "errors" @@ -221,51 +222,52 @@ func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) { if err != nil { return nil, fmt.Errorf("reading EK cert: %v", err) } - return parseCert(ekCert) + return ParseEKCertificate(ekCert) } // EKs returns the endorsement keys burned-in to the platform. -func (t *TPM) EKs() ([]PlatformEK, error) { - var cert *x509.Certificate - var err error +func (t *TPM) EKs() ([]EK, error) { switch t.version { case TPMVersion12: - cert, err = readEKCertFromNVRAM12(t.ctx) - + cert, err := readEKCertFromNVRAM12(t.ctx) if err != nil { return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err) } - + return []EK{ + {Public: crypto.PublicKey(cert.PublicKey), Certificate: cert}, + }, nil case TPMVersion20: - cert, err = readEKCertFromNVRAM20(t.rwc) - - if err != nil { - ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate) - if err != nil { - return nil, fmt.Errorf("EK CreatePrimary failed: %v", err) - } - defer tpm2.FlushContext(t.rwc, ekHnd) - - pub, _, _, err := tpm2.ReadPublic(t.rwc, ekHnd) - if err != nil { - return nil, fmt.Errorf("EK ReadPublic failed: %v", err) - } - if pub.RSAParameters == nil { - return nil, errors.New("ECC EK not yet supported") - } - - return []PlatformEK{ - {nil, &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}}, + if cert, err := readEKCertFromNVRAM20(t.rwc); err == nil { + return []EK{ + {Public: crypto.PublicKey(cert.PublicKey), Certificate: cert}, }, nil } + // Attempt to create an EK. + ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate) + if err != nil { + return nil, fmt.Errorf("EK CreatePrimary failed: %v", err) + } + defer tpm2.FlushContext(t.rwc, ekHnd) + + pub, _, _, err := tpm2.ReadPublic(t.rwc, ekHnd) + if err != nil { + return nil, fmt.Errorf("EK ReadPublic failed: %v", err) + } + if pub.RSAParameters == nil { + return nil, errors.New("ECC EK not yet supported") + } + return []EK{ + { + Public: &rsa.PublicKey{ + E: int(pub.RSAParameters.Exponent()), + N: pub.RSAParameters.Modulus(), + }, + }, + }, nil default: return nil, fmt.Errorf("unsupported TPM version: %x", t.version) } - - return []PlatformEK{ - {cert, cert.PublicKey}, - }, nil } // MintAIK creates an attestation key. diff --git a/attest/tpm_test.go b/attest/tpm_test.go new file mode 100644 index 0000000..a350a03 --- /dev/null +++ b/attest/tpm_test.go @@ -0,0 +1,54 @@ +package attest + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "testing" +) + +// Generated using the following command: +// +// openssl genrsa 2048|openssl rsa -outform PEM -pubout +// +var testRSAKey = mustParseRSAKey(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8zyTXCjVALZzjS8wgNH +nAVdt4ZGM3N450xOnLplx/RbCVwXyu83SWh0B3Ka+92aocqcHzo+j6e6Urppre/I ++7VVKTdUAr8t5gxgSLGvo+ev+zv70GF4DmJthb8JNheHCmk3RnoSFs5TnDuSdvGb +KcSzas0186LQyxvwfFjTxLweGrZKh/CTewD0/f5ozXmbTtJpl+qYrMi9GJamGlg6 +N6EsWKh1xos8J/cEmS2vbyCGGADyBwRV8Zkto5EU1HJaEli10HVZf0D06vuKzzxM ++6W7LzGqzAPeaWvHi07ezShqdr5q5y1KKhFJcy8HOpwN8iFfIw70y3FtMlrMprrU +twIDAQAB +-----END PUBLIC KEY-----`) + +func mustParseRSAKey(data string) *rsa.PublicKey { + pub, err := parseRSAKey(data) + if err != nil { + panic(err) + } + return pub +} + +func parseRSAKey(data string) (*rsa.PublicKey, error) { + b, _ := pem.Decode([]byte(data)) + if b == nil { + return nil, fmt.Errorf("failed to parse PEM key") + } + pub, err := x509.ParsePKIXPublicKey(b.Bytes) + if err != nil { + return nil, fmt.Errorf("parsing public key: %v", err) + } + if rsaPub, ok := pub.(*rsa.PublicKey); ok { + return rsaPub, nil + } + return nil, fmt.Errorf("expected *rsa.PublicKey, got %T", pub) +} + +func TestIntelEKURL(t *testing.T) { + want := "https://ekop.intel.com/ekcertservice/7YtWV2nT3LpvSCfJt7ENIznN1R1jYkj_3S6mez3yyzg=" + got := intelEKURL(testRSAKey) + if got != want { + t.Fatalf("intelEKURL(), got=%q, want=%q", got, want) + } +} diff --git a/attest/tpm_windows.go b/attest/tpm_windows.go index d0cee68..ba79714 100644 --- a/attest/tpm_windows.go +++ b/attest/tpm_windows.go @@ -22,15 +22,11 @@ import ( "crypto/cipher" "crypto/rand" "crypto/rsa" - "crypto/sha256" - "encoding/base64" "encoding/binary" "errors" "fmt" "io" - "io/ioutil" "math/big" - "net/http" tpm1 "github.com/google/go-tpm/tpm" tpmtbs "github.com/google/go-tpm/tpmutil/tbs" @@ -151,35 +147,36 @@ func (t *TPM) Info() (*TPMInfo, error) { } // EKs returns the Endorsement Keys burned-in to the platform. -func (t *TPM) EKs() ([]PlatformEK, error) { +func (t *TPM) EKs() ([]EK, error) { ekCerts, err := t.pcp.EKCerts() if err != nil { return nil, fmt.Errorf("could not read EKCerts: %v", err) } - - var out []PlatformEK - for _, cert := range ekCerts { - out = append(out, PlatformEK{cert, cert.PublicKey}) + if len(ekCerts) > 0 { + var eks []EK + for _, cert := range ekCerts { + eks = append(eks, EK{Certificate: cert, Public: cert.PublicKey}) + } + return eks, nil } - if len(out) == 0 { - i, err := t.Info() - if err != nil { - return nil, err - } - if i.Manufacturer.String() == "Intel" { - eks, err := t.readEKCertFromServer("https://ekop.intel.com/ekcertservice/") - if err != nil { - return nil, err - } - out = append(out, eks...) - } + pub, err := t.ekPub() + if err != nil { + return nil, fmt.Errorf("could not read ek public key from tpm: %v", err) } + ek := EK{Public: pub} - return out, nil + i, err := t.Info() + if err != nil { + return nil, err + } + if i.Manufacturer.String() == manufacturerIntel { + ek.CertificateURL = intelEKURL(pub) + } + return []EK{ek}, nil } -func (t *TPM) readEKCertFromServer(url string) ([]PlatformEK, error) { +func (t *TPM) ekPub() (*rsa.PublicKey, error) { p, err := t.pcp.EKPub() if err != nil { return nil, fmt.Errorf("could not read ekpub: %v", err) @@ -188,25 +185,7 @@ func (t *TPM) readEKCertFromServer(url string) ([]PlatformEK, error) { if err != nil { return nil, fmt.Errorf("could not decode ekpub: %v", err) } - pubHash := sha256.New() - pubHash.Write(ekPub.N.Bytes()) - pubHash.Write([]byte{0x1, 0x00, 0x01}) - - resp, err := http.Get(url + base64.URLEncoding.EncodeToString(pubHash.Sum(nil))) - if err != nil { - return nil, fmt.Errorf("request failed: %v") - } - defer resp.Body.Close() - d, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed reading EKCert from network: %v") - } - - cert, err := parseCert(d) - if err != nil { - return nil, fmt.Errorf("parsing certificate: %v", err) - } - return []PlatformEK{{Cert: cert}}, nil + return ekPub, nil } type bcryptRSABlobHeader struct {