blob: 4135f464ff314161042d9350154268c1829150a7 [file] [log] [blame] [edit]
/**
* @file ex_boot.c
* @author NXP Semiconductors
* @version 1.0
* @par License
*
* Copyright 2016,2020 NXP
* SPDX-License-Identifier: Apache-2.0
*
* @par Description
* Handover of SCP03 session keys from bootloader to OS. Refer to ::exBoot for more details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>
#include "a71ch_ex.h"
#include "a71_debug.h"
#include "sm_types.h"
#include "sm_apdu.h"
#include "tst_sm_util.h"
#include "tst_a71ch_util.h"
#include "sm_printf.h"
#include "axHostCrypto.h"
#include "tstHostCrypto.h"
#include "scp.h"
#include "ax_util.h"
#include "app_boot.h"
#if defined(SCI2C)
#include "sci2c.h"
#endif
#if defined(SCI2C)
static U8 writeStateToFile(char *szFilename, SmCommState_t *commState, Scp03SessionState_t *scp03State);
static U8 readStateFromFile(char *szFilename, SmCommState_t *commState, Scp03SessionState_t *scp03State);
#endif
char* axExBootGetBootModeAsString(U8 bootMode)
{
if (bootMode == BOOT_SKIP_EXAMPLE)
{
return "Skip boot example";
}
else if (bootMode == BOOT_SIMULATED_CYCLE)
{
return "Do full boot cycle (Bootloader/OS combined)";
}
else if (bootMode == BOOT_BOOTLOADER_ROLE)
{
return "Mimick behaviour of bootloader only (store session state on filesystem)";
}
else if (bootMode == BOOT_HOST_OS_RESUME)
{
return "Mimick behaviour of Host OS only (retrieve session state from filesystem)";
}
else
{
PRINTF("bootMode not defined\r\n");
assert(0);
return "not defined";
}
}
static void signalFunctionCallback(ScpEvent_t event, void *context)
{
AX_UNUSED_ARG(context);
PRINTF("scpCallback: ");
switch (event)
{
case SCP_WRONG_PADDING:
PRINTF("Wrong padding\r\n");
break;
case SCP_WRONG_RESPMAC:
PRINTF("Wrong response mac\r\n");
break;
case SCP_GENERIC_FAILURE:
PRINTF("Non specified failure\r\n");
break;
default:
PRINTF("Unknown event type\r\n");
break;
}
return;
}
/// @endcond
/**
* This example illustrates that the HostOS does NOT need to know the SCP03 Base Keys
* to establish an SCP03 session (provided the boot loader has established the
* SCP03 session and saved the SCP03 session state)
*
* When using SCI2C the bootloader must also save the SCI2C state.
*
* - The boot loader
* - Sets an initial SCP03 base key set (based on random data retrieved from
* the SM (Secure Module))
* - Establishes an SCP03 session
* - Stores the SCP03 session (optionally also SCI2C state)
*
* - The OS
* - Retrieves the SCP03 session and communication state
* - Resumes the SCP03 session
*
* \note When the OS takes over from the boot loader the communication link with
* the Secure Element may not be broken.
* \note Storing the session and communication state on the filesystem is done for convenience
* in this example. Use an appropriate and secure sharing mechanism in a product implementation.
* \note Full functionality of this example is only available when connecting to the A71CH over SCI2C.
* @param[in] bootMode One of the following values
* - ::BOOT_SIMULATED_CYCLE Can be used to simulate handover of session keys (no dependency on link protocol)
* - ::BOOT_BOOTLOADER_ROLE Play the role of initial bootloader and store session and communication state to filesystem
* - ::BOOT_HOST_OS_RESUME Play the role of OS and retrieve session and communication state from the filesystem
*/
U8 exBoot(U8 bootMode)
{
U8 result = 1;
U8 initMode = INIT_MODE_NO_RESET;
U8 scpKeyEncBase[SCP_KEY_SIZE];
U8 scpKeyMacBase[SCP_KEY_SIZE];
U8 scpKeyDekBase[SCP_KEY_SIZE];
U8 dataRef[] = {0xBE, 0xFA, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
U16 dataRefLen = sizeof(dataRef);
U8 dataFetch[8];
U16 packetSize;
U16 connectStatus;
U8 Atr[64] = {0};
U16 AtrLen = sizeof(Atr);
SmCommState_t commState = {0};
Scp03SessionState_t sessionState;
U16 sw = SW_OK;
#if defined(SCI2C)
char szFilename[] = "sessionState";
#endif
PRINTF("\r\n-----------\r\nStart exBoot(%s)\r\n------------\r\n", axExBootGetBootModeAsString(bootMode));
if (bootMode == BOOT_SKIP_EXAMPLE)
{
PRINTF("***** WARNING: exBoot example is skipped *****\r\n");
}
else if (bootMode == BOOT_SIMULATED_CYCLE)
{
initMode = INIT_MODE_RESET;
// Installing Callback (this step is optional)
SCP_Subscribe(signalFunctionCallback, NULL);
DEV_ClearChannelState();
#if defined(SCI2C)|| defined(T1oI2C)
SM_Close(NULL, SMCOM_CLOSE_MODE_TERMINATE);
#endif
connectStatus = SM_Connect(NULL, &commState, Atr, &AtrLen);
if (connectStatus != SW_OK)
{
sm_printf(CONSOLE, "Failed to establish connection to Secure Module: 0x%04" PRIX32 "\r\n", connectStatus);
result = 0;
return result;
}
else
{
#ifndef SMCOM_JRCP_V1
int i=0;
sm_printf(CONSOLE, "ATR=0x");
for (i=0; i<AtrLen; i++) sm_printf(CONSOLE, "%02" PRIX16 ".", Atr[i]);
sm_printf(CONSOLE, "\r\n");
#endif
}
// STAGE-0:
// - Force the A71CH into the initial state (only possible when in Debug Mode)
sm_printf(CONSOLE, "Initialize Module\r\n");
result &= a71chInitModule(initMode);
// STAGE-1:
// - Fetch random data from the A71CH
// - Set the SCP03 keys based on this random data
// - This binding between Host and A71CH must only be done once
result &= axExCreateAndSetInitialHostScpKeys(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
// STAGE-2:
// - Locally store the SCP03 base keys on the Host (for re-use in the next boot cycle)
// - Set up the authenticated and encrypted SCP03 channel with the A71CH.
result &= axExAuthenticate(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
// STAGE-3:
// - Write an 8 byte pattern to GPStorage (just using some standard A71CH functionality)
packetSize = dataRefLen;
PRINTF( "\r\nA71_SetGpData(0, %d, ...)\r\n", packetSize);
sw = A71_SetGpData(0, dataRef, packetSize);
result &= AX_CHECK_SW(sw, SW_OK, "A71_SetGpData fails");
// Before Handing over to the Host OS:
// - Retrieve and store the SCP03 session state
// - Retrieve and store SCI2C communication state
SCP_GetScpSessionState(&sessionState);
// At this stage the System Integrator must store Session State in Mailbox for HostOS
#if defined(SCI2C)
commState.param1 = sci2c_GetSequenceCounter();
// At this stage the System Integrator must store communication state in Mailbox for HostOS
SM_Close(NULL, SMCOM_CLOSE_MODE_STD);
#endif
sm_printf(DBGOUT, "\r\n\r\n");
sm_printf(DBGOUT, "******************************************************************\r\n");
sm_printf(DBGOUT, "** System Integrator must store Communication and Session State **\r\n");
sm_printf(DBGOUT, "******************************************************************\r\n");
DEV_ClearChannelState();
// +++ Handover from Boot to HostOS +++
// ** Host OS phase **
SM_ResumeConnection(&commState, &sessionState);
// - Retrieve and compare the 8 byte reference pattern from GPStorage (just using some standard A71CH functionality)
packetSize = dataRefLen;
PRINTF( "\r\nA71_GetGpData(0, %d, ...)\r\n", packetSize);
sw = A71_GetGpData(0, dataFetch, packetSize);
result &= AX_CHECK_SW(sw, SW_OK, "A71_GetGpData fails");
axPrintByteArray ("dataFetch", dataFetch, packetSize, AX_COLON_32);
result &= AX_COMPARE_BYTE_ARRAY("dataRef", dataRef, dataRefLen,
"dataFetch", dataFetch, dataRefLen, AX_COLON_32);
}
#if defined(SCI2C)
else if (bootMode == BOOT_BOOTLOADER_ROLE)
{
initMode = INIT_MODE_RESET;
// Installing Callback (this step is optional)
SCP_Subscribe(signalFunctionCallback, NULL);
DEV_ClearChannelState();
SM_Close(NULL, SMCOM_CLOSE_MODE_TERMINATE);
connectStatus = SM_Connect(NULL, &commState, Atr, &AtrLen);
if (connectStatus != SW_OK)
{
sm_printf(CONSOLE, "Failed to establish connection to Secure Module: 0x%04" PRIX32 "\r\n", connectStatus);
result = 0;
return result;
}
else
{
int i=0;
sm_printf(CONSOLE, "ATR=0x");
for (i=0; i<AtrLen; i++) sm_printf(CONSOLE, "%02" PRIX16 ".", Atr[i]);
sm_printf(CONSOLE, "\r\n");
}
// STAGE-0:
// - Force the A71CH into the initial state (only possible when in Debug Mode)
sm_printf(CONSOLE, "Initialize Module\r\n");
result &= a71chInitModule(initMode);
// STAGE-1:
// - Fetch random data from the A71CH
// - Set the SCP03 keys based on this random data
result &= axExCreateAndSetInitialHostScpKeys(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
// STAGE-2:
// - Locally store the SCP03 base keys
// - Set up the authenticated and encrypted SCP03 channel with the A71CH.
result &= axExAuthenticate(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
// STAGE-3:
// - Write an 8 byte pattern to GPStorage (just using some standard A71CH functionality)
packetSize = dataRefLen;
PRINTF( "\r\nA71_SetGpData(0, %d, ...)\r\n", packetSize);
sw = A71_SetGpData(0, dataRef, packetSize);
result &= AX_CHECK_SW(sw, SW_OK, "A71_SetGpData fails");
// Before Handing over to the Host OS:
// - Retrieve and store the SCP03 session state
// - Retrieve and store SCI2C communication state
SCP_GetScpSessionState(&sessionState);
commState.param1 = sci2c_GetSequenceCounter();
SM_Close(NULL, SMCOM_CLOSE_MODE_STD);
sm_printf(DBGOUT, "\r\n\r\n");
sm_printf(DBGOUT, "******************************************************************\r\n");
sm_printf(DBGOUT, "** System Integrator must store Communication and Session State **\r\n");
sm_printf(DBGOUT, "******************************************************************\r\n");
sm_printf(DBGOUT, "..\r\n..\r\n");
// WARNING: Simply writing the SessionState to the file system is not a secure implementation.
// it must not be used in products.
writeStateToFile(szFilename, &commState, &sessionState);
DEV_ClearChannelState();
}
else if (bootMode == BOOT_HOST_OS_RESUME)
{
SmCommState_t retrCommState;
Scp03SessionState_t retrScp03State;
readStateFromFile(szFilename, &retrCommState, &retrScp03State);
PRINTF("retrCommState.param1 : 0x%02" PRIX16 "\r\n", retrCommState.param1);
PRINTF("retrCommState.hostLibVersion : 0x%02" PRIX16 "\r\n", retrCommState.hostLibVersion);
PRINTF("retrCommState.appletVersion : 0x%04" PRIX32 "\r\n", retrCommState.appletVersion);
DEV_ClearChannelState();
// +++ Handover from Boot to HostOS +++
sm_printf(DBGOUT, "\r\n\r\n");
sm_printf(DBGOUT, "*******************************************************\r\n");
sm_printf(DBGOUT, "** Host OS resumes SCP03 session with Secure Element **\r\n");
sm_printf(DBGOUT, "*******************************************************\r\n");
// ** Host OS phase **
SM_ResumeConnection(&retrCommState, &retrScp03State);
// - Retrieve and compare the 8 byte reference pattern from GPStorage (just using some standard A71CH functionality)
packetSize = dataRefLen;
PRINTF( "\r\nA71_GetGpData(0, %d, ...)\r\n", packetSize);
sw = A71_GetGpData(0, dataFetch, packetSize);
result &= AX_CHECK_SW(sw, SW_OK, "A71_GetGpData fails");
axPrintByteArray ("dataFetch", dataFetch, packetSize, AX_COLON_32);
result &= AX_COMPARE_BYTE_ARRAY("dataRef", dataRef, dataRefLen,
"dataFetch", dataFetch, dataRefLen, AX_COLON_32);
}
#endif // defined(SCI2C)
else
{
PRINTF("bootMode (0x%02" PRIX16 ") is not defined.\r\n", (uint16_t)bootMode);
result = 0;
}
// overall result
PRINTF("\r\n-----------\r\nEnd exBoot(%s), result = %s\r\n------------\r\n",
axExBootGetBootModeAsString(bootMode), ((result == 1)? "OK": "FAILED"));
return result;
}
/**
* The host fetches random data from the A71CH and sets these as SCP03 base keys in the
* A71CH.
*
* @param[in,out] keyEnc IN: Buffer to contain key; OUT: Key created and inserted into A71CH
* @param[in,out] keyMac IN: Buffer to contain key; OUT: Key created and inserted into A71CH
* @param[in,out] keyDek IN: Buffer to contain key; OUT: Key created and inserted into A71CH
*/
U8 axExCreateAndSetInitialHostScpKeys(U8 *keyEnc, U8 *keyMac, U8 *keyDek)
{
U8 result = 1;
U16 err = 0;
U8 random[3*SCP_KEY_SIZE];
U8 randomLen = (U8)sizeof(random);
U8 *currentKeyDek = NULL;
U8 keyVersion = 1;
assert(keyEnc != NULL);
assert(keyMac != NULL);
assert(keyDek != NULL);
PRINTF( "\r\n-----------\r\nStart axExCreateAndSetInitialHostScpKeys()\r\n------------\r\n");
// Security module generates random data for initial SCP03 keys
sm_printf(CONSOLE, "\r\nA71_GetRandom(randomLen=%d)\r\n", randomLen);
err = A71_GetRandom(random, randomLen);
result &= AX_CHECK_SW(err, SW_OK, "err");
// Storing Static Keys
memcpy(keyEnc, random, SCP_KEY_SIZE);
memcpy(keyMac, random + SCP_KEY_SIZE, SCP_KEY_SIZE);
memcpy(keyDek, random + (2*SCP_KEY_SIZE), SCP_KEY_SIZE);
keyVersion = (U8) (SST_HOST_SCP_KEYSET >> 8);
sm_printf(CONSOLE, "\r\nSCP_GP_PutKeys(keyVersion=0x%02" PRIX16 ")\r\n", keyVersion);
err = SCP_GP_PutKeys(keyVersion, keyEnc, keyMac, keyDek, currentKeyDek, AES_KEY_LEN_nBYTE);
result &= AX_CHECK_SW(err, SW_OK, "err");
PRINTF( "\r\n-----------\r\nEnd axExCreateAndSetInitialHostScpKeys(), result = %s\r\n------------\r\n",
((result == 1)? "OK": "FAILED"));
return result;
}
/**
* The host locally stores the SCP03 base keys passed as parameters and establishes
* the authenticated and encrypted SCP03 channel with the A71CH.
*
* @param[in] keyEnc IN: SCP03 base key
* @param[in] keyMac IN: SCP03 base key
* @param[in] keyDek IN: SCP03 base key
*/
U8 axExAuthenticate(U8 *keyEnc, U8 *keyMac, U8 *keyDek)
{
U8 sCounter[3];
U16 sCounterLen = sizeof(sCounter);
U8 result = 1;
U16 err = 0;
PRINTF( "\r\n-----------\r\nStart axExAuthenticate()\r\n------------\r\n");
// Authenticate Channel
sm_printf(CONSOLE, "\r\nSCP_Authenticate()\r\n");
err = SCP_Authenticate(keyEnc, keyMac, keyDek, SCP_KEY_SIZE, sCounter, &sCounterLen);
result &= AX_CHECK_SW(err, SW_OK, "err");
result &= AX_CHECK_U16(sCounterLen, 0, "Only expected when SCP03 is configured for pseudo-random challenge");
PRINTF( "\r\n-----------\r\nEnd axExAuthenticate(), result = %s\r\n------------\r\n",
((result == 1)? "OK": "FAILED"));
return result;
}
#if defined(SCI2C)
static U8 writeStateToFile(char *szFilename, SmCommState_t *commState, Scp03SessionState_t *scp03State)
{
U8 result = 1;
#ifdef FTR_FILE_SYSTEM
FILE *fHandle = NULL;
fHandle = fopen(szFilename, "w");
if (fHandle == NULL)
{
sm_printf(DBGOUT, "Failed to open file %s for writing\r\n", szFilename);
return 0;
}
fwrite((const void *) commState, sizeof(SmCommState_t), 1, fHandle);
fwrite((const void *) scp03State, sizeof(Scp03SessionState_t), 1, fHandle);
fclose(fHandle);
#endif
return result;
}
static U8 readStateFromFile(char *szFilename, SmCommState_t *commState, Scp03SessionState_t *scp03State)
{
U8 result = 1;
#ifdef FTR_FILE_SYSTEM
FILE *fHandle = NULL;
fHandle = fopen(szFilename, "r");
if (fHandle == NULL)
{
sm_printf(DBGOUT, "Failed to open file %s for reading", szFilename);
return 0;
}
fread((void *) commState, sizeof(SmCommState_t), 1, fHandle);
fread((void *) scp03State, sizeof(Scp03SessionState_t), 1, fHandle);
fclose(fHandle);
#endif
return result;
}
#endif