From ec746ef31b9312cb4adcf3933815f07e0e87ef73 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Thu, 28 Apr 2022 16:27:01 -0700 Subject: [PATCH 1/4] 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 | 216 ++++++++++++++++++++++++++++++++++++------ attest/tpm_windows.go | 52 ++++++++-- 3 files changed, 253 insertions(+), 55 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..7caf3335 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,63 +462,119 @@ 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") + utf16Alg, err := windows.UTF16FromString(alg) if err != nil { - return 0, err + return 0, nil, nil, err } - // Create a persistent RSA key of the specified name. - r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0) + // Create a persistent key of the specified name. + r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16Alg[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0) if r != 0 { 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 44c9e4155ca2f99f62200edf0fd3329118872409 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 10 Nov 2022 15:08:08 +0100 Subject: [PATCH 2/4] 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 b6c808344c09609f6361e49a713e94858c0f690b Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 4 Jan 2023 17:00:23 +0100 Subject: [PATCH 3/4] 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 1702994f7d221d2b31e054fa308a5f2b35160116 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 23 May 2023 13:20:59 -0700 Subject: [PATCH 4/4] 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 | 20 +++++++++++++++++--- 2 files changed, 18 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..ceff6345 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -503,14 +503,28 @@ 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) { + var scheme *tpm2.SigScheme + + if opts != nil { + 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 +538,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) }