/** * @file configCmdInfo.c * @author NXP Semiconductors * @version 1.0 * @par License * * Copyright 2017 NXP * SPDX-License-Identifier: Apache-2.0 * * @par Description * Command handling for 'info'. Includes optional console print. */ #include #include #include #include #ifdef OPENSSL #include #endif // project specific include files #include "sm_types.h" #include "sm_apdu.h" #include "tst_sm_util.h" #include "tst_a71ch_util.h" #include "probeAxUtil.h" #include "configCmd.h" #include "configCli.h" // Used for error codes. #include "configState.h" #include "HLSETypes.h" #include "axHostCrypto.h" #include "tstHostCrypto.h" #include "HLSEAPI.h" #define FLOW_VERBOSE_PROBE_A70 #ifdef FLOW_VERBOSE_PROBE_A70 #define FPRINTF(...) printf (__VA_ARGS__) #else #define FPRINTF(...) #endif // #define DBG_PROBE_A70 #ifdef DBG_PROBE_A70 #define DBGPRINTF(...) printf (__VA_ARGS__) #else #define DBGPRINTF(...) #endif #define HLSE_GP_DATA_CHUNK 32 //!< GP Data chunk size byted /// GP Table Data Entry typedef struct { U8 klass; U8 index; U16 length; U16 offset; } HLSE_GP_DATA_TABLE_ENTRY; #define HLSE_GP_DATA_TABLE_ENTRY_SIZE 6 //!< GP Table Entry size in bytes /// Alligned number of chunks in which 'size' bytes occupies #define HLSE_ALIGN_SIZE(size) ((size + HLSE_GP_DATA_CHUNK - 1) / HLSE_GP_DATA_CHUNK) // This is a theoritical max , as it is stored as one byte in the beginning of the Gp Table - up to max 254 entries (0xFE) // elements might be created . value of 0xFF is reserved for a deleted/invalid entry #define HLSE_MAX_OBJECTS_IN_TABLE 254 // Max chunks which could be allowed in GP Data table #define HLSE_GP_DATA_CHUNKS_NUM HLSE_ALIGN_SIZE(((HLSE_MAX_OBJECTS_IN_TABLE * 6) + 2)) /// GP Data Table typedef struct { U8 numOfEntries; //!< num of entries U8 updateCounter; //!< update counter HLSE_GP_DATA_TABLE_ENTRY entries[HLSE_MAX_OBJECTS_IN_TABLE]; //!< entries array } HLSE_GP_DATA_TABLE; static HLSE_RET_CODE DisplayObjectInfo(); /** * A hook for the command line handler to invoke A71 commands * \return Returns ::AX_CLI_EXEC_OK upon success */ int a7xConfigCmdInfo(a71_SecureStorageClass_t ssc, U16 offset, int nSegments, U16 *sw) { int error = AX_CLI_EXEC_FAILED; U8 uid[A71CH_MODULE_UNIQUE_ID_LEN]; U16 uidLen = sizeof(uid); U16 selectResponse = 0; U8 debugOn = 0; U8 restrictedKpIdx = 0; U8 transportLockState = 0; U8 scpState = A71CH_SCP_CHANNEL_STATE_UNKNOWN; U8 injectLockState = 0; U16 gpStorageSize = 0; int nEccPair = 0; int nEccPub = 0; int nCnt = 0; // int nSymKey = 0; int nGpSections = 0; // TODO: Recognize whether SCP03 has already been set up by the config tool. (Store this state in scpReq - to be renamed in scpChannelActive) *sw = a7xCmdInfoDevice(uid, &uidLen, &selectResponse, &debugOn, &restrictedKpIdx, &transportLockState, &scpState, &injectLockState, &gpStorageSize); if (*sw == SW_OK) { error = AX_CLI_EXEC_OK; if ( (ssc == A71_SSC_MODULE) || (ssc == A71_SSC_ALL) ) { a7xCmdInfoDevicePrettyPrint(uid, uidLen, selectResponse, debugOn, restrictedKpIdx, transportLockState, scpState, injectLockState, gpStorageSize); } } else { error = AX_CLI_EXEC_FAILED; return error; } if (gpStorageSize == 1024) { nEccPair = A7X_CONFIG_KEY_PAIR_TYPE_A; nEccPub = A7X_CONFIG_PUBLIC_KEY_TYPE_A; nCnt = A7X_CONFIG_COUNTER_TYPE_A; // nSymKey = A7X_CONFIG_SYM_KEY_TYPE_A; nGpSections = A7X_CONFIG_GP_STORAGE_SECTION_TYPE_A; } else if (gpStorageSize == 4096) { nEccPair = A7X_CONFIG_KEY_PAIR_TYPE_B; nEccPub = A7X_CONFIG_PUBLIC_KEY_TYPE_B; nCnt = A7X_CONFIG_COUNTER_TYPE_B; // nSymKey = A7X_CONFIG_SYM_KEY_TYPE_B; nGpSections = A7X_CONFIG_GP_STORAGE_SECTION_TYPE_B; } else { // Defaulting to smallest credential set printf("Warning: Tool does not know exact amount of credentials stored. Using default values.\n"); nEccPair = A7X_CONFIG_KEY_PAIR_TYPE_A; nEccPub = A7X_CONFIG_PUBLIC_KEY_TYPE_A; nCnt = A7X_CONFIG_COUNTER_TYPE_A; // nSymKey = A7X_CONFIG_SYM_KEY_TYPE_A; nGpSections = A7X_CONFIG_GP_STORAGE_SECTION_TYPE_A; } if ( (scpState != A71CH_SCP_MANDATORY) || ((scpState == A71CH_SCP_MANDATORY) && (a7xConfigGetHostScp03State() == AX_SCP03_CHANNEL_ON)) ) { eccKeyComponents_t eccKc[(A7X_CONFIG_KEY_PAIR_MAX > A7X_CONFIG_PUBLIC_KEY_MAX) ? A7X_CONFIG_KEY_PAIR_MAX : A7X_CONFIG_PUBLIC_KEY_MAX]; a71_CounterWrapper_t counterArray[A7X_CONFIG_COUNTER_MAX]; U8 data[A7X_CONFIG_GP_STORAGE_MAX]; U16 dataLen = sizeof(data); // Only in this case can we query for the value of credentials switch (ssc) { case A71_SSC_KEY_PAIR: a7xCmdInfoEcc(ssc, nEccPair, eccKc); error = a7xCmdInfoEccPrettyPrint(ssc, nEccPair, eccKc); break; case A71_SSC_PUBLIC_KEY: a7xCmdInfoEcc(ssc, nEccPub, eccKc); error = a7xCmdInfoEccPrettyPrint(ssc, nEccPub, eccKc); break; case A71_SSC_COUNTER: a7xCmdInfoCounter(nCnt, counterArray); error = a7xCmdInfoCounterPrettyPrint(nCnt, counterArray); break; case A71_SSC_OBJECTS: error = DisplayObjectInfo(); break; case A71_SSC_GP_DATA: if ( (U16)(nSegments * A71CH_GP_STORAGE_GRANULARITY) > dataLen ) { error = AX_CLI_BUFFER_SIZE_ERROR; } dataLen = (U16)(nSegments * A71CH_GP_STORAGE_GRANULARITY); *sw = a7xCmdInfoGpData(data, dataLen, offset); if (*sw == SW_OK) { error = a7xCmdInfoGpDataPrettyPrint(data, offset, nSegments); } else { printf("Could not retrieve data from gp storage.\n"); error = AX_CLI_EXEC_FAILED; } break; case A71_SSC_CONFIG_KEY: case A71_SSC_SYM_KEY: printf("Not implemented.\n"); error = AX_CLI_NOT_IMPLEMENTED; break; case A71_SSC_ALL: a7xCmdInfoEcc(A71_SSC_KEY_PAIR, nEccPair, eccKc); error = a7xCmdInfoEccPrettyPrint(A71_SSC_KEY_PAIR, nEccPair, eccKc); a7xCmdInfoEcc(A71_SSC_PUBLIC_KEY, nEccPub, eccKc); error = a7xCmdInfoEccPrettyPrint(A71_SSC_PUBLIC_KEY, nEccPub, eccKc); a7xCmdInfoCounter(nCnt, counterArray); error = a7xCmdInfoCounterPrettyPrint(nCnt, counterArray); *sw = a7xCmdInfoGpData(data, (U16)(nGpSections * A71CH_GP_STORAGE_GRANULARITY), 0); if (*sw == SW_OK) { error = a7xCmdInfoGpDataPrettyPrint(data, 0, nGpSections); } else { printf("Could not retrieve data from gp storage.\n"); error = AX_CLI_EXEC_FAILED; } break; case A71_SSC_MODULE: // No action required break; case A71_SSC_UNDEF: printf("Undefined Secure Storage Class.\n"); error = AX_CLI_ARG_VALUE_ERROR; break; } } else { // As we don't know the SCP03 keys - conditionally - display a warning on the console. switch (ssc) { case A71_SSC_KEY_PAIR: case A71_SSC_PUBLIC_KEY: case A71_SSC_GP_DATA: case A71_SSC_OBJECTS: case A71_SSC_CONFIG_KEY: case A71_SSC_SYM_KEY: case A71_SSC_COUNTER: case A71_SSC_ALL: printf("info command needs prior setup of SCP03 channel.\n"); printf("Debug: a7xConfigGetHostScp03State() = 0x%02X\n", a7xConfigGetHostScp03State()); error = AX_CLI_EXEC_FAILED; break; case A71_SSC_MODULE: // No action required break; case A71_SSC_UNDEF: printf("Undefined Secure Storage Class.\n"); error = AX_CLI_ARG_VALUE_ERROR; break; } } return error; } /** * Print to console */ int a7xCmdInfoDevicePrettyPrint(U8 *uid, U16 uidLen, U16 selectResponse, U8 debugOn, U8 restrictedKpIdx, U8 transportLockState, U8 scpState, U8 injectLockState, U16 gpStorageSize) { int idx = 0; U8 certUid[A71CH_MODULE_CERT_UID_LEN]; printf("A71CH in %s (%s)\n", (debugOn == 0) ? "Production Version" : "Debug Mode Version", (scpState == A71CH_SCP_MANDATORY) ? "SCP03 is mandatory" : (scpState == A71CH_SCP_NOT_SET_UP) ? "SCP03 is not set up" : (scpState == A71CH_SCP_KEYS_SET) ? "SCP03 keys set" : "Undefined SCP state"); printf("selectResponse: 0x%04X\n", selectResponse); if (restrictedKpIdx != A71CH_NO_RESTRICTED_KP) { printf("restricted keypair index: 0x%02X\n", restrictedKpIdx); } printf("transportLockState: 0x%02X (%s)\n", transportLockState, (transportLockState == A71CH_TRANSPORT_LOCK_STATE_LOCKED) ? "Transport Lock is set" : (transportLockState == A71CH_TRANSPORT_LOCK_STATE_UNLOCKED) ? "Open device, Transport Lock can no longer be set" : (transportLockState == A71CH_TRANSPORT_LOCK_STATE_ALLOW_LOCK) ? "Transport Lock NOT YET set" : "Undefined Transport Lock state"); printf("injectLockState: 0x%02X (%s)\n", injectLockState, (injectLockState == A71CH_INJECT_LOCK_STATE_LOCKED) ? "Locked" : (injectLockState == A71CH_INJECT_LOCK_STATE_UNLOCKED) ? "Unlocked" : "Undefined Inject Lock State"); printf("gpStorageSize: %d\n", gpStorageSize); axPrintByteArray("uid", uid, uidLen, AX_COLON_32); certUid[idx++] = uid[A71CH_UID_IC_TYPE_OFFSET]; certUid[idx++] = uid[A71CH_UID_IC_TYPE_OFFSET+1]; certUid[idx++] = uid[A71CH_UID_IC_FABRICATION_DATA_OFFSET]; certUid[idx++] = uid[A71CH_UID_IC_FABRICATION_DATA_OFFSET+1]; certUid[idx++] = uid[A71CH_UID_IC_SERIAL_NR_OFFSET]; certUid[idx++] = uid[A71CH_UID_IC_SERIAL_NR_OFFSET+1]; certUid[idx++] = uid[A71CH_UID_IC_SERIAL_NR_OFFSET+2]; certUid[idx++] = uid[A71CH_UID_IC_BATCH_ID_OFFSET]; certUid[idx++] = uid[A71CH_UID_IC_BATCH_ID_OFFSET+1]; certUid[idx++] = uid[A71CH_UID_IC_BATCH_ID_OFFSET+2]; axPrintByteArray("certUid", certUid, (U16)idx, AX_COLON_32); return AX_CLI_EXEC_OK; } /** * API wrapper for info command. Can be called from GUI. */ U16 a7xCmdInfoDevice(U8 *uid, U16 *uidLen, U16 *selectResponse, U8 *debugOn, U8 *restrictedKpIdx, U8 *transportLockState, U8 *scpState, U8 *injectLockState, U16 *gpStorageSize) { U16 sw; *selectResponse = 0x0000; *debugOn = 0x00; *restrictedKpIdx = 0x00; *injectLockState = 0x00; *gpStorageSize = 0x0000; *scpState = A71CH_SCP_CHANNEL_STATE_UNKNOWN; sw = A71_GetModuleInfo(selectResponse, debugOn, restrictedKpIdx, transportLockState, scpState, injectLockState, gpStorageSize); // Retrieving UID can be protected by SCP03 if ( (*uidLen != 0) && (uid != NULL) && (sw == SW_OK) ) { sw = A71_GetUniqueID(uid, uidLen); if (sw != SW_OK) { DBGPRINTF("Failed to retrieve UID.\n"); } } return sw; } /** * Print Public key to console */ int a7xCmdInfoEccPrettyPrint(a71_SecureStorageClass_t ssc, int nEcc, eccKeyComponents_t *eccKc) { int i = 0; if (ssc == A71_SSC_KEY_PAIR) { printf("Public Keys from ECC key pairs:\n"); } else if (ssc == A71_SSC_PUBLIC_KEY) { printf("Public Keys:\n"); } else { printf("Wrong ssc requested: 0x%02X\n", ssc); return AX_CLI_API_ERROR; } for (i=0; i 0) { axPrintByteArray("ECC_PUB", eccKc[i].pub, eccKc[i].pubLen, AX_COLON_32); } else { printf("n.a.\n"); } } return AX_CLI_EXEC_OK; } /** * API wrapper for info command. Can be called from GUI. */ U16 a7xCmdInfoEcc(a71_SecureStorageClass_t ssc, int nEcc, eccKeyComponents_t *eccKc) { U16 sw = SW_OK; int i = 0; // Initialize data structures. for (i=0; inumOfEntries = 0; table->updateCounter = 0; // Read the entire table lReturn = A71_GetGpData(gpSize - dataReadByteSize, dataRead, dataReadByteSize); if (lReturn != HLSE_SW_OK) return lReturn; table->numOfEntries = dataRead[dataReadByteSize - 1]; if (table->numOfEntries == 0xFF) { return HLSE_SW_OK; } table->updateCounter = dataRead[dataReadByteSize - 2]; // Read only Valid entries up to numOfEntries starting from end of GP storage ( high address to low address ) nObj = 1; // first object to check if valid in GP Table for (entryNum = 0; entryNum < table->numOfEntries; ++entryNum) { // Notes: // X + 1 is the address of the last byte of the GP Storage. // N is the object number from 1 to N // read from object 1 to numOfEntries (skipping invalid =(deleted) entries) /* Address Value ------- ---------------------- X-N*6+0 N'th Object Class - 1 byte X-N*6+1 N'th Object Index - 1 byte X-N*6+2 N'th Object Length MSB - 1 byte X-N*6+3 N'th Object Length LSB - 1 byte X-N*6+4 N'th Object Offset MSB - 1 byte X-N*6+5 N'th Object Offset LSB - 1 byte */ bValidEntry = 0; // max objects that could be held in the gp data table nMaxObj = HLSE_MAX_OBJECTS_IN_TABLE; //((HLSE_GP_DATA_CHUNK * HLSE_GP_DATA_CHUNKS_NUM) - 2) / 6; while (!bValidEntry && nObj <= nMaxObj) { // check if entry is valid tmpClass = dataRead[dataReadByteSize - 2 - nObj * 6 + 0]; tmpIndex = dataRead[dataReadByteSize - 2 - nObj * 6 + 1]; // skip to next valid entry if (tmpClass != 0xFF && tmpIndex != 0xFF) { // this is a valid entry bValidEntry = 1; break; } } if (!bValidEntry) { // error - no more valid entries in table , although num of entries in gp table says there are more ! break; } // fill this valid entry in our table table->entries[entryNum].klass = dataRead[dataReadByteSize - 2 - nObj * 6 + 0]; table->entries[entryNum].index = dataRead[dataReadByteSize - 2 - nObj * 6 + 1]; table->entries[entryNum].length = dataRead[dataReadByteSize - 2 - nObj * 6 + 2] * 256 | dataRead[dataReadByteSize - 2 - nObj * 6 + 3]; table->entries[entryNum].offset = dataRead[dataReadByteSize - 2 - nObj * 6 + 4] * 256 | dataRead[dataReadByteSize - 2 - nObj * 6 + 5]; nObj++; } // sort the entries in ascending order of the offset // SortTable(table); return lReturn; } static HLSE_RET_CODE PrintObjectInfo(HLSE_OBJECT_TYPE objType, HLSE_GP_DATA_TABLE* gpTable) { HLSE_RET_CODE ret; HLSE_OBJECT_HANDLE handles[HLSE_MAX_OBJECTS_IN_TABLE]; U16 handlesNum = HLSE_MAX_OBJECTS_IN_TABLE; U16 i, j; ret = HLSE_EnumerateObjects(objType, handles, &handlesNum); if (ret != HLSE_SW_OK) return ret; printf("%s Objects: \n", (objType == HLSE_CERTIFICATE ? "Certificate" : "Data")); for (i = 0; i < handlesNum; ++i) { HLSE_OBJECT_INDEX index; // get object index from applet { HLSE_ATTRIBUTE attr; attr.type = HLSE_ATTR_OBJECT_INDEX; attr.value = &index; attr.valueLen = sizeof(index); ret = HLSE_GetObjectAttribute(handles[i], &attr); if (ret != HLSE_SW_OK) return ret; } for (j = 0; j < gpTable->numOfEntries; ++j) { if (gpTable->entries[j].index == index && gpTable->entries[j].klass == HLSE_GET_LOGICAL_OBJECT_CLASS(objType)) { printf("\t idx=0x%02X Absolute offset = 0x%04X Actual Size = 0x%04X (%d)\n", index, gpTable->entries[j].offset, gpTable->entries[j].length, gpTable->entries[j].length); } } } return ret; } static HLSE_RET_CODE DisplayObjectInfo() { HLSE_GP_DATA_TABLE gpTable; HLSE_RET_CODE ret = HLSE_SW_OK; // Read the mapping table ret = ParseGPDataTable(&gpTable); if (ret != HLSE_SW_OK) return ret; // enumerate Cert + Data objects ret = PrintObjectInfo(HLSE_CERTIFICATE, &gpTable); if (ret != HLSE_SW_OK) return ret; ret = PrintObjectInfo(HLSE_DATA, &gpTable); if (ret != HLSE_SW_OK) return ret; return AX_CLI_EXEC_OK; }