diff --git a/attest/application_key.go b/attest/application_key.go index 2ee710d0..daf7ec84 100644 --- a/attest/application_key.go +++ b/attest/application_key.go @@ -72,6 +72,13 @@ type KeyConfig struct { // Size is used to specify the bit size of the key or elliptic curve. For // example, '256' is used to specify curve P-256. Size int + // QualifyingData is data provided from outside to the TPM when an attestation + // operation is performed. The TPM doesn't interpret the data, but does sign over + // it. It can be used as a nonce to ensure freshness of an attestation. + QualifyingData []byte + // Name is used to specify a name for the key, instead of generating + // a random one. This property is only used on Windows. + Name string } // defaultConfig is used when no other configuration is specified. diff --git a/attest/attest.go b/attest/attest.go index 424622ac..eb467840 100644 --- a/attest/attest.go +++ b/attest/attest.go @@ -105,7 +105,8 @@ type ak interface { activateCredential(tpm tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) attestationParameters() AttestationParameters - certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) + certify(tb tpmBase, handle interface{}, qualifyingData []byte) (*CertificationParameters, error) + blobs() ([]byte, []byte, error) } // AK represents a key which can be used for attestation. @@ -166,11 +167,20 @@ func (k *AK) AttestationParameters() AttestationParameters { // key. Depending on the actual instantiation it can accept different handle // types (e.g., tpmutil.Handle on Linux or uintptr on Windows). func (k *AK) Certify(tpm *TPM, handle interface{}) (*CertificationParameters, error) { - return k.ak.certify(tpm.tpm, handle) + return k.ak.certify(tpm.tpm, handle, nil) +} + +// Blobs returns public and private blobs to be used by tpm2.Load(). +func (k *AK) Blobs() (pub, priv []byte, err error) { + return k.ak.blobs() } // AKConfig encapsulates parameters for minting keys. type AKConfig struct { + // Name is used to specify a name for the key, instead of generating + // a random one. This property is only used on Windows. + Name string + // The EK that will be used for attestation. // If nil, an RSA EK with handle 0x81010001 will be used. // If not nil, it must be one of EKs returned from TPM.EKs(). diff --git a/attest/certification.go b/attest/certification.go index 809fac67..d44343f2 100644 --- a/attest/certification.go +++ b/attest/certification.go @@ -238,7 +238,7 @@ func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, // certify uses AK's handle and the passed signature scheme to certify the key // with the `hnd` handle. -func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigScheme) (*CertificationParameters, error) { +func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, qualifyingData []byte, scheme tpm2.SigScheme) (*CertificationParameters, error) { pub, _, _, err := tpm2.ReadPublic(tpm, hnd) if err != nil { return nil, fmt.Errorf("tpm2.ReadPublic() failed: %v", err) @@ -247,7 +247,7 @@ func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigS if err != nil { return nil, fmt.Errorf("could not encode public key: %v", err) } - att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, nil, scheme) + att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, qualifyingData, scheme) if err != nil { return nil, fmt.Errorf("tpm2.Certify() failed: %v", err) } diff --git a/attest/key_linux.go b/attest/key_linux.go index 0afd58f7..536b9be8 100644 --- a/attest/key_linux.go +++ b/attest/key_linux.go @@ -18,6 +18,7 @@ package attest import ( + "errors" "fmt" "github.com/google/go-tspi/attestation" @@ -96,6 +97,10 @@ func (k *trousersKey12) attestationParameters() AttestationParameters { } } -func (k *trousersKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *trousersKey12) certify(tb tpmBase, handle interface{}, qualifyingData []byte) (*CertificationParameters, error) { return nil, fmt.Errorf("not implemented") } + +func (k *trousersKey12) blobs() ([]byte, []byte, error) { + return nil, nil, errors.New("not implemented") +} diff --git a/attest/key_windows.go b/attest/key_windows.go index 831b9e6c..4452ddac 100644 --- a/attest/key_windows.go +++ b/attest/key_windows.go @@ -18,28 +18,32 @@ package attest import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "errors" "fmt" "github.com/google/go-tpm/legacy/tpm2" tpm1 "github.com/google/go-tpm/tpm" ) -// 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 +53,7 @@ func (k *windowsKey12) marshal() ([]byte, error) { return out.Serialize() } -func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) { +func (k *windowsAK12) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) { tpm, ok := t.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", t) @@ -61,7 +65,7 @@ func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential, ek return decryptCredential(secretKey, in.Secret) } -func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) { +func (k *windowsAK12) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) { if alg != HashSHA1 { return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg) } @@ -98,21 +102,25 @@ func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs }, 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{}, qualifyingData []byte) (*CertificationParameters, error) { return nil, fmt.Errorf("not implemented") } -// windowsKey20 represents a key bound to a TPM 2.0. -type windowsKey20 struct { +func (k *windowsAK12) blobs() ([]byte, []byte, error) { + return nil, nil, errors.New("not implemented") +} + +// windowsAK20 represents a key bound to a TPM 2.0. +type windowsAK20 struct { hnd uintptr pcpKeyName string @@ -122,8 +130,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, @@ -133,7 +141,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, @@ -147,7 +155,7 @@ func (k *windowsKey20) marshal() ([]byte, error) { return out.Serialize() } -func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) { +func (k *windowsAK20) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) { tpm, ok := t.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", t) @@ -155,7 +163,7 @@ func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential, ek return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...)) } -func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) { +func (k *windowsAK20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) { t, ok := tb.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) @@ -172,11 +180,11 @@ func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs return quote20(tpm, tpmKeyHnd, alg.goTPMAlg(), nonce, selectedPCRs) } -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, @@ -185,7 +193,7 @@ func (k *windowsKey20) attestationParameters() AttestationParameters { } } -func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *windowsAK20) certify(tb tpmBase, handle interface{}, qualifyingData []byte) (*CertificationParameters, error) { t, ok := tb.(*windowsTPM) if !ok { return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) @@ -210,5 +218,63 @@ func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*CertificationPa Alg: tpm2.AlgRSASSA, Hash: tpm2.AlgSHA1, // PCP-created AK uses SHA1 } - return certify(tpm, hnd, akHnd, scheme) + return certify(tpm, hnd, akHnd, qualifyingData, 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, pub, createData, createAttest, createSig []byte) key { + return &windowsAK20{ + hnd: hnd, + pcpKeyName: pcpKeyName, + public: pub, + createData: createData, + createAttestation: createAttest, + createSignature: createSig, + } +} + +func (k *windowsAK20) blobs() ([]byte, []byte, error) { + // TODO(hslatman): check if this is required on Windows? `newKey` seems to create + // persistent keys with a name, so it may be possible to load the key by name instead? + 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 p := pub.(type) { + case *ecdsa.PublicKey: + return signECDSA(rw, hnd, digest, p.Curve) + case *rsa.PublicKey: + return signRSA(rw, hnd, digest, opts) + } + + return nil, fmt.Errorf("unsupported signing key type: %T", pub) } 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.go b/attest/tpm.go index 5a251d8b..e1c4b9e7 100644 --- a/attest/tpm.go +++ b/attest/tpm.go @@ -301,8 +301,10 @@ type tpmBase interface { loadAK(opaqueBlob []byte) (*AK, error) newAK(opts *AKConfig) (*AK, error) + deleteAK(opaqueBlob []byte) error loadKey(opaqueBlob []byte) (*Key, error) newKey(ak *AK, opts *KeyConfig) (*Key, error) + deleteKey(opaqueBlob []byte) error pcrs(alg HashAlg) ([]PCR, error) measurementLog() ([]byte, error) } @@ -366,6 +368,10 @@ func (t *TPM) NewAK(opts *AKConfig) (*AK, error) { return t.tpm.newAK(opts) } +func (t *TPM) DeleteAK(opaqueBlob []byte) error { + return t.tpm.deleteAK(opaqueBlob) +} + // NewKey creates an application key certified by the attestation key. If opts is nil // then DefaultConfig is used. func (t *TPM) NewKey(ak *AK, opts *KeyConfig) (*Key, error) { @@ -373,7 +379,8 @@ func (t *TPM) NewKey(ak *AK, opts *KeyConfig) (*Key, error) { opts = defaultConfig } if opts.Algorithm == "" && opts.Size == 0 { - opts = defaultConfig + opts.Algorithm = defaultConfig.Algorithm + opts.Size = defaultConfig.Size } return t.tpm.newKey(ak, opts) } @@ -386,6 +393,10 @@ func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) { return t.tpm.loadKey(opaqueBlob) } +func (t *TPM) DeleteKey(opaqueBlob []byte) error { + return t.tpm.deleteKey(opaqueBlob) +} + // PCRs returns the present value of Platform Configuration Registers with // the given digest algorithm. // diff --git a/attest/tpm12_linux.go b/attest/tpm12_linux.go index 50594967..53ab26ee 100644 --- a/attest/tpm12_linux.go +++ b/attest/tpm12_linux.go @@ -116,6 +116,10 @@ func (t *trousersTPM) loadKey(opaqueBlob []byte) (*Key, error) { return nil, fmt.Errorf("not implemented") } +func (t *trousersTPM) deleteKey(opaqueBlob []byte) error { + return fmt.Errorf("not implemented") +} + func (t *trousersTPM) newAK(opts *AKConfig) (*AK, error) { pub, blob, err := attestation.CreateAIK(t.ctx) if err != nil { @@ -136,6 +140,10 @@ func (t *trousersTPM) loadAK(opaqueBlob []byte) (*AK, error) { return &AK{ak: newTrousersKey12(sKey.Blob, sKey.Public)}, nil } +func (t *trousersTPM) deleteAK(opaqueBlob []byte) error { + return fmt.Errorf("not implemented") +} + // allPCRs12 returns a map of all the PCR values on the TPM func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) { tpm := ctx.GetTPM() diff --git a/attest/tpm_windows.go b/attest/tpm_windows.go index 6bbdcf14..bc65ba72 100644 --- a/attest/tpm_windows.go +++ b/attest/tpm_windows.go @@ -29,6 +29,7 @@ import ( "io" "math/big" + "github.com/google/go-tpm/legacy/tpm2" tpm1 "github.com/google/go-tpm/tpm" tpmtbs "github.com/google/go-tpm/tpmutil/tbs" "golang.org/x/sys/windows" @@ -287,11 +288,11 @@ func decryptCredential(secretKey, blob []byte) ([]byte, error) { } func (t *windowsTPM) newAK(opts *AKConfig) (*AK, error) { - 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, err := getAKName(opts) + if err != nil { + return nil, fmt.Errorf("failed to create name for key: %w", err) } - name := fmt.Sprintf("ak-%x", nameHex) kh, err := t.pcp.NewAK(name) if err != nil { @@ -305,9 +306,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) } @@ -329,20 +330,131 @@ 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) deleteAK(opaqueBlob []byte) error { + sKey, err := deserializeKey(opaqueBlob, TPMVersion20) + if err != nil { + return fmt.Errorf("deserializeKey() failed: %w", err) + } + if sKey.Encoding != keyEncodingOSManaged { + return fmt.Errorf("unsupported key encoding: %x", sKey.Encoding) + } + + hnd, err := t.pcp.LoadKeyByName(sKey.Name) + if err != nil { + return fmt.Errorf("pcp failed to load key: %w", err) + } + + if err := t.pcp.DeleteKey(hnd); err != nil { + return fmt.Errorf("delete public key: %w", err) + } + + return nil +} + +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) + } + + name, err := getKeyName(config) + if err != nil { + return nil, fmt.Errorf("failed to create name for key: %w", err) + } + + 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, config.QualifyingData) + 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) + } + + // TODO(hslatman): blob not used on Windows? + _ = 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) { - return nil, fmt.Errorf("not implemented") + if t.version != TPMVersion20 { + return nil, fmt.Errorf("loading keys on TPM version %v is unsupported", t.version) + } + sKey, err := deserializeKey(opaqueBlob, t.version) + if err != nil { + return nil, fmt.Errorf("deserializeKey() failed: %v", err) + } + if sKey.Encoding != keyEncodingOSManaged { + return nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding) + } + + hnd, err := t.pcp.LoadKeyByName(sKey.Name) + if err != nil { + return nil, fmt.Errorf("pcp failed to load key: %v", err) + } + + tpmPub, err := tpm2.DecodePublic(sKey.Public) + 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: newWindowsKey20(hnd, sKey.Name, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pubKey, tpm: t}, nil +} + +func (t *windowsTPM) deleteKey(opaqueBlob []byte) error { + sKey, err := deserializeKey(opaqueBlob, TPMVersion20) + if err != nil { + return fmt.Errorf("deserializeKey() failed: %w", err) + } + if sKey.Encoding != keyEncodingOSManaged { + return fmt.Errorf("unsupported key encoding: %x", sKey.Encoding) + } + + hnd, err := t.pcp.LoadKeyByName(sKey.Name) + if err != nil { + return fmt.Errorf("pcp failed to load key: %w", err) + } + + if err := t.pcp.DeleteKey(hnd); err != nil { + return fmt.Errorf("delete public key: %w", err) + } + + return nil } func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) { @@ -425,3 +537,29 @@ func (t *windowsTPM) measurementLog() ([]byte, error) { } return logBuffer, nil } + +func getKeyName(config *KeyConfig) (string, error) { + if config.Name != "" { + return config.Name, nil + } + + nameHex := make([]byte, 5) + if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) { + return "", fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err) + } + + return fmt.Sprintf("app-%x", nameHex), nil +} + +func getAKName(config *AKConfig) (string, error) { + if config.Name != "" { + return config.Name, nil + } + + nameHex := make([]byte, 5) + if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) { + return "", fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err) + } + + return fmt.Sprintf("ak-%x", nameHex), nil +} diff --git a/attest/wrapped_tpm20.go b/attest/wrapped_tpm20.go index 65a18e43..b5558436 100644 --- a/attest/wrapped_tpm20.go +++ b/attest/wrapped_tpm20.go @@ -252,7 +252,7 @@ func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) { }() // Certify application key by AK - cp, err := k.certify(t, keyHandle) + cp, err := k.certify(t, keyHandle, opts.QualifyingData) if err != nil { return nil, fmt.Errorf("ak.Certify() failed: %v", err) } @@ -366,6 +366,11 @@ func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) { return &AK{ak: newWrappedAK20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil } +func (t *wrappedTPM20) deleteAK(opaqueBlob []byte) error { + // assuming the *wrappedTPM doesn't store the key at all, there's nothing to do // TODO: verify if this is correct + return nil +} + func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) { hnd, sKey, err := t.deserializeAndLoad(opaqueBlob) if err != nil { @@ -382,6 +387,11 @@ func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) { return &Key{key: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pub, tpm: t}, nil } +func (t *wrappedTPM20) deleteKey(opaqueBlob []byte) error { + // assuming the *wrappedTPM doesn't store the key at all, there's nothing to do // TODO: verify if this is correct + return nil +} + func (t *wrappedTPM20) pcrs(alg HashAlg) ([]PCR, error) { PCRs, err := readAllPCRs20(t.rwc, alg.goTPMAlg()) if err != nil { @@ -502,7 +512,7 @@ func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential, ek }, k.hnd, ekHnd, credential, secret) } -func (k *wrappedKey20) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) { +func (k *wrappedKey20) certify(tb tpmBase, handle interface{}, qualifyingData []byte) (*CertificationParameters, error) { t, ok := tb.(*wrappedTPM20) if !ok { return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) @@ -515,7 +525,7 @@ func (k *wrappedKey20) certify(tb tpmBase, handle interface{}) (*CertificationPa Alg: tpm2.AlgRSASSA, Hash: tpm2.AlgSHA256, } - return certify(t.rwc, hnd, k.hnd, scheme) + return certify(t.rwc, hnd, k.hnd, qualifyingData, scheme) } func (k *wrappedKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {