This library provides support for NationZ I2C and SPI TPM 2.0 devices so they can be used as a hardware crypto library for the ARM processor. The TPM not only may be used for crypto offload, but also for isolated key storage, strong device identity and device attestation. The TPM 2.0 library specification @ www.TrustedComputingGroup.org provides all necessary documentation to interact with the TPM. This particular library offers only support for the NationZ devices, that employ a vendor specific CRB interface and does not offer support for the TCG defined TIS interface.
NationZ_TPM20.cpp
- Committer:
- LordOfDorks
- Date:
- 2015-03-19
- Revision:
- 2:7ef8655b8dca
- Parent:
- 1:be4b399d5099
File content as of revision 2:7ef8655b8dca:
/* mbed NationZ I2C/SPI TPM 2.0 Library, * Copyright (c) 2015, Microsoft Coprporation Inc. * by Stefan Thom (LordOfDorks) StefanTh@Microsoft.com, Stefan@ThomsR.Us * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "NationZ_TPM20.h" // Constructor for the I2C variant of the chip NTZTPM20::NTZTPM20( PinName sda, PinName scl ) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZI2C.Init: "); #endif m_SPITpmDev = NULL; m_SPICSTpmDev = NULL; m_I2CTpmDev = new I2C(sda, scl); m_I2CTpmDev->frequency(400000); m_ExclusiveAccess = false; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK.\n\r"); #endif } // Constructor for the SPI variant of the chip NTZTPM20::NTZTPM20( PinName mosi, PinName miso, PinName clk, PinName cs ) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZSPI.Init: "); #endif m_I2CTpmDev = NULL; m_SPITpmDev = new SPI(mosi, miso, clk); m_SPITpmDev->format(8, 0); m_SPITpmDev->frequency(5000000); m_SPICSTpmDev = new DigitalOut(cs); *m_SPICSTpmDev = 1; m_ExclusiveAccess = false; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK.\n\r"); #endif } // Release all held resources NTZTPM20::~NTZTPM20() { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.Destroy: "); #endif if(m_I2CTpmDev != NULL) { delete m_I2CTpmDev; m_I2CTpmDev = NULL; } if(m_SPITpmDev != NULL) { delete m_SPITpmDev; m_SPITpmDev = NULL; } if(m_SPICSTpmDev != NULL) { delete m_SPICSTpmDev; m_SPICSTpmDev = NULL; } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK.\n\r"); #endif } uint32_t NTZTPM20::Execute( uint8_t* pbCmd, uint32_t cbCmd, uint8_t* pbRsp, uint32_t cbRsp, uint32_t timeout ) { uint32_t result = 0; Timeout watchdog; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.ExecuteWaitForAccess."); #endif // Only one caller should be talking to the TPM at any given time while(m_ExclusiveAccess) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif wait_us(500); } m_ExclusiveAccess = true; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK\n\r"); #endif #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.SetupTimeout\n\r"); #endif // Setup TPM timeout m_TimeoutTriggered = false; watchdog.attach(this, &NTZTPM20::TimeoutTrigger, 0.0001 * timeout); #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.Execute: "); for(uint32_t n = 0; n < cbCmd; n++) printf("%02x ", pbCmd[n]); printf("\n\r"); #endif // Execute command on the TPM if(m_I2CTpmDev != NULL) { result = ExecuteI2C(pbCmd, cbCmd, pbRsp, cbRsp); } else if(m_SPITpmDev != NULL) { result = ExecuteSPI(pbCmd, cbCmd, pbRsp, cbRsp); } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.Response: "); for(uint32_t n = 0; n < result; n++) printf("%02x ", pbRsp[n]); printf("\n\r"); #endif #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.CancelTimeout\n\r"); #endif // Cleanup watchdog.detach(); m_ExclusiveAccess = false; return result; } uint32_t NTZTPM20::ParseResponseHeader( uint8_t* pbRsp, uint32_t rspLen, uint16_t* rspTag, uint32_t* rspSize, uint32_t* cursor ) { uint32_t rspResponseCode = 0; // Check that the response header is well formatted if(rspLen < (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t))) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.ResponseHdr.rspLen = 0x%08x\n\r", rspLen); #endif rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } // Read the header components *rspTag = BYTEARRAY_TO_UINT16(pbRsp, *cursor); *cursor += sizeof(uint16_t); *rspSize = BYTEARRAY_TO_UINT32(pbRsp, *cursor); *cursor += sizeof(uint32_t); rspResponseCode = BYTEARRAY_TO_UINT32(pbRsp, *cursor); *cursor += sizeof(uint32_t); // Check the components if(((*rspTag != TPM_ST_NO_SESSIONS) && (*rspTag != TPM_ST_SESSIONS)) || (*rspSize != rspLen)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.ResponseHdr.rspTag = 0x%04x.rspLen=0x%08x\n\r", *rspTag, rspLen); #endif rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } Cleanup: return rspResponseCode; } uint32_t NTZTPM20::TPM2_Startup( uint16_t startupType ) { uint32_t rspLen = 0; uint16_t rspTag = 0; uint32_t rspSize = 0; uint32_t rspResponseCode = 0; uint32_t cursor = 0; uint8_t tpmCmd[] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00}; UINT16_TO_BYTEARRAY(startupType, tpmCmd, sizeof(tpmCmd) - 2); #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_Startup(0x%04x)\n\r", startupType); #endif if((rspLen = Execute(tpmCmd, sizeof(tpmCmd), tpmCmd, sizeof(tpmCmd), 10000)) == 0) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } if((rspResponseCode = ParseResponseHeader(tpmCmd, rspLen, &rspTag, &rspSize, &cursor)) != TPM_RC_SUCCESS) { goto Cleanup; } if(rspSize != 0x0000000a) { rspResponseCode = TPM_RC_FAILURE; } Cleanup: #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_Startup.ResponseCode = 0x%08x\n\r", rspResponseCode); #endif return rspResponseCode; } uint32_t NTZTPM20::TPM2_Shutdown( uint16_t shutdownType ) { uint32_t rspLen = 0; uint16_t rspTag = 0; uint32_t rspSize = 0; uint32_t rspResponseCode = 0; uint32_t cursor = 0; uint8_t tpmCmd[] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x45, 0x00, 0x00}; UINT16_TO_BYTEARRAY(shutdownType, tpmCmd, sizeof(tpmCmd) - 2); #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_Shutdown(0x%04x)\n\r", shutdownType); #endif if((rspLen = Execute(tpmCmd, sizeof(tpmCmd), tpmCmd, sizeof(tpmCmd), 120000)) == 0) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } if((rspResponseCode = ParseResponseHeader(tpmCmd, rspLen, &rspTag, &rspSize, &cursor)) != TPM_RC_SUCCESS) { goto Cleanup; } if(rspSize != 0x0000000a) { rspResponseCode = TPM_RC_FAILURE; } Cleanup: #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_Shutdown.ResponseCode = 0x%08x\n\r", rspResponseCode); #endif return rspResponseCode; } uint32_t NTZTPM20::TPM2_SelfTest( uint8_t fullTest ) { uint32_t rspLen = 0; uint16_t rspTag = 0; uint32_t rspSize = 0; uint32_t rspResponseCode = 0; uint32_t cursor = 0; uint8_t tpmCmd[] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01, 0x43, 0x00}; tpmCmd[sizeof(tpmCmd) - 1] = fullTest; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_SelfTest(0x%02x)\n\r", fullTest); #endif if((rspLen = Execute(tpmCmd, sizeof(tpmCmd), tpmCmd, sizeof(tpmCmd), 5000)) == 0) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } if((rspResponseCode = ParseResponseHeader(tpmCmd, rspLen, &rspTag, &rspSize, &cursor)) != TPM_RC_SUCCESS) { goto Cleanup; } if(rspSize != 0x0000000a) { rspResponseCode = TPM_RC_FAILURE; } Cleanup: #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_SelfTest.ResponseCode = 0x%08x\n\r", rspResponseCode); #endif return rspResponseCode; } uint32_t NTZTPM20::TPM2_GetRandom( uint16_t bytesRequested, uint8_t* randomBytes ) { uint32_t cursor = 0; uint32_t rspLen = 0; uint16_t rspTag = 0; uint32_t rspSize = 0; uint32_t rspResponseCode = 0; uint32_t tpmMax = sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint16_t) + bytesRequested; uint8_t* tpmCmd = new uint8_t[tpmMax]; uint16_t bytesReturned = 0; if(tpmCmd == NULL) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } // Build command UINT16_TO_BYTEARRAY(TPM_ST_NO_SESSIONS, tpmCmd, cursor); cursor += sizeof(uint16_t) + sizeof(cursor); UINT32_TO_BYTEARRAY(TPM_CC_GetRandom, tpmCmd, cursor); cursor += sizeof(TPM_CC_GetRandom); UINT16_TO_BYTEARRAY(bytesRequested, tpmCmd, cursor); cursor += sizeof(bytesRequested); UINT32_TO_BYTEARRAY(cursor, tpmCmd, sizeof(uint16_t)); #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_GetRandom(%d)\n\r", bytesRequested); #endif if((rspLen = Execute(tpmCmd, cursor, tpmCmd, tpmMax, 5000)) == 0) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } cursor = 0; if((rspResponseCode = ParseResponseHeader(tpmCmd, rspLen, &rspTag, &rspSize, &cursor)) != TPM_RC_SUCCESS) { goto Cleanup; } if(rspSize != tpmMax) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } // Copy the random bytes out bytesReturned = BYTEARRAY_TO_UINT16(tpmCmd, cursor); cursor += sizeof(uint16_t); memcpy(randomBytes, &tpmCmd[cursor], (size_t)min(bytesReturned, bytesRequested)); Cleanup: #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_GetRandom.ResponseCode = 0x%08x\n\r", rspResponseCode); #endif if(tpmCmd != NULL) { delete[] tpmCmd; tpmCmd = NULL; } return rspResponseCode; } uint32_t NTZTPM20::TPM2_StirRandom( uint16_t inDataLen, uint8_t* inData ) { uint32_t cursor = 0; uint32_t rspLen = 0; uint16_t rspTag = 0; uint32_t rspSize = 0; uint32_t rspResponseCode = 0; uint32_t tpmMax = sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint16_t) + inDataLen; uint8_t* tpmCmd = new uint8_t[tpmMax]; if(tpmCmd == NULL) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } // Build command UINT16_TO_BYTEARRAY(TPM_ST_NO_SESSIONS, tpmCmd, cursor); cursor += sizeof(uint16_t) + sizeof(cursor); UINT32_TO_BYTEARRAY(TPM_CC_StirRandom, tpmCmd, cursor); cursor += sizeof(TPM_CC_GetRandom); UINT16_TO_BYTEARRAY(inDataLen, tpmCmd, cursor); cursor += sizeof(inDataLen); memcpy(&tpmCmd[cursor], inData, inDataLen); cursor += inDataLen; UINT32_TO_BYTEARRAY(cursor, tpmCmd, sizeof(uint16_t)); #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_StirRandom(%d)\n\r", inDataLen); #endif if((rspLen = Execute(tpmCmd, cursor, tpmCmd, tpmMax, 5000)) == 0) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } cursor = 0; if((rspResponseCode = ParseResponseHeader(tpmCmd, rspLen, &rspTag, &rspSize, &cursor)) != TPM_RC_SUCCESS) { goto Cleanup; } if(rspSize != 0x0000000a) { rspResponseCode = TPM_RC_FAILURE; goto Cleanup; } Cleanup: #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZ.TPM2_StirRandom.ResponseCode = 0x%08x\n\r", rspResponseCode); #endif if(tpmCmd != NULL) { delete[] tpmCmd; tpmCmd = NULL; } return rspResponseCode; } void NTZTPM20::TimeoutTrigger( void ) { m_TimeoutTriggered = true; } uint32_t NTZTPM20::ExecuteI2C( uint8_t* pbCmd, uint32_t cbCmd, uint8_t* pbRsp, uint32_t cbRsp ) { uint32_t result = 0; uint32_t waitPeriod = 0; uint8_t retry = 0; uint32_t index = 0; uint32_t rspLen = 0; // Send the command buffer. Since the TPM may be sleeping we have to retry until the command is accepted #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZI2C.DeviceWakeup."); #endif while((m_I2CTpmDev->write(m_I2CDevice_Address, (const char *)pbCmd, cbCmd, false) != 0) && (!m_TimeoutTriggered)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif wait_us(100); } // See if device wakeup timed out if(m_TimeoutTriggered) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("Timeout\n\r"); #endif goto Cleanup; } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK\n\rNTZI2C.SentBytes = %d\n\rNTZI2C.WaitForCompletion.", cbCmd); #endif // Wait for the TPM to have a response and then read the TPM response header consisting of TAG, SIZE and RETURNCODE waitPeriod = 100; while((m_I2CTpmDev->read(m_I2CDevice_Address, (char *)&pbRsp[index], (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t)), false) != 0) && (!m_TimeoutTriggered)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif wait_us(waitPeriod); // Progressively wait longer to give the TPM some time to think if(retry++ > 10) { waitPeriod *= 10; retry = 0; } } if(m_TimeoutTriggered) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("Timeout\n\r"); #endif goto Cleanup; } // Move the write pointer index += (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t)); // Get the response size from the buffer and see how much we can read rspLen = min(BYTEARRAY_TO_UINT32(pbRsp, sizeof(uint16_t)), cbRsp); // Read the remaining data from the TPM if((rspLen - index) > 0) { while((m_I2CTpmDev->read(m_I2CDevice_Address, (char *)&pbRsp[index], rspLen - index, false) != 0) && (!m_TimeoutTriggered)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif wait_us(100); } if(m_TimeoutTriggered) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("Timeout\n\r"); #endif goto Cleanup; } } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK\n\rNTZI2C.ReadBytes = %d\r\n", rspLen); #endif result = rspLen; Cleanup: return result; } uint32_t NTZTPM20::ExecuteSPI( uint8_t* pbCmd, uint32_t cbCmd, uint8_t* pbRsp, uint32_t cbRsp ) { uint32_t result = 0; uint32_t waitPeriod = 0; uint8_t retry = 0; uint8_t statusByte = 0; uint32_t index = 0; // Lock the SPI bus for this operation *m_SPICSTpmDev = 0; #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZSPI.Wakeup."); #endif // Wake the TPM up do { if((statusByte = m_SPITpmDev->write(0xAA)) != 0xAA) { wait_us(10); } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif } while((statusByte != 0xAA) && (!m_TimeoutTriggered)); // If the operation timed out bail if((statusByte != 0xAA) && (m_TimeoutTriggered)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("Timeout\r\n"); #endif goto Cleanup; } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK\r\nNTZSPI.CommandReady\r\n"); #endif // Signal commandReady to the TPM m_SPITpmDev->write(0xA5); // Send the command buffer #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZSPI.SendBytes = %d\r\n", cbCmd); #endif for(uint32_t n = 0; n < cbCmd; n ++) { m_SPITpmDev->write(pbCmd[n]); } // Release the bus while the TPM is thinking *m_SPICSTpmDev = 1; // Wait for a response #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZSPI.WaitForCompletion."); #endif waitPeriod = 100; do { wait_us(waitPeriod); // Progressively wait longer to give the TPM some time to think if(retry++ > 10) { waitPeriod *= 10; retry = 0; } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("."); #endif // Lock the bus for this operation *m_SPICSTpmDev = 0; // Poke the TPM so see if it is ready if((pbRsp[index] = m_SPITpmDev->write(0xAA)) != 0xAA) { index++; // Read the TPM response header for(; index < (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t)); index++) { pbRsp[index] = m_SPITpmDev->write(0xFF); } uint32_t rspLen = BYTEARRAY_TO_UINT32(pbRsp, sizeof(uint16_t)); result = min(rspLen, cbRsp); #ifdef TPM_NTZ_DEBUG_OUTPUT if(result < rspLen) { printf("NTZSPI.ResponseBufferTooShortBy = %d", rspLen - result); } #endif // Read the remainder of the response if there is one for(; index < result; index++) { pbRsp[index] = m_SPITpmDev->write(0xFF); } } // release the bus *m_SPICSTpmDev = 1; } while((pbRsp[0] == 0xAA) && (!m_TimeoutTriggered)); if((pbRsp[0] == 0xAA) && (m_TimeoutTriggered)) { #ifdef TPM_NTZ_DEBUG_OUTPUT printf("Timeout\r\n"); #endif goto Cleanup; } #ifdef TPM_NTZ_DEBUG_OUTPUT printf("OK\n\rNTZSPI.ReadBytes = %d\n\r", index); #endif Cleanup: // Make sure to release the bus *m_SPICSTpmDev = 1; // Send the TPM back to sleep #ifdef TPM_NTZ_DEBUG_OUTPUT printf("NTZSPI.Sleep\r\n"); #endif *m_SPICSTpmDev = 0; m_SPITpmDev->write(0x5A); *m_SPICSTpmDev = 1; return result; }