diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 9c986a5..726a6ab 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -352,7 +352,7 @@ typedef struct WOLFCOSE_KEY { * Used for key distribution (wrap, ECDH, direct). */ typedef struct WOLFCOSE_RECIPIENT { - int32_t algId; /**< Key distribution algorithm (-3..-31, -6) */ + int32_t algId; /**< Key distribution algorithm; direct mode requires explicit WOLFCOSE_ALG_DIRECT (-6) on encrypt (-3..-31, -6) */ WOLFCOSE_KEY* key; /**< Caller-owned key (KEK for wrap, recipient pubkey for ECDH) */ const uint8_t* kid; /**< Key ID for recipient lookup */ size_t kidLen; /**< Key ID length */ @@ -549,12 +549,16 @@ WOLFCOSE_API int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx); /** * \brief Peek at the major type of the next item without consuming it. - * \param ctx Decoder context. Must have idx < bufSz. - * \return Major type (0-7). + * \param ctx Decoder context. + * \return Major type (0-7), or 0xFF if ctx is NULL or the buffer is exhausted. */ static inline uint8_t wc_CBOR_PeekType(const WOLFCOSE_CBOR_CTX* ctx) { - return (uint8_t)(((uint32_t)ctx->cbuf[ctx->idx]) >> 5u); + uint8_t majorType = 0xFFu; + if ((ctx != NULL) && (ctx->cbuf != NULL) && (ctx->idx < ctx->bufSz)) { + majorType = (uint8_t)(((uint32_t)ctx->cbuf[ctx->idx]) >> 5u); + } + return majorType; } #endif /* WOLFCOSE_CBOR_DECODE */ @@ -743,7 +747,7 @@ WOLFCOSE_API int wc_CoseSign1_Verify(WOLFCOSE_KEY* key, * \param outLen Output: bytes written to out. * \return WOLFCOSE_SUCCESS or negative error code. */ -WOLFCOSE_API int wc_CoseEncrypt0_Encrypt(WOLFCOSE_KEY* key, int32_t alg, +WOLFCOSE_API int wc_CoseEncrypt0_Encrypt(const WOLFCOSE_KEY* key, int32_t alg, const uint8_t* iv, size_t ivLen, const uint8_t* payload, size_t payloadLen, uint8_t* detachedPayload, size_t detachedSz, size_t* detachedLen, @@ -773,7 +777,7 @@ WOLFCOSE_API int wc_CoseEncrypt0_Encrypt(WOLFCOSE_KEY* key, int32_t alg, * \return WOLFCOSE_SUCCESS or negative error code. * WOLFCOSE_E_DETACHED_PAYLOAD if ciphertext is nil and detachedCt is NULL. */ -WOLFCOSE_API int wc_CoseEncrypt0_Decrypt(WOLFCOSE_KEY* key, +WOLFCOSE_API int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz, const uint8_t* detachedCt, size_t detachedCtLen, const uint8_t* extAad, size_t extAadLen, diff --git a/src/wolfcose.c b/src/wolfcose.c index b06ec14..c4c4ca9 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -1334,6 +1334,30 @@ static int wolfCose_EncodeKeyOptionalFields(WOLFCOSE_CBOR_CTX* ctx, return ret; } +#ifdef WOLFCOSE_HAVE_RSAPSS +/* Finalize a directly-exported RSA bstr (n or d). The caller reserved a 3-byte + * header at hdrPos and wrote the payload at hdrPos+3; emit the preferred CBOR + * length form (0x58 for <256, shifting the payload left over the unused byte; + * 0x59 otherwise) and advance ctx->idx past the value. */ +static void wolfCose_FinalizeRsaBstr(WOLFCOSE_CBOR_CTX* ctx, size_t hdrPos, + size_t payloadLen) +{ + if (payloadLen < 256u) { + (void)XMEMMOVE(&ctx->buf[hdrPos + 2u], &ctx->buf[hdrPos + 3u], + payloadLen); + ctx->buf[hdrPos] = 0x58u; + ctx->buf[hdrPos + 1u] = (uint8_t)payloadLen; + ctx->idx = hdrPos + 2u + payloadLen; + } + else { + ctx->buf[hdrPos] = 0x59u; + ctx->buf[hdrPos + 1u] = (uint8_t)((uint32_t)payloadLen >> 8u); + ctx->buf[hdrPos + 2u] = (uint8_t)((uint32_t)payloadLen & 0xFFu); + ctx->idx = hdrPos + 3u + payloadLen; + } +} +#endif /* WOLFCOSE_HAVE_RSAPSS */ + #ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY /* RFC 8230: write one RSA component (label + bstr) from its mp_int. */ static int wolfCose_EncodeRsaMp(WOLFCOSE_CBOR_CTX* ctx, int64_t label, @@ -1544,12 +1568,7 @@ int wc_CoseKey_Encode(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } else { - ctx.buf[hdrPos] = 0x59u; - ctx.buf[hdrPos + 1u] = - (uint8_t)((uint32_t)nLen >> 8u); - ctx.buf[hdrPos + 2u] = - (uint8_t)((uint32_t)nLen & 0xFFu); - ctx.idx += (size_t)nLen; + wolfCose_FinalizeRsaBstr(&ctx, hdrPos, (size_t)nLen); } } } @@ -1633,12 +1652,8 @@ int wc_CoseKey_Encode(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, (void)XMEMSET(&ctx.buf[dOff], 0, pad); dSz = (word32)rsaEncSz; } - ctx.buf[hdrPos] = 0x59u; - ctx.buf[hdrPos + 1u] = - (uint8_t)((uint32_t)dSz >> 8u); - ctx.buf[hdrPos + 2u] = - (uint8_t)((uint32_t)dSz & 0xFFu); - ctx.idx = dOff + (size_t)dSz; + wolfCose_FinalizeRsaBstr(&ctx, hdrPos, + (size_t)dSz); } /* Zero scratch (e2/n2/p/q) */ (void)wolfCose_ForceZero(&ctx.buf[scrOff], @@ -5127,7 +5142,7 @@ static int wolfCose_BuildEncStructure0(const uint8_t* protectedHdr, } #if defined(WOLFCOSE_ENCRYPT0_ENCRYPT) -int wc_CoseEncrypt0_Encrypt(WOLFCOSE_KEY* key, int32_t alg, +int wc_CoseEncrypt0_Encrypt(const WOLFCOSE_KEY* key, int32_t alg, const uint8_t* iv, size_t ivLen, const uint8_t* payload, size_t payloadLen, uint8_t* detachedPayload, size_t detachedSz, size_t* detachedLen, @@ -5487,7 +5502,7 @@ int wc_CoseEncrypt0_Encrypt(WOLFCOSE_KEY* key, int32_t alg, #endif /* WOLFCOSE_ENCRYPT0_ENCRYPT */ #if defined(WOLFCOSE_ENCRYPT0_DECRYPT) -int wc_CoseEncrypt0_Decrypt(WOLFCOSE_KEY* key, +int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz, const uint8_t* detachedCt, size_t detachedCtLen, const uint8_t* extAad, size_t extAadLen, @@ -6789,8 +6804,12 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, encKey = recipients[0].key->key.symm.key; } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < recipientCount); i++) { - if ((recipients[i].algId != WOLFCOSE_ALG_UNSET) && - (recipients[i].algId != WOLFCOSE_ALG_DIRECT)) { + /* Direct mode requires an explicit WOLFCOSE_ALG_DIRECT so a + * zero-initialized (WOLFCOSE_ALG_UNSET) algId cannot silently + * select the direct-CEK construction. The decrypt path still + * accepts an empty (UNSET) recipient header, which is the on-wire + * representation of a direct recipient. */ + if (recipients[i].algId != WOLFCOSE_ALG_DIRECT) { ret = WOLFCOSE_E_COSE_BAD_ALG; } else if ((recipients[i].key != NULL) && @@ -7918,8 +7937,11 @@ int wc_CoseMac_Create(const WOLFCOSE_RECIPIENT* recipients, /* Encode each recipient */ for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < recipientCount); i++) { - /* Encode recipient protected header */ - if (recipients[i].algId != WOLFCOSE_ALG_UNSET) { + /* Encode recipient protected header. RFC 9053 Section 6.1: the direct + * key algorithm uses a zero-length protected header, so treat an + * explicit WOLFCOSE_ALG_DIRECT the same as the unset direct case. */ + if ((recipients[i].algId != WOLFCOSE_ALG_UNSET) && + (recipients[i].algId != WOLFCOSE_ALG_DIRECT)) { ret = wolfCose_EncodeProtectedHdr(recipients[i].algId, recipientProtectedBuf, sizeof(recipientProtectedBuf), &recipientProtectedLen); diff --git a/tests/test_cbor.c b/tests/test_cbor.c index 5fc867d..cc7013a 100644 --- a/tests/test_cbor.c +++ b/tests/test_cbor.c @@ -936,6 +936,67 @@ static void test_cbor_negative_map_keys(void) } /* ----- Entry point ----- */ +static void test_cbor_boundary_roundtrip(void) +{ + static const struct { uint64_t val; size_t len; } uvec[] = { + {0u, 1u}, {23u, 1u}, {24u, 2u}, {255u, 2u}, {256u, 3u}, + {65535u, 3u}, {65536u, 5u}, {4294967295ULL, 5u}, + {4294967296ULL, 9u} + }; + static const struct { int64_t val; size_t len; } ivec[] = { + {-1, 1u}, {-24, 1u}, {-25, 2u}, {-256, 2u}, {-257, 3u}, + {-65536, 3u}, {-65537, 5u} + }; + uint8_t buf[16]; + WOLFCOSE_CBOR_CTX ctx; + uint64_t uval; + int64_t ival; + int ret; + size_t i; + + printf(" [Boundary Round-trip]\n"); + + for (i = 0; i < (sizeof(uvec) / sizeof(uvec[0])); i++) { + ctx.buf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + ret = wc_CBOR_EncodeUint(&ctx, uvec[i].val); + TEST_ASSERT(ret == 0 && ctx.idx == uvec[i].len, + "uint boundary encode len"); + ctx.cbuf = buf; ctx.bufSz = ctx.idx; ctx.idx = 0; + ret = wc_CBOR_DecodeUint(&ctx, &uval); + TEST_ASSERT(ret == 0 && uval == uvec[i].val, + "uint boundary decode"); + } + + for (i = 0; i < (sizeof(ivec) / sizeof(ivec[0])); i++) { + ctx.buf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + ret = wc_CBOR_EncodeInt(&ctx, ivec[i].val); + TEST_ASSERT(ret == 0 && ctx.idx == ivec[i].len, + "negint boundary encode len"); + ctx.cbuf = buf; ctx.bufSz = ctx.idx; ctx.idx = 0; + ret = wc_CBOR_DecodeInt(&ctx, &ival); + TEST_ASSERT(ret == 0 && ival == ivec[i].val, + "negint boundary decode"); + } +} + +static void test_cbor_peektype_bounds(void) +{ + WOLFCOSE_CBOR_CTX ctx; + uint8_t buf[1] = {0x40}; + + printf(" [PeekType bounds]\n"); + + ctx.cbuf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + TEST_ASSERT(wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_BSTR, + "peektype valid"); + + ctx.idx = sizeof(buf); + TEST_ASSERT(wc_CBOR_PeekType(&ctx) == 0xFFu, + "peektype exhausted sentinel"); + + TEST_ASSERT(wc_CBOR_PeekType(NULL) == 0xFFu, "peektype null sentinel"); +} + int test_cbor(void) { g_failures = 0; @@ -943,6 +1004,8 @@ int test_cbor(void) test_cbor_encode_vectors(); test_cbor_decode_vectors(); test_cbor_roundtrip(); + test_cbor_boundary_roundtrip(); + test_cbor_peektype_bounds(); test_cbor_nested(); test_cbor_skip(); test_cbor_skip_depth(); diff --git a/tests/test_cose.c b/tests/test_cose.c index 34c3d8e..40788de 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -1889,6 +1889,58 @@ static void test_cose_key_rsa_scratch_scrubbed(void) (void)wc_FreeRsaKey(&rsaKey); (void)wc_FreeRng(&rng); } + +static void test_cose_key_rsa_small_modulus_roundtrip(void) +{ + WOLFCOSE_KEY key, key2; + RsaKey rsaKey, rsaKey2; + int ret; + uint8_t cbuf[1024]; + size_t cLen = 0; + uint8_t nBytes[200]; + const uint8_t eBytes[3] = { 0x01, 0x00, 0x01 }; + size_t i; + + TEST_LOG(" [Key RSA sub-256 modulus roundtrip]\n"); + + nBytes[0] = 0xC0; + for (i = 1; i < sizeof(nBytes); i++) { + nBytes[i] = (uint8_t)(i & 0xFF); + } + nBytes[sizeof(nBytes) - 1u] |= 0x01u; + + ret = wc_InitRsaKey(&rsaKey, NULL); + TEST_ASSERT(ret == 0, "rsa small init"); + if (ret == 0) { + ret = wc_RsaPublicKeyDecodeRaw(nBytes, (word32)sizeof(nBytes), + eBytes, (word32)sizeof(eBytes), &rsaKey); + TEST_ASSERT(ret == 0, "rsa small import raw"); + } + if (ret == 0) { + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetRsa(&key, &rsaKey); + TEST_ASSERT(ret == 0, "rsa small set"); + } + if (ret == 0) { + ret = wc_CoseKey_Encode(&key, cbuf, sizeof(cbuf), &cLen); + TEST_ASSERT(ret == 0 && cLen > 0, "rsa small encode"); + } + if (ret == 0) { + (void)wc_InitRsaKey(&rsaKey2, NULL); + (void)wc_CoseKey_Init(&key2); + key2.key.rsa = &rsaKey2; + ret = wc_CoseKey_Decode(&key2, cbuf, cLen); + TEST_ASSERT(ret == 0 && key2.kty == WOLFCOSE_KTY_RSA, + "rsa small modulus decode"); + wc_CoseKey_Free(&key); + (void)wc_FreeRsaKey(&rsaKey2); + } + else { + wc_CoseKey_Free(&key); + } + + (void)wc_FreeRsaKey(&rsaKey); +} #endif /* WOLFCOSE_HAVE_RSAPSS && WOLFSSL_KEY_GEN */ /* ----- COSE_Key ML-DSA encode/decode round-trip ----- */ @@ -3928,6 +3980,75 @@ static void test_cose_mac0_aes_cbc_mac(void) } } +/** + * Multi-block AES-CBC-MAC must chain: flipping an early/middle byte of a + * payload spanning several AES blocks (length unchanged) must change the tag. + * Without IV chaining the MAC would depend only on the final block, so an + * early-byte tamper would verify successfully. + */ +static void test_cose_mac0_aes_cbc_mac_chaining(void) +{ + WOLFCOSE_KEY key128; + uint8_t keyData128[16] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 + }; + uint8_t payload[64]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[512]; + uint8_t tampered[512]; + size_t outLen = 0; + size_t payloadOff; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0; + WOLFCOSE_HDR hdr; + int ret; + size_t i; + + TEST_LOG(" [Mac0 AES-CBC-MAC chaining]\n"); + + for (i = 0; i < sizeof(payload); i++) { + payload[i] = (uint8_t)(i & 0xFF); + } + + (void)wc_CoseKey_Init(&key128); + (void)wc_CoseKey_SetSymmetric(&key128, keyData128, sizeof(keyData128)); + + ret = wc_CoseMac0_Create(&key128, WOLFCOSE_ALG_AES_MAC_128_128, + NULL, 0, + payload, sizeof(payload), + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0 && outLen > 0, "mac0 aes chaining create"); + + ret = wc_CoseMac0_Verify(&key128, out, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == 0 && decPayload != NULL, "mac0 aes chaining verify"); + + payloadOff = (size_t)(decPayload - out); + + /* Flip the first payload byte (earliest block) - length unchanged. */ + memcpy(tampered, out, outLen); + tampered[payloadOff] ^= 0xFF; + ret = wc_CoseMac0_Verify(&key128, tampered, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, "mac0 aes early-byte tamper fails"); + + /* Flip a middle payload byte - length unchanged. */ + memcpy(tampered, out, outLen); + tampered[payloadOff + (sizeof(payload) / 2u)] ^= 0xFF; + ret = wc_CoseMac0_Verify(&key128, tampered, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, "mac0 aes mid-byte tamper fails"); +} + /** * Test AES-CBC-MAC with external AAD */ @@ -4058,6 +4179,166 @@ static void test_cose_mac0_aes_cbc_mac_detached(void) } #endif /* WOLFCOSE_HAVE_AESMAC */ +static int mac0_tag_len(const uint8_t* msg, size_t msgLen, size_t* tagLen) +{ + WOLFCOSE_CBOR_CTX ctx; + const uint8_t* p; + size_t n; + uint64_t t; + int ret; + + ctx.cbuf = msg; + ctx.bufSz = msgLen; + ctx.idx = 0; + ret = wc_CBOR_DecodeTag(&ctx, &t); + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &n); + } + if (ret == 0) { + ret = wc_CBOR_DecodeBstr(&ctx, &p, &n); + } + if (ret == 0) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_DecodeBstr(&ctx, &p, &n); + } + if (ret == 0) { + ret = wc_CBOR_DecodeBstr(&ctx, &p, tagLen); + } + return ret; +} + +/** + * Pin the encoded tag length for every MAC algorithm so a tag-size constant + * mutation that stays in range can no longer round-trip undetected. + */ +static void test_cose_mac0_tag_sizes(void) +{ + WOLFCOSE_KEY key; + uint8_t kd[64]; + const uint8_t payload[] = "tag size pin"; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[512]; + size_t outLen; + size_t tagLen; + int ret; + size_t i; + + TEST_LOG(" [Mac0 tag sizes]\n"); + + for (i = 0; i < sizeof(kd); i++) { + kd[i] = (uint8_t)(i + 1u); + } + +#ifdef WOLFCOSE_HAVE_HMAC +#ifdef WOLFCOSE_HAVE_HMAC256 + outLen = 0; + tagLen = 0; + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, kd, 32u); + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "hmac256 tag create"); + ret = mac0_tag_len(out, outLen, &tagLen); + TEST_ASSERT(ret == 0 && tagLen == 32u, "hmac256 tag len 32"); +#endif +#ifdef WOLFCOSE_HAVE_HMAC384 + outLen = 0; + tagLen = 0; + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, kd, 48u); + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_384_384, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "hmac384 tag create"); + ret = mac0_tag_len(out, outLen, &tagLen); + TEST_ASSERT(ret == 0 && tagLen == 48u, "hmac384 tag len 48"); +#endif +#ifdef WOLFCOSE_HAVE_HMAC512 + outLen = 0; + tagLen = 0; + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, kd, 64u); + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_512_512, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "hmac512 tag create"); + ret = mac0_tag_len(out, outLen, &tagLen); + TEST_ASSERT(ret == 0 && tagLen == 64u, "hmac512 tag len 64"); +#endif +#endif /* WOLFCOSE_HAVE_HMAC */ +#ifdef WOLFCOSE_HAVE_AESMAC + outLen = 0; + tagLen = 0; + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, kd, 16u); + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_AES_MAC_128_64, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "aes-mac 64 tag create"); + ret = mac0_tag_len(out, outLen, &tagLen); + TEST_ASSERT(ret == 0 && tagLen == 8u, "aes-mac tag len 8"); + + outLen = 0; + tagLen = 0; + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_AES_MAC_128_128, NULL, 0, + payload, sizeof(payload) - 1, NULL, 0, NULL, 0, + scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "aes-mac 128 tag create"); + ret = mac0_tag_len(out, outLen, &tagLen); + TEST_ASSERT(ret == 0 && tagLen == 16u, "aes-mac tag len 16"); +#endif /* WOLFCOSE_HAVE_AESMAC */ +} + +#ifdef WOLFCOSE_HAVE_HMAC256 +static void test_cose_mac0_large_payload(void) +{ + WOLFCOSE_KEY key; + uint8_t keyData[32]; + uint8_t payload[4096]; + uint8_t scratch[4096 + 256]; + uint8_t out[4096 + 512]; + size_t outLen = 0; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0; + WOLFCOSE_HDR hdr; + int ret; + size_t i; + + TEST_LOG(" [Mac0 large payload roundtrip]\n"); + + memset(keyData, 0xAB, sizeof(keyData)); + for (i = 0; i < sizeof(payload); i++) { + payload[i] = (uint8_t)(i & 0xFF); + } + + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0, + payload, sizeof(payload), + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0 && outLen > 0, "mac0 large payload create"); + + if (ret == 0) { + ret = wc_CoseMac0_Verify(&key, out, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == 0, "mac0 large payload verify"); + TEST_ASSERT(decPayloadLen == sizeof(payload), + "mac0 large payload length"); + TEST_ASSERT(memcmp(decPayload, payload, decPayloadLen) == 0, + "mac0 large payload match"); + } +} +#endif /* WOLFCOSE_HAVE_HMAC256 */ + /* ----- COSE_Sign Multi-Signer Tests (RFC 9052 Section 4.1) ----- */ #if defined(WOLFCOSE_SIGN) && defined(WOLFCOSE_HAVE_ES256) static void test_cose_sign_multi_signer(void) @@ -4866,12 +5147,12 @@ static void test_cose_encrypt_multi_recipient(void) TEST_ASSERT(ret == 0, "encrypt key2 set"); /* Setup recipients */ - recipients[0].algId = 0; /* Direct key */ + recipients[0].algId = WOLFCOSE_ALG_DIRECT; /* Direct key */ recipients[0].key = &key1; recipients[0].kid = kid1; recipients[0].kidLen = sizeof(kid1) - 1; - recipients[1].algId = 0; /* Direct key */ + recipients[1].algId = WOLFCOSE_ALG_DIRECT; /* Direct key */ recipients[1].key = &key2; recipients[1].kid = kid2; recipients[1].kidLen = sizeof(kid2) - 1; @@ -4982,7 +5263,7 @@ static void test_cose_encrypt_with_aad(void) ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); TEST_ASSERT(ret == 0, "encrypt aad key set"); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -5061,7 +5342,7 @@ static void test_cose_encrypt_a256gcm(void) ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); TEST_ASSERT(ret == 0, "encrypt a256 key set"); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -5121,7 +5402,7 @@ static void test_cose_encrypt_direct_key_alg_pin_roundtrip(void) TEST_ASSERT(ret == 0, "direct alg pin key set"); key.alg = WOLFCOSE_ALG_A128GCM; - recipient.algId = WOLFCOSE_ALG_UNSET; + recipient.algId = WOLFCOSE_ALG_DIRECT; recipient.key = &key; recipient.kid = NULL; recipient.kidLen = 0; @@ -5153,6 +5434,47 @@ static void test_cose_encrypt_direct_key_alg_pin_roundtrip(void) wc_CoseKey_Free(&key); } +static void test_cose_encrypt_unset_alg_rejected(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + int ret; + uint8_t out[256]; + size_t outLen = 0; + uint8_t scratch[256]; + const uint8_t payload[] = "unset alg reject"; + const uint8_t iv[12] = {0}; + const uint8_t keyData[16] = { + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F + }; + + TEST_LOG(" [Encrypt UNSET alg rejected]\n"); + + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + + /* A zero-initialized algId must not silently select direct mode. */ + recipient.algId = WOLFCOSE_ALG_UNSET; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wc_CoseEncrypt_Encrypt(&recipient, 1, + WOLFCOSE_ALG_A128GCM, + iv, sizeof(iv), + payload, sizeof(payload) - 1, + NULL, 0, + NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen, + NULL); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "encrypt UNSET recipient algId rejected"); + + wc_CoseKey_Free(&key); +} + static void test_cose_encrypt_direct_alg_id_key_alg_roundtrip(void) { WOLFCOSE_KEY key; @@ -6395,7 +6717,7 @@ static void test_cose_encrypt_direct_wrong_key_type(void) (void)wc_CoseKey_SetEcc(&eccKey, WOLFCOSE_CRV_P256, &key); /* Try direct encryption (algId=0) with ECC key - should fail */ - recipient.algId = 0; /* Direct key mode */ + recipient.algId = WOLFCOSE_ALG_DIRECT; /* Direct key mode */ recipient.key = &eccKey; recipient.kid = NULL; recipient.kidLen = 0; @@ -6546,6 +6868,87 @@ static void test_cose_mac_multi_recipient(void) wc_CoseKey_Free(&key2); } +static void test_cose_mac_multi_recipient_direct_empty_protected(void) +{ + WOLFCOSE_KEY key1, key2; + WOLFCOSE_RECIPIENT recipients[2]; + WOLFCOSE_CBOR_CTX ctx; + int ret; + uint8_t out[512]; + size_t outLen = 0; + uint8_t scratch[256]; + const uint8_t payload[] = "Direct MAC empty protected"; + const uint8_t keyData[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + const uint8_t* prot; + size_t protLen; + size_t arrCount; + size_t recipCount; + uint64_t tag; + size_t i; + + TEST_LOG(" [Mac Multi-Recipient direct empty protected]\n"); + + (void)wc_CoseKey_Init(&key1); + (void)wc_CoseKey_SetSymmetric(&key1, keyData, sizeof(keyData)); + (void)wc_CoseKey_Init(&key2); + (void)wc_CoseKey_SetSymmetric(&key2, keyData, sizeof(keyData)); + + recipients[0].algId = WOLFCOSE_ALG_DIRECT; + recipients[0].key = &key1; + recipients[0].kid = NULL; + recipients[0].kidLen = 0; + recipients[1].algId = WOLFCOSE_ALG_DIRECT; + recipients[1].key = &key2; + recipients[1].kid = NULL; + recipients[1].kidLen = 0; + + ret = wc_CoseMac_Create(recipients, 2, WOLFCOSE_ALG_HMAC_256_256, + payload, sizeof(payload) - 1, + NULL, 0, + NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "mac direct create"); + + ctx.cbuf = out; + ctx.bufSz = outLen; + ctx.idx = 0; + ret = wc_CBOR_DecodeTag(&ctx, &tag); + TEST_ASSERT(ret == 0 && tag == WOLFCOSE_TAG_MAC, "mac tag"); + ret = wc_CBOR_DecodeArrayStart(&ctx, &arrCount); + TEST_ASSERT(ret == 0 && arrCount == 5, "mac outer array"); + ret = wc_CBOR_DecodeBstr(&ctx, &prot, &protLen); + TEST_ASSERT(ret == 0, "mac body protected"); + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == 0, "mac body unprotected"); + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == 0, "mac payload"); + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == 0, "mac tag bstr"); + ret = wc_CBOR_DecodeArrayStart(&ctx, &recipCount); + TEST_ASSERT(ret == 0 && recipCount == 2, "mac recipients array"); + + for (i = 0; i < recipCount; i++) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &arrCount); + TEST_ASSERT(ret == 0 && arrCount == 3, "recipient array"); + ret = wc_CBOR_DecodeBstr(&ctx, &prot, &protLen); + TEST_ASSERT(ret == 0, "recipient protected decode"); + TEST_ASSERT(protLen == 0, "direct recipient protected empty"); + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == 0, "recipient unprotected"); + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == 0, "recipient cek"); + } + + wc_CoseKey_Free(&key1); + wc_CoseKey_Free(&key2); +} + static void test_cose_mac_multi_recipient_key_alg_mismatch(void) { WOLFCOSE_KEY key1, key2; @@ -10649,6 +11052,7 @@ static void test_cose_encrypt0_empty_payload_roundtrip(void) WOLFCOSE_HDR hdr; const uint8_t keyBytes[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; const uint8_t iv[12] = {0}; + static const uint8_t empty[1] = {0}; TEST_LOG(" [Encrypt0: empty payload roundtrip]\n"); @@ -10657,16 +11061,17 @@ static void test_cose_encrypt0_empty_payload_roundtrip(void) (void)wc_CoseKey_SetSymmetric(&encKey, keyBytes, sizeof(keyBytes)); (void)wc_CoseKey_SetSymmetric(&decKey, keyBytes, sizeof(keyBytes)); - /* Encrypt empty payload */ + /* Encrypt a genuine zero-length plaintext via a non-NULL buffer so the + * ciphertext is just the AEAD tag and decrypt recovers 0 bytes. */ ret = wc_CoseEncrypt0_Encrypt(&encKey, WOLFCOSE_ALG_A128GCM, iv, sizeof(iv), - NULL, 0, + empty, 0, NULL, 0, NULL, NULL, 0, scratch, sizeof(scratch), out, sizeof(out), &outLen); - /* NULL payload + 0 length is accepted as zero-length plaintext path - * only when isDetached is unset; the encrypt API allows this. */ + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "Encrypt0 empty payload encrypt"); + if (ret == WOLFCOSE_SUCCESS) { memset(&hdr, 0, sizeof(hdr)); ret = wc_CoseEncrypt0_Decrypt(&decKey, out, outLen, @@ -10678,10 +11083,56 @@ static void test_cose_encrypt0_empty_payload_roundtrip(void) "Encrypt0_Decrypt empty payload"); TEST_ASSERT(ptLen == 0u, "Encrypt0 empty payload length"); } - else { - /* API rejects NULL payload outright; that is acceptable too. */ - TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, - "Encrypt0 empty payload reject"); + + wc_CoseKey_Free(&encKey); + wc_CoseKey_Free(&decKey); +} + +static void test_cose_encrypt0_large_payload(void) +{ + WOLFCOSE_KEY encKey, decKey; + int ret; + uint8_t payload[4096]; + uint8_t pt[4096]; + uint8_t scratch[4096 + 256]; + uint8_t out[4096 + 512]; + size_t outLen = 0; + size_t ptLen = 0; + WOLFCOSE_HDR hdr; + const uint8_t keyBytes[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; + const uint8_t iv[12] = {0}; + size_t i; + + TEST_LOG(" [Encrypt0: large payload roundtrip]\n"); + + for (i = 0; i < sizeof(payload); i++) { + payload[i] = (uint8_t)(i & 0xFF); + } + + (void)wc_CoseKey_Init(&encKey); + (void)wc_CoseKey_Init(&decKey); + (void)wc_CoseKey_SetSymmetric(&encKey, keyBytes, sizeof(keyBytes)); + (void)wc_CoseKey_SetSymmetric(&decKey, keyBytes, sizeof(keyBytes)); + + ret = wc_CoseEncrypt0_Encrypt(&encKey, WOLFCOSE_ALG_A128GCM, + iv, sizeof(iv), + payload, sizeof(payload), + NULL, 0, NULL, + NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "Encrypt0 large payload encrypt"); + + if (ret == WOLFCOSE_SUCCESS) { + memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt0_Decrypt(&decKey, out, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, pt, sizeof(pt), &ptLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "Encrypt0 large payload decrypt"); + TEST_ASSERT(ptLen == sizeof(payload), "Encrypt0 large payload length"); + TEST_ASSERT(memcmp(pt, payload, ptLen) == 0, + "Encrypt0 large payload match"); } wc_CoseKey_Free(&encKey); @@ -11026,7 +11477,7 @@ static void test_cose_encrypt_multi_ccm_roundtrip(void) ret = wc_CoseKey_SetSymmetric(&key, keyBytes, sizeof(keyBytes)); TEST_ASSERT(ret == 0, "ccm multi key set"); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -11079,7 +11530,7 @@ static void test_cose_encrypt_multi_chacha_roundtrip(void) ret = wc_CoseKey_SetSymmetric(&key, keyBytes, sizeof(keyBytes)); TEST_ASSERT(ret == 0, "chacha multi key set"); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -15889,7 +16340,7 @@ static void test_encrypt_multi_detached_rejected(void) (void)wc_CoseKey_Init(&key1); (void)wc_CoseKey_SetSymmetric(&key1, keyData, sizeof(keyData)); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key1; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -15924,7 +16375,7 @@ static void test_encrypt_multi_wrong_iv_len(void) (void)wc_CoseKey_Init(&key1); (void)wc_CoseKey_SetSymmetric(&key1, keyData, sizeof(keyData)); - recipients[0].algId = 0; + recipients[0].algId = WOLFCOSE_ALG_DIRECT; recipients[0].key = &key1; recipients[0].kid = NULL; recipients[0].kidLen = 0; @@ -16234,6 +16685,7 @@ int test_cose(void) test_cose_key_rsa(); test_cose_key_rsa_scratch_scrubbed(); test_cose_key_rsa_public_decode(); + test_cose_key_rsa_small_modulus_roundtrip(); #endif #ifdef WOLFCOSE_HAVE_MLDSA test_cose_key_mldsa("ML-DSA-44", WOLFCOSE_ALG_ML_DSA_44, WC_ML_DSA_44); @@ -16323,9 +16775,15 @@ int test_cose(void) #endif #endif /* WOLFCOSE_HAVE_HMAC256 */ + test_cose_mac0_tag_sizes(); +#ifdef WOLFCOSE_HAVE_HMAC256 + test_cose_mac0_large_payload(); +#endif + /* AES-CBC-MAC tests */ #ifdef WOLFCOSE_HAVE_AESMAC test_cose_mac0_aes_cbc_mac(); + test_cose_mac0_aes_cbc_mac_chaining(); test_cose_mac0_aes_cbc_mac_with_aad(); test_cose_mac0_aes_cbc_mac_detached(); #endif @@ -16362,6 +16820,7 @@ int test_cose(void) test_cose_encrypt_with_aad(); test_cose_encrypt_a256gcm(); test_cose_encrypt_direct_key_alg_pin_roundtrip(); + test_cose_encrypt_unset_alg_rejected(); test_cose_encrypt_direct_alg_id_key_alg_roundtrip(); test_cose_encrypt_direct_multi_key_alg_mismatch(); #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_HKDF) @@ -16389,6 +16848,7 @@ int test_cose(void) /* Multi-recipient MAC tests */ #if defined(WOLFCOSE_MAC) && defined(WOLFCOSE_HAVE_HMAC256) test_cose_mac_multi_recipient(); + test_cose_mac_multi_recipient_direct_empty_protected(); test_cose_mac_multi_recipient_key_alg_mismatch(); test_cose_mac_with_aad(); test_cose_mac_detached(); @@ -16519,6 +16979,7 @@ int test_cose(void) #if defined(WOLFCOSE_HAVE_AESGCM) && defined(WOLFCOSE_ENCRYPT0_ENCRYPT) && defined(WOLFCOSE_ENCRYPT0_DECRYPT) test_cose_encrypt0_nonce_length(); test_cose_encrypt0_empty_payload_roundtrip(); + test_cose_encrypt0_large_payload(); #endif #if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFCOSE_SIGN) && \ defined(WOLFSSL_KEY_GEN)