diff --git a/tpm/commands.go b/tpm/commands.go index 43b22d96..a8c87679 100644 --- a/tpm/commands.go +++ b/tpm/commands.go @@ -103,6 +103,40 @@ func unseal(rw io.ReadWriter, keyHandle tpmutil.Handle, sealed *tpmStoredData, c return outb, &ra1, &ra2, ret, nil } +// authorizeMigrationKey authorizes a public key for migrations. +func authorizeMigrationKey(rw io.ReadWriter, migrationScheme MigrationScheme, migrationKey pubKey, ca *commandAuth) ([]byte, *responseAuth, uint32, error) { + in := []interface{}{migrationScheme, migrationKey, ca} + var ra responseAuth + var migrationAuth migrationKeyAuth + out := []interface{}{&migrationAuth, &ra} + ret, err := submitTPMRequest(rw, tagRQUAuth1Command, ordAuthorizeMigrationKey, in, out) + if err != nil { + return nil, nil, 0, err + } + authBlob, err := tpmutil.Pack(migrationAuth) + if err != nil { + return nil, nil, 0, err + } + + return authBlob, &ra, ret, nil +} + +// createMigrationBlob migrates a key from the TPM. +func createMigrationBlob(rw io.ReadWriter, parentHandle tpmutil.Handle, migrationScheme MigrationScheme, migrationKey []byte, encData tpmutil.U32Bytes, ca1 *commandAuth, ca2 *commandAuth) ([]byte, []byte, *responseAuth, *responseAuth, uint32, error) { + in := []interface{}{parentHandle, migrationScheme, migrationKey, encData, ca1, ca2} + var rand tpmutil.U32Bytes + var outData tpmutil.U32Bytes + var ra1 responseAuth + var ra2 responseAuth + out := []interface{}{&rand, &outData, &ra1, &ra2} + ret, err := submitTPMRequest(rw, tagRQUAuth2Command, ordCreateMigrationBlob, in, out) + if err != nil { + return nil, nil, nil, nil, 0, err + } + + return rand, outData, &ra1, &ra2, ret, nil +} + // flushSpecific removes a handle from the TPM. Note that removing a handle // doesn't require any authentication. func flushSpecific(rw io.ReadWriter, handle tpmutil.Handle, resourceType uint32) error { diff --git a/tpm/constants.go b/tpm/constants.go index 3e2452d1..39cf40c5 100644 --- a/tpm/constants.go +++ b/tpm/constants.go @@ -36,35 +36,39 @@ const ( // Supported TPM operations. const ( - ordOIAP uint32 = 0x0000000A - ordOSAP uint32 = 0x0000000B - ordTakeOwnership uint32 = 0x0000000D - ordExtend uint32 = 0x00000014 - ordPCRRead uint32 = 0x00000015 - ordQuote uint32 = 0x00000016 - ordSeal uint32 = 0x00000017 - ordUnseal uint32 = 0x00000018 - ordCreateWrapKey uint32 = 0x0000001F - ordGetPubKey uint32 = 0x00000021 - ordSign uint32 = 0x0000003C - ordQuote2 uint32 = 0x0000003E - ordResetLockValue uint32 = 0x00000040 - ordLoadKey2 uint32 = 0x00000041 - ordGetRandom uint32 = 0x00000046 - ordOwnerClear uint32 = 0x0000005B - ordForceClear uint32 = 0x0000005D - ordGetCapability uint32 = 0x00000065 - ordMakeIdentity uint32 = 0x00000079 - ordActivateIdentity uint32 = 0x0000007A - ordReadPubEK uint32 = 0x0000007C - ordOwnerReadInternalPub uint32 = 0x00000081 - ordFlushSpecific uint32 = 0x000000BA - ordNVDefineSpace uint32 = 0x000000CC - ordPcrReset uint32 = 0x000000C8 - ordNVWriteValue uint32 = 0x000000CD - ordNVWriteValueAuth uint32 = 0x000000CE - ordNVReadValue uint32 = 0x000000CF - ordNVReadValueAuth uint32 = 0x000000D0 + ordOIAP uint32 = 0x0000000A + ordOSAP uint32 = 0x0000000B + ordTakeOwnership uint32 = 0x0000000D + ordExtend uint32 = 0x00000014 + ordPCRRead uint32 = 0x00000015 + ordQuote uint32 = 0x00000016 + ordSeal uint32 = 0x00000017 + ordUnseal uint32 = 0x00000018 + ordCreateWrapKey uint32 = 0x0000001F + ordGetPubKey uint32 = 0x00000021 + ordCreateMigrationBlob uint32 = 0x00000028 + ordAuthorizeMigrationKey uint32 = 0x0000002b + ordSign uint32 = 0x0000003C + ordQuote2 uint32 = 0x0000003E + ordResetLockValue uint32 = 0x00000040 + ordLoadKey2 uint32 = 0x00000041 + ordGetRandom uint32 = 0x00000046 + ordOwnerClear uint32 = 0x0000005B + ordForceClear uint32 = 0x0000005D + ordGetCapability uint32 = 0x00000065 + ordCreateEndorsementKeyPair uint32 = 0x00000078 + ordMakeIdentity uint32 = 0x00000079 + ordActivateIdentity uint32 = 0x0000007A + ordReadPubEK uint32 = 0x0000007C + ordOwnerReadInternalPub uint32 = 0x00000081 + ordStartup uint32 = 0x00000099 + ordFlushSpecific uint32 = 0x000000BA + ordNVDefineSpace uint32 = 0x000000CC + ordPcrReset uint32 = 0x000000C8 + ordNVWriteValue uint32 = 0x000000CD + ordNVWriteValueAuth uint32 = 0x000000CE + ordNVReadValue uint32 = 0x000000CF + ordNVReadValueAuth uint32 = 0x000000D0 ) // Capability types. @@ -294,6 +298,28 @@ const ( authPrivUseOnly byte = 0x03 ) +// KeyFlags represents TPM_KEY_FLAGS. +type KeyFlags uint32 + +const ( + keyRedirection KeyFlags = 0x00000001 + keyMigratable KeyFlags = 0x00000002 + keyIsVolatile KeyFlags = 0x00000004 + keyPcrIgnoredOnRead KeyFlags = 0x00000008 + keyMigrateAuthority KeyFlags = 0x00000010 +) + +// MigrationScheme represents TPM_MIGRATE_SCHEME. +type MigrationScheme uint16 + +const ( + msMigrate MigrationScheme = 0x0001 + msRewrap MigrationScheme = 0x0002 + msMaint MigrationScheme = 0x0003 + msRestrictMigrate MigrationScheme = 0x0004 + msRestrictApprove MigrationScheme = 0x0005 +) + // fixedQuote is the fixed constant string used in quoteInfo. var fixedQuote = [4]byte{byte('Q'), byte('U'), byte('O'), byte('T')} diff --git a/tpm/open_other.go b/tpm/open_other.go index e39558b5..0925da93 100644 --- a/tpm/open_other.go +++ b/tpm/open_other.go @@ -27,6 +27,12 @@ import ( // device, then it treats it like a normal TPM device, and if the file is a // Unix domain socket, then it opens a connection to the socket. func OpenTPM(path string) (io.ReadWriteCloser, error) { + return openAndStartupTPM(path, false) +} + +// openAndStartupTPM opens the TPM and optionally runs TPM_Startup if needed. +// This feature is implemented only for testing. +func openAndStartupTPM(path string, doStartup bool) (io.ReadWriteCloser, error) { rwc, err := tpmutil.OpenTPM(path) if err != nil { return nil, err @@ -34,9 +40,14 @@ func OpenTPM(path string) (io.ReadWriteCloser, error) { // Make sure this is a TPM 1.2 _, err = GetManufacturer(rwc) + if doStartup && err == tpmError(errInvalidPostInit) { + if err = startup(rwc); err == nil { + _, err = GetManufacturer(rwc) + } + } if err != nil { rwc.Close() - return nil, fmt.Errorf("open %s: device is not a TPM 1.2", path) + return nil, fmt.Errorf("open %s: device is not a TPM 1.2: %v", path, err) } return rwc, nil } diff --git a/tpm/structures.go b/tpm/structures.go index 0e5b6ad4..f59436f2 100644 --- a/tpm/structures.go +++ b/tpm/structures.go @@ -256,7 +256,7 @@ type symmetricKeyParams struct { type key struct { Version uint32 KeyUsage uint16 - KeyFlags uint32 + KeyFlags KeyFlags AuthDataUsage byte AlgorithmParams keyParams PCRInfo tpmutil.U32Bytes @@ -283,6 +283,13 @@ type pubKey struct { Key tpmutil.U32Bytes } +// A migrationKeyAuth represents the target of a migration. +type migrationKeyAuth struct { + MigrationKey pubKey + MigrationScheme MigrationScheme + Digest Digest +} + // A symKey is a TPM representation of a symmetric key. type symKey struct { AlgID Algorithm diff --git a/tpm/testing.md b/tpm/testing.md new file mode 100644 index 00000000..3fc91f93 --- /dev/null +++ b/tpm/testing.md @@ -0,0 +1,43 @@ +# Testing TPM 1.2 Functionality + +**TODO(https://github.com/google/go-tpm/issues/91):** Support for testing the TPM 1.2 stack against +a simulator is a work in progress. Today, it requires several manual steps. + +## Overview + +As TPM 1.2s are phased out of common developer devices, testing changes to the TPM 1.2 stack is +difficult without special hardware. To support development on the TPM 1.2 stack without special +hardware, a TPM 1.2 simulator or emulator may be used. This document discusses how to use +[IBM's TPM 1.2 simulator](http://ibmswtpm.sourceforge.net) (on a Linux or Mac OS device, Windows is +not yet supported) to run the go-tpm TPM 1.2 tests (in the `go-tpm/tpm/` directory). + +## Downloading, building, and using the IBM TPM 1.2 Simulator + +* Download the latest release of the +[IBM TPM 1.2 Simulator](https://sourceforge.net/projects/ibmswtpm/), unpack the tarball, and `cd` +into it. +* Add `-DTPM_UNIX_DOMAIN_SOCKET` to `tpm/makefile-en-ac`. +* Build the simulator with `make -f tpm/makefile-en-ac` +* Set `TEMP_TPM=/tmp/tpm` or some other suitable temporary location for the TPM state files and Unix + domain socket. +* Start the simulator with `TPM_PATH=${TEMP_TPM} TPM_PORT=${TEMP_TPM}/tpm.sock` + +## Running the TPM 1.2 tests against the IBM TPM 1.2 Simulator + +* Comment out the line `t.Skip()` in `TestTakeOwnership`. This test normally does not work on + physical TPMs, so it is normally disabled. +* Use `TestTakeOwnership` to take ownership of the simulated TPM with `TPM_PATH=${TEMP_TPM}/tpm.sock + go test -v ./tpm/... -run TestTakeOwnership -count=1` +* Run the full test suite with `TPM_PATH=${TEMP_TPM}/tpm.sock go test -v ./tpm/...` + +## Future Improvements + +* Add setup logic to the TPM 1.2 tests to take ownership of an unowned TPM under test. +* Wrap a TPM 1.2 simulator somewhere (possibly in https://github.com/google/go-tpm-tools) and + integrate it into test setup for the TPM 1.2 tests. +* Resolve issues that necessitated the use of `t.Skip()` in current tests. + * Either add an informative comment along with a skip when a test fails for an expected reason, or + remove the test. +* Resolve issues with current tests that fail on the simulator (such as `TestGetAlgs`). +* Automate the use of a simulator in a Continuous Integration environment that is accessible to + GitHub. \ No newline at end of file diff --git a/tpm/tpm.go b/tpm/tpm.go index 60c5db8a..9f54ef40 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -121,7 +121,7 @@ func LoadKey2(rw io.ReadWriter, keyBlob []byte, srkAuth []byte) (tpmutil.Handle, defer zeroBytes(sharedSecret[:]) authIn := []interface{}{ordLoadKey2, k} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return 0, err } @@ -160,7 +160,7 @@ func Quote2(rw io.ReadWriter, handle tpmutil.Handle, data []byte, pcrVals []int, return nil, err } authIn := []interface{}{ordQuote2, hash, pcrSel, addVersion} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, err } @@ -193,7 +193,7 @@ func GetPubKey(rw io.ReadWriter, keyHandle tpmutil.Handle, srkAuth []byte) ([]by defer zeroBytes(sharedSecret[:]) authIn := []interface{}{ordGetPubKey} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, err } @@ -256,18 +256,29 @@ func newOSAPSession(rw io.ReadWriter, entityType uint16, entityValue tpmutil.Han } // newCommandAuth creates a new commandAuth structure over the given -// parameters, using the given secret for HMAC computation. -func newCommandAuth(authHandle tpmutil.Handle, nonceEven Nonce, key []byte, params []interface{}) (*commandAuth, error) { +// parameters, using the given secret and the given odd nonce, if provided, +// for the HMAC. If no odd nonce is provided, one is randomly generated. +func newCommandAuth(authHandle tpmutil.Handle, nonceEven Nonce, nonceOdd *Nonce, key []byte, params []interface{}) (*commandAuth, error) { // Auth = HMAC-SHA1(key, SHA1(params) || NonceEven || NonceOdd || ContSession) digestBytes, err := tpmutil.Pack(params...) if err != nil { return nil, err } - digest := sha1.Sum(digestBytes) - ca := &commandAuth{AuthHandle: authHandle} - if _, err := rand.Read(ca.NonceOdd[:]); err != nil { - return nil, err + + // Use the passed-in nonce if non-nil, otherwise generate it now. + var odd Nonce + if nonceOdd != nil { + odd = *nonceOdd + } else { + if _, err := rand.Read(odd[:]); err != nil { + return nil, err + } + } + + ca := &commandAuth{ + AuthHandle: authHandle, + NonceOdd: odd, } authBytes, err := tpmutil.Pack(digest, nonceEven, ca.NonceOdd, ca.ContSession) @@ -349,7 +360,7 @@ func sealHelper(rw io.ReadWriter, pcrInfo *pcrInfoLong, data []byte, srkAuth []b // len(data) || data) // authIn := []interface{}{ordSeal, sc.EncAuth, uint32(binary.Size(pcrInfo)), pcrInfo, tpmutil.U32Bytes(data)} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, err } @@ -423,14 +434,14 @@ func Unseal(rw io.ReadWriter, sealed []byte, srkAuth []byte) ([]byte, error) { authIn := []interface{}{ordUnseal, tsd} // The first commandAuth uses the shared secret as an HMAC key. - ca1, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca1, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, err } // The second commandAuth is based on OIAP instead of OSAP and uses the // SRK auth value as an HMAC key instead of the shared secret. - ca2, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, srkAuth, authIn) + ca2, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, srkAuth, authIn) if err != nil { return nil, err } @@ -472,7 +483,7 @@ func Quote(rw io.ReadWriter, handle tpmutil.Handle, data []byte, pcrNums []int, return nil, nil, err } authIn := []interface{}{ordQuote, hash, pcrSel} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, nil, err } @@ -586,12 +597,12 @@ func MakeIdentity(rw io.ReadWriter, srkAuth []byte, ownerAuth []byte, aikAuth [] // digest = SHA1(ordMakeIdentity || encAuth || caDigest || aik) // authIn := []interface{}{ordMakeIdentity, encAuth, caDigest, aik} - ca1, err := newCommandAuth(osaprSRK.AuthHandle, osaprSRK.NonceEven, sharedSecretSRK[:], authIn) + ca1, err := newCommandAuth(osaprSRK.AuthHandle, osaprSRK.NonceEven, nil, sharedSecretSRK[:], authIn) if err != nil { return nil, err } - ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return nil, err } @@ -675,11 +686,11 @@ func ActivateIdentity(rw io.ReadWriter, aikAuth []byte, ownerAuth []byte, aik tp defer zeroBytes(sharedSecretOwn[:]) authIn := []interface{}{ordActivateIdentity, tpmutil.U32Bytes(asym)} - ca1, err := newCommandAuth(oiaprAIK.AuthHandle, oiaprAIK.NonceEven, aikAuth, authIn) + ca1, err := newCommandAuth(oiaprAIK.AuthHandle, oiaprAIK.NonceEven, nil, aikAuth, authIn) if err != nil { return nil, fmt.Errorf("newCommandAuth failed: %v", err) } - ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca2, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return nil, fmt.Errorf("newCommandAuth failed: %v", err) } @@ -757,7 +768,7 @@ func ResetLockValue(rw io.ReadWriter, ownerAuth Digest) error { // digest = SHA1(ordResetLockValue) // authIn := []interface{}{ordResetLockValue} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return err } @@ -794,7 +805,7 @@ func ownerReadInternalHelper(rw io.ReadWriter, kh tpmutil.Handle, ownerAuth Dige // digest = SHA1(ordOwnerReadInternalPub || kh) // authIn := []interface{}{ordOwnerReadInternalPub, kh} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return nil, err } @@ -929,7 +940,7 @@ func NVDefineSpace(rw io.ReadWriter, nvData NVDataPublic, ownAuth []byte) error encAuthData := sha1.Sum(xorData) authIn := []interface{}{ordNVDefineSpace, nvData, encAuthData} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return err } @@ -965,7 +976,7 @@ func NVReadValue(rw io.ReadWriter, index, offset, len uint32, ownAuth []byte) ([ defer osaprOwn.Close(rw) defer zeroBytes(sharedSecretOwn[:]) authIn := []interface{}{ordNVReadValue, index, offset, len} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return nil, fmt.Errorf("failed to construct owner auth fields: %v", err) } @@ -996,7 +1007,7 @@ func NVReadValueAuth(rw io.ReadWriter, index, offset, len uint32, auth []byte) ( defer osapr.Close(rw) defer zeroBytes(sharedSecret[:]) authIn := []interface{}{ordNVReadValueAuth, index, offset, len} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, fmt.Errorf("failed to construct auth fields: %v", err) } @@ -1028,7 +1039,7 @@ func NVWriteValue(rw io.ReadWriter, index, offset uint32, data []byte, ownAuth [ defer osaprOwn.Close(rw) defer zeroBytes(sharedSecretOwn[:]) authIn := []interface{}{ordNVWriteValue, index, offset, len(data), data} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return fmt.Errorf("failed to construct owner auth fields: %v", err) } @@ -1059,7 +1070,7 @@ func NVWriteValueAuth(rw io.ReadWriter, index, offset uint32, data []byte, auth defer osapr.Close(rw) defer zeroBytes(sharedSecret[:]) authIn := []interface{}{ordNVWriteValueAuth, index, offset, len(data), data} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return fmt.Errorf("failed to construct auth fields: %v", err) } @@ -1200,7 +1211,7 @@ func OwnerClear(rw io.ReadWriter, ownerAuth Digest) error { // digest = SHA1(ordOwnerClear) // authIn := []interface{}{ordOwnerClear} - ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, sharedSecretOwn[:], authIn) + ca, err := newCommandAuth(osaprOwn.AuthHandle, osaprOwn.NonceEven, nil, sharedSecretOwn[:], authIn) if err != nil { return err } @@ -1279,8 +1290,8 @@ func TakeOwnership(rw io.ReadWriter, newOwnerAuth Digest, newSRKAuth Digest, pub // The digest for TakeOwnership is // // SHA1(ordTakeOwnership || pidOwner || encOwnerAuth || encSRKAuth || srk) - authIn := []interface{}{ordTakeOwnership, pidOwner, encOwnerAuth, encSRKAuth, srk} - ca, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, newOwnerAuth[:], authIn) + authIn := []interface{}{ordTakeOwnership, pidOwner, tpmutil.U32Bytes(encOwnerAuth), tpmutil.U32Bytes(encSRKAuth), srk} + ca, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, newOwnerAuth[:], authIn) if err != nil { return err } @@ -1294,15 +1305,7 @@ func TakeOwnership(rw io.ReadWriter, newOwnerAuth Digest, newSRKAuth Digest, pub return ra.verify(ca.NonceOdd, newOwnerAuth[:], raIn) } -// CreateWrapKey creates a new RSA key for signatures inside the TPM. It is -// wrapped by the SRK (which is to say, the SRK is the parent key). The key can -// be bound to the specified PCR numbers so that it can only be used for -// signing if the PCR values of those registers match. The pcrs parameter can -// be nil in which case the key is not bound to any PCRs. The usageAuth -// parameter defines the auth key for using this new key. The migrationAuth -// parameter would be used for authorizing migration of the key (although this -// code currently disables migration). -func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migrationAuth Digest, pcrs []int) ([]byte, error) { +func createWrapKeyHelper(rw io.ReadWriter, srkAuth []byte, keyFlags KeyFlags, usageAuth Digest, migrationAuth Digest, pcrs []int) (*key, error) { // Run OSAP for the SRK, reading a random OddOSAP for our initial // command and getting back a secret and a handle. sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth) @@ -1317,15 +1320,35 @@ func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migration return nil, err } defer zeroBytes(xorData) + + // We have to come up with NonceOdd early to encrypt the migration auth. + var nonceOdd Nonce + if _, err := rand.Read(nonceOdd[:]); err != nil { + return nil, err + } + + // ADIP (Authorization Data Insertion Protocol) is based on NonceEven for the first auth value + // encrypted by the protocol, and NonceOdd for the second auth value. This is so that the two + // keystreams are independent - otherwise, an eavesdropping attacker could XOR the two encrypted + // values together to cancel out the key and calculate (usageAuth ^ migrationAuth). + xorData2, err := tpmutil.Pack(sharedSecret, nonceOdd) + if err != nil { + return nil, err + } + defer zeroBytes(xorData2) + encAuthDataKey := sha1.Sum(xorData) + defer zeroBytes(encAuthDataKey[:]) + encAuthDataKey2 := sha1.Sum(xorData2) + defer zeroBytes(encAuthDataKey2[:]) var encUsageAuth Digest - for i := range srkAuth { + for i := range usageAuth { encUsageAuth[i] = encAuthDataKey[i] ^ usageAuth[i] } var encMigrationAuth Digest - for i := range srkAuth { - encMigrationAuth[i] = encAuthDataKey[i] ^ migrationAuth[i] + for i := range migrationAuth { + encMigrationAuth[i] = encAuthDataKey2[i] ^ migrationAuth[i] } rParams := rsaKeyParams{ @@ -1352,7 +1375,7 @@ func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migration keyInfo := &key{ Version: 0x01010000, KeyUsage: keySigning, - KeyFlags: 0, + KeyFlags: keyFlags, AuthDataUsage: authAlways, AlgorithmParams: keyParams{ AlgID: AlgRSA, @@ -1364,7 +1387,7 @@ func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migration } authIn := []interface{}{ordCreateWrapKey, encUsageAuth, encMigrationAuth, keyInfo} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, &nonceOdd, sharedSecret[:], authIn) if err != nil { return nil, err } @@ -1379,6 +1402,22 @@ func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migration return nil, err } + return k, nil +} + +// CreateWrapKey creates a new RSA key for signatures inside the TPM. It is +// wrapped by the SRK (which is to say, the SRK is the parent key). The key can +// be bound to the specified PCR numbers so that it can only be used for +// signing if the PCR values of those registers match. The pcrs parameter can +// be nil in which case the key is not bound to any PCRs. The usageAuth +// parameter defines the auth key for using this new key. The migrationAuth +// parameter would be used for authorizing migration of the key (although this +// code currently disables migration). +func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migrationAuth Digest, pcrs []int) ([]byte, error) { + k, err := createWrapKeyHelper(rw, srkAuth, 0, usageAuth, migrationAuth, pcrs) + if err != nil { + return nil, err + } keyblob, err := tpmutil.Pack(k) if err != nil { return nil, err @@ -1386,6 +1425,119 @@ func CreateWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migration return keyblob, nil } +// CreateMigratableWrapKey creates a new RSA key as in CreateWrapKey, but the +// key is migratable (with the given migration auth). +// Returns the loadable KeyBlob as well as just the encrypted private part, for +// migration. +func CreateMigratableWrapKey(rw io.ReadWriter, srkAuth []byte, usageAuth Digest, migrationAuth Digest, pcrs []int) ([]byte, []byte, error) { + k, err := createWrapKeyHelper(rw, srkAuth, keyMigratable, usageAuth, migrationAuth, pcrs) + if err != nil { + return nil, nil, err + } + keyblob, err := tpmutil.Pack(k) + if err != nil { + return nil, nil, err + } + return keyblob, k.EncData, nil +} + +// AuthorizeMigrationKey authorizes a given public key for use in migrating +// migratable keys. The scheme is REWRAP. +func AuthorizeMigrationKey(rw io.ReadWriter, ownerAuth Digest, migrationKey crypto.PublicKey) ([]byte, error) { + // Run OSAP for the OwnerAuth, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etOwner, khOwner, ownerAuth[:]) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + var pub *pubKey + if migrationKey != nil { + pub, err = convertPubKey(migrationKey) + // convertPubKey is designed for signing keys. + pub.AlgorithmParams.EncScheme = esRSAEsOAEPSHA1MGF1 + pub.AlgorithmParams.SigScheme = ssNone + rsaParams := rsaKeyParams{ + KeyLength: 2048, + NumPrimes: 2, + //Exponent: default (omit) + } + pub.AlgorithmParams.Params, err = tpmutil.Pack(rsaParams) + if err != nil { + return nil, err + } + } + + scheme := msRewrap + + // The digest for auth for the authorizeMigrationKey command is computed as + // SHA1(ordAuthorizeMigrationkey || migrationScheme || migrationKey) + authIn := []interface{}{ordAuthorizeMigrationKey, scheme, pub} + + // The commandAuth uses the shared secret as an HMAC key. + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + migrationAuth, _, _, err := authorizeMigrationKey(rw, scheme, *pub, ca) + if err != nil { + return nil, err + } + + // For now, ignore the response authentication. + return migrationAuth, nil +} + +// CreateMigrationBlob performs a Rewrap migration of the given key blob. +func CreateMigrationBlob(rw io.ReadWriter, srkAuth Digest, migrationAuth Digest, keyBlob []byte, migrationKeyBlob []byte) ([]byte, error) { + // Run OSAP for the SRK, reading a random OddOSAP for our initial + // command and getting back a secret and a handle. + sharedSecret, osapr, err := newOSAPSession(rw, etSRK, khSRK, srkAuth[:]) + if err != nil { + return nil, err + } + defer osapr.Close(rw) + defer zeroBytes(sharedSecret[:]) + + // The createMigrationBlob command needs an OIAP session in addition to the + // OSAP session. + oiapr, err := oiap(rw) + if err != nil { + return nil, err + } + defer oiapr.Close(rw) + + encData := tpmutil.U32Bytes(keyBlob) + + // The digest for auth1 and auth2 for the createMigrationBlob command is + // SHA1(ordCreateMigrationBlob || migrationScheme || migrationKeyBlob || encData) + authIn := []interface{}{ordCreateMigrationBlob, msRewrap, migrationKeyBlob, encData} + + // The first commandAuth uses the shared secret as an HMAC key. + ca1, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) + if err != nil { + return nil, err + } + + // The second commandAuth is based on OIAP instead of OSAP and uses the + // migration auth as the HMAC key. + ca2, err := newCommandAuth(oiapr.AuthHandle, oiapr.NonceEven, nil, migrationAuth[:], authIn) + if err != nil { + return nil, err + } + + _, outData, _, _, _, err := createMigrationBlob(rw, khSRK, msRewrap, migrationKeyBlob, encData, ca1, ca2) + if err != nil { + return nil, err + } + + // For now, ignore the response authenticatino. + return outData, nil +} + // https://golang.org/src/crypto/rsa/pkcs1v15.go?s=8762:8862#L204 var hashPrefixes = map[crypto.Hash][]byte{ crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, @@ -1418,7 +1570,7 @@ func Sign(rw io.ReadWriter, keyAuth []byte, keyHandle tpmutil.Handle, hash crypt defer zeroBytes(sharedSecret[:]) authIn := []interface{}{ordSign, tpmutil.U32Bytes(data)} - ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, sharedSecret[:], authIn) + ca, err := newCommandAuth(osapr.AuthHandle, osapr.NonceEven, nil, sharedSecret[:], authIn) if err != nil { return nil, err } @@ -1460,3 +1612,32 @@ func ForceClear(rw io.ReadWriter) error { return err } + +// Startup performs TPM_Startup(TPM_ST_CLEAR) to initialize the TPM. +func startup(rw io.ReadWriter) error { + var typ uint16 = 0x0001 // TPM_ST_CLEAR + in := []interface{}{typ} + out := []interface{}{} + _, err := submitTPMRequest(rw, tagRQUCommand, ordStartup, in, out) + + return err +} + +// createEK performs TPM_CreateEndorsementKeyPair to create the EK in the TPM. +func createEK(rw io.ReadWriter) error { + antiReplay := Nonce{} + keyInfo := []byte{ + 0x00, 0x00, 0x00, 0x01, // Algorithm = RSA + 0x00, 0x03, // EncScheme = OAEP + 0x00, 0x01, // SigScheme = None + 0x00, 0x00, 0x00, 0x0c, // ParamsSize = 12 + 0x00, 0x00, 0x08, 0x00, // KeyLength = 2048 + 0x00, 0x00, 0x00, 0x02, // NumPrimes = 2 + 0x00, 0x00, 0x00, 0x00, // ExponentSize = 0 (default 65537 exponent) + } + in := []interface{}{antiReplay, keyInfo} + out := []interface{}{} + _, err := submitTPMRequest(rw, tagRQUCommand, ordCreateEndorsementKeyPair, in, out) + + return err +} diff --git a/tpm/tpm_other_test.go b/tpm/tpm_other_test.go index 921b0c52..bcbf983d 100644 --- a/tpm/tpm_other_test.go +++ b/tpm/tpm_other_test.go @@ -29,7 +29,7 @@ func openTPMOrSkip(t *testing.T) io.ReadWriteCloser { tpmPath = "/dev/tpm0" } - rwc, err := OpenTPM(tpmPath) + rwc, err := openAndStartupTPM(tpmPath, true) if err != nil { t.Skipf("Skipping test, since we can't open %s for read/write: %s\n", tpmPath, err) } diff --git a/tpm/tpm_test.go b/tpm/tpm_test.go index a2f2d7e0..ef4b3c20 100644 --- a/tpm/tpm_test.go +++ b/tpm/tpm_test.go @@ -17,6 +17,7 @@ package tpm import ( "bytes" "crypto/rand" + "crypto/rsa" "crypto/sha1" "crypto/x509" "io/ioutil" @@ -198,7 +199,7 @@ func TestFetchPCRValues(t *testing.T) { t.Fatal("Couldn't create PCR composite") } - if len(comp) != int(20) { + if len(comp) != 20 { t.Fatal("Invalid PCR composite") } @@ -256,10 +257,11 @@ func TestOIAP(t *testing.T) { defer rwc.Close() // Get auth info from OIAP. - _, err := oiap(rwc) + oiapr, err := oiap(rwc) if err != nil { t.Fatal("Couldn't run OIAP:", err) } + defer oiapr.Close(rwc) } func TestOSAP(t *testing.T) { @@ -276,10 +278,11 @@ func TestOSAP(t *testing.T) { t.Fatal("Couldn't get a random odd OSAP nonce") } - _, err := osap(rwc, osapc) + osapr, err := osap(rwc, osapc) if err != nil { t.Fatal("Couldn't run OSAP:", err) } + defer osapr.Close(rwc) } func TestResizeableSlice(t *testing.T) { @@ -612,6 +615,12 @@ func TestTakeOwnership(t *testing.T) { // This test assumes that the TPM has been cleared using OwnerClear. pubEK, err := ReadPubEK(rwc) + // Create the EK if needed. + if err == tpmError(errNoEndorsement) { + if err = createEK(rwc); err == nil { + pubEK, err = ReadPubEK(rwc) + } + } if err != nil { t.Fatal("Couldn't read the public endorsement key from the TPM:", err) } @@ -688,3 +697,48 @@ func TestNVReadValueNoAuth(t *testing.T) { t.Logf("no values to read in nvram") } } + +func TestKeyMigration(t *testing.T) { + rwc := openTPMOrSkip(t) + defer rwc.Close() + ownerAuth := getAuth(ownerAuthEnvVar) + srkAuth := getAuth(srkAuthEnvVar) + // Generate some random migration and usage auth values. + migrationAuth := Digest{} + usageAuth := Digest{} + rand.Read(migrationAuth[:]) + rand.Read(usageAuth[:]) + + // First generate our private key we'll use for migration. + mk, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("error generating migration key: %v", err) + } + + // Then get the key endorsed by the TPM for doing migrations. + mb, err := AuthorizeMigrationKey(rwc, ownerAuth, mk.Public()) + if err != nil { + t.Fatalf("error authorizing migration key: %v", err) + } + + // Create a migratable key in the TPM. + _, privkey, err := CreateMigratableWrapKey( + rwc, + srkAuth[:], + usageAuth, + migrationAuth, + []int{}, // no PCR's + ) + + // Migrate the key to the saved migration key. + encPriv, err := CreateMigrationBlob(rwc, srkAuth, migrationAuth, privkey, mb) + if err != nil { + t.Fatalf("Error migrating created key: %v", err) + } + + // Decrypt the key. Successful OAEP decryption implies successful migration. + _, err = rsa.DecryptOAEP(sha1.New(), nil, mk, encPriv, []byte{0x54, 0x43, 0x50, 0x41} /* 'TCPA', see TPM 1.2 spec */) + if err != nil { + t.Errorf("Error decrypting migrated key blob: %v", err) + } +}