/* * @author NXP Semiconductors * @version 1.0 * @par License * * Copyright 2016,2020 NXP * SPDX-License-Identifier: Apache-2.0 * * @par History * */ /** * @file scp_a7x.c * @par Description * Conditionally apply SCP03 channel encryption * (This file was renamed from ``scp.c`` into ``scp_a7x.c``.) */ #include #include "scp.h" #include "smCom.h" #include "sm_printf.h" #include "sm_apdu.h" #if defined(SSS_USE_FTR_FILE) #include "fsl_sss_ftr.h" #else #include "fsl_sss_ftr_default.h" #endif #if SSS_HAVE_A71XX || SSS_HAVE_SE050_L #if !defined(TGT_A70CI) && !defined(TGT_A70CM) && !defined(NO_SECURE_CHANNEL_SUPPORT) #define SECURE_CHANNEL_SUPPORTED #endif #ifdef SECURE_CHANNEL_SUPPORTED #include "ax_util.h" #include "HostCryptoAPI.h" //#include "axHostCrypto.h" #endif // SECURE_CHANNEL_SUPPORTED #include #include #ifdef LINUX #if !defined(__APPLE__) #include #endif #endif // LINUX #ifdef DBG_VERBOSE static void localPrintByteArray(const char *pName, const U8 *pData, U16 dataLen) { U16 i = 0; assert(pName != NULL); assert(pData != NULL); printf("%s (LEN=%d):\r\n", pName, dataLen); for (i = 0; i < dataLen ; i++) { printf("%02X", pData[i]); } printf("\r\n"); } #define DPRINTF(...) printf (__VA_ARGS__) #define PRINT_BYTE_STRING(A, B, C) localPrintByteArray(A, B, C) #else #define DPRINTF(...) #define PRINT_BYTE_STRING(A, B, C) #endif #define SCP_BUFFER_SIZE (MAX_CHUNK_LENGTH_LINK+34) // TODO: What is the exact size (instead of 34) that must be added as margin #ifdef SECURE_CHANNEL_SUPPORTED /// @cond #define CMAC_SIZE (8) static S32 scp_GetCommandICV(ChannelId_t channelId, U8* pIcv); static U32 scp_TransformCommand(apdu_t * pApdu); static U32 scp_TransformResponse(apdu_t *pApdu); static U16 scp_PadData(apdu_t * pApdu); static U16 scp_AddMacToCommand(ChannelId_t channelId, apdu_t * pApdu); static SCP_SignalFunction scp_SignalFunctionCb = NULL; /// @endcond U16 SCP_Subscribe(SCP_SignalFunction callback, void *context) { AX_UNUSED_ARG(context); scp_SignalFunctionCb = callback; return SCP_OK; } #endif U32 scp_Transceive(void *conn_ctx, apdu_t * pApdu, scp_CommandType_t type) { U32 rv = SMCOM_OK; #if defined(SMCOM_JRCP_V1_AM) scp_CommandType_t type_temp = type; #endif #ifdef SECURE_CHANNEL_SUPPORTED scp_CommandType_t channelCommandType; #ifdef TGT_EDEV ChannelId_t channelId = DEV_GetSelectedChannel(&channelCommandType); #else //TGT_EDEV DEV_GetSelectedChannel(&channelCommandType); #endif //TGT_EDEV // Even if C_MAC or C_ENC is requested, but the channel has not been set up successfully, // it will not be granted. if (type != NO_C_MAC_NO_C_ENC_NO_R_MAC_NO_R_ENC) { type = channelCommandType; } // Transform command if (type != NO_C_MAC_NO_C_ENC_NO_R_MAC_NO_R_ENC) { rv = scp_TransformCommand(pApdu); if (rv != SW_OK) { pApdu->rxlen = 0; return rv; } } else { #if defined(SMCOM_JRCP_V1_AM) #else // scp_TransformCommand (if branch) already invokes smApduAdaptLc smApduAdaptLc(pApdu, pApdu->lc); #endif } #if defined(SMCOM_JRCP_V1_AM) #else smApduAdaptLe(pApdu, 0); #endif #ifdef TGT_EDEV // EDEV Specific: Conditionally adapt CLA byte to label the command as a HOST channel command (as opposed to ADMIN channel) if (channelId == AX_HOST_CHANNEL) { pApdu->pBuf[0] |= 0xE0; } #endif //TGT_EDEV #else //SECURE_CHANNEL_SUPPORTED smApduAdaptLcLe(pApdu, pApdu->lc, 0); #endif //SECURE_CHANNEL_SUPPORTED #if defined(SMCOM_JRCP_V1_AM) pApdu->pBuf[pApdu->buflen++] = pApdu->extendedLength; pApdu->pBuf[pApdu->buflen++] = pApdu->hasData; pApdu->pBuf[pApdu->buflen++] = (pApdu->lc & 0xFF); pApdu->pBuf[pApdu->buflen++] = ((pApdu->lc >> 8) & 0xFF); pApdu->pBuf[pApdu->buflen++] = pApdu->lcLength; pApdu->pBuf[pApdu->buflen++] = pApdu->hasLe; pApdu->pBuf[pApdu->buflen++] = (pApdu->le & 0xFF); pApdu->pBuf[pApdu->buflen++] = ((pApdu->le >> 8) & 0xFF); pApdu->pBuf[pApdu->buflen++] = pApdu->leLength; pApdu->pBuf[pApdu->buflen++] = (pApdu->offset & 0xFF); pApdu->pBuf[pApdu->buflen++] = ((pApdu->offset >> 8) & 0xFF); pApdu->pBuf[pApdu->buflen++] = (U8)type_temp; #endif rv = smCom_Transceive(conn_ctx, pApdu); #ifdef SECURE_CHANNEL_SUPPORTED // transform response if ( (type != NO_C_MAC_NO_C_ENC_NO_R_MAC_NO_R_ENC) && (rv == SMCOM_OK) ) { rv = scp_TransformResponse(pApdu); if (rv != SMCOM_OK) { pApdu->rxlen = 0; } } #endif // SECURE_CHANNEL_SUPPORTED return rv; } #ifdef SECURE_CHANNEL_SUPPORTED /* * scp_TransformCommand * * SCPP03 spec, section 6.2.6 */ static U32 scp_TransformCommand(apdu_t *pApdu) { S32 nRet = HOST_CRYPTO_OK; U16 rv; U16 Lcc = 0; U8 iv[16]; U8* pIv = (U8*) iv; scp_CommandType_t ignoreChannelCommandTypeAtThisLevel; ChannelId_t channelId = DEV_GetSelectedChannel(&ignoreChannelCommandTypeAtThisLevel); U8 apduPayloadToEncrypt[SCP_BUFFER_SIZE]; Scp03SessionState_t session; // Get the current security level. (should be set to AUTH_C_DECRYPTION_R_ENCRYPTION in the GP_ExternalAuthenticate command) /* The Off-Card Entity first encrypts the Command Data field and then computes the C-MAC on the command with the ciphered data field as described in section 6.2.4 APDU Command C-MAC Generation and Verification. No encryption shall be applied to a command where there is no command data field. */ // Patch CLA byte to indicate we are using SCP03 pApdu->pBuf[0] |= 0x04; if (pApdu->hasData == 1) { int payloadOffset = 0; /* Prior to encrypting the data, the data shall be padded as defined in section 4.1.4. This padding becomes part of the data field.*/ // DPRINTF("pApdu->lc: %d\r\n", pApdu->lc); Lcc = pApdu->lc; Lcc += scp_PadData(pApdu); // DPRINTF("Lcc: %d (@line %d)\r\n", Lcc, __LINE__); /* The final Lc value (Lcc) is the sum of: initial Lc + length of the padding + length of C-MAC */ Lcc += CMAC_SIZE; // DPRINTF("Lcc: %d (@line %d)\r\n", Lcc, __LINE__); smApduAdaptLc(pApdu, Lcc); nRet = scp_GetCommandICV(channelId, pIv); if (nRet == HOST_CRYPTO_OK) { SCP_HostLocal_GetSessionState(channelId, &session); if (pApdu->extendedLength) { payloadOffset = APDU_OFFSET_LC + 3; // (3 bytes reserved for LC field) } else { payloadOffset = APDU_OFFSET_LC + 1; // (1 byte reserved for LC field) } memcpy(apduPayloadToEncrypt, &(pApdu->pBuf[payloadOffset]), (pApdu->buflen - payloadOffset)); DPRINTF("SCP: Encrypting %d byte\r\n", pApdu->buflen - payloadOffset); { HLSE_MECHANISM_INFO mechInfo; U32 outLen = SCP_KEY_SIZE; memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CBC_ENCRYPT; mechInfo.pParameter = pIv; mechInfo.ulParameterLen = 16; nRet = HLCRYPT_Encrypt(&mechInfo, session.sEnc, SCP_KEY_SIZE, apduPayloadToEncrypt, pApdu->buflen - payloadOffset, &(pApdu->pBuf[payloadOffset]), &outLen); } // nRet = HOST_AES_CBC_Process(session.sEnc, SCP_KEY_SIZE, pIv, HOST_ENCRYPT, apduPayloadToEncrypt, // pApdu->buflen - payloadOffset, &(pApdu->pBuf[payloadOffset])); } } else // Handle commands with no command data field. { /* spec: * The length of the command message (Lc) shall be incremented by 8 to indicate the inclusion of the * C-MAC in the data field of the command message. */ Lcc = pApdu->lc + CMAC_SIZE; // The MAC will become the payload of the APDU. so indicate there is a datapayload pApdu->hasData = 1; pApdu->lcLength = 1; pApdu->buflen += 1; smApduAdaptLc(pApdu, Lcc); } if (nRet == HOST_CRYPTO_OK) { // Add the MAC rv = scp_AddMacToCommand(channelId, pApdu); } else { rv = ERR_CRYPTO_ENGINE_FAILED; } return rv; } /* * scp_PadData * * (spec 4.1.4) Unless specified otherwise, padding prior to performing an AES operation across a block of data is * achieved in the following manner: * - Append an '80' to the right of the data block; * - If the resultant data block length is a multiple of 16, no further padding is required; * - Append binary zeroes to the right of the data block until the data block length is a multiple of 16. */ static U16 scp_PadData(apdu_t * pApdu) { U16 zeroBytesToPad = 0; U16 bytesToPad = 1; // pad the payload and adjust the length of the APDU if (pApdu->extendedLength == false) { // TODO: remove hard coded values if (pApdu->buflen > 5) // payload present => padding needed { pApdu->pBuf[pApdu->buflen++] = 0x80; zeroBytesToPad = (SCP_KEY_SIZE - ((pApdu->buflen - 5) % SCP_KEY_SIZE)) % SCP_KEY_SIZE; } } else { if (pApdu->buflen > 7) // payload present => padding needed { pApdu->pBuf[pApdu->buflen++] = 0x80; zeroBytesToPad = (SCP_KEY_SIZE - ((pApdu->buflen - 7) % SCP_KEY_SIZE)) % SCP_KEY_SIZE; } } bytesToPad += zeroBytesToPad; while (zeroBytesToPad > 0) { pApdu->pBuf[pApdu->buflen++] = 0x00; zeroBytesToPad--; } pApdu->offset = pApdu->buflen; // DPRINTF("Padded %d bytes.\r\n", bytesToPad); return bytesToPad; } /* * scp_AddMacToCommand * */ static U16 scp_AddMacToCommand(ChannelId_t channelId, apdu_t *pApdu) { S32 nRet; Scp03SessionState_t session; U8 macToAdd[16] = {0}; //axHcCmacCtx_t *cmacCtx; HLSE_CONTEXT_HANDLE hContext; HLSE_MECHANISM_INFO mechInfo; U32 signatureLen = sizeof(macToAdd); memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CMAC; SCP_HostLocal_GetSessionState(channelId, &session); nRet = HLCRYPT_SignInit(&mechInfo, session.sMac, SCP_KEY_SIZE, &hContext); // nRet = HOST_CMAC_Init(&cmacCtx, session.sMac, SCP_KEY_SIZE); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } nRet = HLCRYPT_SignUpdate(hContext, session.mcv, SCP_KEY_SIZE); // nRet = HOST_CMAC_Update(cmacCtx, session.mcv, SCP_KEY_SIZE); nRet &= HLCRYPT_SignUpdate(hContext, pApdu->pBuf, pApdu->buflen); // nRet &= HOST_CMAC_Update(cmacCtx, pApdu->pBuf, pApdu->buflen); nRet &= HLCRYPT_SignFinal(hContext, macToAdd, &signatureLen); // nRet &= HOST_CMAC_Finish(cmacCtx, macToAdd); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } // Store updated mcv! SCP_HostLocal_SetMacChainingValue(channelId, macToAdd); memcpy(&(pApdu->pBuf[pApdu->buflen]), macToAdd, SCP_GP_IU_CARD_CRYPTOGRAM_LEN); pApdu->buflen += SCP_GP_IU_CARD_CRYPTOGRAM_LEN; pApdu->offset = pApdu->buflen; return SW_OK; } /* * scp_GetCommandICV * * The ICV shall be calculated as follows: * - A counter shall be incremented for each command (i.e. for each pair of command and response * APDU) within a secure channel session. * - The starting value shall be 1 for the first command after a successful EXTERNAL AUTHENTICATE. * - The binary counter value shall be left padded with zeroes to form a full block. * - This block shall be encrypted with S-ENC; the result shall be used as ICV. */ static S32 scp_GetCommandICV(ChannelId_t channelId, U8* pIcv) { S32 nRet; U8 ivZero[SCP_KEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; Scp03SessionState_t session; SCP_HostLocal_GetSessionState(channelId, &session); PRINT_BYTE_STRING("SCP: SndCmdCounter", session.cCounter, SCP_KEY_SIZE); { HLSE_MECHANISM_INFO mechInfo; U32 outLen = SCP_KEY_SIZE; memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CBC_ENCRYPT; mechInfo.pParameter = ivZero; mechInfo.ulParameterLen = SCP_KEY_SIZE; nRet = HLCRYPT_Encrypt(&mechInfo, session.sEnc, SCP_KEY_SIZE, session.cCounter, SCP_KEY_SIZE, pIcv, &outLen); } // nRet = HOST_AES_CBC_Process(session.sEnc, SCP_KEY_SIZE, ivZero, HOST_ENCRYPT, session.cCounter, SCP_KEY_SIZE, pIcv); return nRet; } static S32 scp_GetResponseICV(ChannelId_t channelId, U8* pIcv) { S32 nRet; U8 ivZero[SCP_KEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; U8 commandCounter[SCP_KEY_SIZE]; Scp03SessionState_t session; SCP_HostLocal_GetSessionState(channelId, &session); memcpy(commandCounter, session.cCounter, SCP_KEY_SIZE); commandCounter[0] = 0x80; // Section 6.2.7 of SCP03 spec PRINT_BYTE_STRING("SCP: RcvCmdCounter", commandCounter, 16); { HLSE_MECHANISM_INFO mechInfo; U32 outLen = SCP_KEY_SIZE; memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CBC_ENCRYPT; mechInfo.pParameter = ivZero; mechInfo.ulParameterLen = SCP_KEY_SIZE; nRet = HLCRYPT_Encrypt(&mechInfo, session.sEnc, SCP_KEY_SIZE, commandCounter, sizeof(commandCounter), pIcv, &outLen); } // nRet = HOST_AES_CBC_Process(session.sEnc, SCP_KEY_SIZE, ivZero, HOST_ENCRYPT, commandCounter, sizeof(commandCounter), pIcv); return nRet; } static U32 scp_RemovePaddingRestoreSw(apdu_t *pApdu, U8 *plaintextResponse, U16 plaintextResponseLen, U8 *sw) { int i; int removePaddingOk = 0; i = plaintextResponseLen; while ( (i > 1) && (i > (plaintextResponseLen-SCP_KEY_SIZE)) ) { if (plaintextResponse[i-1] == 0x00) { i--; } else if (plaintextResponse[i-1] == 0x80) { // We have found padding delimitor memcpy(&plaintextResponse[i-1], sw, SCP_GP_SW_LEN); memcpy(pApdu->pBuf, plaintextResponse, i+1); pApdu->rxlen = (U16)(i+1); removePaddingOk = 1; PRINT_BYTE_STRING("SCP: plaintextResponseStripped+SW", pApdu->pBuf, pApdu->rxlen); break; } else { // We've found a non-padding character while removing padding // Most likely the cipher text was not properly decoded. DPRINTF("Decoding failed.\r\n"); break; } } if (removePaddingOk == 0) { DPRINTF("Invoking callback. 'Found illegal (or no) padding'\r\n"); DPRINTF("**************************************************\r\n"); if (scp_SignalFunctionCb != NULL) { scp_SignalFunctionCb(SCP_WRONG_PADDING, NULL); } return SCP_DECODE_FAIL; } return SMCOM_OK; } /** * @brief scp_TransformResponse * @param pApdu * @retval ::SMCOM_OK * @retval ::SCP_RSP_MAC_FAIL * @retval ::SCP_DECODE_FAIL */ static U32 scp_TransformResponse(apdu_t *pApdu) { U32 status = SMCOM_OK; U8 iv[16]; U8* pIv = (U8*) iv; scp_CommandType_t dummy; ChannelId_t channelId = DEV_GetSelectedChannel(&dummy); Scp03SessionState_t session; U8 response[SCP_BUFFER_SIZE]; U8 plaintextResponse[SCP_BUFFER_SIZE]; U8 sw[SCP_GP_SW_LEN] = {0}; U8 pMac[16] = {0}; //axHcCmacCtx_t *cmacCtx; HLSE_CONTEXT_HANDLE hContext; HLSE_MECHANISM_INFO mechInfo; U32 signatureLen = sizeof(pMac); memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CMAC; SCP_HostLocal_GetSessionState(channelId, &session); if (pApdu->rxlen >= 10) { S32 nRet = 1; memcpy(sw, &(pApdu->pBuf[pApdu->rxlen-SCP_GP_SW_LEN]), SCP_GP_SW_LEN); // get the MAC chaining value nRet = HLCRYPT_SignInit(&mechInfo, session.sRMac, SCP_KEY_SIZE, &hContext); // nRet = HOST_CMAC_Init(&cmacCtx, session.sRMac, SCP_KEY_SIZE); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } nRet = HLCRYPT_SignUpdate(hContext, session.mcv, SCP_CMAC_SIZE); // nRet = HOST_CMAC_Update(cmacCtx, session.mcv, SCP_CMAC_SIZE); if (pApdu->rxlen > 10) { nRet &= HLCRYPT_SignUpdate(hContext, pApdu->pBuf, pApdu->rxlen - SCP_COMMAND_MAC_SIZE - SCP_GP_SW_LEN); // nRet &= HOST_CMAC_Update(cmacCtx, pApdu->pBuf, pApdu->rxlen - SCP_COMMAND_MAC_SIZE - SCP_GP_SW_LEN); } nRet &= HLCRYPT_SignUpdate(hContext, sw, SCP_GP_SW_LEN); // nRet &= HOST_CMAC_Update(cmacCtx, sw, SCP_GP_SW_LEN); nRet &= HLCRYPT_SignFinal(hContext, pMac, &signatureLen); // nRet &= HOST_CMAC_Finish(cmacCtx, pMac); PRINT_BYTE_STRING("SCP: Calculated Response Mac", pMac, SCP_CMAC_SIZE); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } // Do a comparison of the received and the calculated mac if ( memcmp(pMac, &pApdu->pBuf[pApdu->rxlen-SCP_COMMAND_MAC_SIZE-SCP_GP_SW_LEN], SCP_COMMAND_MAC_SIZE) != 0 ) { DPRINTF("Invoking callback. 'RESPONSE MAC DID NOT VERIFY!'\r\n"); DPRINTF("*************************************************\r\n"); if (scp_SignalFunctionCb != NULL) { scp_SignalFunctionCb(SCP_WRONG_RESPMAC, NULL); } return SCP_RSP_MAC_FAIL; } } // Decrypt Response Data Field in case Reponse Mac verified OK if (pApdu->rxlen > 10) { S32 nRet; memcpy(response, pApdu->pBuf, pApdu->rxlen - 10); PRINT_BYTE_STRING("SCP: EncResponse (MAC and SW stripped)", response, pApdu->rxlen - 10); memcpy(sw, &(pApdu->pBuf[pApdu->rxlen-SCP_GP_SW_LEN]), SCP_GP_SW_LEN); // PRINT_BYTE_STRING("sw", sw, 2); nRet = scp_GetResponseICV(channelId, pIv); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } { U32 outLen = SCP_BUFFER_SIZE; memset(&mechInfo, 0, sizeof(mechInfo)); mechInfo.mechanism = HLSE_AES_CBC_DECRYPT; mechInfo.pParameter = pIv; mechInfo.ulParameterLen = SCP_KEY_SIZE; nRet = HLCRYPT_Decrypt(&mechInfo, session.sEnc, SCP_KEY_SIZE, response, pApdu->rxlen - 10, plaintextResponse, &outLen); } // nRet = HOST_AES_CBC_Process(session.sEnc, SCP_KEY_SIZE, pIv, HOST_DECRYPT, response, pApdu->rxlen - 10, plaintextResponse); if (nRet != HOST_CRYPTO_OK) { return ERR_CRYPTO_ENGINE_FAILED; } PRINT_BYTE_STRING("SCP: Plaintext Response", plaintextResponse, pApdu->rxlen - 10); // Remove the padding from the plaintextResponse status = scp_RemovePaddingRestoreSw(pApdu, plaintextResponse, pApdu->rxlen - 10, sw); } else if (pApdu->rxlen == 10) { // There's no data payload in response memcpy(pApdu->pBuf, sw, SCP_GP_SW_LEN); pApdu->rxlen = SCP_GP_SW_LEN; } else { // We're receiving a response with an unexpected response length DPRINTF("SCP: Unexpected Response Length: %d\r\n", pApdu->rxlen); } SCP_HostLocal_IncIcvCCounter(channelId); return status; } #endif /* SSS_HAVE_A71XX */ #endif // SECURE_CHANNEL_SUPPORTED