From d67913e46c7592e3cd05a39140e975f19933046f Mon Sep 17 00:00:00 2001 From: night1rider Date: Mon, 20 Jul 2026 13:40:06 -0600 Subject: [PATCH] Add AES key wrap with padding (RFC 5649) and crypto callback support Adds wc_AesKeyWrap_Pad/wc_AesKeyUnWrap_Pad and their _ex variants plus crypto callback dispatch, routing blocks through wc_AesEcb* so an ECB only callback works. --- .github/workflows/cryptocb-only.yml | 4 +- .github/workflows/os-check.yml | 9 + configure.ac | 27 +- tests/api.c | 3 + tests/api/test_aes.c | 693 ++++++++++++++++++++++++++++ tests/api/test_aes.h | 26 ++ tests/swdev/swdev.c | 60 +++ wolfcrypt/src/aes.c | 479 +++++++++++++++++-- wolfcrypt/src/cryptocb.c | 84 ++++ wolfcrypt/test/test.c | 657 ++++++++++++++++++++++++++ wolfssl/wolfcrypt/aes.h | 32 ++ wolfssl/wolfcrypt/cryptocb.h | 18 + wolfssl/wolfcrypt/types.h | 1 + 13 files changed, 2051 insertions(+), 42 deletions(-) diff --git a/.github/workflows/cryptocb-only.yml b/.github/workflows/cryptocb-only.yml index 52d00a4642a..44e55a972e7 100644 --- a/.github/workflows/cryptocb-only.yml +++ b/.github/workflows/cryptocb-only.yml @@ -69,7 +69,7 @@ jobs: "--enable-swdev", "--enable-cryptocb", "--enable-ecc", "--enable-rsa", "--enable-dh", "--enable-aesgcm", "--enable-aesccm", "--enable-aesctr", "--enable-aescfb", - "--enable-aeskeywrap", "--enable-aessiv", "--enable-aesofb", + "--enable-aeskeywrap=padding", "--enable-aessiv", "--enable-aesofb", "--enable-aesxts", "--enable-camellia", "--enable-chacha", "--enable-poly1305", "--enable-sha", "--enable-sha3", "--enable-shake128", "--enable-shake256", "--enable-blake2", @@ -100,7 +100,7 @@ jobs: "comment": "Same as sha512 but tells swdev to refuse the SHA-384 / SHA-512/224 / SHA-512/256 variant callbacks (WOLFSSL_SWDEV_SHA512_GENERAL_ONLY). That forces the cryptocb dispatcher's fallback-to-plain-SHA-512-with-truncation path. The sha512 entry above instead has swdev handle every variant end-to-end, so the dispatcher fallback is otherwise uncovered.", "configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_SHA512 -DWOLFSSL_SWDEV_SHA512_GENERAL_ONLY"]}, {"name": "aes", - "comment": "WOLF_CRYPTO_CB_ONLY_AES: strips software AES; swdev provides the software path via cryptocb.", + "comment": "WOLF_CRYPTO_CB_ONLY_AES: strips software AES; swdev provides the software path via cryptocb. aeskeywrap=padding covers RFC 3394 + RFC 5649 key wrap via swdev_aes_keywrap.", "configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_AES"]}, {"name": "aes-gcm-via-ecb", "comment": "Same as aes but tells swdev to refuse AES-GCM (SWDEV_AES_ONLYECB). That forces the parent's CB_ONLY_AES host-side GCM software path: GHASH runs on the host while AES-CTR blocks dispatch back through cryptocb ECB. The aes entry instead has swdev handle GCM end-to-end, so the host-side GCM path is otherwise uncovered.", diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 25f2b6f8810..5a2cfeaed12 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -299,6 +299,15 @@ 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": "aeskeywrap-padding-cryptocb-ecb", "minutes": 1.6, + "comment": "Key wrap with HAVE_AES_ECB so the RFC 3394 loops route each block through wc_AesEcb*; runs test_wc_CryptoCb_AesKeyWrapEcbCompose (key wrap composed from an ECB-only crypto callback).", + "configure": ["--enable-cryptocb", "--enable-aeskeywrap=padding", "--enable-aesecb"]}, {"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 4c5c326174f..9e9012ef9ff 100644 --- a/configure.ac +++ b/configure.ac @@ -6751,12 +6751,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], @@ -11398,6 +11417,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], @@ -13378,6 +13402,7 @@ echo " * AES-GCM-SIV: $ENABLED_AESGCMSIV" 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 0abaf2ebc8f..93d1b07c481 100644 --- a/tests/api.c +++ b/tests/api.c @@ -37066,6 +37066,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 f7fe62b0c27..53d98ac2022 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -10230,6 +10230,434 @@ int test_wc_AesSivArgMcdc(void) } #endif /* WOLFSSL_AES_SIV && WOLFSSL_AES_128 */ +/*----------------------------------------------------------------------------* + | AES Key Wrap with Padding (RFC 5649) Test + *----------------------------------------------------------------------------*/ + +#if defined(HAVE_AES_KEYWRAP) && defined(WOLFSSL_AES_KEYWRAP_PADDING) + +/* Test wc_AesKeyWrap_Pad / wc_AesKeyUnWrap_Pad (RFC 5649). KAT vectors: RFC + * 5649 s6 for 192-bit, OpenSSL aes-wrap-pad for 128/256 (cross-checked). */ +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); + } + + /* Everything below indexes kats[0]; when no AES key size is enabled kats[] + * is empty (katCnt == 0), so skip it to keep the accesses in-bounds. */ + if (katCnt == 0) + return EXPECT_RESULT(); + + /* --- 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 checks 2 and 3: a decrypted block is + * AIV(8)|P(8), so ECB-encrypting a chosen block fixes what unwrap sees. */ + { + 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)); + } + +#if defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_192) || \ + defined(WOLFSSL_AES_256) + /* Extended coverage per key size: pyca/cryptography boundary-size KATs + * (reproduce RFC 5649 s6), exhaustive round-trip 1..64, and in-place. */ + { +#ifdef WOLFSSL_AES_128 + static const byte k128_1[] = { + 0xdc, 0x0c, 0xed, 0x32, 0x50, 0xa4, 0x92, 0x77, 0x59, 0xb4, 0xe9, 0x28, + 0x73, 0x2e, 0x16, 0x8a }; + static const byte k128_8[] = { + 0x23, 0xe1, 0xcd, 0x73, 0x92, 0xf0, 0xcc, 0x69, 0xdc, 0x20, 0xdf, 0x56, + 0x48, 0x9f, 0xfd, 0xd7 }; + static const byte k128_9[] = { + 0xe6, 0xe7, 0x6a, 0xfc, 0xf1, 0xf7, 0x1f, 0x43, 0x63, 0x4b, 0x96, 0x91, + 0x9c, 0x1e, 0x36, 0xa9, 0x5b, 0xf7, 0xb0, 0x26, 0x0f, 0x51, 0x9b, 0x4b }; + static const byte k128_16[] = { + 0xa7, 0x41, 0x46, 0x28, 0x3b, 0x00, 0x85, 0x06, 0x23, 0xd3, 0x02, 0xf4, + 0x57, 0xb1, 0x8c, 0x96, 0xac, 0xe5, 0xb5, 0xd3, 0x64, 0x7c, 0xc2, 0xf1 }; + static const byte k128_17[] = { + 0x8d, 0x69, 0xe5, 0xc5, 0x01, 0x98, 0x70, 0xd3, 0x50, 0x37, 0x3a, 0x00, + 0xa8, 0xe3, 0xa5, 0x32, 0xdf, 0xce, 0x76, 0x8a, 0x6b, 0x79, 0xef, 0x2c, + 0x34, 0xcf, 0xed, 0x5c, 0xb4, 0x09, 0xff, 0xf4 }; + static const byte k128_31[] = { + 0x50, 0x3e, 0xc4, 0xff, 0x2e, 0xd3, 0x01, 0x14, 0xfa, 0x5a, 0x02, 0x47, + 0x9f, 0x20, 0x4c, 0xb1, 0xd8, 0xcb, 0xa2, 0xa3, 0xa3, 0x7d, 0x7b, 0xa5, + 0x60, 0x77, 0x01, 0x46, 0xd6, 0x03, 0x93, 0xe0, 0xf0, 0x01, 0xf7, 0x88, + 0xb0, 0x4b, 0xc6, 0xb2 }; +#endif +#ifdef WOLFSSL_AES_192 + static const byte k192_1[] = { + 0x95, 0xf7, 0xba, 0x0a, 0x72, 0x6e, 0xed, 0x9a, 0x90, 0xa9, 0x90, 0x00, + 0x94, 0xc5, 0xd9, 0x2d }; + static const byte k192_8[] = { + 0xaa, 0x6f, 0x7d, 0x3b, 0xab, 0x34, 0x91, 0xcc, 0xd9, 0x52, 0xc9, 0x86, + 0x64, 0x42, 0x8c, 0x40 }; + static const byte k192_9[] = { + 0x52, 0xa0, 0xf3, 0xda, 0x5a, 0x48, 0xaf, 0xe9, 0xac, 0x1e, 0x8f, 0x96, + 0x84, 0x25, 0x93, 0x8e, 0xd5, 0x35, 0xaa, 0xe9, 0xbc, 0xe1, 0x0b, 0x52 }; + static const byte k192_16[] = { + 0xe8, 0xba, 0xab, 0xb4, 0xa1, 0xf3, 0x57, 0x6e, 0x72, 0xe4, 0x71, 0xca, + 0x51, 0x2b, 0x5b, 0x64, 0xfb, 0x25, 0x25, 0x97, 0xfc, 0x80, 0x75, 0xe3 }; + static const byte k192_17[] = { + 0xaf, 0x1e, 0xc0, 0xdf, 0x04, 0xef, 0xde, 0xb6, 0x0d, 0xa4, 0xdf, 0xf5, + 0x89, 0x84, 0x14, 0x91, 0x11, 0xdf, 0xda, 0x2d, 0xef, 0xc1, 0x30, 0x6e, + 0x54, 0x46, 0x2e, 0xc3, 0xac, 0x57, 0xf7, 0x8a }; + static const byte k192_31[] = { + 0x1d, 0x59, 0x4c, 0x1a, 0x06, 0x03, 0x33, 0x60, 0x03, 0x12, 0x1e, 0x69, + 0x81, 0xd8, 0xbe, 0xc6, 0x0a, 0xef, 0x71, 0x7f, 0x62, 0x1e, 0x95, 0xb1, + 0xfb, 0x29, 0x96, 0x61, 0x39, 0x78, 0xbb, 0x5f, 0x52, 0xee, 0xc6, 0xda, + 0xed, 0xd8, 0x48, 0x97 }; +#endif +#ifdef WOLFSSL_AES_256 + static const byte k256_1[] = { + 0xcc, 0xc4, 0x9f, 0xbf, 0x20, 0xf2, 0xac, 0xe7, 0xeb, 0x31, 0xa8, 0xdd, + 0xe2, 0x26, 0x50, 0x6a }; + static const byte k256_8[] = { + 0xea, 0x91, 0xdd, 0x60, 0xe5, 0x9b, 0xd6, 0x8b, 0xad, 0x0d, 0x6e, 0x25, + 0x4b, 0x5e, 0x1c, 0x39 }; + static const byte k256_9[] = { + 0xc9, 0x72, 0x2a, 0x95, 0x51, 0xdf, 0xa2, 0x83, 0x2a, 0xa1, 0xca, 0xe4, + 0x87, 0x82, 0x1e, 0x06, 0x99, 0x12, 0x94, 0xae, 0xbc, 0xe0, 0x98, 0x48 }; + static const byte k256_16[] = { + 0xb3, 0x33, 0x58, 0x13, 0x95, 0xce, 0xdf, 0x83, 0x56, 0xdc, 0x35, 0x6c, + 0x1c, 0xc7, 0x9e, 0x9a, 0x88, 0x5c, 0xb4, 0x98, 0x8e, 0xd6, 0x29, 0xb8 }; + static const byte k256_17[] = { + 0x18, 0x58, 0x36, 0x63, 0x5d, 0xea, 0xaf, 0x4d, 0xa8, 0x27, 0x0c, 0x04, + 0x89, 0x09, 0xae, 0xbf, 0xe9, 0x21, 0x11, 0x66, 0xca, 0x2f, 0xdb, 0x34, + 0xa0, 0x93, 0x69, 0xfe, 0x9b, 0xb8, 0x6b, 0x04 }; + static const byte k256_31[] = { + 0x16, 0x44, 0xf1, 0x8b, 0x57, 0xc7, 0xb3, 0xf1, 0x85, 0x51, 0xbe, 0x73, + 0xff, 0xd0, 0x9c, 0xa7, 0x42, 0xf7, 0xf1, 0x56, 0x26, 0x1d, 0x58, 0x95, + 0xaa, 0xc0, 0x97, 0xc4, 0xb6, 0x4e, 0x02, 0x80, 0xfc, 0x80, 0xbd, 0xac, + 0x45, 0x2c, 0x90, 0x10 }; +#endif + static const word32 bsz[] = { 1, 8, 9, 16, 17, 31 }; + static const word32 bes[] = { 16, 16, 24, 24, 32, 40 }; + const struct { const byte* kek; word32 kekSz; const byte* exp[6]; } ek[] = { +#ifdef WOLFSSL_AES_128 + { kek128, (word32)sizeof(kek128), + { k128_1, k128_8, k128_9, k128_16, k128_17, k128_31 } }, +#endif +#ifdef WOLFSSL_AES_192 + { kek192, (word32)sizeof(kek192), + { k192_1, k192_8, k192_9, k192_16, k192_17, k192_31 } }, +#endif +#ifdef WOLFSSL_AES_256 + { kek256, (word32)sizeof(kek256), + { k256_1, k256_8, k256_9, k256_16, k256_17, k256_31 } }, +#endif + }; + byte ewrap[80]; + byte eback[80]; + byte rpt[64]; + word32 ki, bi, s, t; + + for (t = 0; t < (word32)sizeof(rpt); t++) + rpt[t] = (byte)(0xA0 + t); + + for (ki = 0; ki < (word32)(sizeof(ek) / sizeof(ek[0])); ki++) { + /* boundary-size known-answer + round-trip */ + for (bi = 0; bi < (word32)(sizeof(bsz) / sizeof(bsz[0])); bi++) { + XMEMSET(ewrap, 0, sizeof(ewrap)); + XMEMSET(eback, 0, sizeof(eback)); + ExpectIntEQ(wc_AesKeyWrap_Pad(ek[ki].kek, ek[ki].kekSz, + rpt, bsz[bi], ewrap, sizeof(ewrap), NULL), + (int)bes[bi]); + ExpectBufEQ(ewrap, ek[ki].exp[bi], bes[bi]); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(ek[ki].kek, ek[ki].kekSz, + ewrap, bes[bi], eback, sizeof(eback), NULL), + (int)bsz[bi]); + ExpectBufEQ(eback, rpt, bsz[bi]); + } + + /* exhaustive round-trip for every input size 1..64 */ + for (s = 1; s <= (word32)sizeof(rpt); s++) { + int w; + word32 expW = ((s + 7u) / 8u) * 8u + 8u; + + XMEMSET(ewrap, 0, sizeof(ewrap)); + XMEMSET(eback, 0, sizeof(eback)); + w = wc_AesKeyWrap_Pad(ek[ki].kek, ek[ki].kekSz, + rpt, s, ewrap, sizeof(ewrap), NULL); + ExpectIntEQ(w, (int)expW); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(ek[ki].kek, ek[ki].kekSz, + ewrap, (word32)w, eback, sizeof(eback), NULL), + (int)s); + ExpectBufEQ(eback, rpt, s); + } + + /* in-place (in == out aliasing must be supported) */ + { + byte buf[80]; + int w; + + XMEMSET(buf, 0, sizeof(buf)); + XMEMCPY(buf, data20, sizeof(data20)); + w = wc_AesKeyWrap_Pad(ek[ki].kek, ek[ki].kekSz, buf, + (word32)sizeof(data20), buf, + sizeof(buf), NULL); + ExpectIntGE(w, 0); + ExpectIntEQ(wc_AesKeyUnWrap_Pad(ek[ki].kek, ek[ki].kekSz, buf, + (word32)w, buf, sizeof(buf), NULL), + (int)sizeof(data20)); + ExpectBufEQ(buf, data20, sizeof(data20)); + } + } + } +#endif /* WOLFSSL_AES_128 || WOLFSSL_AES_192 || WOLFSSL_AES_256 */ + + return EXPECT_RESULT(); +} /* END test_wc_AesKeyWrap_Pad */ + +#endif /* HAVE_AES_KEYWRAP && WOLFSSL_AES_KEYWRAP_PADDING */ + /*----------------------------------------------------------------------------* | AES-SIV Test *----------------------------------------------------------------------------*/ @@ -10407,6 +10835,271 @@ 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: run the (un)wrap in software via the op's own Aes (holds the + * KEK) with devId cleared so the _ex call does not re-enter the callback. */ +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(); +} + +#if defined(HAVE_AES_ECB) && !defined(WOLF_CRYPTO_CB_ONLY_AES) +/* Second mock device: services only AES-ECB and declines key wrap. With + * HAVE_AES_ECB the RFC 3394 loops call wc_AesEcbEncrypt/Decrypt per block, which + * dispatch to a registered ECB crypto callback. This device counts those ECB + * dispatches and declines them so the real work falls back to software - proving + * key wrap routes its blocks through the ECB callback even when the device has + * no key wrap support. (Needs a software AES fallback, so not for CB_ONLY_AES.) */ +#define TEST_CRYPTOCB_KEYWRAP_ECB_DEVID 11 + +static int cbKwEcbEncCalled = 0; +static int cbKwEcbDecCalled = 0; + +static int test_CryptoCb_KeyWrapEcb_Cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)ctx; + if (devId != TEST_CRYPTOCB_KEYWRAP_ECB_DEVID) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + if (info->algo_type == WC_ALGO_TYPE_CIPHER && + info->cipher.type == WC_CIPHER_AES_ECB) { + if (info->cipher.enc) + cbKwEcbEncCalled++; + else + cbKwEcbDecCalled++; + } + /* Decline everything (key wrap and ECB): software performs the actual work; + * we only prove the ECB dispatch is reached during key wrap. */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + +int test_wc_CryptoCb_AesKeyWrapEcbCompose(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]; + + cbKwEcbEncCalled = 0; + cbKwEcbDecCalled = 0; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_CRYPTOCB_KEYWRAP_ECB_DEVID, + test_CryptoCb_KeyWrapEcb_Cb, NULL), 0); + + /* wrap: device offers only ECB, so key wrap composes RFC 3394 from ECB + * block dispatches; software fallback produces the KAT output. */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_ECB_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(cbKwEcbEncCalled, 1); /* ECB callback reached during wrap */ + wc_AesFree(&aes); + + /* unwrap: composes from ECB decrypt block dispatches. */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, TEST_CRYPTOCB_KEYWRAP_ECB_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(cbKwEcbDecCalled, 1); /* ECB callback reached during unwrap */ + wc_AesFree(&aes); + + wc_CryptoCb_UnRegisterDevice(TEST_CRYPTOCB_KEYWRAP_ECB_DEVID); + + return EXPECT_RESULT(); +} +#endif /* HAVE_AES_ECB && !WOLF_CRYPTO_CB_ONLY_AES */ + +#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 7b067a21d05..5e67f630bb1 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -96,6 +96,9 @@ int test_wc_AesEaxArgMcdc(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); @@ -162,6 +165,23 @@ int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); #define TEST_CRYPTOCB_AESOFB_DECL #endif +#if defined(WOLF_CRYPTO_CB) && defined(HAVE_AES_KEYWRAP) && \ + !defined(NO_AES) && defined(WOLFSSL_AES_128) +int test_wc_CryptoCb_AesKeyWrap(void); +#if defined(HAVE_AES_ECB) && !defined(WOLF_CRYPTO_CB_ONLY_AES) +int test_wc_CryptoCb_AesKeyWrapEcbCompose(void); +#define TEST_CRYPTOCB_AES_KEYWRAP_ECB_DECL \ + , TEST_DECL_GROUP("aes", test_wc_CryptoCb_AesKeyWrapEcbCompose) +#else +#define TEST_CRYPTOCB_AES_KEYWRAP_ECB_DECL +#endif +#define TEST_CRYPTOCB_AES_KEYWRAP_DECL \ + , TEST_DECL_GROUP("aes", test_wc_CryptoCb_AesKeyWrap) \ + TEST_CRYPTOCB_AES_KEYWRAP_ECB_DECL +#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), \ @@ -228,6 +248,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 \ TEST_CRYPTOCB_AESCFB_DECL \ TEST_CRYPTOCB_AESOFB_DECL @@ -248,6 +269,11 @@ int test_wc_CryptoCb_Tls13_Key_No_Zero_Without_Offload(void); TEST_DECL_GROUP("aes-siv", test_wc_AesSivArgMcdc) #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/tests/swdev/swdev.c b/tests/swdev/swdev.c index 3b69a74cba9..607a8f04de0 100644 --- a/tests/swdev/swdev.c +++ b/tests/swdev/swdev.c @@ -791,6 +791,62 @@ static int swdev_aes_ecb(wc_CryptoInfo* info) } #endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT */ +#if defined(HAVE_AES_KEYWRAP) && !defined(SWDEV_AES_ONLYECB) +/* AES Key Wrap (RFC 3394) and, when built, Key Wrap with Padding (RFC 5649). + * enc selects wrap (forward cipher) vs unwrap (inverse cipher); pad selects the + * RFC 5649 variant. The wrap/unwrap helpers return the produced byte count, + * which the cryptocb contract reports via outResSz with a 0 return. Gated like + * swdev_aes_gcm so SWDEV_AES_ONLYECB instead forces the parent's CB_ONLY_AES + * host-side key wrap (block ops dispatch back through cryptocb ECB). */ +static int swdev_aes_keywrap(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aeskeywrap.aes; + const byte* in = info->cipher.aeskeywrap.in; + word32 inSz = info->cipher.aeskeywrap.inSz; + byte* out = info->cipher.aeskeywrap.out; + word32 outSz = info->cipher.aeskeywrap.outSz; + const byte* iv = info->cipher.aeskeywrap.iv; + Aes shadow; + int ret; + int dir; + + if (info->cipher.enc) + dir = AES_ENCRYPTION; + else + dir = AES_DECRYPTION; + + ret = swdev_aes_shadow_init(&shadow, aes, dir); + if (ret != 0) + return ret; + + if (info->cipher.enc) { +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (info->cipher.aeskeywrap.pad) + ret = wc_AesKeyWrap_Pad_ex(&shadow, in, inSz, out, outSz, iv); + else +#endif + ret = wc_AesKeyWrap_ex(&shadow, in, inSz, out, outSz, iv); + } + else { +#ifdef WOLFSSL_AES_KEYWRAP_PADDING + if (info->cipher.aeskeywrap.pad) + ret = wc_AesKeyUnWrap_Pad_ex(&shadow, in, inSz, out, outSz, iv); + else +#endif + ret = wc_AesKeyUnWrap_ex(&shadow, in, inSz, out, outSz, iv); + } + + wc_AesFree(&shadow); + + if (ret < 0) + return ret; + + /* success: report produced length; cryptocb returns outResSz on ret==0 */ + info->cipher.aeskeywrap.outResSz = (word32)ret; + return 0; +} +#endif /* HAVE_AES_KEYWRAP && !SWDEV_AES_ONLYECB */ + /* SWDEV_AES_ONLYECB: when defined, swdev's AES backend returns * CRYPTOCB_UNAVAILABLE for AES-GCM so the parent's CB_ONLY_AES host-side * GCM software path runs (GHASH on the host; AES-CTR blocks dispatch back @@ -1032,6 +1088,10 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info, #ifdef HAVE_AESCCM case WC_CIPHER_AES_CCM: return swdev_aes_ccm(info); + #endif + #if defined(HAVE_AES_KEYWRAP) && !defined(SWDEV_AES_ONLYECB) + case WC_CIPHER_AES_KEYWRAP: + return swdev_aes_keywrap(info); #endif default: return CRYPTOCB_UNAVAILABLE; diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 46a89a6ad80..55ca5316ce7 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -56,6 +56,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: @@ -16718,8 +16719,9 @@ 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 at out+8, initial A in aiv; writes + * C[0]=A and wrapped R[i] in place. Caller owns output-buffer sizing. */ +static int AesKeyWrapRaw(Aes* aes, word32 inSz, byte* out, const byte* aiv) { word32 i; byte* r; @@ -16729,34 +16731,34 @@ 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); +#ifndef HAVE_AES_ECB + /* Direct block access must save vector registers across the loop; with + * HAVE_AES_ECB wc_AesEcbEncrypt saves them and can route to an ECB cb. */ VECTOR_REGISTERS_PUSH; +#endif for (j = 0; j <= 5; j++) { for (i = 1; i <= inSz / KEYWRAP_BLOCK_SIZE; i++) { /* load R[i] */ XMEMCPY(tmp + KEYWRAP_BLOCK_SIZE, r, KEYWRAP_BLOCK_SIZE); +#ifdef HAVE_AES_ECB + ret = wc_AesEcbEncrypt(aes, tmp, tmp, WC_AES_BLOCK_SIZE); +#else ret = wc_AesEncryptDirect(aes, tmp, tmp); +#endif if (ret != 0) break; @@ -16773,7 +16775,9 @@ int wc_AesKeyWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, r = out + KEYWRAP_BLOCK_SIZE; } +#ifndef HAVE_AES_ECB VECTOR_REGISTERS_POP; +#endif if (ret != 0) return ret; @@ -16781,6 +16785,55 @@ 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]; + + /* >= two 64-bit blocks on a 64-bit boundary, output fits outSz; inSz + * capped at INT_MAX-8 so the returned inSz+8 stays a 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) { + /* wipe the plaintext staged at out+8 (and any partial cipher state + * left there) so it is not leaked to the caller on failure */ + ForceZero(out + KEYWRAP_BLOCK_SIZE, inSz); + return ret; + } + return (int)(inSz + KEYWRAP_BLOCK_SIZE); } @@ -16820,8 +16873,10 @@ 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` to n blocks in + * out and recovered A in 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; @@ -16831,31 +16886,22 @@ 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)); +#ifndef HAVE_AES_ECB + /* Like AesKeyWrapRaw: HAVE_AES_ECB routes each block through wc_AesEcbDecrypt + * (saves registers + ECB cb); otherwise save vector registers here. */ VECTOR_REGISTERS_PUSH; +#endif /* initialize counter to 6n */ n = (inSz - 1) / KEYWRAP_BLOCK_SIZE; @@ -16871,7 +16917,11 @@ int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, /* load R[i], starting at end of R */ r = out + ((i - 1) * KEYWRAP_BLOCK_SIZE); XMEMCPY(tmp + KEYWRAP_BLOCK_SIZE, r, KEYWRAP_BLOCK_SIZE); +#ifdef HAVE_AES_ECB + ret = wc_AesEcbDecrypt(aes, tmp, tmp, WC_AES_BLOCK_SIZE); +#else ret = wc_AesDecryptDirect(aes, tmp, tmp); +#endif if (ret != 0) break; @@ -16882,14 +16932,70 @@ int wc_AesKeyUnWrap_ex(Aes *aes, const byte* in, word32 inSz, byte* out, break; } +#ifndef HAVE_AES_ECB VECTOR_REGISTERS_POP; +#endif 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 + }; + + /* (n+1) >= 3 blocks on a 64-bit boundary, n blocks fit outSz; inSz capped + * at INT_MAX so the returned inSz-8 stays a 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) { + /* IV check failed: wipe the recovered plaintext key material left in + * out before returning so it is not leaked to the caller */ + ForceZero(out, inSz - KEYWRAP_BLOCK_SIZE); return BAD_KEYWRAP_IV_E; + } return (int)(inSz - KEYWRAP_BLOCK_SIZE); } @@ -16932,6 +17038,301 @@ 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 capped at INT_MAX-(2*8-1) so rounding up to whole blocks plus the + * AIV block can't overflow padSz+8; too-small output -> BAD_FUNC_ARG. */ + 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); +#ifdef HAVE_AES_ECB + /* Route through wc_AesEcbEncrypt so an ECB crypto callback can service + * the block; it saves its own registers. */ + ret = wc_AesEcbEncrypt(aes, out, tmp, WC_AES_BLOCK_SIZE); +#else + VECTOR_REGISTERS_PUSH; + ret = wc_AesEncryptDirect(aes, out, tmp); + VECTOR_REGISTERS_POP; +#endif + /* tmp held AIV | plaintext key material */ + ForceZero(tmp, sizeof(tmp)); + } + else { + /* run the RFC 3394 loop with the AIV as the initial value */ + ret = AesKeyWrapRaw(aes, padSz, out, aiv); + } + if (ret != 0) { + /* wipe the plaintext staged at out+8 (and any partial cipher state) + * so it is not leaked to the caller on failure */ + ForceZero(out + KEYWRAP_BLOCK_SIZE, padSz); + 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; + byte a[KEYWRAP_BLOCK_SIZE]; + byte expConst[sizeof(kwpAivConst)]; + + /* (n+1) >= 2 blocks on a 64-bit boundary; inSz capped at INT_MAX so the + * returned MLI stays a non-negative int; too-small output -> BAD_FUNC_ARG. */ + 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]; +#ifdef HAVE_AES_ECB + /* Route through wc_AesEcbDecrypt so an ECB crypto callback can service + * the block; it saves its own registers. */ + ret = wc_AesEcbDecrypt(aes, tmp, in, WC_AES_BLOCK_SIZE); +#else + VECTOR_REGISTERS_PUSH; + ret = wc_AesDecryptDirect(aes, tmp, in); + VECTOR_REGISTERS_POP; +#endif + if (ret == 0) { + XMEMCPY(a, tmp, KEYWRAP_BLOCK_SIZE); + XMEMCPY(out, tmp + KEYWRAP_BLOCK_SIZE, KEYWRAP_BLOCK_SIZE); + } + /* tmp held AIV | plaintext key material */ + ForceZero(tmp, sizeof(tmp)); + } + 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)); + } + + /* MLI = LSB(32,A) in network order */ + mli = ((word32)a[4] << 24) | ((word32)a[5] << 16) | + ((word32)a[6] << 8) | (word32)a[7]; + + /* Validate the three RFC 5649 checks in constant time: fold failures into + * one mask and branch once, so timing does not reveal which check failed. */ + { + word32 dataSz = inSz - KEYWRAP_BLOCK_SIZE; /* 8*n plaintext octets */ + word32 lastBlk = dataSz - KEYWRAP_BLOCK_SIZE; /* offset 8*(n-1) */ + word32 fail; + word32 j; + + /* check 1: MSB(32,A) == constant */ + fail = (word32)ctMaskNotEq(ConstantCompare(a, expConst, + (int)sizeof(kwpAivConst)), 0); + +#ifdef WORD64_AVAILABLE + /* check 2: 8*(n-1) < MLI <= 8*n */ + fail |= ~(ctMaskWord32GTE(mli, lastBlk + 1) /* MLI >= 8*(n-1)+1 */ + & ctMaskWord32GTE(dataSz, mli)); /* 8*n >= MLI */ + + /* check 3: octets in [MLI, 8*n) are zero. A valid MLI is in the final + * block, so scan it at fixed offsets, requiring zero where off >= MLI. */ + for (j = 0; j < KEYWRAP_BLOCK_SIZE; j++) { + word32 off = lastBlk + j; + fail |= ctMaskWord32GTE(off, mli) /* off >= MLI */ + & (word32)ctMaskNotEq((int)out[off], 0); + } +#else + /* No word64: compare in int range. MLI with its high bit set (>= 2^31 + * > 8*n) is forced to fail so the int compares see valid values. */ + { + byte lowMask = (byte)~(byte)(0u - ((mli >> 31) & 1u)); + int mliInt = (int)(mli & 0x7FFFFFFFu); + + /* check 2: 8*(n-1) < MLI <= 8*n */ + fail |= (word32)(byte)~(byte)(ctMaskGT(mliInt, (int)lastBlk) + & ctMaskLTE(mliInt, (int)dataSz) + & lowMask); + + /* check 3: octets in [MLI, 8*n) are zero (see note above). */ + for (j = 0; j < KEYWRAP_BLOCK_SIZE; j++) { + int off = (int)(lastBlk + j); + byte isPad = (byte)(ctMaskGTE(off, mliInt) & lowMask); + fail |= (word32)(byte)(isPad & + ctMaskNotEq((int)out[lastBlk + j], 0)); + } + } +#endif + + if (fail != 0) { + goto badIv; + } + } + + return (int)mli; + +badIv: + /* integrity check failed: wipe the recovered plaintext in out so it is + * not leaked to the caller */ + ForceZero(out, inSz - KEYWRAP_BLOCK_SIZE); + return BAD_KEYWRAP_IV_E; +} + +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 be5eccb0139..4e792896cd3 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -173,6 +173,7 @@ static const char* GetCipherTypeStr(int cipher) case WC_CIPHER_AES_XTS: return "AES XTS"; case WC_CIPHER_AES_CFB: return "AES CFB"; case WC_CIPHER_AES_OFB: return "AES OFB"; + 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"; @@ -2142,6 +2143,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 feed8d31c6a..1ba2d59a1a7 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -900,6 +900,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aesgcm_siv_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); @@ -2949,6 +2952,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) @@ -23056,8 +23065,587 @@ 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: RFC 5649 s6 for 192-bit, OpenSSL + * aes-wrap-pad for 128/256 (cross-checked). 20-octet = loop, 7 = ECB. */ + + /* 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); + } + } + + /* Everything below indexes test_kwp[0]; when no AES key size is enabled + * test_kwp[] is empty (kwpSz == 0), so skip it to stay in-bounds. */ + if (kwpSz == 0) + return 0; + + /* 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); + } + + /* Negative: invalid arguments must be rejected with BAD_FUNC_ARG. */ + if (wc_AesKeyWrap_Pad(NULL, test_kwp[0].kekLen, test_kwp[0].data, + test_kwp[0].dataLen, output, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, NULL, + test_kwp[0].dataLen, output, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, test_kwp[0].data, + 0, output, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, test_kwp[0].data, + test_kwp[0].dataLen, NULL, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + /* output buffer too small: 7 octets need 16 wrapped bytes, give 8 */ + if (wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, kwpData2, + (word32)sizeof(kwpData2), output, 8, NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyUnWrap_Pad(NULL, test_kwp[0].kekLen, output, 16, plain, + sizeof(plain), NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, NULL, 16, plain, + sizeof(plain), NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + /* ciphertext length must be a multiple of 8 and at least 16 octets */ + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, output, 8, plain, + sizeof(plain), NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, output, 17, + plain, sizeof(plain), NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + /* unwrap output buffer smaller than the recovered padded plaintext */ + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, output, 16, + plain, 4, NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + /* an inSz that would overflow ceil(inSz/8)+1 blocks is rejected up front */ + if (wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, test_kwp[0].data, + 0xFFFFFFF1U, output, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + + /* Negative: an invalid KEK size drives the SetKey-failure path in the + * wrappers (20 octets is not a valid AES key length). */ + { + WOLFSSL_SMALL_STACK_STATIC const byte badKek[20] = { + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c + }; + if (wc_AesKeyWrap_Pad(badKek, (word32)sizeof(badKek), kwpData2, + (word32)sizeof(kwpData2), output, sizeof(output), NULL) + != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyUnWrap_Pad(badKek, (word32)sizeof(badKek), output, 16, + plain, sizeof(plain), NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + } + + /* IV override: a custom 4-byte AIV round-trips, and the same blob is + * rejected under the default AIV (check 1, AIV-constant mismatch). */ + { + WOLFSSL_SMALL_STACK_STATIC const byte altIv[4] = { + 0x12, 0x34, 0x56, 0x78 + }; + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + wrapSz = wc_AesKeyWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, kwpData2, + (word32)sizeof(kwpData2), output, sizeof(output), altIv); + if (wrapSz < 0) + return WC_TEST_RET_ENC_EC(wrapSz); + plainSz = wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, + output, (word32)wrapSz, plain, sizeof(plain), altIv); + if ((plainSz != (int)sizeof(kwpData2)) || + (XMEMCMP(plain, kwpData2, sizeof(kwpData2)) != 0)) + return WC_TEST_RET_ENC_NC; + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, output, + (word32)wrapSz, plain, sizeof(plain), NULL) + != WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)) + return WC_TEST_RET_ENC_NC; + } + +#if defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_192) || \ + defined(WOLFSSL_AES_256) + /* Boundary-size KATs (pyca/cryptography, reproduces RFC 5649 s6) plus an + * exhaustive 1..31 round-trip across every enabled key size. */ + { +#ifdef WOLFSSL_AES_128 + WOLFSSL_SMALL_STACK_STATIC const byte kb128_1[] = { + 0xdc, 0x0c, 0xed, 0x32, 0x50, 0xa4, 0x92, 0x77, 0x59, 0xb4, 0xe9, 0x28, + 0x73, 0x2e, 0x16, 0x8a }; + WOLFSSL_SMALL_STACK_STATIC const byte kb128_8[] = { + 0x23, 0xe1, 0xcd, 0x73, 0x92, 0xf0, 0xcc, 0x69, 0xdc, 0x20, 0xdf, 0x56, + 0x48, 0x9f, 0xfd, 0xd7 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb128_9[] = { + 0xe6, 0xe7, 0x6a, 0xfc, 0xf1, 0xf7, 0x1f, 0x43, 0x63, 0x4b, 0x96, 0x91, + 0x9c, 0x1e, 0x36, 0xa9, 0x5b, 0xf7, 0xb0, 0x26, 0x0f, 0x51, 0x9b, 0x4b }; + WOLFSSL_SMALL_STACK_STATIC const byte kb128_16[] = { + 0xa7, 0x41, 0x46, 0x28, 0x3b, 0x00, 0x85, 0x06, 0x23, 0xd3, 0x02, 0xf4, + 0x57, 0xb1, 0x8c, 0x96, 0xac, 0xe5, 0xb5, 0xd3, 0x64, 0x7c, 0xc2, 0xf1 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb128_17[] = { + 0x8d, 0x69, 0xe5, 0xc5, 0x01, 0x98, 0x70, 0xd3, 0x50, 0x37, 0x3a, 0x00, + 0xa8, 0xe3, 0xa5, 0x32, 0xdf, 0xce, 0x76, 0x8a, 0x6b, 0x79, 0xef, 0x2c, + 0x34, 0xcf, 0xed, 0x5c, 0xb4, 0x09, 0xff, 0xf4 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb128_31[] = { + 0x50, 0x3e, 0xc4, 0xff, 0x2e, 0xd3, 0x01, 0x14, 0xfa, 0x5a, 0x02, 0x47, + 0x9f, 0x20, 0x4c, 0xb1, 0xd8, 0xcb, 0xa2, 0xa3, 0xa3, 0x7d, 0x7b, 0xa5, + 0x60, 0x77, 0x01, 0x46, 0xd6, 0x03, 0x93, 0xe0, 0xf0, 0x01, 0xf7, 0x88, + 0xb0, 0x4b, 0xc6, 0xb2 }; +#endif +#ifdef WOLFSSL_AES_192 + WOLFSSL_SMALL_STACK_STATIC const byte kb192_1[] = { + 0x95, 0xf7, 0xba, 0x0a, 0x72, 0x6e, 0xed, 0x9a, 0x90, 0xa9, 0x90, 0x00, + 0x94, 0xc5, 0xd9, 0x2d }; + WOLFSSL_SMALL_STACK_STATIC const byte kb192_8[] = { + 0xaa, 0x6f, 0x7d, 0x3b, 0xab, 0x34, 0x91, 0xcc, 0xd9, 0x52, 0xc9, 0x86, + 0x64, 0x42, 0x8c, 0x40 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb192_9[] = { + 0x52, 0xa0, 0xf3, 0xda, 0x5a, 0x48, 0xaf, 0xe9, 0xac, 0x1e, 0x8f, 0x96, + 0x84, 0x25, 0x93, 0x8e, 0xd5, 0x35, 0xaa, 0xe9, 0xbc, 0xe1, 0x0b, 0x52 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb192_16[] = { + 0xe8, 0xba, 0xab, 0xb4, 0xa1, 0xf3, 0x57, 0x6e, 0x72, 0xe4, 0x71, 0xca, + 0x51, 0x2b, 0x5b, 0x64, 0xfb, 0x25, 0x25, 0x97, 0xfc, 0x80, 0x75, 0xe3 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb192_17[] = { + 0xaf, 0x1e, 0xc0, 0xdf, 0x04, 0xef, 0xde, 0xb6, 0x0d, 0xa4, 0xdf, 0xf5, + 0x89, 0x84, 0x14, 0x91, 0x11, 0xdf, 0xda, 0x2d, 0xef, 0xc1, 0x30, 0x6e, + 0x54, 0x46, 0x2e, 0xc3, 0xac, 0x57, 0xf7, 0x8a }; + WOLFSSL_SMALL_STACK_STATIC const byte kb192_31[] = { + 0x1d, 0x59, 0x4c, 0x1a, 0x06, 0x03, 0x33, 0x60, 0x03, 0x12, 0x1e, 0x69, + 0x81, 0xd8, 0xbe, 0xc6, 0x0a, 0xef, 0x71, 0x7f, 0x62, 0x1e, 0x95, 0xb1, + 0xfb, 0x29, 0x96, 0x61, 0x39, 0x78, 0xbb, 0x5f, 0x52, 0xee, 0xc6, 0xda, + 0xed, 0xd8, 0x48, 0x97 }; +#endif +#ifdef WOLFSSL_AES_256 + WOLFSSL_SMALL_STACK_STATIC const byte kb256_1[] = { + 0xcc, 0xc4, 0x9f, 0xbf, 0x20, 0xf2, 0xac, 0xe7, 0xeb, 0x31, 0xa8, 0xdd, + 0xe2, 0x26, 0x50, 0x6a }; + WOLFSSL_SMALL_STACK_STATIC const byte kb256_8[] = { + 0xea, 0x91, 0xdd, 0x60, 0xe5, 0x9b, 0xd6, 0x8b, 0xad, 0x0d, 0x6e, 0x25, + 0x4b, 0x5e, 0x1c, 0x39 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb256_9[] = { + 0xc9, 0x72, 0x2a, 0x95, 0x51, 0xdf, 0xa2, 0x83, 0x2a, 0xa1, 0xca, 0xe4, + 0x87, 0x82, 0x1e, 0x06, 0x99, 0x12, 0x94, 0xae, 0xbc, 0xe0, 0x98, 0x48 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb256_16[] = { + 0xb3, 0x33, 0x58, 0x13, 0x95, 0xce, 0xdf, 0x83, 0x56, 0xdc, 0x35, 0x6c, + 0x1c, 0xc7, 0x9e, 0x9a, 0x88, 0x5c, 0xb4, 0x98, 0x8e, 0xd6, 0x29, 0xb8 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb256_17[] = { + 0x18, 0x58, 0x36, 0x63, 0x5d, 0xea, 0xaf, 0x4d, 0xa8, 0x27, 0x0c, 0x04, + 0x89, 0x09, 0xae, 0xbf, 0xe9, 0x21, 0x11, 0x66, 0xca, 0x2f, 0xdb, 0x34, + 0xa0, 0x93, 0x69, 0xfe, 0x9b, 0xb8, 0x6b, 0x04 }; + WOLFSSL_SMALL_STACK_STATIC const byte kb256_31[] = { + 0x16, 0x44, 0xf1, 0x8b, 0x57, 0xc7, 0xb3, 0xf1, 0x85, 0x51, 0xbe, 0x73, + 0xff, 0xd0, 0x9c, 0xa7, 0x42, 0xf7, 0xf1, 0x56, 0x26, 0x1d, 0x58, 0x95, + 0xaa, 0xc0, 0x97, 0xc4, 0xb6, 0x4e, 0x02, 0x80, 0xfc, 0x80, 0xbd, 0xac, + 0x45, 0x2c, 0x90, 0x10 }; +#endif + WOLFSSL_SMALL_STACK_STATIC const word32 bsz[] = { 1, 8, 9, 16, 17, 31 }; + WOLFSSL_SMALL_STACK_STATIC const word32 bes[] = { 16, 16, 24, 24, 32, 40 }; + struct { const byte* kek; word32 kekSz; const byte* exp[6]; } ek[] = { +#ifdef WOLFSSL_AES_128 + { kwpKek128, (word32)sizeof(kwpKek128), + { kb128_1, kb128_8, kb128_9, kb128_16, kb128_17, kb128_31 } }, +#endif +#ifdef WOLFSSL_AES_192 + { kwpKek192, (word32)sizeof(kwpKek192), + { kb192_1, kb192_8, kb192_9, kb192_16, kb192_17, kb192_31 } }, +#endif +#ifdef WOLFSSL_AES_256 + { kwpKek256, (word32)sizeof(kwpKek256), + { kb256_1, kb256_8, kb256_9, kb256_16, kb256_17, kb256_31 } }, +#endif + }; + byte rtin[32]; + int ekSz = (int)(sizeof(ek) / sizeof(ek[0])); + int bSz = (int)(sizeof(bsz) / sizeof(bsz[0])); + int ki, bi, s, t; + + for (t = 0; t < (int)sizeof(rtin); t++) + rtin[t] = (byte)(0xA0 + t); + + for (ki = 0; ki < ekSz; ki++) { + /* boundary-size known-answer + round-trip */ + for (bi = 0; bi < bSz; bi++) { + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + wrapSz = wc_AesKeyWrap_Pad(ek[ki].kek, ek[ki].kekSz, + rtin, bsz[bi], output, sizeof(output), NULL); + if ((wrapSz < 0) || (wrapSz != (int)bes[bi])) + return WC_TEST_RET_ENC_I(ki * 100 + bi); + if (XMEMCMP(output, ek[ki].exp[bi], bes[bi]) != 0) + return WC_TEST_RET_ENC_I(ki * 100 + bi); + plainSz = wc_AesKeyUnWrap_Pad(ek[ki].kek, ek[ki].kekSz, output, + (word32)wrapSz, plain, sizeof(plain), NULL); + if ((plainSz != (int)bsz[bi]) || + (XMEMCMP(plain, rtin, bsz[bi]) != 0)) + return WC_TEST_RET_ENC_I(ki * 100 + bi); + } + + /* exhaustive round-trip for every input size 1..31 */ + for (s = 1; s <= (int)sizeof(rtin) - 1; s++) { + int ew = (((s + 7) / 8) * 8) + 8; + XMEMSET(output, 0, sizeof(output)); + XMEMSET(plain, 0, sizeof(plain)); + wrapSz = wc_AesKeyWrap_Pad(ek[ki].kek, ek[ki].kekSz, + rtin, (word32)s, output, sizeof(output), NULL); + if (wrapSz != ew) + return WC_TEST_RET_ENC_I(ki * 100 + 30 + s); + plainSz = wc_AesKeyUnWrap_Pad(ek[ki].kek, ek[ki].kekSz, output, + (word32)wrapSz, plain, sizeof(plain), NULL); + if ((plainSz != s) || + (XMEMCMP(plain, rtin, (word32)s) != 0)) + return WC_TEST_RET_ENC_I(ki * 100 + 30 + s); + } + } + } +#endif /* WOLFSSL_AES_128 || WOLFSSL_AES_192 || WOLFSSL_AES_256 */ + +#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 the single-block + * and RFC 3394 loop paths, exercising the XMEMMOVE staging. */ + 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); + } + + /* Forge decrypted blocks AIV(8)|P(8) to drive unwrap checks 2 (MLI range) + * and 3 (nonzero pad); ECB-encrypting a chosen block fixes what unwrap sees. */ + { + Aes* faes = (Aes*)XMALLOC(sizeof(Aes), HEAP_HINT, DYNAMIC_TYPE_AES); + byte forgeBlk[WC_AES_BLOCK_SIZE]; + byte forged[WC_AES_BLOCK_SIZE]; + int fr; + + if (faes == NULL) + return WC_TEST_RET_ENC_NC; + + /* check 2: correct AIV constant but MLI = 0 */ + XMEMSET(forgeBlk, 0, sizeof(forgeBlk)); + forgeBlk[0] = 0xa6; forgeBlk[1] = 0x59; forgeBlk[2] = 0x59; + forgeBlk[3] = 0xa6; + forgeBlk[8] = 0x11; /* arbitrary recovered plaintext octet */ + fr = wc_AesInit(faes, HEAP_HINT, devId); + if (fr == 0) + fr = wc_AesSetKey(faes, test_kwp[0].kek, test_kwp[0].kekLen, NULL, + AES_ENCRYPTION); + if (fr == 0) + fr = wc_AesEncryptDirect(faes, forged, forgeBlk); + wc_AesFree(faes); + if (fr != 0) { + XFREE(faes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_EC(fr); + } + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, forged, 16, + plain, sizeof(plain), NULL) + != WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)) { + XFREE(faes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + + /* 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 */ + fr = wc_AesInit(faes, HEAP_HINT, devId); + if (fr == 0) + fr = wc_AesSetKey(faes, test_kwp[0].kek, test_kwp[0].kekLen, NULL, + AES_ENCRYPTION); + if (fr == 0) + fr = wc_AesEncryptDirect(faes, forged, forgeBlk); + wc_AesFree(faes); + if (fr != 0) { + XFREE(faes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_EC(fr); + } + if (wc_AesKeyUnWrap_Pad(test_kwp[0].kek, test_kwp[0].kekLen, forged, 16, + plain, sizeof(plain), NULL) + != WC_NO_ERR_TRACE(BAD_KEYWRAP_IV_E)) { + XFREE(faes, HEAP_HINT, DYNAMIC_TYPE_AES); + return WC_TEST_RET_ENC_NC; + } + + XFREE(faes, HEAP_HINT, DYNAMIC_TYPE_AES); + } +#endif /* !HAVE_FIPS && !HAVE_SELFTEST */ + + return 0; +} +#endif /* WOLFSSL_AES_KEYWRAP_PADDING */ #endif /* HAVE_AES_KEYWRAP */ #endif /* !NO_AES */ @@ -77335,6 +77923,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) { @@ -79133,6 +79782,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 183aad072f8..5901b9f24e9 100644 --- a/wolfssl/wolfcrypt/aes.h +++ b/wolfssl/wolfcrypt/aes.h @@ -709,6 +709,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, @@ -725,6 +732,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 8d2e587e542..fd8c99e32a7 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -492,6 +492,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 @@ -939,6 +951,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 af98e43ba30..b1ef4f15248 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1560,6 +1560,7 @@ enum wc_CipherType { WC_CIPHER_AES_CCM = 12, WC_CIPHER_AES_ECB = 13, WC_CIPHER_AES_OFB = 14, + WC_CIPHER_AES_KEYWRAP = 15, WC_CIPHER_DES3 = 7, WC_CIPHER_DES = 8, WC_CIPHER_CHACHA = 9,