blob: f8bd0afb465f9e81cf51d7cb4ee2b3069cd9c7a0 [file] [log] [blame]
/*
* @author NXP Semiconductors
* @version 1.2
* @par License
*
* Copyright 2017,2020 NXP
* SPDX-License-Identifier: Apache-2.0
*
* @par History
* 1.0 2016-Aug-26: Initial version
* 1.1 2017-Nov-30: Adding function A71_EccRestrictedSign
* 1.2 2018-Dec-12: Modified implementation of A71_EccSign to adapt reported size of ECDSA signature
*
*****************************************************************************/
/**
* @file a71ch_crypto_ecc.c
* @par Description
* Wrap the ECC cryptographic functionality of the A71CH.
*/
#include <stdio.h>
#include "a71ch_api.h"
#include "sm_apdu.h"
#include "sm_errors.h"
#include "hcAsn.h"
#include "axHostCrypto.h"
/// @cond
#define ASN_SKIP_INTEGER_NORMALIZE 0x00
#define ASN_DO_INTEGER_NORMALIZE 0x01
static U16 A71_EccSign_Local(SST_Index_t index, const U8 *pHash, U16 hashLen, U8 *pSignature, U16 *pSignatureLen, U8 normalize);
/// @endcond
/**
* Generates an ECC keypair at storage location \p index.
* @pre INJECTION_LOCKED has not been set
* @param[in] index Storage index of the keypair to be created.
* @retval ::SW_OK Upon successful execution
*/
U16 A71_GenerateEccKeyPair(SST_Index_t index)
{
U16 rv = 0;
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
pApdu->cla = A71CH_CLA;
pApdu->ins = A71CH_INS_SET_ECC_KEYPAIR;
pApdu->p1 = index;
pApdu->p2 = 0x00;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
// No response data expected
rv = CheckNoResponseData(pApdu);
}
FreeAPDUBuffer(pApdu);
return rv;
}
/**
* Generates an ECC keypair at storage location \p index.
* This function must be called instead of ::A71_GenerateEccKeyPair in case INJECTION_LOCKED was set.
*
* To use this function the value of the Key Pair configuration key must be known on the host. If
* this is not the case use ::A71_GenerateEccKeyPairWithCode instead.
*
* @param[in] index Storage index of the keypair to be created.
* @param[in] configKey Value of Key Pair configuration key. This value has a high level of
* confidentiality and may not be available to the Host.
* @param[in] configKeyLen Length of Key Pair configuration key
* @retval ::SW_OK Upon successful execution
*/
U16 A71_GenerateEccKeyPairWithChallenge(SST_Index_t index, const U8 *configKey, U16 configKeyLen)
{
U16 rv;
S32 hcRet = 0;
U8 challenge[A71CH_MODULE_UNLOCK_CHALLENGE_LEN] = {0};
U16 challengeLen = sizeof(challenge);
U8 code[A71CH_MODULE_UNLOCK_CHALLENGE_LEN] = {0};
#ifndef A71_IGNORE_PARAM_CHECK
if (configKey == NULL) {
return ERR_API_ERROR;
}
#endif
if (configKeyLen != 16)
{
return ERR_API_ERROR;
}
rv = A71_GetKeyPairChallenge(challenge, &challengeLen);
if (rv != SW_OK) { return rv; }
// Decrypt challenge
hcRet = HOST_AES_ECB_DECRYPT(code, challenge, configKey, configKeyLen);
if (hcRet != HOST_CRYPTO_OK)
{
return ERR_CRYPTO_ENGINE_FAILED;
}
rv = A71_GenerateEccKeyPairWithCode(index, code, A71CH_MODULE_UNLOCK_CHALLENGE_LEN);
return rv;
}
/**
* Generates an ECC keypair at storage location \p index.
* This function must be called instead of ::A71_GenerateEccKeyPair in case INJECTION_LOCKED was set.
*
* The assumption is the value of the Key Pair configuration key is not known on the host.
* If this does not apply use ::A71_GenerateEccKeyPairWithChallenge instead.
*
* The code is calculated as follows:
- Request a challenge from A71CH using ::A71_GetUnlockChallenge.
- Decrypt the challenge in ECB mode using the appropriate configuration key value (the same as stored at index ::A71XX_CFG_KEY_IDX_PRIVATE_KEYS).
- The decrypted value is the value of \p code
*
* @param[in] index Storage index of the keypair to be created.
* @param[in] code Value of unlock code.
* @param[in] codeLen Length of unlock code (must be 16)
* @retval ::SW_OK Upon successful execution
*/
U16 A71_GenerateEccKeyPairWithCode(SST_Index_t index, const U8 *code, U16 codeLen)
{
U16 rv;
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
pApdu->cla = AX_CLA;
pApdu->ins = A71CH_INS_SET_ECC_KEYPAIR;
pApdu->p1 = index;
pApdu->p2 = (U8)codeLen;
if (codeLen != 16)
{
return ERR_API_ERROR;
}
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, code, codeLen);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
// No response data expected
rv = CheckNoResponseData(pApdu);
}
FreeAPDUBuffer(pApdu);
return rv;
}
/**
* Signs the hash \p pHash using the keypair at the indicated index.
* @param[in] index Storage index of the keypair (private key) to be used.
* @param[in] pHash Pointer to the provided hash (or any other bytestring).
* @param[in] hashLen Length of the provided hash.
* @param[in,out] pSignature Pointer to the computed signature.
* @param[in,out] pSignatureLen Pointer to the length of the computed signature.
* @retval ::SW_OK Upon successful execution
*/
U16 A71_EccSign(SST_Index_t index, const U8 *pHash, U16 hashLen, U8 *pSignature, U16 *pSignatureLen)
{
return A71_EccSign_Local(index, pHash, hashLen, pSignature, pSignatureLen, ASN_SKIP_INTEGER_NORMALIZE);
}
/**
* Signs the hash \p pHash using the keypair at the indicated index.
*
* The integer representation of the ECDSA signatures'
* r and s component is modified to be in line with ASN.1 (Ensuring an integer value is always encoded in the
* smallest possible number of octets)
* @param[in] index Storage index of the keypair (private key) to be used.
* @param[in] pHash Pointer to the provided hash (or any other bytestring).
* @param[in] hashLen Length of the provided hash.
* @param[in,out] pSignature Pointer to the computed signature.
* @param[in,out] pSignatureLen Pointer to the length of the computed signature.
* @retval ::SW_OK Upon successful execution
*/
U16 A71_EccNormalizedAsnSign(SST_Index_t index, const U8 *pHash, U16 hashLen, U8 *pSignature, U16 *pSignatureLen)
{
return A71_EccSign_Local(index, pHash, hashLen, pSignature, pSignatureLen, ASN_DO_INTEGER_NORMALIZE);
}
/// @cond
static U16 A71_EccSign_Local(SST_Index_t index, const U8 *pHash, U16 hashLen, U8 *pSignature, U16 *pSignatureLen, U8 normalize)
{
U8 isOk = 0;
U16 rv;
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
#ifndef A71_IGNORE_PARAM_CHECK
if ((pHash == NULL) || (pSignature == NULL) || (pSignatureLen == NULL) || (*pSignatureLen < 72)) {
return ERR_API_ERROR;
}
#endif
pApdu->cla = A71CH_CLA;
pApdu->ins = A71CH_INS_SIGN_HASH_ECC_KEYPAIR;
pApdu->p1 = index;
pApdu->p2 = 0x00;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, pHash, hashLen);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
rv = smGetSw(pApdu, &isOk);
if (isOk)
{
rv = smApduGetResponseBody(pApdu, pSignature, pSignatureLen);
if (rv != SW_OK)
{
return ERR_WRONG_RESPONSE;
}
if (normalize == ASN_DO_INTEGER_NORMALIZE)
{
if ( hcNormalizeAsnSignatureEcc(pSignature, pSignatureLen) != SW_OK)
{
return ERR_WRONG_RESPONSE;
}
}
else
{
U16 actualSignatureLen;
// Detect whether padded bytes must be removed from pSignature
// As we're not inspecting whether we received a valid ASN structure, at least ensure there
// is an 'L' byte in the signature
if (*pSignatureLen >= 2)
{
actualSignatureLen = pSignature[1] + 2;
if (actualSignatureLen < *pSignatureLen)
{
// Patch the value of *pSignatureLen
*pSignatureLen = actualSignatureLen;
}
}
else
{
return ERR_WRONG_RESPONSE;
}
}
}
}
FreeAPDUBuffer(pApdu);
return rv;
}
/// @endcond
/**
* Patches a predetermined fixed size memory region in GP storage with the byte array \p updateBytes
* Creates a signed certificate - in place in GP storage - using a predetermined block of GP storage data
*
* @param[in] index Storage index of the keypair (private key) to be used.
* @param[in] updateBytes Byte array to be written into GP storage
* @param[in] updateBytesLen Length of the provided byte array (\p updateBytes).
* @param[out] invocationCount Amount of times the underlying APDU has been called succesfully.
* @retval ::SW_OK Upon successful execution
*/
U16 A71_EccRestrictedSign(SST_Index_t index, const U8 *updateBytes, U16 updateBytesLen, U8 *invocationCount)
{
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
U16 rv;
U8 isOk = 0;
#ifndef A71_IGNORE_PARAM_CHECK
if ((updateBytes == NULL) || (invocationCount == NULL)) {
return ERR_API_ERROR;
}
#endif
if (updateBytesLen > (U16)0x00FF)
{
return ERR_API_ERROR;
}
*invocationCount = 0;
pApdu->cla = A71CH_CLA;
pApdu->ins = A71CH_INS_SIGN_HASH_ECC_KEYPAIR;
pApdu->p1 = index;
pApdu->p2 = 0x00;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, updateBytes, updateBytesLen);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
U8 result = 0x00;
U16 resultLen = 1;
rv = smGetSw(pApdu, &isOk);
if (isOk)
{
rv = smApduGetResponseBody(pApdu, &result, &resultLen);
if ((rv != SW_OK) || (resultLen != 1))
{
rv = ERR_WRONG_RESPONSE;
}
else
{
*invocationCount = result;
}
}
}
FreeAPDUBuffer(pApdu);
return rv;
}
/**
* Verifies whether \p pSignature is the signature of \p pHash
* using the public key stored under \p index as the verifying public key.
*
* The index refers to an instance of the PUBLIC_KEY secure storage class on the A71CH.
* \note The public key of an ECC key pair cannot be used for a verify operation.
* \note ::A71_EccVerifyWithKey allows to pass the value of the public key rather than use a stored public key.
*
* @param[in] index Storage index of the key used for the verification.
* @param[in] pHash Pointer to the provided hash (or any other bytestring).
* @param[in] hashLen Length of the provided hash (\p pHash).
* @param[in] pSignature Pointer to the provided signature.
* @param[in] signatureLen Length of the provided signature (\p pSignature)
* @param[out] pResult Pointer to the computed result of the verification.
Points to a value of 0x01 in case of successful verification
* @retval ::SW_OK Upon successful execution
*/
U16 A71_EccVerify(SST_Index_t index, const U8 *pHash, U16 hashLen, const U8 *pSignature, U16 signatureLen, U8 *pResult)
{
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
U16 rv;
U8 isOk = 0;
#ifndef A71_IGNORE_PARAM_CHECK
if ((pHash == NULL) || (pSignature == NULL) || (pResult == NULL)) {
return ERR_API_ERROR;
}
#endif
*pResult = 0;
pApdu->cla = A71CH_CLA;
pApdu->ins = A71CH_INS_VERIFY_SIG_ECC_PUBLIC_KEY;
pApdu->p1 = index;
pApdu->p2 = P2_PUBKEY_ABSENT;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, pHash, hashLen);
smApduAppendCmdData(pApdu, pSignature, signatureLen);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
U8 result = AX_VERIFY_FAILURE;
U16 resultLen = 1;
rv = smGetSw(pApdu, &isOk);
if (isOk)
{
rv = smApduGetResponseBody(pApdu, &result, &resultLen);
if ((rv != SW_OK) || (resultLen != 1) || ((result != AX_VERIFY_SUCCESS) && (result != AX_VERIFY_FAILURE)))
{
rv = ERR_WRONG_RESPONSE;
}
else
{
if (result == AX_VERIFY_SUCCESS)
{
*pResult = 1;
}
else
{
*pResult = 0;
}
}
}
}
FreeAPDUBuffer(pApdu);
return rv;
}
/**
* Generates and retrieves a shared secret ECC point \p pSharedSecret using the private key stored
* at \p index and a public key \p pOtherPublicKey passed as argument.
* @param[in] index to the key pair (private key to be used)
* @param[in] pOtherPublicKey Pointer to the given public key.
* @param[in] otherPublicKeyLen Length of the given public key.
* @param[in,out] pSharedSecret Pointer to the computed shared secret.
* @param[in,out] pSharedSecretLen Pointer to the length of the computed shared secret.
* @retval ::SW_OK Upon successful execution
*/
U16 A71_EcdhGetSharedSecret(U8 index, const U8 *pOtherPublicKey, U16 otherPublicKeyLen, U8 *pSharedSecret, U16 *pSharedSecretLen)
{
U16 rv;
U8 isOk = 0;
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
#ifndef A71_IGNORE_PARAM_CHECK
if ((pOtherPublicKey == NULL) || (pSharedSecret == NULL) || (pSharedSecretLen == NULL)) {
return ERR_API_ERROR;
}
#endif
pApdu->cla = A71CH_CLA;
pApdu->ins = A71CH_INS_SHARED_SECRET_ECC_KEYPAIR;
pApdu->p1 = index;
pApdu->p2 = 0x00;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, pOtherPublicKey, otherPublicKeyLen);
rv = (U16)scp_Transceive(NULL, pApdu, SCP_MODE);
if (rv == SMCOM_OK)
{
rv = smGetSw(pApdu, &isOk);
if (isOk)
{
rv = smApduGetResponseBody(pApdu, pSharedSecret, pSharedSecretLen);
}
}
FreeAPDUBuffer(pApdu);
return rv;
}