/* Microsoft Reference Implementation for TPM 2.0 * * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and * contributor rights, including patent rights, and no such rights are granted * under this license. * * Copyright (c) Microsoft Corporation * * All rights reserved. * * BSD License * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ //** Introduction // // This file contains the implementation of the symmetric block cipher modes // allowed for a TPM. These functions only use the single block encryption functions // of the selected symmetric crypto library. //** Includes, Defines, and Typedefs #include "Tpm.h" #include "CryptSym.h" #define KEY_BLOCK_SIZES(ALG, alg) \ static const INT16 alg##KeyBlockSizes[] = { \ ALG##_KEY_SIZES_BITS, -1, ALG##_BLOCK_SIZES }; FOR_EACH_SYM(KEY_BLOCK_SIZES) //** Initialization and Data Access Functions // //*** CryptSymInit() // This function is called to do _TPM_Init processing BOOL CryptSymInit( void ) { return TRUE; } //*** CryptSymStartup() // This function is called to do TPM2_Startup() processing BOOL CryptSymStartup( void ) { return TRUE; } //*** CryptGetSymmetricBlockSize() // This function returns the block size of the algorithm. The table of bit sizes has // an entry for each allowed key size. The entry for a key size is 0 if the TPM does // not implement that key size. The key size table is delimited with a negative number // (-1). After the delimiter is a list of block sizes with each entry corresponding // to the key bit size. For most symmetric algorithms, the block size is the same // regardless of the key size but this arrangement allows them to be different. // Return Type: INT16 // <= 0 cipher not supported // > 0 the cipher block size in bytes LIB_EXPORT INT16 CryptGetSymmetricBlockSize( TPM_ALG_ID symmetricAlg, // IN: the symmetric algorithm UINT16 keySizeInBits // IN: the key size ) { const INT16 *sizes; INT16 i; #define ALG_CASE(SYM, sym) case TPM_ALG_##SYM: sizes = sym##KeyBlockSizes; break switch(symmetricAlg) { #define GET_KEY_BLOCK_POINTER(SYM, sym) \ case TPM_ALG_##SYM: \ sizes = sym##KeyBlockSizes; \ break; // Get the pointer to the block size array FOR_EACH_SYM(GET_KEY_BLOCK_POINTER); default: return 0; } // Find the index of the indicated keySizeInBits for(i = 0; *sizes >= 0; i++, sizes++) { if(*sizes == keySizeInBits) break; } // If sizes is pointing at the end of the list of key sizes, then the desired // key size was not found so set the block size to zero. if(*sizes++ < 0) return 0; // Advance until the end of the list is found while(*sizes++ >= 0); // sizes is pointing to the first entry in the list of block sizes. Use the // ith index to find the block size for the corresponding key size. return sizes[i]; } //** Symmetric Encryption // This function performs symmetric encryption based on the mode. // Return Type: TPM_RC // TPM_RC_SIZE 'dSize' is not a multiple of the block size for an // algorithm that requires it // TPM_RC_FAILURE Fatal error LIB_EXPORT TPM_RC CryptSymmetricEncrypt( BYTE *dOut, // OUT: TPM_ALG_ID algorithm, // IN: the symmetric algorithm UINT16 keySizeInBits, // IN: key size in bits const BYTE *key, // IN: key buffer. The size of this buffer // in bytes is (keySizeInBits + 7) / 8 TPM2B_IV *ivInOut, // IN/OUT: IV for decryption. TPM_ALG_ID mode, // IN: Mode to use INT32 dSize, // IN: data size (may need to be a // multiple of the blockSize) const BYTE *dIn // IN: data buffer ) { BYTE *pIv; int i; BYTE tmp[MAX_SYM_BLOCK_SIZE]; BYTE *pT; tpmCryptKeySchedule_t keySchedule; INT16 blockSize; TpmCryptSetSymKeyCall_t encrypt; BYTE *iv; BYTE defaultIv[MAX_SYM_BLOCK_SIZE] = {0}; // pAssert(dOut != NULL && key != NULL && dIn != NULL); if(dSize == 0) return TPM_RC_SUCCESS; TEST(algorithm); blockSize = CryptGetSymmetricBlockSize(algorithm, keySizeInBits); if(blockSize == 0) return TPM_RC_FAILURE; // If the iv is provided, then it is expected to be block sized. In some cases, // the caller is providing an array of 0's that is equal to [MAX_SYM_BLOCK_SIZE] // with no knowledge of the actual block size. This function will set it. if((ivInOut != NULL) && (mode != TPM_ALG_ECB)) { ivInOut->t.size = blockSize; iv = ivInOut->t.buffer; } else iv = defaultIv; pIv = iv; // Create encrypt key schedule and set the encryption function pointer. switch (algorithm) { FOR_EACH_SYM(ENCRYPT_CASE) default: return TPM_RC_SYMMETRIC; } switch(mode) { #if ALG_CTR case TPM_ALG_CTR: for(; dSize > 0; dSize -= blockSize) { // Encrypt the current value of the IV(counter) ENCRYPT(&keySchedule, iv, tmp); //increment the counter (counter is big-endian so start at end) for(i = blockSize - 1; i >= 0; i--) if((iv[i] += 1) != 0) break; // XOR the encrypted counter value with input and put into output pT = tmp; for(i = (dSize < blockSize) ? dSize : blockSize; i > 0; i--) *dOut++ = *dIn++ ^ *pT++; } break; #endif #if ALG_OFB case TPM_ALG_OFB: // This is written so that dIn and dOut may be the same for(; dSize > 0; dSize -= blockSize) { // Encrypt the current value of the "IV" ENCRYPT(&keySchedule, iv, iv); // XOR the encrypted IV into dIn to create the cipher text (dOut) pIv = iv; for(i = (dSize < blockSize) ? dSize : blockSize; i > 0; i--) *dOut++ = (*pIv++ ^ *dIn++); } break; #endif #if ALG_CBC case TPM_ALG_CBC: // For CBC the data size must be an even multiple of the // cipher block size if((dSize % blockSize) != 0) return TPM_RC_SIZE; // XOR the data block into the IV, encrypt the IV into the IV // and then copy the IV to the output for(; dSize > 0; dSize -= blockSize) { pIv = iv; for(i = blockSize; i > 0; i--) *pIv++ ^= *dIn++; ENCRYPT(&keySchedule, iv, iv); pIv = iv; for(i = blockSize; i > 0; i--) *dOut++ = *pIv++; } break; #endif // CFB is not optional case TPM_ALG_CFB: // Encrypt the IV into the IV, XOR in the data, and copy to output for(; dSize > 0; dSize -= blockSize) { // Encrypt the current value of the IV ENCRYPT(&keySchedule, iv, iv); pIv = iv; for(i = (int)(dSize < blockSize) ? dSize : blockSize; i > 0; i--) // XOR the data into the IV to create the cipher text // and put into the output *dOut++ = *pIv++ ^= *dIn++; } // If the inner loop (i loop) was smaller than blockSize, then dSize // would have been smaller than blockSize and it is now negative. If // it is negative, then it indicates how many bytes are needed to pad // out the IV for the next round. for(; dSize < 0; dSize++) *pIv++ = 0; break; #if ALG_ECB case TPM_ALG_ECB: // For ECB the data size must be an even multiple of the // cipher block size if((dSize % blockSize) != 0) return TPM_RC_SIZE; // Encrypt the input block to the output block for(; dSize > 0; dSize -= blockSize) { ENCRYPT(&keySchedule, dIn, dOut); dIn = &dIn[blockSize]; dOut = &dOut[blockSize]; } break; #endif default: return TPM_RC_FAILURE; } return TPM_RC_SUCCESS; } //*** CryptSymmetricDecrypt() // This function performs symmetric decryption based on the mode. // Return Type: TPM_RC // TPM_RC_FAILURE A fatal error // TPM_RCS_SIZE 'dSize' is not a multiple of the block size for an // algorithm that requires it LIB_EXPORT TPM_RC CryptSymmetricDecrypt( BYTE *dOut, // OUT: decrypted data TPM_ALG_ID algorithm, // IN: the symmetric algorithm UINT16 keySizeInBits, // IN: key size in bits const BYTE *key, // IN: key buffer. The size of this buffer // in bytes is (keySizeInBits + 7) / 8 TPM2B_IV *ivInOut, // IN/OUT: IV for decryption. TPM_ALG_ID mode, // IN: Mode to use INT32 dSize, // IN: data size (may need to be a // multiple of the blockSize) const BYTE *dIn // IN: data buffer ) { BYTE *pIv; int i; BYTE tmp[MAX_SYM_BLOCK_SIZE]; BYTE *pT; tpmCryptKeySchedule_t keySchedule; INT16 blockSize; BYTE *iv; TpmCryptSetSymKeyCall_t encrypt; TpmCryptSetSymKeyCall_t decrypt; BYTE defaultIv[MAX_SYM_BLOCK_SIZE] = {0}; // These are used but the compiler can't tell because they are initialized // in case statements and it can't tell if they are always initialized // when needed, so... Comment these out if the compiler can tell or doesn't // care that these are initialized before use. encrypt = NULL; decrypt = NULL; pAssert(dOut != NULL && key != NULL && dIn != NULL); if(dSize == 0) return TPM_RC_SUCCESS; TEST(algorithm); blockSize = CryptGetSymmetricBlockSize(algorithm, keySizeInBits); if(blockSize == 0) return TPM_RC_FAILURE; // If the iv is provided, then it is expected to be block sized. In some cases, // the caller is providing an array of 0's that is equal to [MAX_SYM_BLOCK_SIZE] // with no knowledge of the actual block size. This function will set it. if((ivInOut != NULL) && (mode != TPM_ALG_ECB)) { ivInOut->t.size = blockSize; iv = ivInOut->t.buffer; } else iv = defaultIv; pIv = iv; // Use the mode to select the key schedule to create. Encrypt always uses the // encryption schedule. Depending on the mode, decryption might use either // the decryption or encryption schedule. switch(mode) { #if ALG_CBC || ALG_ECB case TPM_ALG_CBC: // decrypt = decrypt case TPM_ALG_ECB: // For ECB and CBC, the data size must be an even multiple of the // cipher block size if((dSize % blockSize) != 0) return TPM_RC_SIZE; switch (algorithm) { FOR_EACH_SYM(DECRYPT_CASE) default: return TPM_RC_SYMMETRIC; } break; #endif default: // For the remaining stream ciphers, use encryption to decrypt switch (algorithm) { FOR_EACH_SYM(ENCRYPT_CASE) default: return TPM_RC_SYMMETRIC; } } // Now do the mode-dependent decryption switch(mode) { #if ALG_CBC case TPM_ALG_CBC: // Copy the input data to a temp buffer, decrypt the buffer into the // output, XOR in the IV, and copy the temp buffer to the IV and repeat. for(; dSize > 0; dSize -= blockSize) { pT = tmp; for(i = blockSize; i > 0; i--) *pT++ = *dIn++; DECRYPT(&keySchedule, tmp, dOut); pIv = iv; pT = tmp; for(i = blockSize; i > 0; i--) { *dOut++ ^= *pIv; *pIv++ = *pT++; } } break; #endif case TPM_ALG_CFB: for(; dSize > 0; dSize -= blockSize) { // Encrypt the IV into the temp buffer ENCRYPT(&keySchedule, iv, tmp); pT = tmp; pIv = iv; for(i = (dSize < blockSize) ? dSize : blockSize; i > 0; i--) // Copy the current cipher text to IV, XOR // with the temp buffer and put into the output *dOut++ = *pT++ ^ (*pIv++ = *dIn++); } // If the inner loop (i loop) was smaller than blockSize, then dSize // would have been smaller than blockSize and it is now negative // If it is negative, then it indicates how may fill bytes // are needed to pad out the IV for the next round. for(; dSize < 0; dSize++) *pIv++ = 0; break; #if ALG_CTR case TPM_ALG_CTR: for(; dSize > 0; dSize -= blockSize) { // Encrypt the current value of the IV(counter) ENCRYPT(&keySchedule, iv, tmp); //increment the counter (counter is big-endian so start at end) for(i = blockSize - 1; i >= 0; i--) if((iv[i] += 1) != 0) break; // XOR the encrypted counter value with input and put into output pT = tmp; for(i = (dSize < blockSize) ? dSize : blockSize; i > 0; i--) *dOut++ = *dIn++ ^ *pT++; } break; #endif #if ALG_ECB case TPM_ALG_ECB: for(; dSize > 0; dSize -= blockSize) { DECRYPT(&keySchedule, dIn, dOut); dIn = &dIn[blockSize]; dOut = &dOut[blockSize]; } break; #endif #if ALG_OFB case TPM_ALG_OFB: // This is written so that dIn and dOut may be the same for(; dSize > 0; dSize -= blockSize) { // Encrypt the current value of the "IV" ENCRYPT(&keySchedule, iv, iv); // XOR the encrypted IV into dIn to create the cipher text (dOut) pIv = iv; for(i = (dSize < blockSize) ? dSize : blockSize; i > 0; i--) *dOut++ = (*pIv++ ^ *dIn++); } break; #endif default: return TPM_RC_FAILURE; } return TPM_RC_SUCCESS; } //*** CryptSymKeyValidate() // Validate that a provided symmetric key meets the requirements of the TPM // Return Type: TPM_RC // TPM_RC_KEY_SIZE Key size specifiers do not match // TPM_RC_KEY Key is not allowed TPM_RC CryptSymKeyValidate( TPMT_SYM_DEF_OBJECT *symDef, TPM2B_SYM_KEY *key ) { if(key->t.size != BITS_TO_BYTES(symDef->keyBits.sym)) return TPM_RCS_KEY_SIZE; #if ALG_TDES if(symDef->algorithm == TPM_ALG_TDES && !CryptDesValidateKey(key)) return TPM_RCS_KEY; #endif // ALG_TDES return TPM_RC_SUCCESS; }