diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index a6b9b94594..0229bf985b 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -288,6 +288,12 @@ jobs: "configure": ["--disable-sni", "--disable-ecc", "--disable-tls13", "--disable-secure-renegotiation-info"]}, {"name": "default", "minutes": 1.6}, + {"name": "aeskeywrap-padding", "minutes": 1.6, + "comment": "RFC 5649 AES key wrap with padding; exercises the =padding sub-option (not pulled in by --enable-all).", + "configure": ["--enable-aeskeywrap=padding"]}, + {"name": "aeskeywrap-padding-cryptocb", "minutes": 1.6, + "comment": "Key wrap (RFC 3394 + RFC 5649) over WOLF_CRYPTO_CB device offload; runs test_wc_CryptoCb_AesKeyWrap.", + "configure": ["--enable-cryptocb", "--enable-aeskeywrap=padding"]}, {"name": "no-client-no-client-auth", "minutes": 1.6, "configure": ["CPPFLAGS=-DNO_WOLFSSL_CLIENT -DWOLFSSL_NO_CLIENT_AUTH"]}, {"name": "ascon-experimental", "minutes": 1.6, diff --git a/configure.ac b/configure.ac index bdf5d3df29..e91ac714cf 100644 --- a/configure.ac +++ b/configure.ac @@ -6287,12 +6287,31 @@ AC_ARG_ENABLE([entropy-memuse], ) # AES key wrap +# Accepts a comma-separated value list; "padding" adds RFC 5649 key wrap with +# padding on top of the base RFC 3394 key wrap. AC_ARG_ENABLE([aeskeywrap], - [AS_HELP_STRING([--enable-aeskeywrap],[Enable AES key wrap support (default: disabled)])], + [AS_HELP_STRING([--enable-aeskeywrap],[Enable AES key wrap support, optionally with RFC 5649 padding via "=padding" (default: disabled)])], [ ENABLED_AESKEYWRAP=$enableval ], [ ENABLED_AESKEYWRAP=no ] ) +ENABLED_AESKEYWRAP_PADDING=no +for v in `echo $ENABLED_AESKEYWRAP | tr "," " "` +do + case $v in + yes | no) + ;; + padding) + # padding (RFC 5649) builds on the base key wrap support + ENABLED_AESKEYWRAP_PADDING=yes + ENABLED_AESKEYWRAP=yes + ;; + *) + AC_MSG_ERROR([Invalid aeskeywrap option. Valid are: yes, no, padding. Seen: $ENABLED_AESKEYWRAP.]) + ;; + esac +done + # FIPS feature and macro setup AS_CASE([$FIPS_VERSION], @@ -10881,6 +10900,11 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE_AES_KEYWRAP -DWOLFSSL_AES_DIRECT" fi +if test "$ENABLED_AESKEYWRAP_PADDING" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_AES_KEYWRAP_PADDING" +fi + # Old name support for backwards compatibility AC_ARG_ENABLE([oldnames], @@ -12789,6 +12813,7 @@ echo " * AES-SIV: $ENABLED_AESSIV" echo " * AES-EAX: $ENABLED_AESEAX" echo " * AES Bitspliced: $ENABLED_AESBS" echo " * AES Key Wrap: $ENABLED_AESKEYWRAP" +echo " * AES Key Wrap Padding: $ENABLED_AESKEYWRAP_PADDING" echo " * ARIA: $ENABLED_ARIA" echo " * ASCON: $ENABLED_ASCON" echo " * DES3: $ENABLED_DES3" diff --git a/tests/api.c b/tests/api.c index d34a5afef8..338bd6f0ff 100644 --- a/tests/api.c +++ b/tests/api.c @@ -34634,6 +34634,9 @@ TEST_CASE testCases[] = { #if defined(WOLFSSL_AES_SIV) && defined(WOLFSSL_AES_128) TEST_AES_SIV_DECLS, #endif /* WOLFSSL_AES_SIV && WOLFSSL_AES_128 */ +#if defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_KEYWRAP_PADDING) + TEST_AES_KEYWRAP_DECLS, +#endif /* HAVE_AES_KEYWRAP && WOLFSSL_AES_KEYWRAP_PADDING */ TEST_GMAC_DECLS, /* Ascon */ TEST_ASCON_DECLS, diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 72221cd04a..4995b8dc4e 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -7781,6 +7781,288 @@ int test_wc_AesEaxStream(void) * (!HAVE_FIPS || FIPS_VERSION_GE(5, 3)) && !HAVE_SELFTEST */ +/*----------------------------------------------------------------------------* + | AES Key Wrap with Padding (RFC 5649) Test + *----------------------------------------------------------------------------*/ + +#if defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_KEYWRAP_PADDING) + +/* + * Testing wc_AesKeyWrap_Pad and wc_AesKeyUnWrap_Pad (RFC 5649). + * + * KATs: the 192-bit cases are the two published RFC 5649 section 6 examples; + * the 128-bit and 256-bit cases are known-answer vectors generated with + * OpenSSL's aes-{128,256}-wrap-pad over the same two plaintexts (cross-checked + * against the RFC vectors). The 20-octet input exercises the RFC 3394 loop + * path; the 7-octet input exercises the single-block ECB path. + */ +int test_wc_AesKeyWrap_Pad(void) +{ + EXPECT_DECLS; + + /* shared plaintexts */ + const byte data20[] = { /* 20 octets -> 32-byte wrap (3394 loop path) */ + 0xc3, 0x7b, 0x7e, 0x64, 0x92, 0x58, 0x43, 0x40, + 0xbe, 0xd1, 0x22, 0x07, 0x80, 0x89, 0x41, 0x15, + 0x50, 0x68, 0xf7, 0x38 + }; + const byte data7[] = { /* 7 octets -> 16-byte wrap (single-block ECB) */ + 0x46, 0x6f, 0x72, 0x50, 0x61, 0x73, 0x69 + }; + +#ifdef WOLFSSL_AES_128 + const byte kek128[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + const byte verify128_20[] = { + 0xe1, 0xf7, 0x17, 0x6e, 0xcb, 0xd7, 0x5d, 0x42, + 0xe8, 0x2b, 0x24, 0xf9, 0x89, 0xa2, 0x81, 0x6c, + 0x20, 0x9c, 0x6e, 0xf2, 0xd1, 0xaa, 0x94, 0xd2, + 0xa3, 0xe6, 0x02, 0x84, 0x90, 0x0d, 0x03, 0xa2 + }; + const byte verify128_7[] = { + 0xbe, 0x80, 0x53, 0x5e, 0x12, 0xe9, 0x39, 0x4c, + 0x8f, 0x8d, 0xf2, 0x6b, 0xd9, 0x52, 0x8a, 0x35 + }; +#endif +#ifdef WOLFSSL_AES_192 + const byte kek192[] = { + 0x58, 0x40, 0xdf, 0x6e, 0x29, 0xb0, 0x2a, 0xf1, + 0xab, 0x49, 0x3b, 0x70, 0x5b, 0xf1, 0x6e, 0xa1, + 0xae, 0x83, 0x38, 0xf4, 0xdc, 0xc1, 0x76, 0xa8 + }; + const byte verify192_20[] = { + 0x13, 0x8b, 0xde, 0xaa, 0x9b, 0x8f, 0xa7, 0xfc, + 0x61, 0xf9, 0x77, 0x42, 0xe7, 0x22, 0x48, 0xee, + 0x5a, 0xe6, 0xae, 0x53, 0x60, 0xd1, 0xae, 0x6a, + 0x5f, 0x54, 0xf3, 0x73, 0xfa, 0x54, 0x3b, 0x6a + }; + const byte verify192_7[] = { + 0xaf, 0xbe, 0xb0, 0xf0, 0x7d, 0xfb, 0xf5, 0x41, + 0x92, 0x00, 0xf2, 0xcc, 0xb5, 0x0b, 0xb2, 0x4f + }; +#endif +#ifdef WOLFSSL_AES_256 + const byte kek256[] = { + 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 byte verify256_20[] = { + 0x29, 0xb7, 0xfa, 0x19, 0x1c, 0x21, 0x65, 0x68, + 0x43, 0x74, 0xee, 0xe9, 0xf7, 0x45, 0x95, 0xe2, + 0xa4, 0x2b, 0xac, 0xe7, 0x5c, 0x42, 0x5b, 0x30, + 0x53, 0xef, 0xa2, 0x6f, 0xfe, 0x1b, 0xb3, 0x2f + }; + const byte verify256_7[] = { + 0x44, 0x3b, 0x17, 0x83, 0x7b, 0xb3, 0x93, 0x48, + 0x61, 0x0d, 0x19, 0x20, 0x2d, 0xf8, 0xa1, 0xf9 + }; +#endif + + struct kwpKat { + const byte* kek; + word32 kekSz; + const byte* in; + word32 inSz; + const byte* exp; + word32 expSz; + }; + const struct kwpKat kats[] = { + #ifdef WOLFSSL_AES_128 + {kek128, (word32)sizeof(kek128), data20, (word32)sizeof(data20), + verify128_20, (word32)sizeof(verify128_20)}, + {kek128, (word32)sizeof(kek128), data7, (word32)sizeof(data7), + verify128_7, (word32)sizeof(verify128_7)}, + #endif + #ifdef WOLFSSL_AES_192 + {kek192, (word32)sizeof(kek192), data20, (word32)sizeof(data20), + verify192_20, (word32)sizeof(verify192_20)}, + {kek192, (word32)sizeof(kek192), data7, (word32)sizeof(data7), + verify192_7, (word32)sizeof(verify192_7)}, + #endif + #ifdef WOLFSSL_AES_256 + {kek256, (word32)sizeof(kek256), data20, (word32)sizeof(data20), + verify256_20, (word32)sizeof(verify256_20)}, + {kek256, (word32)sizeof(kek256), data7, (word32)sizeof(data7), + verify256_7, (word32)sizeof(verify256_7)}, + #endif + }; + word32 katCnt = (word32)(sizeof(kats) / sizeof(kats[0])); + word32 i; + byte out[40]; + byte plain[32]; + + /* --- Known-answer + roundtrip for every KEK size and both paths --- */ + for (i = 0; i < katCnt; i++) { + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + + /* wrap matches the published / cross-checked vector */ + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[i].kek, kats[i].kekSz, + kats[i].in, kats[i].inSz, + out, sizeof(out), NULL), + (int)kats[i].expSz); + ExpectBufEQ(out, kats[i].exp, kats[i].expSz); + + /* unwrap recovers the original plaintext and length */ + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[i].kek, kats[i].kekSz, + out, kats[i].expSz, + plain, sizeof(plain), NULL), + (int)kats[i].inSz); + ExpectBufEQ(plain, kats[i].in, kats[i].inSz); + } + + /* --- Negative: a corrupted wrap must fail the integrity check --- */ + XMEMSET(out, 0, sizeof(out)); + ExpectIntGE(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, + kats[0].in, kats[0].inSz, + out, sizeof(out), NULL), 0); + out[0] ^= 0x01; + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, + out, kats[0].expSz, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)); + + /* --- Bad args: wrap --- */ + ExpectIntEQ(wc_AesKeyWrap_Pad(NULL, kats[0].kekSz, data7, sizeof(data7), + out, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, NULL, + sizeof(data7), out, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data7, 0, + out, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data7, + sizeof(data7), NULL, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* 7 octets need 16 output bytes; an 8-byte buffer is too small */ + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data7, + sizeof(data7), out, 8, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* an inSz that would overflow ceil(inSz/8)+1 blocks is rejected up front + * (returns before reading 'in', so the short buffer is never accessed) */ + ExpectIntEQ(wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data7, + 0xFFFFFFF1U, out, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* --- Bad args: unwrap --- */ + ExpectIntEQ(wc_AesKeyUnWrap_Pad(NULL, kats[0].kekSz, out, 16, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, NULL, 16, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, out, 16, + NULL, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* input must be at least two 64-bit blocks and a multiple of 8 */ + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, out, 8, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, out, 17, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* output buffer smaller than the recovered padded plaintext */ + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, out, 16, + plain, 4, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* --- IV override: a custom 4-byte AIV high-half constant --- */ + { + const byte altIv[4] = { 0x12, 0x34, 0x56, 0x78 }; + int sz; + + /* single-block (ECB) path */ + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + sz = wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data7, sizeof(data7), + out, sizeof(out), altIv); + ExpectIntGE(sz, 0); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, + out, (word32)sz, plain, sizeof(plain), altIv), + (int)sizeof(data7)); + ExpectBufEQ(plain, data7, sizeof(data7)); + /* same blob must be rejected when unwrapped under the default AIV */ + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, out, + (word32)sz, plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)); + + /* multi-block (RFC 3394 loop) path */ + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + sz = wc_AesKeyWrap_Pad(kats[0].kek, kats[0].kekSz, data20, + sizeof(data20), out, sizeof(out), altIv); + ExpectIntGE(sz, 0); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, + out, (word32)sz, plain, sizeof(plain), altIv), + (int)sizeof(data20)); + ExpectBufEQ(plain, data20, sizeof(data20)); + } + + /* --- Invalid KEK size exercises the SetKey-failure path in wrappers --- */ + { + byte badKey[32]; + XMEMSET(badKey, 0x0c, sizeof(badKey)); + /* 20 octets is not a valid AES key size */ + ExpectIntEQ(wc_AesKeyWrap_Pad(badKey, 20, data7, sizeof(data7), + out, sizeof(out), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(badKey, 20, out, 16, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + + /* --- Forge ciphertexts to drive unwrap integrity checks 2 and 3 --- * + * A single decrypted block is AIV(8)|P(8); by ECB-encrypting a chosen * + * block under the KEK we control exactly what unwrap will recover. */ + { + Aes faes; + byte forgeBlk[WC_AES_BLOCK_SIZE]; + byte forged[WC_AES_BLOCK_SIZE]; + + XMEMSET(&faes, 0, sizeof(faes)); + + /* check 2: correct AIV constant but MLI = 0 (fails 8*(n-1) < MLI) */ + XMEMSET(forgeBlk, 0, sizeof(forgeBlk)); + forgeBlk[0] = 0xa6; forgeBlk[1] = 0x59; forgeBlk[2] = 0x59; + forgeBlk[3] = 0xa6; + /* MLI bytes [4..7] remain 0 */ + forgeBlk[8] = 0x11; /* arbitrary recovered plaintext octet */ + ExpectIntEQ(wc_AesInit(&faes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&faes, kats[0].kek, kats[0].kekSz, NULL, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesEncryptDirect(&faes, forged, forgeBlk), 0); + wc_AesFree(&faes); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, forged, 16, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)); + + /* check 3: correct AIV constant, MLI = 1, but a nonzero pad octet */ + XMEMSET(forgeBlk, 0, sizeof(forgeBlk)); + forgeBlk[0] = 0xa6; forgeBlk[1] = 0x59; forgeBlk[2] = 0x59; + forgeBlk[3] = 0xa6; + forgeBlk[7] = 0x01; /* MLI = 1 */ + forgeBlk[8] = 0x41; /* the one real octet... */ + forgeBlk[9] = 0xff; /* ...followed by a nonzero pad octet -> rejected */ + ExpectIntEQ(wc_AesInit(&faes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&faes, kats[0].kek, kats[0].kekSz, NULL, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesEncryptDirect(&faes, forged, forgeBlk), 0); + wc_AesFree(&faes); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(kats[0].kek, kats[0].kekSz, forged, 16, + plain, sizeof(plain), NULL), + WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)); + } + + return EXPECT_RESULT(); +} /* END test_wc_AesKeyWrap_Pad */ + +#endif /* HAVE_AES_KEYWRAP && WOLFSSL_AES_KEYWRAP_PADDING */ + /*----------------------------------------------------------------------------* | AES-SIV Test *----------------------------------------------------------------------------*/ @@ -7958,6 +8240,184 @@ int test_wc_AesSivEncryptDecrypt(void) #endif /* WOLFSSL_AES_SIV && WOLFSSL_AES_128 */ +/*----------------------------------------------------------------------------* + | CryptoCB AES Key Wrap Test + *----------------------------------------------------------------------------*/ + +#if defined(WOLF_CRYPTO_CB) && defined(HAVE_AES_KEYWRAP) && \ + !defined(NO_AES) && defined(WOLFSSL_AES_128) + +#include + +/* Test CryptoCB device IDs (must be unique across test_aes.c); 7/8/9 are used + * by the SetKey/AES-GCM/TLS13 tests above. */ +#define TEST_CRYPTOCB_KEYWRAP_DEVID 10 + +static int cbKwWrapCalled = 0; +static int cbKwUnwrapCalled = 0; + +/* Mock device: runs the key (un)wrap in software using the op's own Aes (which + * already holds the KEK) by temporarily clearing its devId so the _ex call does + * not re-enter the callback, then reports the produced length back through + * aeskeywrap.outResSz. This is the canonical wolfCrypt crypto-callback pattern + * (mirrors myCryptoDevCb in wolfcrypt/test/test.c). */ +static int test_CryptoCb_KeyWrap_Cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + Aes* aes; + int r; + int devIdSave; + const byte* in; + word32 inSz; + byte* out; + word32 outSz; + const byte* iv; + int pad; + (void)ctx; + + if (devId != TEST_CRYPTOCB_KEYWRAP_DEVID) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + if (info->algo_type != WC_ALGO_TYPE_CIPHER || + info->cipher.type != WC_CIPHER_AES_KEYWRAP) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + + aes = info->cipher.aeskeywrap.aes; + in = info->cipher.aeskeywrap.in; + inSz = info->cipher.aeskeywrap.inSz; + out = info->cipher.aeskeywrap.out; + outSz = info->cipher.aeskeywrap.outSz; + iv = info->cipher.aeskeywrap.iv; + pad = info->cipher.aeskeywrap.pad; + + devIdSave = aes->devId; + aes->devId = INVALID_DEVID; /* force software, no callback re-entry */ + + if (info->cipher.enc) { + cbKwWrapCalled++; + #ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (pad) + r = wc_AesKeyWrap_Pad_ex(aes, in, inSz, out, outSz, iv); + else + #endif + r = wc_AesKeyWrap_ex(aes, in, inSz, out, outSz, iv); + } + else { + cbKwUnwrapCalled++; + #ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (pad) + r = wc_AesKeyUnWrap_Pad_ex(aes, in, inSz, out, outSz, iv); + else + #endif + r = wc_AesKeyUnWrap_ex(aes, in, inSz, out, outSz, iv); + } + (void)pad; + + aes->devId = devIdSave; + + if (r < 0) + return r; + info->cipher.aeskeywrap.outResSz = (word32)r; + return 0; +} + +int test_wc_CryptoCb_AesKeyWrap(void) +{ + EXPECT_DECLS; + Aes aes; + int sz; + /* RFC 3394 section 4.1: wrap 128 bits with a 128-bit KEK */ + WOLFSSL_SMALL_STACK_STATIC const byte kek[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + WOLFSSL_SMALL_STACK_STATIC const byte data[] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + WOLFSSL_SMALL_STACK_STATIC const byte verify[] = { + 0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47, + 0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82, + 0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5 + }; + byte out[40]; + byte plain[32]; + + cbKwWrapCalled = 0; + cbKwUnwrapCalled = 0; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_KEYWRAP_DEVID, + test_CryptoCb_KeyWrap_Cb, NULL), 0); + + /* RFC 3394 wrap: encryption-keyed Aes carrying the devId dispatches to the + * device, which wraps in software and matches the KAT. */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, kek, (word32)sizeof(kek), NULL, + AES_ENCRYPTION), 0); + XMEMSET(out, 0, sizeof(out)); + sz = wc_AesKeyWrap_ex(&aes, data, (word32)sizeof(data), out, + (word32)sizeof(out), NULL); + ExpectIntEQ(sz, (int)sizeof(verify)); + ExpectBufEQ(out, verify, sizeof(verify)); + ExpectIntGE(cbKwWrapCalled, 1); + wc_AesFree(&aes); + + /* RFC 3394 unwrap: decryption-keyed Aes carrying the devId. */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, kek, (word32)sizeof(kek), NULL, + AES_DECRYPTION), 0); + XMEMSET(plain, 0, sizeof(plain)); + sz = wc_AesKeyUnWrap_ex(&aes, verify, (word32)sizeof(verify), plain, + (word32)sizeof(plain), NULL); + ExpectIntEQ(sz, (int)sizeof(data)); + ExpectBufEQ(plain, data, sizeof(data)); + ExpectIntGE(cbKwUnwrapCalled, 1); + wc_AesFree(&aes); + +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + /* RFC 5649 padded wrap/unwrap (7 octets -> single-block path) also + * dispatches to the device and round-trips. */ + { + WOLFSSL_SMALL_STACK_STATIC const byte pdata[] = { + 0x46, 0x6f, 0x72, 0x50, 0x61, 0x73, 0x69 + }; + cbKwWrapCalled = 0; + cbKwUnwrapCalled = 0; + + /* padded wrap (encryption key) */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, kek, (word32)sizeof(kek), NULL, + AES_ENCRYPTION), 0); + XMEMSET(out, 0, sizeof(out)); + sz = wc_AesKeyWrap_Pad_ex(&aes, pdata, (word32)sizeof(pdata), out, + (word32)sizeof(out), NULL); + ExpectIntGE(sz, 0); + ExpectIntGE(cbKwWrapCalled, 1); + wc_AesFree(&aes); + + /* padded unwrap (decryption key) */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_DEVID), 0); + ExpectIntEQ(wc_AesSetKey(&aes, kek, (word32)sizeof(kek), NULL, + AES_DECRYPTION), 0); + XMEMSET(plain, 0, sizeof(plain)); + sz = wc_AesKeyUnWrap_Pad_ex(&aes, out, (word32)sz, plain, + (word32)sizeof(plain), NULL); + ExpectIntEQ(sz, (int)sizeof(pdata)); + ExpectBufEQ(plain, pdata, sizeof(pdata)); + ExpectIntGE(cbKwUnwrapCalled, 1); + wc_AesFree(&aes); + } +#endif + + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_KEYWRAP_DEVID); + + return EXPECT_RESULT(); +} + +#endif /* WOLF_CRYPTO_CB && HAVE_AES_KEYWRAP && !NO_AES && WOLFSSL_AES_128 */ + /*----------------------------------------------------------------------------* | CryptoCB AES SetKey Test *----------------------------------------------------------------------------*/ diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index 73e4b715ac..d4acdf7351 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -79,6 +79,9 @@ int test_wc_AesEaxStream(void); #if defined(WOLFSSL_AES_SIV) && defined(WOLFSSL_AES_128) int test_wc_AesSivEncryptDecrypt(void); #endif +#if defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_KEYWRAP_PADDING) +int test_wc_AesKeyWrap_Pad(void); +#endif int test_wc_AesCbc_MonteCarlo(void); int test_wc_AesCtr_MonteCarlo(void); @@ -121,6 +124,15 @@ int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); #define TEST_CRYPTOCB_AES_SETKEY_DECL #endif +#if defined(WOLF_CRYPTO_CB) && defined(HAVE_AES_KEYWRAP) && \ + !defined(NO_AES) && defined(WOLFSSL_AES_128) +int test_wc_CryptoCb_AesKeyWrap(void); +#define TEST_CRYPTOCB_AES_KEYWRAP_DECL \ + , TEST_DECL_GROUP("aes", test_wc_CryptoCb_AesKeyWrap) +#else +#define TEST_CRYPTOCB_AES_KEYWRAP_DECL +#endif + #define TEST_AES_DECLS \ TEST_DECL_GROUP("aes", test_wc_AesSetKey), \ TEST_DECL_GROUP("aes", test_wc_AesSetIV), \ @@ -174,6 +186,7 @@ int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); TEST_DECL_GROUP("aes", test_wc_AesCfb_MonteCarlo), \ TEST_DECL_GROUP("aes", test_wc_AesOfb_MonteCarlo) \ TEST_CRYPTOCB_AES_SETKEY_DECL \ + TEST_CRYPTOCB_AES_KEYWRAP_DECL \ TEST_CRYPTOCB_TLS13_KEY_ZERO_DECL #if defined(WOLFSSL_AES_EAX) && defined(WOLFSSL_AES_256) && \ @@ -190,6 +203,11 @@ int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); TEST_DECL_GROUP("aes-siv", test_wc_AesSivEncryptDecrypt) #endif /* WOLFSSL_AES_SIV && WOLFSSL_AES_128 */ +#if defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_KEYWRAP_PADDING) +#define TEST_AES_KEYWRAP_DECLS \ + TEST_DECL_GROUP("aes-keywrap", test_wc_AesKeyWrap_Pad) +#endif /* HAVE_AES_KEYWRAP && WOLFSSL_AES_KEYWRAP_PADDING */ + #define TEST_GMAC_DECLS \ TEST_DECL_GROUP("gmac", test_wc_GmacSetKey), \ TEST_DECL_GROUP("gmac", test_wc_GmacUpdate) diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 8223bcc8fb..688340f455 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -55,6 +55,7 @@ block cipher mechanism that uses n-bit binary string parameter key with 128-bits * WOLFSSL_CMAC: Enable AES-CMAC (RFC 4493) default: off * HAVE_AESCCM: Enable AES-CCM mode default: off * HAVE_AES_KEYWRAP: Enable AES key wrap (RFC 3394) default: off + * WOLFSSL_AES_KEYWRAP_PADDING: AES key wrap padding (RFC 5649) default: off * WOLFSSL_AES_CBC_LENGTH_CHECKS: Validate CBC input length default: off * * AES-GCM: @@ -15218,8 +15219,10 @@ static WC_INLINE void DecrementKeyWrapCounter(byte* inOutCtr) } } -int wc_AesKeyWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, - word32 outSz, const byte* iv) +/* Core RFC 3394 wrapping loop: plaintext blocks already at out+8, initial A in + * aiv. Writes C[0]=A and wrapped R[i] in place. Validates structural inputs; + * the caller still owns output-buffer sizing (outSz not visible here). */ +static int AesKeyWrapRaw(Aes* aes, word32 inSz, byte* out, const byte* aiv) { word32 i; byte* r; @@ -15229,25 +15232,17 @@ int wc_AesKeyWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, byte t[KEYWRAP_BLOCK_SIZE]; byte tmp[WC_AES_BLOCK_SIZE]; - /* n must be at least 2 64-bit blocks, output size is (n + 1) 8 bytes (64-bit) */ - if (aes == NULL || in == NULL || inSz < 2*KEYWRAP_BLOCK_SIZE || - out == NULL || outSz < (inSz + KEYWRAP_BLOCK_SIZE)) - return BAD_FUNC_ARG; - - /* input must be multiple of 64-bits */ - if (inSz % KEYWRAP_BLOCK_SIZE != 0) + /* at least two 64-bit blocks, on a 64-bit boundary */ + if (aes == NULL || out == NULL || aiv == NULL || + inSz < 2 * KEYWRAP_BLOCK_SIZE || (inSz % KEYWRAP_BLOCK_SIZE) != 0) { return BAD_FUNC_ARG; + } - r = out + 8; - XMEMCPY(r, in, inSz); + r = out + KEYWRAP_BLOCK_SIZE; XMEMSET(t, 0, sizeof(t)); - /* user IV is optional */ - if (iv == NULL) { - XMEMSET(tmp, 0xA6, KEYWRAP_BLOCK_SIZE); - } else { - XMEMCPY(tmp, iv, KEYWRAP_BLOCK_SIZE); - } + /* A = initial value */ + XMEMCPY(tmp, aiv, KEYWRAP_BLOCK_SIZE); VECTOR_REGISTERS_PUSH; @@ -15281,6 +15276,53 @@ int wc_AesKeyWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, /* C[0] = A */ XMEMCPY(out, tmp, KEYWRAP_BLOCK_SIZE); + return 0; +} + +int wc_AesKeyWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv) +{ + int ret; + byte aiv[KEYWRAP_BLOCK_SIZE]; + + /* n must be at least two 64-bit blocks, on a 64-bit boundary; the (n + 1)- + * block output must fit in outSz. inSz is capped at INT_MAX - 8 so the + * returned length (inSz + 8) stays a representable non-negative int. */ + if (aes == NULL || in == NULL || out == NULL || + inSz < 2 * KEYWRAP_BLOCK_SIZE || (inSz % KEYWRAP_BLOCK_SIZE) != 0 || + inSz > 0x7FFFFFFFU - KEYWRAP_BLOCK_SIZE || + outSz < inSz + KEYWRAP_BLOCK_SIZE) + return BAD_FUNC_ARG; + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (aes->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_AesKeyWrap(aes, in, inSz, out, outSz, iv, 0); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + /* fall through to software when unavailable */ + } +#endif + + /* user IV is optional */ + if (iv == NULL) { + XMEMSET(aiv, 0xA6, KEYWRAP_BLOCK_SIZE); + } + else { + XMEMCPY(aiv, iv, KEYWRAP_BLOCK_SIZE); + } + + /* stage plaintext at out+8; XMEMMOVE so in-place wrap (in == out) is safe */ + XMEMMOVE(out + KEYWRAP_BLOCK_SIZE, in, inSz); + + ret = AesKeyWrapRaw(aes, inSz, out, aiv); + if (ret != 0) { + return ret; + } + return (int)(inSz + KEYWRAP_BLOCK_SIZE); } @@ -15320,8 +15362,11 @@ int wc_AesKeyWrap(const byte* key, word32 keySz, const byte* in, word32 inSz, return ret; } -int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, - word32 outSz, const byte* iv) +/* Core RFC 3394 unwrapping loop: decrypts (n+1) blocks in `in`, writes n + * recovered blocks to out and recovered A to aOut. No integrity check; caller + * verifies A. */ +static int AesKeyUnWrapRaw(Aes* aes, const byte* in, word32 inSz, byte* out, + byte* aOut) { byte* r; word32 i, n; @@ -15331,28 +15376,15 @@ int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, byte t[KEYWRAP_BLOCK_SIZE]; byte tmp[WC_AES_BLOCK_SIZE]; - const byte* expIv; - const byte defaultIV[] = { - 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 - }; - - if (aes == NULL || in == NULL || inSz < 3 * KEYWRAP_BLOCK_SIZE || - out == NULL || outSz < (inSz - KEYWRAP_BLOCK_SIZE)) - return BAD_FUNC_ARG; - - /* input must be multiple of 64-bits */ - if (inSz % KEYWRAP_BLOCK_SIZE != 0) + /* (n+1) blocks in, n >= 2 recovered blocks out, on a 64-bit boundary */ + if (aes == NULL || in == NULL || out == NULL || aOut == NULL || + inSz < 3 * KEYWRAP_BLOCK_SIZE || (inSz % KEYWRAP_BLOCK_SIZE) != 0) { return BAD_FUNC_ARG; + } - /* user IV optional */ - if (iv != NULL) - expIv = iv; - else - expIv = defaultIV; - - /* A = C[0], R[i] = C[i] */ + /* A = C[0], R[i] = C[i]; XMEMMOVE so in-place unwrap (in == out) is safe */ XMEMCPY(tmp, in, KEYWRAP_BLOCK_SIZE); - XMEMCPY(out, in + KEYWRAP_BLOCK_SIZE, inSz - KEYWRAP_BLOCK_SIZE); + XMEMMOVE(out, in + KEYWRAP_BLOCK_SIZE, inSz - KEYWRAP_BLOCK_SIZE); XMEMSET(t, 0, sizeof(t)); VECTOR_REGISTERS_PUSH; @@ -15387,9 +15419,61 @@ int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, if (ret != 0) return ret; + /* return recovered A */ + XMEMCPY(aOut, tmp, KEYWRAP_BLOCK_SIZE); + + return 0; +} + +int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv) +{ + int ret; + byte a[KEYWRAP_BLOCK_SIZE]; + + const byte* expIv; + const byte defaultIV[] = { + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 + }; + + /* ciphertext is (n + 1) >= 3 blocks on a 64-bit boundary; the n recovered + * blocks must fit in outSz. inSz is capped at INT_MAX so the returned + * length (inSz - 8) stays a representable non-negative int. */ + if (aes == NULL || in == NULL || out == NULL || + inSz < 3 * KEYWRAP_BLOCK_SIZE || (inSz % KEYWRAP_BLOCK_SIZE) != 0 || + inSz > 0x7FFFFFFFU || outSz < inSz - KEYWRAP_BLOCK_SIZE) + return BAD_FUNC_ARG; + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (aes->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_AesKeyUnWrap(aes, in, inSz, out, outSz, iv, 0); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + /* fall through to software when unavailable */ + } +#endif + + /* user IV optional */ + if (iv != NULL) { + expIv = iv; + } + else { + expIv = defaultIV; + } + + ret = AesKeyUnWrapRaw(aes, in, inSz, out, a); + if (ret != 0) { + return ret; + } + /* verify IV */ - if (ConstantCompare(tmp, expIv, KEYWRAP_BLOCK_SIZE) != 0) + if (ConstantCompare(a, expIv, KEYWRAP_BLOCK_SIZE) != 0) { return BAD_KEYWRAP_IV_E; + } return (int)(inSz - KEYWRAP_BLOCK_SIZE); } @@ -15432,6 +15516,251 @@ int wc_AesKeyUnWrap(const byte* key, word32 keySz, const byte* in, word32 inSz, return ret; } +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + +/* RFC 5649 AIV high-half constant; the low half carries the 32-bit MLI. */ +static const byte kwpAivConst[] = { 0xA6, 0x59, 0x59, 0xA6 }; + +/* Build the RFC 5649 AIV: 4-byte constant (iv override or default) | 4-byte + * big-endian MLI m. */ +static void BuildKwpAiv(byte* aiv, const byte* iv, word32 m) +{ + if (iv == NULL) { + XMEMCPY(aiv, kwpAivConst, sizeof(kwpAivConst)); + } + else { + XMEMCPY(aiv, iv, sizeof(kwpAivConst)); + } + + aiv[4] = (byte)(m >> 24); + aiv[5] = (byte)(m >> 16); + aiv[6] = (byte)(m >> 8); + aiv[7] = (byte)(m); +} + +int wc_AesKeyWrap_Pad_ex(Aes* aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv) +{ + int ret; + word32 n; + word32 padSz; + byte aiv[KEYWRAP_BLOCK_SIZE]; + + /* inSz is capped at INT_MAX - (2*8 - 1) so that rounding up to whole 64-bit + * blocks and adding the AIV block cannot overflow (which would let padSz + 8 + * wrap and silently bypass the output-size check) and so the returned length + * (padSz + 8) stays a representable non-negative int; RFC 5649 caps key data + * at 2^32 octets, so this never rejects a legitimately wrappable key. A + * too-small output returns BAD_FUNC_ARG to match the non-padded paths. */ + if (aes == NULL || in == NULL || inSz == 0 || out == NULL || + inSz > 0x7FFFFFFFU - (2 * KEYWRAP_BLOCK_SIZE - 1)) + return BAD_FUNC_ARG; + + /* n = ceil(m/8) padded blocks; output is (n+1) blocks */ + n = (inSz + KEYWRAP_BLOCK_SIZE - 1) / KEYWRAP_BLOCK_SIZE; + padSz = n * KEYWRAP_BLOCK_SIZE; + if (outSz < padSz + KEYWRAP_BLOCK_SIZE) + return BAD_FUNC_ARG; + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (aes->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_AesKeyWrap(aes, in, inSz, out, outSz, iv, 1); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + /* fall through to software when unavailable */ + } +#endif + + /* AIV = const | MLI(inSz) */ + BuildKwpAiv(aiv, iv, inSz); + + /* stage plaintext at out+8 (XMEMMOVE for in-place), zeroing the pad octets */ + XMEMMOVE(out + KEYWRAP_BLOCK_SIZE, in, inSz); + if (padSz > inSz) { + XMEMSET(out + KEYWRAP_BLOCK_SIZE + inSz, 0, padSz - inSz); + } + + if (n == 1) { + /* single block: C[0]|C[1] = ENC(K, AIV | P[1]) */ + byte tmp[WC_AES_BLOCK_SIZE]; + XMEMCPY(tmp, aiv, KEYWRAP_BLOCK_SIZE); + XMEMCPY(tmp + KEYWRAP_BLOCK_SIZE, out + KEYWRAP_BLOCK_SIZE, + KEYWRAP_BLOCK_SIZE); + VECTOR_REGISTERS_PUSH; + ret = wc_AesEncryptDirect(aes, out, tmp); + VECTOR_REGISTERS_POP; + } + else { + /* run the RFC 3394 loop with the AIV as the initial value */ + ret = AesKeyWrapRaw(aes, padSz, out, aiv); + } + if (ret != 0) { + return ret; + } + + return (int)(padSz + KEYWRAP_BLOCK_SIZE); +} + +int wc_AesKeyWrap_Pad(const byte* key, word32 keySz, const byte* in, + word32 inSz, byte* out, word32 outSz, const byte* iv) +{ + WC_DECLARE_VAR(aes, Aes, 1, NULL); + int ret; + + if (key == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(aes, Aes, 1, NULL, DYNAMIC_TYPE_AES, return MEMORY_E); + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret != 0) { + goto out; + } + + ret = wc_AesSetKey(aes, key, keySz, NULL, AES_ENCRYPTION); + if (ret != 0) { + wc_AesFree(aes); + goto out; + } + + ret = wc_AesKeyWrap_Pad_ex(aes, in, inSz, out, outSz, iv); + + wc_AesFree(aes); + + out: + WC_FREE_VAR_EX(aes, NULL, DYNAMIC_TYPE_AES); + + return ret; +} + +int wc_AesKeyUnWrap_Pad_ex(Aes* aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv) +{ + int ret; + word32 n; + word32 mli; + word32 b; + byte a[KEYWRAP_BLOCK_SIZE]; + byte expConst[sizeof(kwpAivConst)]; + byte zeros[KEYWRAP_BLOCK_SIZE]; + + /* ciphertext is (n + 1) >= 2 blocks on a 64-bit boundary; the full + * (inSz - 8) padded plaintext is written into out in place. inSz is capped + * at INT_MAX so the returned length (the recovered MLI, <= inSz) stays a + * representable non-negative int. A too-small output returns BAD_FUNC_ARG + * to match the non-padded RFC 3394 paths. */ + if (aes == NULL || in == NULL || out == NULL || + inSz < 2 * KEYWRAP_BLOCK_SIZE || (inSz % KEYWRAP_BLOCK_SIZE) != 0 || + inSz > 0x7FFFFFFFU || outSz < inSz - KEYWRAP_BLOCK_SIZE) + return BAD_FUNC_ARG; + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (aes->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_AesKeyUnWrap(aes, in, inSz, out, outSz, iv, 1); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + return ret; + } + /* fall through to software when unavailable */ + } +#endif + + /* number of padded 64-bit plaintext blocks */ + n = (inSz / KEYWRAP_BLOCK_SIZE) - 1; + + if (n == 1) { + /* single block: AIV|P[1] = DEC(K, C[0]|C[1]) */ + byte tmp[WC_AES_BLOCK_SIZE]; + VECTOR_REGISTERS_PUSH; + ret = wc_AesDecryptDirect(aes, tmp, in); + VECTOR_REGISTERS_POP; + if (ret == 0) { + XMEMCPY(a, tmp, KEYWRAP_BLOCK_SIZE); + XMEMCPY(out, tmp + KEYWRAP_BLOCK_SIZE, KEYWRAP_BLOCK_SIZE); + } + } + else { + /* recover A and padded plaintext via the RFC 3394 loop (no check) */ + ret = AesKeyUnWrapRaw(aes, in, inSz, out, a); + } + if (ret != 0) { + return ret; + } + + /* expected high-half constant (iv override or default) */ + if (iv == NULL) { + XMEMCPY(expConst, kwpAivConst, sizeof(kwpAivConst)); + } + else { + XMEMCPY(expConst, iv, sizeof(kwpAivConst)); + } + + /* check 1: MSB(32,A) == constant */ + if (ConstantCompare(a, expConst, (int)sizeof(kwpAivConst)) != 0) { + return BAD_KEYWRAP_IV_E; + } + + /* MLI = LSB(32,A) in network order */ + mli = ((word32)a[4] << 24) | ((word32)a[5] << 16) | + ((word32)a[6] << 8) | (word32)a[7]; + + /* check 2: 8*(n-1) < MLI <= 8*n */ + if (mli <= KEYWRAP_BLOCK_SIZE * (n - 1) || mli > KEYWRAP_BLOCK_SIZE * n) { + return BAD_KEYWRAP_IV_E; + } + + /* check 3: the trailing b padding octets are zero */ + b = (KEYWRAP_BLOCK_SIZE * n) - mli; + XMEMSET(zeros, 0, sizeof(zeros)); + if (b > 0 && ConstantCompare(out + mli, zeros, (int)b) != 0) { + return BAD_KEYWRAP_IV_E; + } + + return (int)mli; +} + +int wc_AesKeyUnWrap_Pad(const byte* key, word32 keySz, const byte* in, + word32 inSz, byte* out, word32 outSz, const byte* iv) +{ + WC_DECLARE_VAR(aes, Aes, 1, NULL); + int ret; + + if (key == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(aes, Aes, 1, NULL, DYNAMIC_TYPE_AES, return MEMORY_E); + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret != 0) { + goto out; + } + + ret = wc_AesSetKey(aes, key, keySz, NULL, AES_DECRYPTION); + if (ret != 0) { + wc_AesFree(aes); + goto out; + } + + ret = wc_AesKeyUnWrap_Pad_ex(aes, in, inSz, out, outSz, iv); + + wc_AesFree(aes); + + out: + WC_FREE_VAR_EX(aes, NULL, DYNAMIC_TYPE_AES); + + return ret; +} + +#endif /* WOLFSSL_AES_KEYWRAP_PADDING */ + #endif /* HAVE_AES_KEYWRAP */ #ifdef WOLFSSL_AES_XTS diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index 3218e6efb2..a6b33e0458 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -170,6 +170,7 @@ static const char* GetCipherTypeStr(int cipher) case WC_CIPHER_AES_CTR: return "AES CTR"; case WC_CIPHER_AES_XTS: return "AES XTS"; case WC_CIPHER_AES_CFB: return "AES CFB"; + case WC_CIPHER_AES_KEYWRAP: return "AES KeyWrap"; case WC_CIPHER_DES3: return "DES3"; case WC_CIPHER_DES: return "DES"; case WC_CIPHER_CHACHA: return "ChaCha20"; @@ -1808,6 +1809,89 @@ int wc_CryptoCb_AesEcbDecrypt(Aes* aes, byte* out, } #endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT || WOLF_CRYPTO_CB_ONLY_AES */ +#ifdef HAVE_AES_KEYWRAP +/* On success returns the number of output bytes produced (the callback reports + * it via aeskeywrap.outResSz), otherwise a negative error or + * CRYPTOCB_UNAVAILABLE. pad selects RFC 5649 (1) vs RFC 3394 (0). */ +int wc_CryptoCb_AesKeyWrap(Aes* aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv, int pad) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + /* locate registered callback */ + if (aes) { + dev = wc_CryptoCb_FindDevice(aes->devId, WC_ALGO_TYPE_CIPHER); + } + else { + /* locate first callback and try using it */ + dev = wc_CryptoCb_FindDeviceByIndex(0); + } + + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_CIPHER; + cryptoInfo.cipher.type = WC_CIPHER_AES_KEYWRAP; + cryptoInfo.cipher.enc = 1; + cryptoInfo.cipher.aeskeywrap.aes = aes; + cryptoInfo.cipher.aeskeywrap.in = in; + cryptoInfo.cipher.aeskeywrap.inSz = inSz; + cryptoInfo.cipher.aeskeywrap.out = out; + cryptoInfo.cipher.aeskeywrap.outSz = outSz; + cryptoInfo.cipher.aeskeywrap.iv = iv; + cryptoInfo.cipher.aeskeywrap.pad = pad; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + if (ret == 0) { + /* device reports the wrapped length */ + return (int)cryptoInfo.cipher.aeskeywrap.outResSz; + } + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_AesKeyUnWrap(Aes* aes, const byte* in, word32 inSz, byte* out, + word32 outSz, const byte* iv, int pad) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + /* locate registered callback */ + if (aes) { + dev = wc_CryptoCb_FindDevice(aes->devId, WC_ALGO_TYPE_CIPHER); + } + else { + /* locate first callback and try using it */ + dev = wc_CryptoCb_FindDeviceByIndex(0); + } + + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_CIPHER; + cryptoInfo.cipher.type = WC_CIPHER_AES_KEYWRAP; + cryptoInfo.cipher.enc = 0; + cryptoInfo.cipher.aeskeywrap.aes = aes; + cryptoInfo.cipher.aeskeywrap.in = in; + cryptoInfo.cipher.aeskeywrap.inSz = inSz; + cryptoInfo.cipher.aeskeywrap.out = out; + cryptoInfo.cipher.aeskeywrap.outSz = outSz; + cryptoInfo.cipher.aeskeywrap.iv = iv; + cryptoInfo.cipher.aeskeywrap.pad = pad; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + if (ret == 0) { + /* device reports the recovered length */ + return (int)cryptoInfo.cipher.aeskeywrap.outResSz; + } + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* HAVE_AES_KEYWRAP */ + #ifdef WOLF_CRYPTO_CB_AES_SETKEY int wc_CryptoCb_AesSetKey(Aes* aes, const byte* key, word32 keySz) { diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 204c30f507..d224080927 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -880,6 +880,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aesgcm_default_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t gmac_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aesccm_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_test(void); +#ifdef WOLFSSL_AES_KEYWRAP_PADDING +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_pad_test(void); +#endif WOLFSSL_TEST_SUBROUTINE wc_test_ret_t camellia_test(void); #ifdef WOLFSSL_SM4 WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sm4_test(void); @@ -2885,6 +2888,12 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_FAIL("AES Key Wrap test failed!\n", ret); else TEST_PASS("AES Key Wrap test passed!\n"); +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + if ( (ret = aeskeywrap_pad_test()) != 0) + TEST_FAIL("AES Key Wrap Pad test failed!\n", ret); + else + TEST_PASS("AES Key Wrap Pad test passed!\n"); +#endif #endif #if defined(WOLFSSL_AES_SIV) && defined(WOLFSSL_AES_128) if ( (ret = aes_siv_test()) != 0) @@ -22224,8 +22233,306 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_test(void) return WC_TEST_RET_ENC_EC(plainSz); } +#if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + /* Drive wc_AesKeyWrap_ex/wc_AesKeyUnWrap_ex directly with a caller Aes; the + * KAT loop above already covers every vector via the key-based wrappers. */ + { + Aes* aes = (Aes*)XMALLOC(sizeof(Aes), HEAP_HINT, DYNAMIC_TYPE_AES); + if (aes == NULL) + return WC_TEST_RET_ENC_NC; + + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + if (wc_AesSetKey(aes, test_wrap[0].kek, test_wrap[0].kekLen, NULL, + AES_ENCRYPTION) != 0) { + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + wrapSz = wc_AesKeyWrap_ex(aes, test_wrap[0].data, test_wrap[0].dataLen, + output, sizeof(output), NULL); + wc_AesFree(aes); + if ( (wrapSz < 0) || (wrapSz != (int)test_wrap[0].verifyLen) || + XMEMCMP(output, test_wrap[0].verify, test_wrap[0].verifyLen) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + if (wc_AesSetKey(aes, test_wrap[0].kek, test_wrap[0].kekLen, NULL, + AES_DECRYPTION) != 0) { + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + plainSz = wc_AesKeyUnWrap_ex(aes, output, (word32)wrapSz, + plain, sizeof(plain), NULL); + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + if ( (plainSz < 0) || (plainSz != (int)test_wrap[0].dataLen) || + XMEMCMP(plain, test_wrap[0].data, test_wrap[0].dataLen) != 0) + return WC_TEST_RET_ENC_NC; + } + + /* In-place round-trip (in == out): wrap then unwrap a single buffer. + * Exercises the XMEMMOVE staging in wc_AesKeyWrap_ex / AesKeyUnWrapRaw. */ + { + XMEMSET(output, 0, sizeof(output)); + XMEMCPY(output, test_wrap[0].data, test_wrap[0].dataLen); + + wrapSz = wc_AesKeyWrap(test_wrap[0].kek, test_wrap[0].kekLen, + output, test_wrap[0].dataLen, + output, sizeof(output), NULL); + if ( (wrapSz < 0) || (wrapSz != (int)test_wrap[0].verifyLen) ) + return WC_TEST_RET_ENC_NC; + if (XMEMCMP(output, test_wrap[0].verify, test_wrap[0].verifyLen) != 0) + return WC_TEST_RET_ENC_NC; + + plainSz = wc_AesKeyUnWrap(test_wrap[0].kek, test_wrap[0].kekLen, + output, (word32)wrapSz, + output, sizeof(output), NULL); + if ( (plainSz < 0) || (plainSz != (int)test_wrap[0].dataLen) ) + return WC_TEST_RET_ENC_NC; + if (XMEMCMP(output, test_wrap[0].data, test_wrap[0].dataLen) != 0) + return WC_TEST_RET_ENC_NC; + } +#endif /* !HAVE_FIPS && !HAVE_SELFTEST */ + + return 0; +} + +#if defined(WOLFSSL_AES_KEYWRAP_PADDING) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_pad_test(void) +{ + int wrapSz, plainSz, kwpSz, i; + byte output[MAX_KEYWRAP_TEST_OUTLEN]; + byte plain [MAX_KEYWRAP_TEST_PLAINLEN]; + + /* RFC 5649 padded key wrap vectors. The 192-bit cases are the two + * published RFC 5649 section 6 examples; the 128-bit and 256-bit cases + * are known-answer vectors generated with OpenSSL's aes-{128,256}-wrap-pad + * over the same two plaintexts (cross-checked against the RFC vectors). + * Each KEK size exercises both code paths: the 20-octet input takes the + * RFC 3394 loop path, the 7-octet input takes the single-block ECB path. */ + + /* shared plaintexts */ + /* 20 octets -> 32-byte wrap (RFC 3394 loop path) */ + WOLFSSL_SMALL_STACK_STATIC const byte kwpData1[] = { + 0xc3, 0x7b, 0x7e, 0x64, 0x92, 0x58, 0x43, 0x40, + 0xbe, 0xd1, 0x22, 0x07, 0x80, 0x89, 0x41, 0x15, + 0x50, 0x68, 0xf7, 0x38 + }; + /* 7 octets -> 16-byte wrap (single-block ECB path) */ + WOLFSSL_SMALL_STACK_STATIC const byte kwpData2[] = { + 0x46, 0x6f, 0x72, 0x50, 0x61, 0x73, 0x69 + }; + +#ifdef WOLFSSL_AES_128 + WOLFSSL_SMALL_STACK_STATIC const byte kwpKek128[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify128_1[] = { + 0xe1, 0xf7, 0x17, 0x6e, 0xcb, 0xd7, 0x5d, 0x42, + 0xe8, 0x2b, 0x24, 0xf9, 0x89, 0xa2, 0x81, 0x6c, + 0x20, 0x9c, 0x6e, 0xf2, 0xd1, 0xaa, 0x94, 0xd2, + 0xa3, 0xe6, 0x02, 0x84, 0x90, 0x0d, 0x03, 0xa2 + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify128_2[] = { + 0xbe, 0x80, 0x53, 0x5e, 0x12, 0xe9, 0x39, 0x4c, + 0x8f, 0x8d, 0xf2, 0x6b, 0xd9, 0x52, 0x8a, 0x35 + }; +#endif +#ifdef WOLFSSL_AES_192 + WOLFSSL_SMALL_STACK_STATIC const byte kwpKek192[] = { + 0x58, 0x40, 0xdf, 0x6e, 0x29, 0xb0, 0x2a, 0xf1, + 0xab, 0x49, 0x3b, 0x70, 0x5b, 0xf1, 0x6e, 0xa1, + 0xae, 0x83, 0x38, 0xf4, 0xdc, 0xc1, 0x76, 0xa8 + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify192_1[] = { + 0x13, 0x8b, 0xde, 0xaa, 0x9b, 0x8f, 0xa7, 0xfc, + 0x61, 0xf9, 0x77, 0x42, 0xe7, 0x22, 0x48, 0xee, + 0x5a, 0xe6, 0xae, 0x53, 0x60, 0xd1, 0xae, 0x6a, + 0x5f, 0x54, 0xf3, 0x73, 0xfa, 0x54, 0x3b, 0x6a + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify192_2[] = { + 0xaf, 0xbe, 0xb0, 0xf0, 0x7d, 0xfb, 0xf5, 0x41, + 0x92, 0x00, 0xf2, 0xcc, 0xb5, 0x0b, 0xb2, 0x4f + }; +#endif +#ifdef WOLFSSL_AES_256 + WOLFSSL_SMALL_STACK_STATIC const byte kwpKek256[] = { + 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 + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify256_1[] = { + 0x29, 0xb7, 0xfa, 0x19, 0x1c, 0x21, 0x65, 0x68, + 0x43, 0x74, 0xee, 0xe9, 0xf7, 0x45, 0x95, 0xe2, + 0xa4, 0x2b, 0xac, 0xe7, 0x5c, 0x42, 0x5b, 0x30, + 0x53, 0xef, 0xa2, 0x6f, 0xfe, 0x1b, 0xb3, 0x2f + }; + WOLFSSL_SMALL_STACK_STATIC const byte kwpVerify256_2[] = { + 0x44, 0x3b, 0x17, 0x83, 0x7b, 0xb3, 0x93, 0x48, + 0x61, 0x0d, 0x19, 0x20, 0x2d, 0xf8, 0xa1, 0xf9 + }; +#endif + const keywrapVector test_kwp[] = { + #ifdef WOLFSSL_AES_128 + {kwpKek128, kwpData1, kwpVerify128_1, sizeof(kwpKek128), + sizeof(kwpData1), sizeof(kwpVerify128_1)}, + {kwpKek128, kwpData2, kwpVerify128_2, sizeof(kwpKek128), + sizeof(kwpData2), sizeof(kwpVerify128_2)}, + #endif + #ifdef WOLFSSL_AES_192 + {kwpKek192, kwpData1, kwpVerify192_1, sizeof(kwpKek192), + sizeof(kwpData1), sizeof(kwpVerify192_1)}, + {kwpKek192, kwpData2, kwpVerify192_2, sizeof(kwpKek192), + sizeof(kwpData2), sizeof(kwpVerify192_2)}, + #endif + #ifdef WOLFSSL_AES_256 + {kwpKek256, kwpData1, kwpVerify256_1, sizeof(kwpKek256), + sizeof(kwpData1), sizeof(kwpVerify256_1)}, + {kwpKek256, kwpData2, kwpVerify256_2, sizeof(kwpKek256), + sizeof(kwpData2), sizeof(kwpVerify256_2)}, + #endif + }; + + WOLFSSL_ENTER("aeskeywrap_pad_test"); + + kwpSz = (int)(sizeof(test_kwp) / sizeof(keywrapVector)); + + for (i = 0; i < kwpSz; i++) { + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + + wrapSz = wc_AesKeyWrap_Pad(test_kwp[i].kek, test_kwp[i].kekLen, + test_kwp[i].data, test_kwp[i].dataLen, + output, sizeof(output), NULL); + if ( (wrapSz < 0) || (wrapSz != (int)test_kwp[i].verifyLen) ) { + return WC_TEST_RET_ENC_I(i); + } + + if (XMEMCMP(output, test_kwp[i].verify, test_kwp[i].verifyLen) != 0) { + return WC_TEST_RET_ENC_I(i); + } + + plainSz = wc_AesKeyUnWrap_Pad(test_kwp[i].kek, test_kwp[i].kekLen, + output, (word32)wrapSz, + plain, sizeof(plain), NULL); + if ( (plainSz < 0) || (plainSz != (int)test_kwp[i].dataLen) ) { + return WC_TEST_RET_ENC_I(i); + } + + if (XMEMCMP(plain, test_kwp[i].data, test_kwp[i].dataLen) != 0) { + return WC_TEST_RET_ENC_I(i); + } + } + + /* Negative test: corrupted wrapped data must be rejected with + * BAD_KEYWRAP_IV_E. */ + wrapSz = wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, + test_kwp[0].data, test_kwp[0].dataLen, + output, sizeof(output), NULL); + if (wrapSz < 0) { + return WC_TEST_RET_ENC_EC(wrapSz); + } + + output[0] ^= 0x01; + + plainSz = wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, + output, (word32)wrapSz, + plain, sizeof(plain), NULL); + if (plainSz != WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)) { + return WC_TEST_RET_ENC_EC(plainSz); + } + +#if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + /* Drive wc_AesKeyWrap_Pad_ex/wc_AesKeyUnWrap_Pad_ex directly with a caller + * Aes; the KAT loop above already covers every vector via the wrappers. */ + { + Aes* aes = (Aes*)XMALLOC(sizeof(Aes), HEAP_HINT, DYNAMIC_TYPE_AES); + if (aes == NULL) + return WC_TEST_RET_ENC_NC; + + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + if (wc_AesSetKey(aes, test_kwp[0].kek, test_kwp[0].kekLen, NULL, + AES_ENCRYPTION) != 0) { + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + wrapSz = wc_AesKeyWrap_Pad_ex(aes, test_kwp[0].data, test_kwp[0].dataLen, + output, sizeof(output), NULL); + wc_AesFree(aes); + if ( (wrapSz < 0) || (wrapSz != (int)test_kwp[0].verifyLen) || + XMEMCMP(output, test_kwp[0].verify, test_kwp[0].verifyLen) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + + if (wc_AesInit(aes, HEAP_HINT, devId) != 0) { + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + if (wc_AesSetKey(aes, test_kwp[0].kek, test_kwp[0].kekLen, NULL, + AES_DECRYPTION) != 0) { + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + plainSz = wc_AesKeyUnWrap_Pad_ex(aes, output, (word32)wrapSz, + plain, sizeof(plain), NULL); + wc_AesFree(aes); + XFREE(aes, HEAP_HINT, DYNAMIC_TYPE_AES); + if ( (plainSz < 0) || (plainSz != (int)test_kwp[0].dataLen) || + XMEMCMP(plain, test_kwp[0].data, test_kwp[0].dataLen) != 0) + return WC_TEST_RET_ENC_NC; + } + + /* In-place round-trip (in == out) for every vector: covers both the + * single-block (7-octet) and RFC 3394 loop (20-octet) paths, exercising the + * XMEMMOVE staging in wc_AesKeyWrap_Pad_ex and AesKeyUnWrapRaw. */ + for (i = 0; i < kwpSz; i++) { + XMEMSET(output, 0, sizeof(output)); + XMEMCPY(output, test_kwp[i].data, test_kwp[i].dataLen); + + wrapSz = wc_AesKeyWrap_Pad(test_kwp[i].kek, test_kwp[i].kekLen, + output, test_kwp[i].dataLen, + output, sizeof(output), NULL); + if ( (wrapSz < 0) || (wrapSz != (int)test_kwp[i].verifyLen) ) + return WC_TEST_RET_ENC_I(i); + if (XMEMCMP(output, test_kwp[i].verify, test_kwp[i].verifyLen) != 0) + return WC_TEST_RET_ENC_I(i); + + plainSz = wc_AesKeyUnWrap_Pad(test_kwp[i].kek, test_kwp[i].kekLen, + output, (word32)wrapSz, + output, sizeof(output), NULL); + if ( (plainSz < 0) || (plainSz != (int)test_kwp[i].dataLen) ) + return WC_TEST_RET_ENC_I(i); + if (XMEMCMP(output, test_kwp[i].data, test_kwp[i].dataLen) != 0) + return WC_TEST_RET_ENC_I(i); + } +#endif /* !HAVE_FIPS && !HAVE_SELFTEST */ + return 0; } +#endif /* WOLFSSL_AES_KEYWRAP_PADDING */ #endif /* HAVE_AES_KEYWRAP */ #endif /* !NO_AES */ @@ -73269,6 +73576,67 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) } } #endif /* HAVE_AES_ECB */ + #ifdef HAVE_AES_KEYWRAP + if (info->cipher.type == WC_CIPHER_AES_KEYWRAP) { + int kwRet; + + /* set devId to invalid, so software is used */ + info->cipher.aeskeywrap.aes->devId = INVALID_DEVID; + + if (info->cipher.enc) { + #ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (info->cipher.aeskeywrap.pad) + kwRet = wc_AesKeyWrap_Pad_ex( + info->cipher.aeskeywrap.aes, + info->cipher.aeskeywrap.in, + info->cipher.aeskeywrap.inSz, + info->cipher.aeskeywrap.out, + info->cipher.aeskeywrap.outSz, + info->cipher.aeskeywrap.iv); + else + #endif + kwRet = wc_AesKeyWrap_ex( + info->cipher.aeskeywrap.aes, + info->cipher.aeskeywrap.in, + info->cipher.aeskeywrap.inSz, + info->cipher.aeskeywrap.out, + info->cipher.aeskeywrap.outSz, + info->cipher.aeskeywrap.iv); + } + else { + #ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (info->cipher.aeskeywrap.pad) + kwRet = wc_AesKeyUnWrap_Pad_ex( + info->cipher.aeskeywrap.aes, + info->cipher.aeskeywrap.in, + info->cipher.aeskeywrap.inSz, + info->cipher.aeskeywrap.out, + info->cipher.aeskeywrap.outSz, + info->cipher.aeskeywrap.iv); + else + #endif + kwRet = wc_AesKeyUnWrap_ex( + info->cipher.aeskeywrap.aes, + info->cipher.aeskeywrap.in, + info->cipher.aeskeywrap.inSz, + info->cipher.aeskeywrap.out, + info->cipher.aeskeywrap.outSz, + info->cipher.aeskeywrap.iv); + } + + /* reset devId */ + info->cipher.aeskeywrap.aes->devId = devIdArg; + + if (kwRet < 0) { + ret = kwRet; + } + else { + /* report produced length back to the dispatcher */ + info->cipher.aeskeywrap.outResSz = (word32)kwRet; + ret = 0; + } + } + #endif /* HAVE_AES_KEYWRAP */ #if defined(WOLFSSL_AES_COUNTER) && !defined(HAVE_FIPS) && \ !defined(HAVE_SELFTEST) if (info->cipher.type == WC_CIPHER_AES_CTR) { @@ -74670,6 +75038,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) if (ret == 0) ret = aesccm_test(); #endif + #ifdef HAVE_AES_KEYWRAP + if (ret == 0) + ret = aeskeywrap_test(); + #ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (ret == 0) + ret = aeskeywrap_pad_test(); + #endif + #endif /* HAVE_AES_KEYWRAP */ #endif /* !NO_AES && !WOLF_CRYPTO_CB_ONLY_AES */ #ifndef NO_DES3 if (ret == 0) diff --git a/wolfssl/wolfcrypt/aes.h b/wolfssl/wolfcrypt/aes.h index e3d7637470..42902be33d 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -702,6 +702,13 @@ WOLFSSL_API WARN_UNUSED_RESULT int wc_AesGcmDecryptFinal(Aes* aes, #endif /* HAVE_AESCCM */ #ifdef HAVE_AES_KEYWRAP + /* RFC 3394 AES key wrap. On success return the produced (wrap) or recovered + * (unwrap) byte count (>= 0); on failure a negative error (BAD_FUNC_ARG, + * BAD_KEYWRAP_IV_E, ...). in and out may alias (in-place is supported). iv is + * the optional 8-byte integrity check value; NULL uses the default A6..A6. + * The _ex variants take a caller-provided Aes already keyed with the KEK + * (AES_ENCRYPTION to wrap, AES_DECRYPTION to unwrap) and, when its devId is set, + * route through a registered crypto callback. */ WOLFSSL_API int wc_AesKeyWrap(const byte* key, word32 keySz, const byte* in, word32 inSz, byte* out, word32 outSz, @@ -718,6 +725,31 @@ WOLFSSL_API WARN_UNUSED_RESULT int wc_AesGcmDecryptFinal(Aes* aes, const byte* in, word32 inSz, byte* out, word32 outSz, const byte* iv); +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + /* RFC 5649 AES key wrap with padding. Wrap accepts any inSz >= 1 and produces + * ceil(inSz/8)*8 + 8 bytes; unwrap recovers and returns the original inSz. On + * success return that byte count (>= 0); on failure a negative error. in and + * out may alias (in-place is supported). iv is optional: NULL uses the RFC + * 5649 AIV constant (A6 59 59 A6). If non-NULL it overrides ONLY that 4-byte + * high half - the low 4 bytes always carry the computed length indicator. NOTE + * this differs from the non-padded wc_AesKeyWrap* iv, which is a full 8 bytes. */ + WOLFSSL_API int wc_AesKeyWrap_Pad(const byte* key, word32 keySz, + const byte* in, word32 inSz, + byte* out, word32 outSz, + const byte* iv); + WOLFSSL_API int wc_AesKeyWrap_Pad_ex(Aes* aes, + const byte* in, word32 inSz, + byte* out, word32 outSz, + const byte* iv); + WOLFSSL_API int wc_AesKeyUnWrap_Pad(const byte* key, word32 keySz, + const byte* in, word32 inSz, + byte* out, word32 outSz, + const byte* iv); + WOLFSSL_API int wc_AesKeyUnWrap_Pad_ex(Aes* aes, + const byte* in, word32 inSz, + byte* out, word32 outSz, + const byte* iv); +#endif /* WOLFSSL_AES_KEYWRAP_PADDING */ #endif /* HAVE_AES_KEYWRAP */ #ifdef WOLFSSL_AES_XTS diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index b0aaad2f37..95e81edb2e 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -441,6 +441,18 @@ typedef struct wc_CryptoInfo { const byte* key; word32 keySz; } aessetkey; + #endif + #if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) + struct { + Aes* aes; + const byte* in; + word32 inSz; + byte* out; + word32 outSz; /* size of out buffer (input) */ + word32 outResSz; /* bytes produced (output, set by cb) */ + const byte* iv; + int pad; /* 1 = RFC 5649 padded, 0 = RFC 3394 */ + } aeskeywrap; #endif void* ctx; #ifdef HAVE_ANONYMOUS_INLINE_AGGREGATES @@ -843,6 +855,12 @@ WOLFSSL_LOCAL int wc_CryptoCb_AesEcbDecrypt(Aes* aes, byte* out, #ifdef WOLF_CRYPTO_CB_AES_SETKEY WOLFSSL_API int wc_CryptoCb_AesSetKey(Aes* aes, const byte* key, word32 keySz); #endif /* WOLF_CRYPTO_CB_AES_SETKEY */ +#ifdef HAVE_AES_KEYWRAP +WOLFSSL_LOCAL int wc_CryptoCb_AesKeyWrap(Aes* aes, const byte* in, + word32 inSz, byte* out, word32 outSz, const byte* iv, int pad); +WOLFSSL_LOCAL int wc_CryptoCb_AesKeyUnWrap(Aes* aes, const byte* in, + word32 inSz, byte* out, word32 outSz, const byte* iv, int pad); +#endif /* HAVE_AES_KEYWRAP */ #endif /* !NO_AES */ #ifndef NO_DES3 diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 3a1efb7955..a73c593985 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1538,6 +1538,7 @@ enum wc_CipherType { WC_CIPHER_AES_CFB = 6, WC_CIPHER_AES_CCM = 12, WC_CIPHER_AES_ECB = 13, + WC_CIPHER_AES_KEYWRAP = 14, WC_CIPHER_DES3 = 7, WC_CIPHER_DES = 8, WC_CIPHER_CHACHA = 9,