7#include "CW_SecureChannel.h"
10#include "CW_Platform.h"
19#define SC_AES_BLOCK_BYTES (16U)
20#define SC_AES_KEY_BYTES (32U)
21#define SC_EC_COORD_BYTES (32U)
22#define SC_EC_PUBKEY_BYTES (64U)
25#define SC_CERT_MARKER_BYTES (1U)
26#define SC_CERT_NONCE_BYTES (8U)
27#define SC_CERT_KEY65_BYTES (65U)
28#define SC_CERT_TOTAL_BYTES (74U)
29#define SC_CERT_KEY_OFFSET (SC_CERT_MARKER_BYTES + SC_CERT_NONCE_BYTES)
32#define SC_SW_BYTES (2U)
33#define SC_SELECT_RESP_BYTES (26U)
34#define SC_GET_CERT_RESP_BYTES (148U)
35#define SC_OPEN_SC_RESP_BYTES (34U)
36#define SC_MUTUAL_AUTH_RESP_BYTES (66U)
37#define SC_SALT_BYTES (32U)
38#define SC_IV_BYTES (16U)
39#define SC_SHA512_OUT_BYTES (64U)
42#define SC_APDU_HEADER_LEN (4U)
43#define SC_APDU_LC_OFFSET (4U)
44#define SC_APDU_MAC_OFFSET (5U)
45#define SC_APDU_MAC_BYTES (16U)
48#define SC_CARD_RESP_PAYLOAD_BYTES (4U)
49#define SC_CARD_RESP_TOTAL_BYTES (SC_CARD_RESP_PAYLOAD_BYTES + SC_SW_BYTES)
52#define MOCK_MAX_SCRIPTS (8U)
53#define MOCK_MAX_RESP_BYTES (255U)
54#define MOCK_UART_BAUD_RATE (115200UL)
61 0x60U, 0xfeU, 0xd4U, 0xbaU, 0x25U, 0x5aU, 0x9dU, 0x31U,
62 0xc9U, 0x61U, 0xebU, 0x74U, 0xc6U, 0x35U, 0x6dU, 0x68U,
63 0xc0U, 0x49U, 0xb8U, 0x92U, 0x3bU, 0x61U, 0xfaU, 0x6cU,
64 0xe6U, 0x69U, 0x62U, 0x2eU, 0x60U, 0xf2U, 0x9fU, 0xb6U,
66 0x79U, 0x03U, 0xfeU, 0x10U, 0x08U, 0xb8U, 0xbcU, 0x99U,
67 0xa4U, 0x1aU, 0xe9U, 0xe9U, 0x56U, 0x28U, 0xbcU, 0x64U,
68 0xf2U, 0xf1U, 0xb2U, 0x0cU, 0x2dU, 0x7eU, 0x9fU, 0x51U,
69 0x77U, 0xa3U, 0xc2U, 0x94U, 0xd4U, 0x46U, 0x22U, 0x99U
75 0x60U, 0x3dU, 0xebU, 0x10U, 0x15U, 0xcaU, 0x71U, 0xbeU,
76 0x2bU, 0x73U, 0xaeU, 0xf0U, 0x85U, 0x7dU, 0x77U, 0x81U,
77 0x1fU, 0x35U, 0x2cU, 0x07U, 0x3bU, 0x61U, 0x08U, 0xd7U,
78 0x2dU, 0x98U, 0x10U, 0xa3U, 0x09U, 0x14U, 0xdfU, 0xf4U
81 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U,
82 0x08U, 0x09U, 0x0aU, 0x0bU, 0x0cU, 0x0dU, 0x0eU, 0x0fU,
83 0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U,
84 0x18U, 0x19U, 0x1aU, 0x1bU, 0x1cU, 0x1dU, 0x1eU, 0x1fU
89 0xdeU, 0xadU, 0xbeU, 0xefU, 0x90U, 0x00U
101 void print(
const __FlashStringHelper*)
override {
107 void print(uint8_t,
int = DEC)
override {
109 void print(uint16_t,
int = DEC)
override {
111 void print(uint32_t,
int = DEC)
override {
113 void print(
int,
int = DEC)
override {
117 void println(
const __FlashStringHelper*)
override {
169 void addScript(
const uint8_t* data, uint8_t len,
bool succeed =
true) {
190 bool sendAPDU(
const uint8_t* apdu, uint8_t apduLen,
191 uint8_t* response, uint8_t& responseLen)
override {
200 (void)memcpy(response, e.
data,
static_cast<size_t>(e.
len));
241 bool sendAPDU(
const uint8_t* apdu, uint8_t apduLen,
242 uint8_t* response, uint8_t& responseLen)
override {
245 if ((apdu != NULL) &&
256 uint16_t cipherRespLen =
crypto->aesCbcEncrypt(
261 static_cast<uint8_t
>(
sizeof(
session->aesKey)),
269 macInput[0U] = totalDataLen;
271 static_cast<size_t>(cipherRespLen));
277 uint16_t macOutLen =
crypto->aesCbcEncrypt(
282 static_cast<uint8_t
>(
sizeof(
session->macKey)),
287 const uint8_t* responseMac =
291 uint8_t totalRespLen =
static_cast<uint8_t
>(
296 static_cast<size_t>(cipherRespLen));
299 responseLen = totalRespLen;
324TEST_CASE(
"checkStatusWord: SW 0x9000 returns true",
"[secure_channel]")
326 static const uint8_t resp[] = {
327 0x01U, 0x02U, 0x03U, 0x04U, 0x90U, 0x00U
331 bool ok = channel.checkStatusWord(resp,
static_cast<uint8_t
>(
sizeof(resp)),
334 TEST_ASSERT_TRUE(ok);
337TEST_CASE(
"checkStatusWord: SW mismatch returns false",
"[secure_channel]")
339 static const uint8_t resp[] = {
340 0x01U, 0x02U, 0x6aU, 0x82U
344 bool ok = channel.checkStatusWord(resp,
static_cast<uint8_t
>(
sizeof(resp)),
347 TEST_ASSERT_FALSE(ok);
350TEST_CASE(
"checkStatusWord: response shorter than 2 bytes returns false",
"[secure_channel]")
352 static const uint8_t resp[] = { 0x90U };
355 bool ok = channel.checkStatusWord(resp,
static_cast<uint8_t
>(
sizeof(resp)),
358 TEST_ASSERT_FALSE(ok);
365TEST_CASE(
"extractCardEphemeralKey: extracts 64-byte key from synthetic certificate",
388 bool ok = channel.extractCardEphemeralKey(cert, pubKey, fullKey65);
390 TEST_ASSERT_TRUE(ok);
393 TEST_ASSERT_EQUAL_HEX8(0x04U, fullKey65[0U]);
400TEST_CASE(
"extractCardEphemeralKey: null cert pointer returns false",
"[secure_channel]")
405 bool ok = channel.extractCardEphemeralKey(NULL, pubKey, NULL);
407 TEST_ASSERT_FALSE(ok);
414TEST_CASE(
"selectApdu: succeeds when transport returns SW 0x9000",
"[secure_channel]")
424 bool ok = channel.selectApdu();
426 TEST_ASSERT_TRUE(ok);
429TEST_CASE(
"selectApdu: fails when transport returns error SW",
"[secure_channel]")
439 bool ok = channel.selectApdu();
441 TEST_ASSERT_FALSE(ok);
444TEST_CASE(
"getCardCertificate: extracts 146 certificate bytes from mock response",
452 resp[i] =
static_cast<uint8_t
>(i + 1U);
461 uint8_t certLen = 0U;
464 bool ok = channel.getCardCertificate(certBuf, certLen);
466 TEST_ASSERT_TRUE(ok);
468 TEST_ASSERT_EQUAL_HEX8_ARRAY(resp, certBuf, certLen);
471TEST_CASE(
"openSecureChannel: extracts 32-byte salt from mock response",
"[secure_channel]")
475 for (uint8_t i = 0U; i < static_cast<uint8_t>(
SC_SALT_BYTES); i++) {
476 resp[i] =
static_cast<uint8_t
>(0xA0U + i);
487 CW_Curve curve = CW_CURVE_SECP256R1;
490 bool ok = channel.openSecureChannel(salt, clientPub, clientPriv, curve);
492 TEST_ASSERT_TRUE(ok);
501 TEST_ASSERT_FALSE(CW_Utils::secure_compare(clientPriv, zeroPriv,
SC_EC_COORD_BYTES));
504TEST_CASE(
"mutuallyAuthenticate: sets session IV to first 16 bytes of mock response",
512 for (uint8_t i = 0U; i < static_cast<uint8_t>(
SC_IV_BYTES); i++) {
513 resp[i] =
static_cast<uint8_t
>(0xC0U + i);
529 CW_Curve curve = CW_CURVE_SECP256R1;
531 bool keyOk =
s_crypto.makeKey(clientPub, clientPriv, curve);
532 TEST_ASSERT_TRUE(keyOk);
534 CW_SecureSession session;
536 bool ok = channel.mutuallyAuthenticate(session, salt, clientPub, clientPriv,
537 curve, cardEphemeralPub);
539 TEST_ASSERT_TRUE(ok);
542 TEST_ASSERT_EQUAL_HEX8_ARRAY(resp, session.iv,
SC_IV_BYTES);
546 TEST_ASSERT_FALSE(CW_Utils::secure_compare(session.aesKey, zeroKey,
SC_AES_KEY_BYTES));
547 TEST_ASSERT_FALSE(CW_Utils::secure_compare(session.macKey, zeroKey,
SC_AES_KEY_BYTES));
562TEST_CASE(
"key derivation: ECDH + SHA-512 split yields distinct Kenc and Kmac",
565 CW_Curve curve = CW_CURVE_SECP256R1;
573 bool okA =
s_crypto.makeKey(pubA, privA, curve);
574 bool okB =
s_crypto.makeKey(pubB, privB, curve);
575 TEST_ASSERT_TRUE(okA);
576 TEST_ASSERT_TRUE(okB);
581 bool ecdhAB =
s_crypto.ecdh(pubB, privA, secretAB, curve);
582 bool ecdhBA =
s_crypto.ecdh(pubA, privB, secretBA, curve);
583 TEST_ASSERT_TRUE(ecdhAB);
584 TEST_ASSERT_TRUE(ecdhBA);
589 0x11U, 0x22U, 0x33U, 0x44U, 0x55U, 0x66U, 0x77U, 0x88U,
590 0x99U, 0xaaU, 0xbbU, 0xccU, 0xddU, 0xeeU, 0xffU, 0x00U,
591 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U, 0x08U,
592 0x09U, 0x0aU, 0x0bU, 0x0cU, 0x0dU, 0x0eU, 0x0fU, 0x10U
597 (void)memcpy(concat +
SC_EC_COORD_BYTES, CW_PAIRING_DATA, CW_PAIRING_DATA_BYTES);
601 s_crypto.sha512(concat,
sizeof(concat), sha512Out);
604 uint8_t* derivedKenc = sha512Out;
607 TEST_ASSERT_FALSE(CW_Utils::secure_compare(derivedKenc, derivedKmac,
SC_AES_KEY_BYTES));
617 static_cast<uint8_t
>(
sizeof(mutualResp)));
619 CW_SecureSession session;
624 bool ok = channel.mutuallyAuthenticate(session, SALT, pubA, privA, curve, pubB);
625 TEST_ASSERT_TRUE(ok);
627 TEST_ASSERT_EQUAL_HEX8_ARRAY(derivedKenc, session.aesKey,
SC_AES_KEY_BYTES);
628 TEST_ASSERT_EQUAL_HEX8_ARRAY(derivedKmac, session.macKey,
SC_AES_KEY_BYTES);
639TEST_CASE(
"aesCbcEncrypt/aesCbcDecrypt: round-trip via reflective mock returns card payload",
643 CW_SecureSession session;
655 static const uint8_t plaintext[] = { 0xAAU, 0xBBU, 0xCCU };
656 static const uint8_t apduHeader[] = { 0x80U, 0x01U, 0x00U, 0x00U };
658 uint8_t decryptedOut[32U] = { 0U };
659 uint16_t decryptedLen = 0U;
661 bool ok = channel.aesCbcEncrypt(
664 static_cast<uint16_t
>(
sizeof(apduHeader)),
666 static_cast<uint16_t
>(
sizeof(plaintext)),
670 TEST_ASSERT_TRUE(ok);
CW_CryptoProvider backed by mbedTLS and the ESP32 hardware TRNG.
void print(uint8_t, int=DEC) override
void println(const char *) override
void print(char) override
void println(uint8_t, int=DEC) override
bool begin(unsigned long=MOCK_UART_BAUD_RATE) override
void println(uint32_t, int=DEC) override
void println(char) override
void println(const __FlashStringHelper *) override
void println(int, int=DEC) override
void print(uint32_t, int=DEC) override
void println(uint16_t, int=DEC) override
void print(const __FlashStringHelper *) override
void print(const char *) override
void print(uint16_t, int=DEC) override
void print(int, int=DEC) override
CW_CryptoProvider * crypto
bool inListPassiveTarget() override
void resetReader() override
bool sendAPDU(const uint8_t *apdu, uint8_t apduLen, uint8_t *response, uint8_t &responseLen) override
const CW_SecureSession * session
bool printFirmwareVersion() override
~ReflectiveMockNfcTransport() override
void resetReader() override
bool sendAPDU(const uint8_t *apdu, uint8_t apduLen, uint8_t *response, uint8_t &responseLen) override
bool inListPassiveTarget() override
bool printFirmwareVersion() override
MockScriptEntry scripts[MOCK_MAX_SCRIPTS]
~ScriptedMockNfcTransport() override
void addScript(const uint8_t *data, uint8_t len, bool succeed=true)
CW_CryptoProvider implementation for ESP32 using mbedTLS and the hardware TRNG.
uint8_t data[MOCK_MAX_RESP_BYTES]
static const uint8_t K_CARD_RESP_PLAINTEXT[SC_CARD_RESP_TOTAL_BYTES]
#define SC_CERT_KEY_OFFSET
#define SC_CERT_KEY65_BYTES
static ReflectiveMockNfcTransport s_reflectiveTransport
#define SC_AES_BLOCK_BYTES
#define SC_GET_CERT_RESP_BYTES
#define MOCK_UART_BAUD_RATE
#define SC_CERT_MARKER_BYTES
#define SC_MUTUAL_AUTH_RESP_BYTES
static ESP32CryptoProvider s_crypto
#define SC_SELECT_RESP_BYTES
static const uint8_t K_TEST_KENC[SC_AES_KEY_BYTES]
static const uint8_t K_TEST_KMAC[SC_AES_KEY_BYTES]
#define SC_OPEN_SC_RESP_BYTES
static ScriptedMockNfcTransport s_scriptedTransport
static const uint8_t K_CARD_EPHEMERAL_PUB[SC_EC_PUBKEY_BYTES]
#define SC_EC_COORD_BYTES
#define SC_CERT_TOTAL_BYTES
#define SC_CARD_RESP_PAYLOAD_BYTES
#define SC_EC_PUBKEY_BYTES
#define SC_APDU_MAC_OFFSET
#define SC_CARD_RESP_TOTAL_BYTES
#define SC_SHA512_OUT_BYTES
TEST_CASE("checkStatusWord: SW 0x9000 returns true", "[secure_channel]")
#define MOCK_MAX_RESP_BYTES
#define SC_CERT_NONCE_BYTES
#define SC_APDU_MAC_BYTES
static MockLogger s_logger
static MockPlatform s_platform