KW45Z41083 CPU, MCUXpresso SDK v11.10.0_3148.
I ran across an issue with GCM decryption in my software and I'm able to replicate it with a few changes to the ele_symmetric.c example from the SDK. First, here's the code that reproduces this 100%:
#define ELE_MAX_SUBSYSTEM_WAIT (0xFFFFFFFFu)
#define ELE_SUBSYSTEM (kType_SSS_Ele200)
#define KEY_ID (0u)
/* Variables used by example */
static sscp_context_t sscpContext = {0};
static sss_sscp_session_t sssSession = {0};
static sss_sscp_key_store_t keyStore = {0};
// Taken mostly from ele_symmetric.c example.
status_t AESGCMBug()
{
status_t status = kStatus_Fail;
//-------------------------------------------------------------------------------
// Initialization code from main() ...
//-------------------------------------------------------------------------------
do
{
status = ELEMU_mu_wait_for_ready(ELEMUA, ELE_MAX_SUBSYSTEM_WAIT);
if (status != kStatus_Success)
break;
/****************** Start ***********************/
status = sscp_mu_init(&sscpContext, (ELEMU_Type*) (uintptr_t) ELEMUA);
if (status != kStatus_SSCP_Success)
break;
/* open session to specific security subsystem */
status = sss_sscp_open_session(&sssSession, 0u, ELE_SUBSYSTEM, &sscpContext);
if (status != kStatus_SSS_Success)
return status;
/* Init keystore */
status = sss_sscp_key_store_init(&keyStore, &sssSession);
if (status != kStatus_SSS_Success)
break;
} while (0);
//-------------------------------------------------------------------------------
// Encryption code from test_aes_gcm() ...
//-------------------------------------------------------------------------------
/* Test data for AES GCM*/
/*! @brief 16 bytes key for GCM method. */
SDK_ALIGN(static const uint8_t s_GcmKey[16], 8u) =
{ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08};
/*! @brief Plaintext for GCM method. */
SDK_ALIGN(static const uint8_t s_GcmPlain[], 8u) =
{ 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26,
0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31,
0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49,
0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39};
/*! @brief Expected ciphertext from GCM method. */
static const uint8_t s_GcmCipherExpected[] =
{ 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21,
0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54,
0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac,
0x97, 0x3d, 0x58, 0xe0, 0x91 };
/*! @brief Encrypted ciphertext from GCM method goes here. */
static uint8_t s_OutBuffer[sizeof(s_GcmCipherExpected)]; // was "s_GcmCipher" in example
/*! @brief Initialization vector for GCM method. */
SDK_ALIGN(static const uint8_t s_GcmIv[12], 8u) =
{ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce,
0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88};
/*! @brief Additional authenticated data for GCM method. */
SDK_ALIGN(static const uint8_t s_GcmAad[], 8u) =
{ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed,
0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2};
/*! @brief Expected tag from GCM method. */
static const uint8_t s_GcmTagExpected[] =
{ 0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb, 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47 };
/*! @brief Encrypted tag from GCM method goes here. */
static uint8_t s_GcmTag[sizeof(s_GcmTagExpected)];
sss_sscp_object_t sssKey = { 0 };
sss_sscp_aead_t ctx_encrypt = { 0 };
sss_sscp_aead_t ctx_decrypt = { 0 };
size_t taglen = sizeof(s_GcmTag);
do
{
/* Init key object */
status = sss_sscp_key_object_init(&sssKey, &keyStore);
if (status != kStatus_SSS_Success)
{
break;
}
/* Allocate keystore handle */
status = sss_sscp_key_object_allocate_handle(&sssKey, KEY_ID, /* key id */
kSSS_KeyPart_Default, kSSS_CipherType_AES, 16u, kSSS_KeyProp_CryptoAlgo_AEAD);
if (status != kStatus_SSS_Success)
{
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
/* Set key into key object*/
status = sss_sscp_key_store_set_key(&keyStore, &sssKey, s_GcmKey, sizeof(s_GcmKey), (sizeof(s_GcmKey) * 8U),
kSSS_KeyPart_Default);
if (status != kStatus_SSS_Success)
{
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
/* Init aead context */
status = sss_sscp_aead_context_init(&ctx_encrypt, &sssSession, &sssKey, kAlgorithm_SSS_AES_GCM, kMode_SSS_Encrypt);
if (status != kStatus_SSS_Success)
{
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
/* RUN AES-GCM Encryption */
status = sss_sscp_aead_one_go(&ctx_encrypt, s_GcmPlain, s_OutBuffer, sizeof(s_GcmPlain), (uint8_t *)(uint32_t)s_GcmIv, sizeof(s_GcmIv),
s_GcmAad, sizeof(s_GcmAad), s_GcmTag, &taglen);
if (status != kStatus_SSS_Success)
{
(void) sss_sscp_aead_context_free(&ctx_encrypt);
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
else
{
if (memcmp(s_OutBuffer, s_GcmCipherExpected, sizeof(s_OutBuffer)) == 0
&& memcmp(s_GcmTag, s_GcmTagExpected, sizeof(s_GcmTag)) == 0)
{
//PRINTF("AES-GCM crypto encryption success and output matches expected result.\r\n\r\n");
status = kStatus_Success;
}
else
{
status = kStatus_Fail;
break;
}
}
//-------------------------------------------------------------------------------
// My additions to test GCM decryption ...
//-------------------------------------------------------------------------------
/* Init a 2nd context for decryptions */
status = sss_sscp_aead_context_init(&ctx_decrypt, &sssSession, &sssKey, kAlgorithm_SSS_AES_GCM, kMode_SSS_Decrypt);
if (status != kStatus_SSS_Success)
{
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
// Flip one bit in byte 0 of the tag data to force a decryption failure.
static const uint8_t s_GcmTagInvalid[] =
{ 0xdb, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb, 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47 };
/* RUN AES-GCM Decryption */
status = sss_sscp_aead_one_go(&ctx_decrypt, s_GcmCipherExpected, s_OutBuffer, sizeof(s_GcmCipherExpected), (uint8_t *)(uint32_t)s_GcmIv, sizeof(s_GcmIv),
s_GcmAad, sizeof(s_GcmAad), (uint8_t *)(uint32_t)s_GcmTagInvalid, &taglen);
if (status == kStatus_SSS_Success) // previous call should have reported an error
{
(void) sss_sscp_aead_context_free(&ctx_decrypt);
(void) sss_sscp_key_object_free(&sssKey, kSSS_keyObjFree_KeysStoreDefragment);
break;
}
//-------------------------------------------------------------------------------
// Remainder of test_aes_gcm() ...
//-------------------------------------------------------------------------------
// This hangs at "while ((mu->TSR & mask) == 0u)" in ELEMU_mu_hal_send_data() with call stack:
// ELEMU_mu_hal_send_data() at fsl_elemu.c:48 0x44bfc
// ELEMU_mu_send_message() at fsl_elemu.c:117 0x3254a
// MU_SendMsg() at fsl_sscp_mu.c:54 0x30fc8
// sscp_mu_invoke_command() at fsl_sscp_mu.c:223 0x31400
// sss_sscp_aead_context_free() at fsl_sss_sscp.c:455 0x31964
// AESGCMBug() at HSM.c:267 0x3f75e
status = sss_sscp_aead_context_free(&ctx_decrypt);
// The rest of test_aes_gcm() is deleted / not needed for this example.
} while (0);
return status;
}
95% of this is directly from the SDK example ele_symmetric.c. The example doesn't show how to decrypt and verify the tag is correct, so I added that in the "My additions to test GCM decryption ..." block.
Observations:
1) If I set the data in s_GcmTagInvalid[] to match exactly the data in s_GcmTagExpected[], the decrypt and tag checking inside EdgeLock work perfectly as expected. The sss_sscp_aead_one_go() call for decryption succeeds and all is well.
2) If I run the code as shown above (I purposeIy corrupted the tag data) I get some strange results... sss_sscp_aead_one_go() eventually calls sscp_mu_invoke_command() to send a kSSCP_CMD_SSS_AeadOneGo (commandId == 0x29) command to EdgeLock. A response is received but the check at "if (muReplyHeader->command != commandId)" fails due to muReplyHeader->command being 0x1D. I don't see an enum value for 0x1D so no idea what that means.
After this, the MU interface to EdgeLock is completely hosed and the next attempt at an MU command (sss_sscp_aead_context_free() in my example) causes a watchdog.
3) If I drop the "const" from "static const uint8_t s_GcmTagInvalid[]", the call to sss_sscp_aead_one_go() does what it's supposed to do... it sends the kSSCP_CMD_SSS_AeadOneGo command, receives a proper response with muReplyHeader->command == kSSCP_CMD_SSS_AeadOneGo, and returns an kStatus_SSS_Fail as expected to report the failed decryption / tag check.
HOWEVER, I noticed that after this, my s_GcmTagInvalid[] array is now all zeros! This should not happen. The Tag buffer is an input in GCM decryption, it should not get corrupted as a result of a failed decryption!
I assume the issue/lockup reported in #2 is caused by EdgeLock trying to write back to my Tag array but the Tag array is read-only in flash.
A workaround could be to copy the Tag data for decryptions to a separate RAM buffer and pass that to sss_sscp_aead_one_go() but I hate that solution because it wastes CPU cycles unnecessarily and forces me to allocate a separate buffer for decryption Tag data. I'd have to dynamically allocate that buffer based on application needs for each decryption, or artificially impose some limit on max Tag size. I really don't want to do that.