blob: 605c4fc71cf9b83d15342a871624c23a64d110ad [file] [log] [blame]
/*
* @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 <string.h>
#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 <stdio.h>
#include <assert.h>
#ifdef LINUX
#if !defined(__APPLE__)
#include <malloc.h>
#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;
#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
{
// scp_TransformCommand (if branch) already invokes smApduAdaptLc
smApduAdaptLc(pApdu, pApdu->lc);
}
smApduAdaptLe(pApdu, 0);
#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
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];
//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];
//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