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
This commit is contained in:
Eric Chiang 2019-08-21 10:26:55 -07:00 committed by Tom D
parent cd07b32602
commit bfcbe8f1e2
10 changed files with 181 additions and 122 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"encoding/hex" "encoding/hex"
@ -107,9 +108,11 @@ func runCommand(tpm *attest.TPM) error {
return fmt.Errorf("failed to read EKs: %v", err) return fmt.Errorf("failed to read EKs: %v", err)
} }
for _, ek := range eks { for _, ek := range eks {
if ek.Cert != nil { data, err := encodeEK(ek)
fmt.Printf("EK certificate: %x\n", ek.Cert.Raw) if err != nil {
return fmt.Errorf("encoding ek: %v", err)
} }
fmt.Printf("%s\n", data)
} }
case "list-pcrs": case "list-pcrs":
@ -145,6 +148,33 @@ func runCommand(tpm *attest.TPM) error {
return nil 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) { func runDump(tpm *attest.TPM) (*internal.Dump, error) {
var ( var (
out internal.Dump out internal.Dump
@ -201,11 +231,8 @@ func rsaEKPEM(tpm *attest.TPM) ([]byte, error) {
buf bytes.Buffer buf bytes.Buffer
) )
for _, ek := range eks { for _, ek := range eks {
if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA { if pub, ok := ek.Public.(*rsa.PublicKey); ok {
pk = ek.Cert.PublicKey.(*rsa.PublicKey) pk = pub
break
} else if ek.Public != nil {
pk = ek.Public.(*rsa.PublicKey)
break break
} }
} }

View File

@ -156,11 +156,19 @@ type PCR struct {
DigestAlg crypto.Hash DigestAlg crypto.Hash
} }
// PlatformEK represents a burned-in Endorsement Key, and its // EK is a burned-in endorcement key bound to a TPM. This optionally contains
// corrresponding EKCert (where present). // a certificate that can chain to the TPM manufacturer.
type PlatformEK struct { type EK struct {
Cert *x509.Certificate // Public key of the EK.
Public crypto.PublicKey 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 // AttestationParameters describes information about a key which is necessary

View File

@ -19,11 +19,8 @@ package attest
import ( import (
"bytes" "bytes"
"crypto" "crypto"
"crypto/rsa"
"testing" "testing"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm-tools/simulator" "github.com/google/go-tpm-tools/simulator"
) )
@ -49,7 +46,7 @@ func TestSimTPM20EK(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("EKs() failed: %v", err) 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) 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) { func TestSimTPM20ActivateCredential(t *testing.T) {
sim, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer sim.Close() defer sim.Close()
@ -129,7 +110,7 @@ func TestSimTPM20ActivateCredential(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("EKs() failed: %v", err) t.Fatalf("EKs() failed: %v", err)
} }
ek := chooseEKPub(t, EKs) ek := chooseEK(t, EKs)
ap := ActivationParameters{ ap := ActivationParameters{
TPMVersion: TPMVersion20, TPMVersion: TPMVersion20,

View File

@ -17,11 +17,8 @@ package attest
import ( import (
"bytes" "bytes"
"crypto" "crypto"
"crypto/rsa"
"flag" "flag"
"testing" "testing"
"github.com/google/certificate-transparency-go/x509"
) )
var ( var (
@ -121,18 +118,14 @@ func TestAIKCreateAndLoad(t *testing.T) {
} }
// chooseEK selects the EK public which will be activated against. // 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() t.Helper()
for _, ek := range eks { for _, ek := range eks {
if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA { return ek.Public
return ek.Cert.PublicKey.(*rsa.PublicKey)
} else if ek.Public != nil {
return ek.Public
}
} }
t.Skip("No suitable RSA EK found") t.Fatalf("No suitable EK found")
return nil return nil
} }

View File

@ -77,16 +77,14 @@ func TestTPM12EKs(t *testing.T) {
tpm := openTPM12(t) tpm := openTPM12(t)
defer tpm.Close() defer tpm.Close()
EKs, err := tpm.EKs() eks, err := tpm.EKs()
if err != nil { if err != nil {
t.Fatalf("Failed to get EKs: %v", err) t.Fatalf("Failed to get EKs: %v", err)
} }
if len(EKs) == 0 { if len(eks) == 0 {
t.Fatalf("EKs returned nothing") t.Fatalf("EKs returned nothing")
} }
t.Logf("EKCert Raw: %x\n", EKs[0].Cert.Raw)
} }
func TestMintAIK(t *testing.T) { func TestMintAIK(t *testing.T) {
@ -151,7 +149,7 @@ func TestTPMActivateCredential(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to read EKs: %v", err) t.Fatalf("failed to read EKs: %v", err)
} }
ek := chooseEKPub(t, EKs) ek := chooseEK(t, EKs)
ap := ActivationParameters{ ap := ActivationParameters{
TPMVersion: TPMVersion12, TPMVersion: TPMVersion12,

View File

@ -404,7 +404,7 @@ func (h *winPCP) EKCerts() ([]*x509.Certificate, error) {
var out []*x509.Certificate var out []*x509.Certificate
for _, der := range c { for _, der := range c {
cert, err := parseCert(der) cert, err := ParseEKCertificate(der)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -16,6 +16,9 @@ package attest
import ( import (
"bytes" "bytes"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
@ -147,7 +150,8 @@ func readTPM2VendorAttributes(tpm io.ReadWriter) (tpm20Info, error) {
}, nil }, 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 var wasWrapped bool
// TCG PC Specific Implementation section 7.3.2 specifies // TCG PC Specific Implementation section 7.3.2 specifies
@ -184,12 +188,25 @@ func parseCert(ekCert []byte) (*x509.Certificate, error) {
return c, nil 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) { func readEKCertFromNVRAM20(tpm io.ReadWriter) (*x509.Certificate, error) {
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0) ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err) 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) { func quote20(tpm io.ReadWriter, aikHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte) (*Quote, error) {

View File

@ -17,6 +17,7 @@
package attest package attest
import ( import (
"crypto"
"crypto/rsa" "crypto/rsa"
"encoding/binary" "encoding/binary"
"errors" "errors"
@ -221,51 +222,52 @@ func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err) 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. // EKs returns the endorsement keys burned-in to the platform.
func (t *TPM) EKs() ([]PlatformEK, error) { func (t *TPM) EKs() ([]EK, error) {
var cert *x509.Certificate
var err error
switch t.version { switch t.version {
case TPMVersion12: case TPMVersion12:
cert, err = readEKCertFromNVRAM12(t.ctx) cert, err := readEKCertFromNVRAM12(t.ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err) return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err)
} }
return []EK{
{Public: crypto.PublicKey(cert.PublicKey), Certificate: cert},
}, nil
case TPMVersion20: case TPMVersion20:
cert, err = readEKCertFromNVRAM20(t.rwc) if cert, err := readEKCertFromNVRAM20(t.rwc); err == nil {
return []EK{
if err != nil { {Public: crypto.PublicKey(cert.PublicKey), Certificate: cert},
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()}},
}, nil }, 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: default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version) return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
} }
return []PlatformEK{
{cert, cert.PublicKey},
}, nil
} }
// MintAIK creates an attestation key. // MintAIK creates an attestation key.

54
attest/tpm_test.go Normal file
View File

@ -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)
}
}

View File

@ -22,15 +22,11 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math/big" "math/big"
"net/http"
tpm1 "github.com/google/go-tpm/tpm" tpm1 "github.com/google/go-tpm/tpm"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs" 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. // 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() ekCerts, err := t.pcp.EKCerts()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read EKCerts: %v", err) return nil, fmt.Errorf("could not read EKCerts: %v", err)
} }
if len(ekCerts) > 0 {
var out []PlatformEK var eks []EK
for _, cert := range ekCerts { for _, cert := range ekCerts {
out = append(out, PlatformEK{cert, cert.PublicKey}) eks = append(eks, EK{Certificate: cert, Public: cert.PublicKey})
}
return eks, nil
} }
if len(out) == 0 { pub, err := t.ekPub()
i, err := t.Info() if err != nil {
if err != nil { return nil, fmt.Errorf("could not read ek public key from tpm: %v", err)
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...)
}
} }
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() p, err := t.pcp.EKPub()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read ekpub: %v", err) 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 { if err != nil {
return nil, fmt.Errorf("could not decode ekpub: %v", err) return nil, fmt.Errorf("could not decode ekpub: %v", err)
} }
pubHash := sha256.New() return ekPub, nil
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
} }
type bcryptRSABlobHeader struct { type bcryptRSABlobHeader struct {