From 181d8036e8ca47aa48d940beb13c773497458afa Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Thu, 28 Apr 2022 16:27:01 -0700 Subject: [PATCH 1/5] Add TPM 2 application key support for Windows There's currently no support for creating application keys on Windows systems. This patch transitions the Windows key type to specifically refer to attestation keys, and reuses the existing wrapped key support for application keys. This allows the creation of keys in the platform store, while still allowing said keys to be manipulated with existing TPM functionality rather than duplicating it. --- attest/key_windows.go | 40 ++++---- attest/pcp_windows.go | 212 ++++++++++++++++++++++++++++++++++++------ attest/tpm_windows.go | 52 +++++++++-- 3 files changed, 251 insertions(+), 53 deletions(-) diff --git a/attest/key_windows.go b/attest/key_windows.go index 62b49aac..70bef4e2 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -24,22 +24,22 @@ import ( "github.com/google/go-tpm/tpm2" ) -// windowsKey12 represents a Windows-managed key on a TPM1.2 TPM. -type windowsKey12 struct { +// windowsAK12 represents a Windows-managed key on a TPM1.2 TPM. +type windowsAK12 struct { hnd uintptr pcpKeyName string public []byte } -func newWindowsKey12(hnd uintptr, pcpKeyName string, public []byte) ak { - return &windowsKey12{ +func newWindowsAK12(hnd uintptr, pcpKeyName string, public []byte) ak { + return &windowsAK12{ hnd: hnd, pcpKeyName: pcpKeyName, public: public, } } -func (k *windowsKey12) marshal() ([]byte, error) { +func (k *windowsAK12) marshal() ([]byte, error) { out := serializedKey{ Encoding: keyEncodingOSManaged, TPMVersion: TPMVersion12, @@ -49,7 +49,7 @@ func (k *windowsKey12) marshal() ([]byte, error) { return out.Serialize() } -func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { +func (k *windowsAK12) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { tpm, ok := t.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", t) @@ -61,7 +61,7 @@ func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential) ([] return decryptCredential(secretKey, in.Secret) } -func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { +func (k *windowsAK12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { if alg != HashSHA1 { return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg) } @@ -103,21 +103,21 @@ func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, err }, nil } -func (k *windowsKey12) close(tpm tpmBase) error { +func (k *windowsAK12) close(tpm tpmBase) error { return closeNCryptObject(k.hnd) } -func (k *windowsKey12) attestationParameters() AttestationParameters { +func (k *windowsAK12) attestationParameters() AttestationParameters { return AttestationParameters{ Public: k.public, } } -func (k *windowsKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *windowsAK12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { return nil, fmt.Errorf("not implemented") } -// windowsKey20 represents a key bound to a TPM 2.0. -type windowsKey20 struct { +// windowsAK20 represents a key bound to a TPM 2.0. +type windowsAK20 struct { hnd uintptr pcpKeyName string @@ -127,8 +127,8 @@ type windowsKey20 struct { createSignature []byte } -func newWindowsKey20(hnd uintptr, pcpKeyName string, public, createData, createAttest, createSig []byte) ak { - return &windowsKey20{ +func newWindowsAK20(hnd uintptr, pcpKeyName string, public, createData, createAttest, createSig []byte) ak { + return &windowsAK20{ hnd: hnd, pcpKeyName: pcpKeyName, public: public, @@ -138,7 +138,7 @@ func newWindowsKey20(hnd uintptr, pcpKeyName string, public, createData, createA } } -func (k *windowsKey20) marshal() ([]byte, error) { +func (k *windowsAK20) marshal() ([]byte, error) { out := serializedKey{ Encoding: keyEncodingOSManaged, TPMVersion: TPMVersion20, @@ -152,7 +152,7 @@ func (k *windowsKey20) marshal() ([]byte, error) { return out.Serialize() } -func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { +func (k *windowsAK20) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { tpm, ok := t.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", t) @@ -160,7 +160,7 @@ func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential) ([] return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...)) } -func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { +func (k *windowsAK20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { t, ok := tb.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) @@ -177,11 +177,11 @@ func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, err return quote20(tpm, tpmKeyHnd, alg.goTPMAlg(), nonce) } -func (k *windowsKey20) close(tpm tpmBase) error { +func (k *windowsAK20) close(tpm tpmBase) error { return closeNCryptObject(k.hnd) } -func (k *windowsKey20) attestationParameters() AttestationParameters { +func (k *windowsAK20) attestationParameters() AttestationParameters { return AttestationParameters{ Public: k.public, CreateData: k.createData, @@ -190,7 +190,7 @@ func (k *windowsKey20) attestationParameters() AttestationParameters { } } -func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *windowsAK20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { t, ok := tb.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) diff --git a/attest/pcp_windows.go b/attest/pcp_windows.go index b78a3812..a13efb96 100644 --- a/attest/pcp_windows.go +++ b/attest/pcp_windows.go @@ -38,8 +38,17 @@ const ( // The below is documented in this Microsoft whitepaper: // https://github.com/Microsoft/TSS.MSR/blob/master/PCPTool.v11/Using%20the%20Windows%208%20Platform%20Crypto%20Provider%20and%20Associated%20TPM%20Functionality.pdf ncryptOverwriteKeyFlag = 0x80 + // Key usage value for generic keys + nCryptPropertyPCPKeyUsagePolicyGeneric = 0x3 // Key usage value for AKs. nCryptPropertyPCPKeyUsagePolicyIdentity = 0x8 + + // PCP key magic + pcpKeyMagic = 0x4D504350 + + // TPM types from PCP_KEY_BLOB header data + tpm12 = 0x1 + tpm20 = 0x2 ) // DLL references. @@ -53,6 +62,7 @@ var ( nCryptCreatePersistedKey = nCrypt.MustFindProc("NCryptCreatePersistedKey") nCryptFinalizeKey = nCrypt.MustFindProc("NCryptFinalizeKey") nCryptDeleteKey = nCrypt.MustFindProc("NCryptDeleteKey") + nCryptExportKey = nCrypt.MustFindProc("NCryptExportKey") crypt32 = windows.MustLoadDLL("crypt32.dll") crypt32CertEnumCertificatesInStore = crypt32.MustFindProc("CertEnumCertificatesInStore") @@ -452,16 +462,15 @@ func getPCPCerts(hProv uintptr, propertyName string) ([][]byte, error) { return out, nil } -// NewAK creates a persistent attestation key of the specified name. -func (h *winPCP) NewAK(name string) (uintptr, error) { +func (h *winPCP) newKey(name string, alg string, length uint32, policy uint32) (uintptr, []byte, []byte, error) { var kh uintptr utf16Name, err := windows.UTF16FromString(name) if err != nil { - return 0, err + return 0, nil, nil, err } - utf16RSA, err := windows.UTF16FromString("RSA") + utf16RSA, err := windows.UTF16FromString(alg) if err != nil { - return 0, err + return 0, nil, nil, err } // Create a persistent RSA key of the specified name. @@ -470,45 +479,102 @@ func (h *winPCP) NewAK(name string) (uintptr, error) { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } - return 0, fmt.Errorf("NCryptCreatePersistedKey returned %X: %v", r, msg) + return 0, nil, nil, fmt.Errorf("NCryptCreatePersistedKey returned %X: %v", r, msg) } - // Specify generated key length to be 2048 bits. - utf16Length, err := windows.UTF16FromString("Length") - if err != nil { - return 0, err + + // Set the length if provided + if length != 0 { + utf16Length, err := windows.UTF16FromString("Length") + if err != nil { + return 0, nil, nil, err + } + r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0) + if r != 0 { + if tpmErr := maybeWinErr(r); tpmErr != nil { + msg = tpmErr + } + return 0, nil, nil, fmt.Errorf("NCryptSetProperty (Length) returned %X: %v", r, msg) + } + } + // Specify the generated key usage policy if appropriate + if policy != 0 { + utf16KeyPolicy, err := windows.UTF16FromString("PCP_KEY_USAGE_POLICY") + if err != nil { + return 0, nil, nil, err + } + r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16KeyPolicy[0])), uintptr(unsafe.Pointer(&policy)), unsafe.Sizeof(policy), 0) + if r != 0 { + if tpmErr := maybeWinErr(r); tpmErr != nil { + msg = tpmErr + } + return 0, nil, nil, fmt.Errorf("NCryptSetProperty (PCP KeyUsage Policy) returned %X: %v", r, msg) + } } - var length uint32 = 2048 - r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0) + + // Finalize (create) the key. + r, _, msg = nCryptFinalizeKey.Call(kh, 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } - return 0, fmt.Errorf("NCryptSetProperty (Length) returned %X: %v", r, msg) + return 0, nil, nil, fmt.Errorf("NCryptFinalizeKey returned %X: %v", r, msg) } - // Specify the generated key can only be used for identity attestation. - utf16KeyPolicy, err := windows.UTF16FromString("PCP_KEY_USAGE_POLICY") + + // Obtain the key blob. + var sz uint32 + typeString, err := windows.UTF16FromString("OpaqueKeyBlob") if err != nil { - return 0, err + return 0, nil, nil, err } - var policy uint32 = nCryptPropertyPCPKeyUsagePolicyIdentity - r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16KeyPolicy[0])), uintptr(unsafe.Pointer(&policy)), unsafe.Sizeof(policy), 0) - if r != 0 { + + if r, _, err := nCryptExportKey.Call(kh, 0, uintptr(unsafe.Pointer(&typeString[0])), 0, 0, 0, uintptr(unsafe.Pointer(&sz)), 0); r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { - msg = tpmErr + err = tpmErr } - return 0, fmt.Errorf("NCryptSetProperty (PCP KeyUsage Policy) returned %X: %v", r, msg) + return 0, nil, nil, fmt.Errorf("NCryptGetProperty for hKey blob original query returned %X (%v)", r, err) } - // Finalize (create) the key. - r, _, msg = nCryptFinalizeKey.Call(kh, 0) - if r != 0 { + keyBlob := make([]byte, sz) + + if r, _, err := nCryptExportKey.Call(kh, 0, uintptr(unsafe.Pointer(&typeString[0])), 0, uintptr(unsafe.Pointer(&keyBlob[0])), uintptr(sz), uintptr(unsafe.Pointer(&sz)), 0); r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { - msg = tpmErr + err = tpmErr } - return 0, fmt.Errorf("NCryptFinalizeKey returned %X: %v", r, msg) + return 0, nil, nil, fmt.Errorf("NCryptGetProperty for hKey blob returned %X (%v)", r, err) + } + + pubBlob, privBlob, err := decodeKeyBlob(keyBlob) + if err != nil { + return 0, nil, nil, fmt.Errorf("decodeKeyBlob failed: %v", err) } - return kh, nil + return kh, pubBlob, privBlob, nil +} + +// NewAK creates a persistent attestation key of the specified name. +func (h *winPCP) NewAK(name string) (uintptr, error) { + // AKs need to be RSA due to platform limitations + key, _, _, err := h.newKey(name, "RSA", 2048, nCryptPropertyPCPKeyUsagePolicyIdentity) + return key, err +} + +// NewKey creates a persistent application key of the specified name. +func (h *winPCP) NewKey(name string, config *KeyConfig) (uintptr, []byte, []byte, error) { + if config.Algorithm == RSA { + return h.newKey(name, "RSA", uint32(config.Size), 0) + } else if config.Algorithm == ECDSA { + switch config.Size { + case 256: + return h.newKey(name, "ECDSA_P256", 0, 0) + case 384: + return h.newKey(name, "ECDSA_P384", 0, 0) + case 521: + return h.newKey(name, "ECDSA_P521", 0, 0) + default: + return 0, nil, nil, fmt.Errorf("unsupported ECDSA key size: %v", config.Size) + } + } + return 0, nil, nil, fmt.Errorf("unsupported algorithm type: %q", config.Algorithm) } // EKPub returns a BCRYPT_RSA_BLOB structure representing the EK. @@ -635,6 +701,98 @@ func decodeAKProps20(r *bytes.Reader) (*akProps, error) { return &out, nil } +func decodeKeyBlob(keyBlob []byte) ([]byte, []byte, error) { + r := bytes.NewReader(keyBlob) + + var magic uint32 + if err := binary.Read(r, binary.LittleEndian, &magic); err != nil { + return nil, nil, fmt.Errorf("failed to read header magic: %v", err) + } + if magic != pcpKeyMagic { + return nil, nil, fmt.Errorf("invalid header magic %X", magic) + } + + var headerSize uint32 + if err := binary.Read(r, binary.LittleEndian, &headerSize); err != nil { + return nil, nil, fmt.Errorf("failed to read header size: %v", err) + } + + var tpmType uint32 + if err := binary.Read(r, binary.LittleEndian, &tpmType); err != nil { + return nil, nil, fmt.Errorf("failed to read tpm type: %v", err) + } + + if tpmType == tpm12 { + return nil, nil, fmt.Errorf("TPM 1.2 currently unsupported") + } + + var flags uint32 + if err := binary.Read(r, binary.LittleEndian, &flags); err != nil { + return nil, nil, fmt.Errorf("failed to read key flags: %v", err) + } + + var pubLen uint32 + if err := binary.Read(r, binary.LittleEndian, &pubLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of public key: %v", err) + } + + var privLen uint32 + if err := binary.Read(r, binary.LittleEndian, &privLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of private blob: %v", err) + } + + var pubMigrationLen uint32 + if err := binary.Read(r, binary.LittleEndian, &pubMigrationLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of public migration blob: %v", err) + } + + var privMigrationLen uint32 + if err := binary.Read(r, binary.LittleEndian, &privMigrationLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of private migration blob: %v", err) + } + + var policyDigestLen uint32 + if err := binary.Read(r, binary.LittleEndian, &policyDigestLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of policy digest: %v", err) + } + + var pcrBindingLen uint32 + if err := binary.Read(r, binary.LittleEndian, &pcrBindingLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of PCR binding: %v", err) + } + + var pcrDigestLen uint32 + if err := binary.Read(r, binary.LittleEndian, &pcrDigestLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of PCR digest: %v", err) + } + + var encryptedSecretLen uint32 + if err := binary.Read(r, binary.LittleEndian, &encryptedSecretLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of hostage import symmetric key: %v", err) + } + + var tpm12HostageLen uint32 + if err := binary.Read(r, binary.LittleEndian, &tpm12HostageLen); err != nil { + return nil, nil, fmt.Errorf("failed to read length of hostage import private key: %v", err) + } + + // Skip over any padding + r.Seek(int64(headerSize), 0) + + pubKey := make([]byte, pubLen) + + if err := binary.Read(r, binary.BigEndian, &pubKey); err != nil { + return nil, nil, fmt.Errorf("failed to read public key: %v", err) + } + + privBlob := make([]byte, privLen) + if err := binary.Read(r, binary.BigEndian, &privBlob); err != nil { + return nil, nil, fmt.Errorf("failed to read private blob: %v", err) + } + + return pubKey[2:], privBlob[2:], nil +} + // LoadKeyByName returns a handle to the persistent PCP key with the specified // name. func (h *winPCP) LoadKeyByName(name string) (uintptr, error) { diff --git a/attest/tpm_windows.go b/attest/tpm_windows.go index 0203c767..f379cff1 100644 --- a/attest/tpm_windows.go +++ b/attest/tpm_windows.go @@ -30,6 +30,8 @@ import ( "math/big" tpm1 "github.com/google/go-tpm/tpm" + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpmutil" tpmtbs "github.com/google/go-tpm/tpmutil/tbs" "golang.org/x/sys/windows" ) @@ -293,9 +295,9 @@ func (t *windowsTPM) newAK(opts *AKConfig) (*AK, error) { switch t.version { case TPMVersion12: - return &AK{ak: newWindowsKey12(kh, name, props.RawPublic)}, nil + return &AK{ak: newWindowsAK12(kh, name, props.RawPublic)}, nil case TPMVersion20: - return &AK{ak: newWindowsKey20(kh, name, props.RawPublic, props.RawCreationData, props.RawAttest, props.RawSignature)}, nil + return &AK{ak: newWindowsAK20(kh, name, props.RawPublic, props.RawCreationData, props.RawAttest, props.RawSignature)}, nil default: return nil, fmt.Errorf("cannot handle TPM version: %v", t.version) } @@ -317,16 +319,54 @@ func (t *windowsTPM) loadAK(opaqueBlob []byte) (*AK, error) { switch t.version { case TPMVersion12: - return &AK{ak: newWindowsKey12(hnd, sKey.Name, sKey.Public)}, nil + return &AK{ak: newWindowsAK12(hnd, sKey.Name, sKey.Public)}, nil case TPMVersion20: - return &AK{ak: newWindowsKey20(hnd, sKey.Name, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil + return &AK{ak: newWindowsAK20(hnd, sKey.Name, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil default: return nil, fmt.Errorf("cannot handle TPM version: %v", t.version) } } -func (t *windowsTPM) newKey(*AK, *KeyConfig) (*Key, error) { - return nil, fmt.Errorf("not implemented") +func (t *windowsTPM) newKey(ak *AK, config *KeyConfig) (*Key, error) { + if t.version != TPMVersion20 { + return nil, fmt.Errorf("key generation on TPM version %v is unsupported", t.version) + } + k, ok := ak.ak.(*windowsAK20) + if !ok { + return nil, fmt.Errorf("expected *windowsAK20, got: %T", k) + } + + nameHex := make([]byte, 5) + if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) { + return nil, fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err) + } + name := fmt.Sprintf("app-%x", nameHex) + + hnd, pub, blob, err := t.pcp.NewKey(name, config) + if err != nil { + return nil, fmt.Errorf("pcp failed to mint application key: %v", err) + } + + cp, err := k.certify(t, hnd) + if err != nil { + return nil, fmt.Errorf("ak.Certify() failed: %v", err) + } + + if !bytes.Equal(pub, cp.Public) { + return nil, fmt.Errorf("certified incorrect key, expected: %v, certified: %v", pub, cp.Public) + } + + tpmPub, err := tpm2.DecodePublic(pub) + if err != nil { + return nil, fmt.Errorf("decode public key: %v", err) + } + + pubKey, err := tpmPub.Key() + if err != nil { + return nil, fmt.Errorf("access public key: %v", err) + } + + return &Key{key: newWrappedKey20(tpmutil.Handle(hnd), blob, pub, nil, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil } func (t *windowsTPM) loadKey(opaqueBlob []byte) (*Key, error) { From 6f99d74c6691bb80bce662bd8dcc5b0f128b60c9 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 10 Nov 2022 15:08:08 +0100 Subject: [PATCH 2/5] Add signing support for keys generated on Windows When generating a new key using a Windows TPM, a `wrappedKey20` was returned, which couldn't be used for signing on Windows, as it's backed by a `windowsTPM`. The `wrappedKey20` seems to be a type specifically aimed at usage with a `wrappedTPM20`, which in turn seems to be used on Linux and for testing, but not when instantiating a TPM on Windows. This commit adds the `newWindowsKey20` function, which returns a key backed by a `windowsTPM`. The key is a `windowsAK20`, now also conforming to the `key` interface, so that it can be used for signing purposes. --- attest/key_windows.go | 60 +++++++++++++++++++++++++++++++++++++++++++ attest/tpm_windows.go | 9 +++++-- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/attest/key_windows.go b/attest/key_windows.go index 70bef4e2..21489c32 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -18,6 +18,10 @@ package attest import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "errors" "fmt" tpm1 "github.com/google/go-tpm/tpm" @@ -217,3 +221,59 @@ func (k *windowsAK20) certify(tb tpmBase, handle interface{}) (*CertificationPar } return certify(tpm, hnd, akHnd, scheme) } + +// newWindowsKey20 returns a pointer to a windowsAK20, conforming to the key interface. This +// allows the resulting windowsAK20 to be used as a signing key. +func newWindowsKey20(hnd uintptr, pcpKeyName string, public, createData, createAttest, createSig []byte) key { + return &windowsAK20{ + hnd: hnd, + pcpKeyName: pcpKeyName, + public: public, + createData: createData, + createAttestation: createAttest, + createSignature: createSig, + } +} + +func (k *windowsAK20) blobs() ([]byte, []byte, error) { + return nil, nil, errors.New("not implemented") +} + +func (k *windowsAK20) certificationParameters() CertificationParameters { + return CertificationParameters{ + Public: k.public, + CreateAttestation: k.createAttestation, + CreateSignature: k.createSignature, + } +} + +func (k *windowsAK20) decrypt(tpmBase, []byte) ([]byte, error) { + return nil, errors.New("not implemented") +} + +func (k *windowsAK20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) { + + t, ok := tb.(*windowsTPM) + if !ok { + return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) + } + + rw, err := t.pcp.TPMCommandInterface() + if err != nil { + return nil, fmt.Errorf("error getting TPM command interface: %w", err) + } + + hnd, err := t.pcp.TPMKeyHandle(k.hnd) + if err != nil { + return nil, fmt.Errorf("TPMKeyHandle() failed: %v", err) + } + + switch pub.(type) { + case *ecdsa.PublicKey: + return signECDSA(rw, hnd, digest) + case *rsa.PublicKey: + return signRSA(rw, hnd, digest, opts) + } + + return nil, fmt.Errorf("unsupported signing key type: %T", pub) +} diff --git a/attest/tpm_windows.go b/attest/tpm_windows.go index f379cff1..99b86ffe 100644 --- a/attest/tpm_windows.go +++ b/attest/tpm_windows.go @@ -31,7 +31,6 @@ import ( tpm1 "github.com/google/go-tpm/tpm" "github.com/google/go-tpm/tpm2" - "github.com/google/go-tpm/tpmutil" tpmtbs "github.com/google/go-tpm/tpmutil/tbs" "golang.org/x/sys/windows" ) @@ -366,7 +365,13 @@ func (t *windowsTPM) newKey(ak *AK, config *KeyConfig) (*Key, error) { return nil, fmt.Errorf("access public key: %v", err) } - return &Key{key: newWrappedKey20(tpmutil.Handle(hnd), blob, pub, nil, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil + // TODO(hslatman): do we need the blob? + _ = blob + + // Return a new windowsAK20 certified by the ak, conforming to the key interface. This allows the + // key to be verified to have been generated by the same TPM as the ak was generated with. The + // resulting key can be used for signing purposes. + return &Key{key: newWindowsKey20(hnd, name, pub, cp.CreateData, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil } func (t *windowsTPM) loadKey(opaqueBlob []byte) (*Key, error) { From 23380a3d31c73cc3d6aec40cc681708c81b89a9e Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 4 Jan 2023 17:00:23 +0100 Subject: [PATCH 3/5] Fix missing `ECDSA` curve when signing on Windows --- attest/key_windows.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/attest/key_windows.go b/attest/key_windows.go index 21489c32..d7b62c0a 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -268,9 +268,9 @@ func (k *windowsAK20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts return nil, fmt.Errorf("TPMKeyHandle() failed: %v", err) } - switch pub.(type) { + switch p := pub.(type) { case *ecdsa.PublicKey: - return signECDSA(rw, hnd, digest) + return signECDSA(rw, hnd, digest, p.Curve) case *rsa.PublicKey: return signRSA(rw, hnd, digest, opts) } From 6e414e29adf34121e41a1ded1741eb7a8f84608f Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 23 May 2023 13:20:59 -0700 Subject: [PATCH 4/5] Explicitly set scheme for ECDSA signing My system is returning RCScheme if TPM_ALG_NULL is passed here. This should be causing the key's default scheme to be used, but for some reason it seems unhappy. Just explicitly set the scheme for now to avoid that. --- attest/key_windows.go | 2 +- attest/wrapped_tpm20.go | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/attest/key_windows.go b/attest/key_windows.go index d7b62c0a..3a3f6b2e 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -270,7 +270,7 @@ func (k *windowsAK20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts switch p := pub.(type) { case *ecdsa.PublicKey: - return signECDSA(rw, hnd, digest, p.Curve) + return signECDSA(rw, hnd, digest, p.Curve, opts) case *rsa.PublicKey: return signRSA(rw, hnd, digest, opts) } diff --git a/attest/wrapped_tpm20.go b/attest/wrapped_tpm20.go index 5d334f89..b7d76ddf 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -503,14 +503,24 @@ func (k *wrappedKey20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opt } switch p := pub.(type) { case *ecdsa.PublicKey: - return signECDSA(t.rwc, k.hnd, digest, p.Curve) + return signECDSA(t.rwc, k.hnd, digest, p.Curve, opts) 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, curve elliptic.Curve) ([]byte, error) { +func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, curve elliptic.Curve, opts crypto.SignerOpts) ([]byte, error) { + h, err := tpm2.HashToAlgorithm(opts.HashFunc()) + if err != nil { + return nil, fmt.Errorf("incorrect hash algorithm: %v", err) + } + + scheme := &tpm2.SigScheme{ + Alg: tpm2.AlgECDSA, + Hash: h, + } + // https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/crypto/ecdsa/ecdsa.go;l=181 orderBits := curve.Params().N.BitLen() orderBytes := (orderBits + 7) / 8 @@ -524,7 +534,7 @@ func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, curve ellipt } digest = ret.Bytes() - sig, err := tpm2.Sign(rw, key, "", digest, nil, nil) + sig, err := tpm2.Sign(rw, key, "", digest, nil, scheme) if err != nil { return nil, fmt.Errorf("cannot sign: %v", err) } From f28ecb795216376d353e8519d16e68ea37190c77 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 23 Jan 2024 13:22:29 -0800 Subject: [PATCH 5/5] Revert "Remove certificate-transparency-go dependancy" This reverts commit 03018e682822c6294edf5d182abca218f341a46e. --- attest/application_key_test.go | 3 ++- attest/attest-tool/attest-tool.go | 2 +- attest/attest.go | 2 +- attest/challenge_test.go | 3 ++- attest/internal/events.go | 3 ++- attest/pcp_windows.go | 3 ++- attest/secureboot.go | 2 +- attest/tpm.go | 9 +++++---- attest/tpm12_linux.go | 2 +- attest/tpm_test.go | 3 ++- attest/wrapped_tpm20.go | 2 +- attributecert/attributecert.go | 6 +++--- attributecert/attributecert_test.go | 3 ++- go.mod | 1 + 14 files changed, 26 insertions(+), 18 deletions(-) diff --git a/attest/application_key_test.go b/attest/application_key_test.go index 466bd3af..3ff42b0c 100644 --- a/attest/application_key_test.go +++ b/attest/application_key_test.go @@ -28,10 +28,11 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - "crypto/x509" "encoding/asn1" "math/big" "testing" + + "github.com/google/certificate-transparency-go/x509" ) func TestSimTPM20KeyCreateAndLoad(t *testing.T) { diff --git a/attest/attest-tool/attest-tool.go b/attest/attest-tool/attest-tool.go index fa00e88e..8f393585 100644 --- a/attest/attest-tool/attest-tool.go +++ b/attest/attest-tool/attest-tool.go @@ -6,7 +6,6 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/rsa" - "crypto/x509" "encoding/hex" "encoding/json" "encoding/pem" @@ -15,6 +14,7 @@ import ( "fmt" "os" + "github.com/google/certificate-transparency-go/x509" "github.com/google/go-attestation/attest" "github.com/google/go-attestation/attest/attest-tool/internal" ) diff --git a/attest/attest.go b/attest/attest.go index c2adb2f4..62674fc1 100644 --- a/attest/attest.go +++ b/attest/attest.go @@ -17,12 +17,12 @@ package attest import ( "crypto" - "crypto/x509" "errors" "fmt" "io" "strings" + "github.com/google/certificate-transparency-go/x509" "github.com/google/go-tpm/tpm" "github.com/google/go-tpm/tpm2" ) diff --git a/attest/challenge_test.go b/attest/challenge_test.go index 2b1a0203..8ae7b530 100644 --- a/attest/challenge_test.go +++ b/attest/challenge_test.go @@ -4,8 +4,9 @@ import ( "bytes" "crypto/rand" "crypto/rsa" - "crypto/x509" "testing" + + "github.com/google/certificate-transparency-go/x509" ) func TestMakeActivationBlob(t *testing.T) { diff --git a/attest/internal/events.go b/attest/internal/events.go index 3090eec9..52146d88 100644 --- a/attest/internal/events.go +++ b/attest/internal/events.go @@ -2,12 +2,13 @@ package internal import ( "bytes" - "crypto/x509" "encoding/binary" "errors" "fmt" "io" "unicode/utf16" + + "github.com/google/certificate-transparency-go/x509" ) const ( diff --git a/attest/pcp_windows.go b/attest/pcp_windows.go index a13efb96..a4977e10 100644 --- a/attest/pcp_windows.go +++ b/attest/pcp_windows.go @@ -19,13 +19,14 @@ package attest import ( "bytes" - "crypto/x509" "encoding/binary" "fmt" "io" "syscall" "unsafe" + "github.com/google/certificate-transparency-go/x509" + "github.com/google/go-tpm/tpmutil" tpmtbs "github.com/google/go-tpm/tpmutil/tbs" "golang.org/x/sys/windows" diff --git a/attest/secureboot.go b/attest/secureboot.go index 33715b97..ff04c52f 100644 --- a/attest/secureboot.go +++ b/attest/secureboot.go @@ -16,10 +16,10 @@ package attest import ( "bytes" - "crypto/x509" "errors" "fmt" + "github.com/google/certificate-transparency-go/x509" "github.com/google/go-attestation/attest/internal" ) diff --git a/attest/tpm.go b/attest/tpm.go index 13057bf4..df416886 100644 --- a/attest/tpm.go +++ b/attest/tpm.go @@ -18,14 +18,15 @@ import ( "bytes" "crypto/rsa" "crypto/sha256" - "crypto/x509" - "encoding/asn1" "encoding/base64" "encoding/binary" "fmt" "io" "strings" + "github.com/google/certificate-transparency-go/asn1" + "github.com/google/certificate-transparency-go/x509" + "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil" ) @@ -198,12 +199,12 @@ func ParseEKCertificate(ekCert []byte) (*x509.Certificate, error) { var cert struct { Raw asn1.RawContent } - if _, err := asn1.UnmarshalWithParams(ekCert, &cert, "lax"); err != nil { + if _, err := asn1.UnmarshalWithParams(ekCert, &cert, "lax"); err != nil && x509.IsFatal(err) { return nil, fmt.Errorf("asn1.Unmarshal() failed: %v, wasWrapped=%v", err, wasWrapped) } c, err := x509.ParseCertificate(cert.Raw) - if err != nil { + if err != nil && x509.IsFatal(err) { return nil, fmt.Errorf("x509.ParseCertificate() failed: %v", err) } return c, nil diff --git a/attest/tpm12_linux.go b/attest/tpm12_linux.go index a2eba7d2..386ccf96 100644 --- a/attest/tpm12_linux.go +++ b/attest/tpm12_linux.go @@ -19,11 +19,11 @@ package attest import ( "crypto" - "crypto/x509" "encoding/binary" "fmt" "os" + "github.com/google/certificate-transparency-go/x509" "github.com/google/go-tspi/attestation" "github.com/google/go-tspi/tspi" "github.com/google/go-tspi/tspiconst" diff --git a/attest/tpm_test.go b/attest/tpm_test.go index 7699a989..92a77611 100644 --- a/attest/tpm_test.go +++ b/attest/tpm_test.go @@ -2,10 +2,11 @@ package attest import ( "crypto/rsa" - "crypto/x509" "encoding/pem" "fmt" "testing" + + "github.com/google/certificate-transparency-go/x509" ) // Generated using the following command: diff --git a/attest/wrapped_tpm20.go b/attest/wrapped_tpm20.go index b7d76ddf..dc653391 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -20,12 +20,12 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" - "encoding/asn1" "errors" "fmt" "io" "math/big" + "github.com/google/certificate-transparency-go/asn1" "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil" ) diff --git a/attributecert/attributecert.go b/attributecert/attributecert.go index ea4331b3..716049e5 100644 --- a/attributecert/attributecert.go +++ b/attributecert/attributecert.go @@ -9,15 +9,15 @@ package attributecert import ( "bytes" "crypto" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "errors" "fmt" "math/big" "time" "github.com/google/go-attestation/oid" + "github.com/google/certificate-transparency-go/asn1" + "github.com/google/certificate-transparency-go/x509/pkix" + "github.com/google/certificate-transparency-go/x509" ) var ( diff --git a/attributecert/attributecert_test.go b/attributecert/attributecert_test.go index c38ad1c1..586392fb 100644 --- a/attributecert/attributecert_test.go +++ b/attributecert/attributecert_test.go @@ -15,12 +15,13 @@ package attributecert import ( - "crypto/x509" "encoding/json" "os" "reflect" "strings" "testing" + + "github.com/google/certificate-transparency-go/x509" ) func TestVerifyAttributeCert(t *testing.T) { diff --git a/go.mod b/go.mod index 9446478c..ee3433e0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/google/go-attestation go 1.16 require ( + github.com/google/certificate-transparency-go v1.1.2 github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.3.3 github.com/google/go-tpm-tools v0.3.9