diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 25f2b6f881..39ac468dd0 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -266,6 +266,21 @@ jobs: {"name": "cryptocb-aesgcm-setkey-free", "minutes": 2.1, "configure": ["--enable-cryptocb", "--enable-aesgcm", "CPPFLAGS=-DWOLF_CRYPTO_CB_AES_SETKEY -DWOLF_CRYPTO_CB_FREE"]}, + {"name": "ecies-sec1-gcm-static-nonce", "minutes": 2.0, + "comment": "ECIES with the AES-GCM DEM in the default SEC1 IV mode; WOLFSSL_ECIES_STATIC_GCM_NONCE opts into the fixed-nonce GCM path so the GCM KAT/round-trip and cryptocb tests run.", + "configure": ["--enable-eccencrypt", "--enable-aesgcm", "--enable-aesctr", + "--enable-x963kdf", "--enable-cryptocb", "--enable-keygen", + "CPPFLAGS=-DWOLFSSL_ECIES_STATIC_GCM_NONCE"]}, + {"name": "ecies-geniv-gcm-static-nonce", "minutes": 2.0, + "comment": "Same ECIES-GCM coverage in the WOLFSSL_ECIES_GEN_IV mode (random embedded nonce).", + "configure": ["--enable-eccencrypt=geniv", "--enable-aesgcm", "--enable-aesctr", + "--enable-x963kdf", "--enable-cryptocb", "--enable-keygen", + "CPPFLAGS=-DWOLFSSL_ECIES_STATIC_GCM_NONCE"]}, + {"name": "ecies-old-gcm-static-nonce", "minutes": 2.0, + "comment": "Same ECIES-GCM coverage in the legacy WOLFSSL_ECIES_OLD mode (KDF-derived nonce, no ephemeral pubkey prepended).", + "configure": ["--enable-eccencrypt=old", "--enable-aesgcm", "--enable-aesctr", + "--enable-x963kdf", "--enable-cryptocb", "--enable-keygen", + "CPPFLAGS=-DWOLFSSL_ECIES_STATIC_GCM_NONCE"]}, {"name": "opensslextra-x509small", "minutes": 2.0, "configure": ["--enable-opensslextra=x509small"]}, {"name": "cryptocb-keygen-find", "minutes": 2.0, diff --git a/doc/dox_comments/header_files/ecc.h b/doc/dox_comments/header_files/ecc.h index c754d19c9a..f8f9f46d0a 100644 --- a/doc/dox_comments/header_files/ecc.h +++ b/doc/dox_comments/header_files/ecc.h @@ -2025,6 +2025,11 @@ int wc_ecc_ctx_set_info(ecEncCtx* ctx, const byte* info, int sz); \param ctx Optional: pointer to an ecEncCtx object specifying different encryption algorithms to use + \note Selecting an AES-GCM DEM algorithm (ecAES_128_GCM, ecAES_256_GCM) in + the default IV mode requires the WOLFSSL_ECIES_STATIC_GCM_NONCE build macro; + otherwise this function returns NOT_COMPILED_IN. See wc_ecc_encrypt_ex for + the full rationale. + _Example_ \code byte msg[] = { initialize with msg to encrypt. Ensure padded to block size }; @@ -2072,6 +2077,9 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, small to store the encrypted ciphertext \return MEMORY_E Returned if there is an error allocating memory for the shared secret key + \return NOT_COMPILED_IN Returned if an AES-GCM DEM algorithm + (ecAES_128_GCM or ecAES_256_GCM) is requested in the default IV mode + without the WOLFSSL_ECIES_STATIC_GCM_NONCE build macro defined \param privKey pointer to the ecc_key object containing the private key to use for encryption @@ -2088,6 +2096,16 @@ int wc_ecc_encrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, encryption algorithms to use \param compressed Public key field is to be output in compressed format. + \note The AES-GCM DEM algorithms (ecAES_128_GCM, ecAES_256_GCM) in the + default IV mode use a fixed all-zero GCM nonce. That is safe only because + ECIES derives a fresh symmetric key from a fresh ephemeral key on every + encryption, so the (key, nonce) pair never repeats; reusing the ephemeral + key is catastrophic for AES-GCM. For that reason the fixed-nonce GCM DEM is + off by default and must be enabled with the WOLFSSL_ECIES_STATIC_GCM_NONCE + build macro, otherwise this function returns NOT_COMPILED_IN for a GCM + algorithm. The macro is not needed with WOLFSSL_ECIES_GEN_IV (random + per-message nonce) or WOLFSSL_ECIES_OLD (nonce derived from the KDF output). + _Example_ \code byte msg[] = { initialize with msg to encrypt. Ensure padded to block size }; @@ -2136,6 +2154,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, small to store the decrypted plaintext \return MEMORY_E Returned if there is an error allocating memory for the shared secret key + \return NOT_COMPILED_IN Returned if an AES-GCM DEM algorithm + (ecAES_128_GCM or ecAES_256_GCM) is requested in the default IV mode + without the WOLFSSL_ECIES_STATIC_GCM_NONCE build macro defined \param privKey pointer to the ecc_key object containing the private key to use for decryption @@ -2150,6 +2171,11 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, \param ctx Optional: pointer to an ecEncCtx object specifying different decryption algorithms to use + \note Selecting an AES-GCM DEM algorithm (ecAES_128_GCM, ecAES_256_GCM) in + the default IV mode requires the WOLFSSL_ECIES_STATIC_GCM_NONCE build macro; + otherwise this function returns NOT_COMPILED_IN. See wc_ecc_encrypt_ex for + the full rationale. + _Example_ \code byte cipher[] = { initialize with diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 7ac7d1fb7e..473570e94e 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -1402,14 +1402,97 @@ int test_wc_ecc_ctx_set_info(void) return EXPECT_RESULT(); } /* END test_wc_ecc_ctx_set_info */ +/* + * Testing the crypto-callback context accessors wc_ecc_ctx_get_algo, + * wc_ecc_ctx_get_kdf_salt and wc_ecc_ctx_get_info (built only when + * WOLF_CRYPTO_CB is enabled). + */ +int test_wc_ecc_ctx_getters(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) + ecEncCtx* ctx = NULL; + WC_RNG rng; + byte encAlgo = 0, kdfAlgo = 0, macAlgo = 0; + const byte* got = NULL; + word32 gotSz = 0; + WOLFSSL_SMALL_STACK_STATIC const byte salt[EXCHANGE_SALT_SZ] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f + }; + const char* info = "ctx getter info"; + word32 infoSz = (word32)XSTRLEN(info); + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(ctx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng)); + + /* get_algo: set then read back */ + ExpectIntEQ(wc_ecc_ctx_set_algo(ctx, ecAES_256_GCM, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + ExpectIntEQ(wc_ecc_ctx_get_algo(ctx, &encAlgo, &kdfAlgo, &macAlgo), 0); + ExpectIntEQ(encAlgo, ecAES_256_GCM); + ExpectIntEQ(kdfAlgo, ecHKDF_SHA256); + ExpectIntEQ(macAlgo, ecHMAC_SHA256); + /* individual NULL out-params are allowed (skipped) */ + encAlgo = 0; + ExpectIntEQ(wc_ecc_ctx_get_algo(ctx, &encAlgo, NULL, NULL), 0); + ExpectIntEQ(encAlgo, ecAES_256_GCM); + /* bad arg: NULL ctx */ + ExpectIntEQ(wc_ecc_ctx_get_algo(NULL, &encAlgo, &kdfAlgo, &macAlgo), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* get_kdf_salt: set then read back */ + ExpectIntEQ(wc_ecc_ctx_set_kdf_salt(ctx, salt, (word32)sizeof(salt)), 0); + ExpectIntEQ(wc_ecc_ctx_get_kdf_salt(ctx, &got, &gotSz), 0); + ExpectIntEQ(gotSz, (word32)sizeof(salt)); + ExpectNotNull(got); + ExpectIntEQ(XMEMCMP(got, salt, sizeof(salt)), 0); + /* bad args */ + ExpectIntEQ(wc_ecc_ctx_get_kdf_salt(NULL, &got, &gotSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_kdf_salt(ctx, NULL, &gotSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_kdf_salt(ctx, &got, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* get_info: set then read back */ + got = NULL; gotSz = 0; + ExpectIntEQ(wc_ecc_ctx_set_info(ctx, (const byte*)info, (int)infoSz), 0); + ExpectIntEQ(wc_ecc_ctx_get_info(ctx, &got, &gotSz), 0); + ExpectIntEQ(gotSz, infoSz); + ExpectNotNull(got); + ExpectIntEQ(XMEMCMP(got, info, infoSz), 0); + /* bad args */ + ExpectIntEQ(wc_ecc_ctx_get_info(NULL, &got, &gotSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_info(ctx, NULL, &gotSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_ecc_ctx_get_info(ctx, &got, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + wc_ecc_ctx_free(ctx); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ctx_getters */ + /* * Testing wc_ecc_encrypt() and wc_ecc_decrypt() */ int test_wc_ecc_encryptDecrypt(void) { EXPECT_DECLS; +/* The test drives the default DEM (NULL ctx). That is AES-CBC when available; + * otherwise ecc_ctx_init falls back to AES-GCM, which in the default IV mode + * needs an opt-in (GEN_IV, OLD, or STATIC_GCM_NONCE) or wc_ecc_encrypt returns + * NOT_COMPILED_IN. Only build the test when the default DEM actually works. */ #if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ - defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) ecc_key srvKey; ecc_key cliKey; ecc_key tmpKey; @@ -1510,6 +1593,304 @@ int test_wc_ecc_encryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_ecc_encryptDecrypt */ +/* + * Testing ECIES with the AES-256-GCM DEM. Exercises, each with its own + * single-use client/server ctx pair: + * tc 0: round-trip succeeds and matches + * tc 1: tag corruption -> decrypt rejected (GCM authentication) + * tc 2: encrypt into too-small output buffer -> BUFFER_E + * tc 3: decrypt truncated input -> BAD_FUNC_ARG + * tc 4: decrypt into too-small plaintext buffer -> BUFFER_E + */ +int test_wc_ecc_ecies_gcm(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_256) && \ + defined(HAVE_HKDF) && defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) && \ + !defined(WOLFSSL_NO_MALLOC) + WC_RNG rng; + int tc; + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + + for (tc = 0; tc <= 4 && EXPECT_SUCCESS(); tc++) { + ecc_key cliKey; + ecc_key srvKey; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt = NULL; + /* OLD format has no embedded ephemeral key, so decrypt needs the + * sender's public key; newer formats read it from the message. */ +#ifdef WOLFSSL_ECIES_OLD + ecc_key* decPub; +#else + ecc_key* decPub = NULL; +#endif + int i; + + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &srvKey), 0); +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); +#endif +#ifdef WOLFSSL_ECIES_OLD + decPub = &cliKey; +#endif + + ExpectNotNull(cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &rng)); + ExpectNotNull(srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &rng)); + ExpectIntEQ(wc_ecc_ctx_set_algo(cliCtx, ecAES_256_GCM, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + ExpectIntEQ(wc_ecc_ctx_set_algo(srvCtx, ecAES_256_GCM, ecHKDF_SHA256, + ecHMAC_SHA256), 0); + + /* exchange salts */ + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx)); + if (tmpSalt != NULL) + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectNotNull(tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx)); + if (tmpSalt != NULL) + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt), 0); + ExpectIntEQ(wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt), 0); + + if (tc == 2) { + /* encrypt into a buffer too small for pubKey+nonce+ct+tag */ + word32 smallSz = 8; + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &smallSz, cliCtx), WC_NO_ERR_TRACE(BUFFER_E)); + } + else { + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, + &outSz, cliCtx), 0); + + if (tc == 0) { + ExpectIntEQ(wc_ecc_decrypt(&srvKey, decPub, out, outSz, plain, + &plainSz, srvCtx), 0); + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + } + else if (tc == 1) { + /* flip a bit in the trailing GCM tag -> auth must reject */ + if (EXPECT_SUCCESS() && outSz > 0) + out[outSz - 1] ^= 0x01; + ExpectIntNE(wc_ecc_decrypt(&srvKey, decPub, out, outSz, plain, + &plainSz, srvCtx), 0); + } + else if (tc == 3) { + /* truncated input: below the smallest valid size in any IV + * mode (OLD needs only the tag, so use < digestSz) */ + ExpectIntEQ(wc_ecc_decrypt(&srvKey, decPub, out, 8, plain, + &plainSz, srvCtx), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + } + else { /* tc == 4: plaintext buffer too small */ + plainSz = 4; + ExpectIntEQ(wc_ecc_decrypt(&srvKey, decPub, out, outSz, plain, + &plainSz, srvCtx), WC_NO_ERR_TRACE(BUFFER_E)); + } + } + + wc_ecc_ctx_free(cliCtx); + wc_ecc_ctx_free(srvCtx); + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + } + + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_gcm */ + +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) +/* CryptoCb that services ECIES by forwarding to software (proving the dispatch + * path is reached), and reports UNAVAILABLE for everything else. The registered + * ctx is an int* invocation flag, set per direction; passing it through ctx (not + * a shared global) keeps the test safe if run concurrently. */ +static int myEciesApiCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + int* invoked = (int*)ctx; + if (info->algo_type == WC_ALGO_TYPE_PK) { + if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + if (invoked != NULL) + *invoked = 1; + info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, + info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, + info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, + info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, + info->pk.eciesencrypt.compressed); + info->pk.eciesencrypt.privKey->devId = devIdArg; + } + else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + if (invoked != NULL) + *invoked = 1; + info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, + info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, + info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, + info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); + info->pk.eciesdecrypt.privKey->devId = devIdArg; + } + } + return ret; +} +#endif + +/* + * Testing ECIES encrypt/decrypt dispatch through the CryptoCb framework. + */ +int test_wc_ecc_ecies_cryptocb(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) && \ + (defined(HAVE_AES_CBC) || \ + (defined(HAVE_AESGCM) && (defined(WOLFSSL_ECIES_GEN_IV) || \ + defined(WOLFSSL_ECIES_OLD) || \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE)))) && defined(WOLFSSL_AES_128) + const int cbDevId = 0x45434230; /* 'ECB0' */ + ecc_key cliKey; + ecc_key srvKey; + WC_RNG rng; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + int i; + int registered = 0; + int cbInvoked = 0; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(cbDevId, myEciesApiCryptoCb, + &cbInvoked), 0); + if (EXPECT_SUCCESS()) + registered = 1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + /* build keys with software, then route ECIES through the callback */ + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &srvKey), 0); +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ExpectIntEQ(wc_ecc_set_rng(&cliKey, &rng), 0); + ExpectIntEQ(wc_ecc_set_rng(&srvKey, &rng), 0); +#endif + cliKey.devId = cbDevId; + srvKey.devId = cbDevId; + + cbInvoked = 0; + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + NULL), 0); + /* callback must have serviced the encrypt */ + ExpectIntEQ(cbInvoked, 1); + + cbInvoked = 0; + /* OLD format needs the sender's public key supplied; newer formats take + * NULL and read the ephemeral key from the message. */ +#ifdef WOLFSSL_ECIES_OLD + ExpectIntEQ(wc_ecc_decrypt(&srvKey, &cliKey, out, outSz, plain, &plainSz, + NULL), 0); +#else + ExpectIntEQ(wc_ecc_decrypt(&srvKey, NULL, out, outSz, plain, &plainSz, + NULL), 0); +#endif + ExpectIntEQ(cbInvoked, 1); + ExpectIntEQ(plainSz, sizeof(msg)); + ExpectIntEQ(XMEMCMP(plain, msg, sizeof(msg)), 0); + + cliKey.devId = INVALID_DEVID; + srvKey.devId = INVALID_DEVID; + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); + if (registered) + wc_CryptoCb_UnRegisterDevice(cbDevId); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_cryptocb */ + +/* + * The ECIES AES-GCM DEM needs an RNG only in GEN_IV mode, where it generates a + * random per-message nonce (default mode uses a fixed nonce and OLD derives it + * from the KDF - neither needs an RNG). MISSING_RNG_E is therefore observable + * only when GCM is the *default* DEM (no AES-CBC/CTR in the build) AND + * WOLFSSL_ECIES_GEN_IV is set, so that encrypting with a NULL context (no RNG) + * and a key with no RNG hits the guard. Otherwise the test compiles out. + */ +int test_wc_ecc_ecies_gcm_no_rng(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) && !defined(WC_NO_RNG) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(HAVE_AES_CBC) && \ + !defined(WOLFSSL_AES_COUNTER) && defined(WOLFSSL_ECIES_GEN_IV) && \ + !defined(WOLFSSL_NO_MALLOC) && \ + (defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_256)) + WC_RNG rng; + ecc_key cliKey; + ecc_key srvKey; + byte msg[32]; + byte out[256]; + word32 outSz = (word32)sizeof(out); + int i; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&cliKey, 0, sizeof(cliKey)); + XMEMSET(&srvKey, 0, sizeof(srvKey)); + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&cliKey), 0); + ExpectIntEQ(wc_ecc_init(&srvKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &cliKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &srvKey), 0); + + /* Deliberately do NOT call wc_ecc_set_rng() on cliKey, and pass a NULL + * context so no RNG is available for the GCM nonce. */ + ExpectIntEQ(wc_ecc_encrypt(&cliKey, &srvKey, msg, sizeof(msg), out, &outSz, + NULL), WC_NO_ERR_TRACE(MISSING_RNG_E)); + + wc_ecc_free(&srvKey); + wc_ecc_free(&cliKey); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_ecies_gcm_no_rng */ + /* * Testing wc_ecc_del_point() and wc_ecc_new_point() */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index bb3a7f58b0..33cb7c57ef 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -51,7 +51,11 @@ int test_wc_ecc_ctx_new(void); int test_wc_ecc_ctx_reset(void); int test_wc_ecc_ctx_set_peer_salt(void); int test_wc_ecc_ctx_set_info(void); +int test_wc_ecc_ctx_getters(void); int test_wc_ecc_encryptDecrypt(void); +int test_wc_ecc_ecies_gcm(void); +int test_wc_ecc_ecies_gcm_no_rng(void); +int test_wc_ecc_ecies_cryptocb(void); int test_wc_ecc_del_point(void); int test_wc_ecc_pointFns(void); int test_wc_ecc_shared_secret_ssh(void); @@ -90,7 +94,11 @@ int test_wc_EccPrivateKeyToDer(void); TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_reset), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_peer_salt), \ TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_set_info), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ctx_getters), \ TEST_DECL_GROUP("ecc", test_wc_ecc_encryptDecrypt), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_gcm_no_rng), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_ecies_cryptocb), \ TEST_DECL_GROUP("ecc", test_wc_ecc_del_point), \ TEST_DECL_GROUP("ecc", test_wc_ecc_pointFns), \ TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret_ssh), \ diff --git a/wolfcrypt/benchmark/benchmark.c b/wolfcrypt/benchmark/benchmark.c index c8b5bf834f..e557f73978 100644 --- a/wolfcrypt/benchmark/benchmark.c +++ b/wolfcrypt/benchmark/benchmark.c @@ -13385,6 +13385,32 @@ void bench_ecc(int useDeviceID, int curveId) #ifdef HAVE_ECC_ENCRYPT +/* Drive an ECIES exchange context to the SALT_SET state with fixed salts and a + * chosen DEM cipher. The ECIES contexts are single-use per message, so the + * per-cipher benchmark loops reset and re-prime the context before each op. + * Fixed salts let the encrypt/decrypt directions agree (required for GCM's + * authentication tag to verify). */ +static int bench_ecies_prep(ecEncCtx* ctx, byte encAlgo, const byte* ownSalt, + const byte* peerSalt) +{ + int ret; + byte* own; + + ret = wc_ecc_ctx_reset(ctx, &gRng); + if (ret == 0) + ret = wc_ecc_ctx_set_algo(ctx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); + if (ret == 0) { + own = (byte*)wc_ecc_ctx_get_own_salt(ctx); + if (own == NULL) + ret = BAD_FUNC_ARG; + else { + XMEMCPY(own, ownSalt, EXCHANGE_SALT_SZ); + ret = wc_ecc_ctx_set_peer_salt(ctx, peerSalt); + } + } + return ret; +} + void bench_eccEncrypt(int curveId) { #define BENCH_ECCENCRYPT_MSG_SIZE 48 @@ -13469,62 +13495,143 @@ void bench_eccEncrypt(int curveId) msg[i] = (byte)i; } - bench_stats_start(&count, &start); - do { - for (i = 0; i < ntimes; i++) { - /* encrypt msg to B */ - ret = wc_ecc_encrypt(userA, userB, msg, BENCH_ECCENCRYPT_MSG_SIZE, - out, &outSz, NULL); - if (ret != 0) { - printf("wc_ecc_encrypt failed! %d\n", ret); - goto exit_enc; - } - RECORD_MULTI_VALUE_STATS(); + /* One enc/dec benchmark pair per DEM cipher available in this build so the + * AES-CBC/CTR/GCM variants can be compared side by side. A trailing {0,NULL} + * sentinel keeps the table non-empty when only one cipher is enabled. */ + { + WOLFSSL_SMALL_STACK_STATIC const byte fixedCliSalt[EXCHANGE_SALT_SZ] = { + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; + WOLFSSL_SMALL_STACK_STATIC const byte fixedSrvSalt[EXCHANGE_SALT_SZ] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f + }; + static const struct { byte algo; const char* label; } eciesCiphers[] = { + #if !defined(NO_AES) && defined(HAVE_AES_CBC) + #ifdef WOLFSSL_AES_128 + { ecAES_128_CBC, "AES128CBC" }, + #endif + #ifdef WOLFSSL_AES_256 + { ecAES_256_CBC, "AES256CBC" }, + #endif + #endif + #if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) + #ifdef WOLFSSL_AES_128 + { ecAES_128_CTR, "AES128CTR" }, + #endif + #ifdef WOLFSSL_AES_256 + { ecAES_256_CTR, "AES256CTR" }, + #endif + #endif + #if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) + #ifdef WOLFSSL_AES_128 + { ecAES_128_GCM, "AES128GCM" }, + #endif + #ifdef WOLFSSL_AES_256 + { ecAES_256_GCM, "AES256GCM" }, + #endif + #endif + { 0, NULL } /* sentinel */ + }; + ecEncCtx* cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, &gRng); + ecEncCtx* srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, &gRng); + char encDesc[32]; + char decDesc[32]; + size_t c; + #ifdef WOLFSSL_ECIES_OLD + /* OLD format carries no ephemeral point, so decrypt needs the sender's + * public key. */ + ecc_key* decPubKey = userA; + #else + /* SEC1 decrypt imports the ephemeral point into its pubKey argument, so + * pass NULL to avoid clobbering userA between cipher iterations. */ + ecc_key* decPubKey = NULL; + #endif + + (void)XSNPRINTF(name, BENCH_ECC_NAME_SZ, "ECC [%15s]", + wc_ecc_get_name(curveId)); + + if (cliCtx == NULL || srvCtx == NULL) { + printf("bench_eccEncrypt ctx alloc failed\n"); + wc_ecc_ctx_free(cliCtx); + wc_ecc_ctx_free(srvCtx); + goto exit; } - count += i; - } while (bench_stats_check(start) + + for (c = 0; eciesCiphers[c].label != NULL; c++) { + byte algo = eciesCiphers[c].algo; + + (void)XSNPRINTF(encDesc, sizeof(encDesc), "%s-%s", desc[6], + eciesCiphers[c].label); + (void)XSNPRINTF(decDesc, sizeof(decDesc), "%s-%s", desc[7], + eciesCiphers[c].label); + + /* encrypt msg to B */ + bench_stats_start(&count, &start); + do { + for (i = 0; i < ntimes; i++) { + outSz = BENCH_ECCENCRYPT_OUT_SIZE; + ret = bench_ecies_prep(cliCtx, algo, fixedCliSalt, + fixedSrvSalt); + if (ret == 0) + ret = wc_ecc_encrypt(userA, userB, msg, + BENCH_ECCENCRYPT_MSG_SIZE, out, &outSz, cliCtx); + if (ret != 0) { + printf("wc_ecc_encrypt failed! %d\n", ret); + goto exit_ecies; + } + RECORD_MULTI_VALUE_STATS(); + } + count += i; + } while (bench_stats_check(start) #ifdef MULTI_VALUE_STATISTICS - || runs < minimum_runs + || runs < minimum_runs #endif - ); - -exit_enc: - (void)XSNPRINTF(name, BENCH_ECC_NAME_SZ, "ECC [%15s]", - wc_ecc_get_name(curveId)); - bench_stats_asym_finish(name, keySize * 8, desc[6], 0, count, start, ret); + ); + bench_stats_asym_finish(name, keySize * 8, encDesc, 0, count, start, + ret); #ifdef MULTI_VALUE_STATISTICS - bench_multi_value_stats(max, min, sum, squareSum, runs); + bench_multi_value_stats(max, min, sum, squareSum, runs); #endif + RESET_MULTI_VALUE_STATS_VARS(); - RESET_MULTI_VALUE_STATS_VARS(); - - if (ret != 0) - goto exit; - - bench_stats_start(&count, &start); - do { - for (i = 0; i < ntimes; i++) { - /* decrypt msg from A */ - ret = wc_ecc_decrypt(userB, userA, out, outSz, bench_plain, - &bench_plainSz, NULL); - if (ret != 0) { - printf("wc_ecc_decrypt failed! %d\n", ret); - goto exit_dec; - } - RECORD_MULTI_VALUE_STATS(); - } - count += i; - } while (bench_stats_check(start) + /* decrypt the last ciphertext produced above (fixed salts make the + * server's derived keys match the client's, so GCM auth passes) */ + bench_stats_start(&count, &start); + do { + for (i = 0; i < ntimes; i++) { + bench_plainSz = bench_size; + ret = bench_ecies_prep(srvCtx, algo, fixedSrvSalt, + fixedCliSalt); + if (ret == 0) + ret = wc_ecc_decrypt(userB, decPubKey, out, outSz, + bench_plain, &bench_plainSz, srvCtx); + if (ret != 0) { + printf("wc_ecc_decrypt failed! %d\n", ret); + goto exit_ecies; + } + RECORD_MULTI_VALUE_STATS(); + } + count += i; + } while (bench_stats_check(start) #ifdef MULTI_VALUE_STATISTICS - || runs < minimum_runs + || runs < minimum_runs #endif - ); - -exit_dec: - bench_stats_asym_finish(name, keySize * 8, desc[7], 0, count, start, ret); + ); + bench_stats_asym_finish(name, keySize * 8, decDesc, 0, count, start, + ret); #ifdef MULTI_VALUE_STATISTICS - bench_multi_value_stats(max, min, sum, squareSum, runs); + bench_multi_value_stats(max, min, sum, squareSum, runs); #endif + RESET_MULTI_VALUE_STATS_VARS(); + } + +exit_ecies: + wc_ecc_ctx_free(cliCtx); + wc_ecc_ctx_free(srvCtx); + } exit: diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index c3067b97b8..4a7a2882b2 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -1018,6 +1018,70 @@ int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv) return wc_CryptoCb_TranslateErrorCode(ret); } #endif /* HAVE_ECC_CHECK_KEY */ + +#ifdef HAVE_ECC_ENCRYPT +int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int compressed) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (privKey == NULL) + return ret; + + /* locate registered callback */ + dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_ENCRYPT; + cryptoInfo.pk.eciesencrypt.privKey = privKey; + cryptoInfo.pk.eciesencrypt.pubKey = pubKey; + cryptoInfo.pk.eciesencrypt.msg = msg; + cryptoInfo.pk.eciesencrypt.msgSz = msgSz; + cryptoInfo.pk.eciesencrypt.out = out; + cryptoInfo.pk.eciesencrypt.outSz = outSz; + cryptoInfo.pk.eciesencrypt.ctx = ctx; + cryptoInfo.pk.eciesencrypt.compressed = compressed; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (privKey == NULL) + return ret; + + /* locate registered callback */ + dev = wc_CryptoCb_FindDevice(privKey->devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_ECIES_DECRYPT; + cryptoInfo.pk.eciesdecrypt.privKey = privKey; + cryptoInfo.pk.eciesdecrypt.pubKey = pubKey; + cryptoInfo.pk.eciesdecrypt.msg = msg; + cryptoInfo.pk.eciesdecrypt.msgSz = msgSz; + cryptoInfo.pk.eciesdecrypt.out = out; + cryptoInfo.pk.eciesdecrypt.outSz = outSz; + cryptoInfo.pk.eciesdecrypt.ctx = ctx; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* HAVE_ECC_ENCRYPT */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 2775571cdc..bf3963dad9 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -133,6 +133,18 @@ Possible ECC enable options: * (includes public key in shared secret). * WOLFSSL_ECIES_GEN_IV: Generates random IV for ECIES default: off * encryption instead of deriving from KDF. + * WOLFSSL_ECIES_STATIC_GCM_NONCE: default: off + * Allows the AES-GCM DEM in the default IV mode, where the + * GCM nonce is a fixed all-zero value that is not sent. + * This is ONLY safe because ECIES derives a fresh key from + * a fresh ephemeral key each message, so the (key, nonce) + * pair never repeats. Reusing the ephemeral key would + * reuse both the key and the nonce - catastrophic for GCM. + * Off by default: without this define, the GCM DEM returns + * NOT_COMPILED_IN in the default IV mode so the fixed nonce + * cannot be selected by accident. Not needed with + * WOLFSSL_ECIES_GEN_IV (random nonce) or WOLFSSL_ECIES_OLD + * (KDF-derived nonce). * * Fixed Point Cache options (requires FP_ECC): * FP_ENTRIES: Number of FP cache entries default: 15 @@ -14790,6 +14802,50 @@ int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo) return 0; } +#ifdef WOLF_CRYPTO_CB +/* Read back the parameters a caller configured on the context. Intended for + * crypto-callback backends (e.g. a hardware ECIES engine) that must reproduce + * the algorithm, KDF salt and KDF info to run the operation off-device. Only + * built when crypto callbacks are enabled, so default builds are unaffected. */ +int wc_ecc_ctx_get_algo(ecEncCtx* ctx, byte* encAlgo, byte* kdfAlgo, + byte* macAlgo) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + if (encAlgo != NULL) + *encAlgo = ctx->encAlgo; + if (kdfAlgo != NULL) + *kdfAlgo = ctx->kdfAlgo; + if (macAlgo != NULL) + *macAlgo = ctx->macAlgo; + + return 0; +} + +int wc_ecc_ctx_get_kdf_salt(ecEncCtx* ctx, const byte** salt, word32* sz) +{ + if (ctx == NULL || salt == NULL || sz == NULL) + return BAD_FUNC_ARG; + + *salt = ctx->kdfSalt; + *sz = ctx->kdfSaltSz; + + return 0; +} + +int wc_ecc_ctx_get_info(ecEncCtx* ctx, const byte** info, word32* sz) +{ + if (ctx == NULL || info == NULL || sz == NULL) + return BAD_FUNC_ARG; + + *info = ctx->kdfInfo; + *sz = ctx->kdfInfoSz; + + return 0; +} +#endif /* WOLF_CRYPTO_CB */ + const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx) { @@ -14977,6 +15033,12 @@ static void ecc_ctx_init(ecEncCtx* ctx, int flags, WC_RNG* rng) #else ctx->encAlgo = ecAES_128_CTR; #endif + #elif !defined(NO_AES) && defined(HAVE_AESGCM) + #ifdef WOLFSSL_AES_128 + ctx->encAlgo = ecAES_128_GCM; + #else + ctx->encAlgo = ecAES_256_GCM; + #endif #else #error "No valid encryption algorithm for ECIES configured." #endif @@ -15046,6 +15108,18 @@ void wc_ecc_ctx_free(ecEncCtx* ctx) } } +#if !defined(NO_AES) && defined(HAVE_AESGCM) +/* Is the ECIES encryption algorithm an AES-GCM (AEAD) mode? GCM replaces the + * AES + HMAC pair with a single authenticated-encryption pass. */ +static WC_INLINE int ecc_is_gcm(byte encAlgo) +{ + return (encAlgo == ecAES_128_GCM || encAlgo == ecAES_256_GCM); +} +#else +/* Compile GCM branches out entirely when AES-GCM isn't available. */ +#define ecc_is_gcm(encAlgo) (0) +#endif + static int ecc_get_key_sizes(ecEncCtx* ctx, int* encKeySz, int* ivSz, int* keysLen, word32* digestSz, word32* blockSz) { @@ -15074,26 +15148,57 @@ static int ecc_get_key_sizes(ecEncCtx* ctx, int* encKeySz, int* ivSz, *ivSz = 12; *blockSz = 1; break; + #endif + #if !defined(NO_AES) && defined(HAVE_AESGCM) + case ecAES_128_GCM: + *encKeySz = KEY_SIZE_128; + *ivSz = AES_GCM_NONCE_SZ; + *blockSz = 1; + break; + case ecAES_256_GCM: + *encKeySz = KEY_SIZE_256; + *ivSz = AES_GCM_NONCE_SZ; + *blockSz = 1; + break; #endif default: return BAD_FUNC_ARG; } - switch (ctx->macAlgo) { - case ecHMAC_SHA256: - *digestSz = WC_SHA256_DIGEST_SIZE; - break; - default: - return BAD_FUNC_ARG; + if (ecc_is_gcm(ctx->encAlgo)) { + /* GCM is AEAD: the 16-byte tag replaces the HMAC digest, and no + * separate MAC algorithm/key is used. */ + *digestSz = AES_GCM_TAG_SZ; + } + else { + switch (ctx->macAlgo) { + case ecHMAC_SHA256: + *digestSz = WC_SHA256_DIGEST_SIZE; + break; + default: + return BAD_FUNC_ARG; + } } } else return BAD_FUNC_ARG; + if (ecc_is_gcm(ctx->encAlgo)) { + /* No MAC key. The nonce follows the ECIES IV build mode, matching the + * CBC/CTR DEMs: derived from the KDF (OLD), a fresh random value + * (GEN_IV), or a fixed zero (default). */ #ifdef WOLFSSL_ECIES_OLD - *keysLen = *encKeySz + *ivSz + (int)*digestSz; + *keysLen = *encKeySz + *ivSz; /* nonce is part of the KDF output */ #else - *keysLen = *encKeySz + (int)*digestSz; + *keysLen = *encKeySz; #endif + } + else { +#ifdef WOLFSSL_ECIES_OLD + *keysLen = *encKeySz + *ivSz + (int)*digestSz; +#else + *keysLen = *encKeySz + (int)*digestSz; +#endif + } return 0; } @@ -15140,11 +15245,31 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; byte* encIv = NULL; byte* macKey = NULL; + /* devId to hand the DEM AES/HMAC primitives; ecc_key only carries a devId + * field with PLUTON_CRYPTO_ECC or WOLF_CRYPTO_CB, so default to INVALID. */ + int eciesDevId = INVALID_DEVID; if (privKey == NULL || pubKey == NULL || msg == NULL || out == NULL || outSz == NULL) return BAD_FUNC_ARG; +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + eciesDevId = privKey->devId; +#endif + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (privKey->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_EciesEncrypt(privKey, pubKey, msg, msgSz, out, outSz, + ctx, compressed); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall-through to software */ + } +#endif + if (ctx == NULL) { /* use defaults */ ecc_ctx_init(&localCtx, 0, NULL); ctx = &localCtx; @@ -15155,6 +15280,15 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (ret != 0) return ret; +#if !defined(WOLFSSL_ECIES_OLD) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + !defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) + /* Default IV mode gives the GCM DEM a fixed (zero) nonce, which is only safe + * with a fresh ephemeral key per message. Require explicit opt-in so it is + * never selected by accident; see WOLFSSL_ECIES_STATIC_GCM_NONCE. */ + if (ecc_is_gcm(ctx->encAlgo)) + return NOT_COMPILED_IN; +#endif + #ifndef WOLFSSL_ECIES_OLD if (!compressed) { pubKeySz = 1 + (word32)wc_ecc_size(privKey) * 2; @@ -15188,16 +15322,32 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if ((msgSz % blockSz) != 0) return BAD_PADDING_E; + if (ecc_is_gcm(ctx->encAlgo)) { + /* Nonce is embedded in the output only in GEN_IV mode; OLD derives it + * from the KDF and default uses a fixed nonce (neither is sent). */ #ifdef WOLFSSL_ECIES_OLD - if (*outSz < (msgSz + digestSz)) - return BUFFER_E; + if (*outSz < (msgSz + digestSz)) + return BUFFER_E; #elif defined(WOLFSSL_ECIES_GEN_IV) - if (*outSz < (pubKeySz + ivSz + msgSz + digestSz)) - return BUFFER_E; + if (*outSz < (pubKeySz + (word32)ivSz + msgSz + digestSz)) + return BUFFER_E; #else - if (*outSz < (pubKeySz + msgSz + digestSz)) - return BUFFER_E; + if (*outSz < (pubKeySz + msgSz + digestSz)) + return BUFFER_E; #endif + } + else { +#ifdef WOLFSSL_ECIES_OLD + if (*outSz < (msgSz + digestSz)) + return BUFFER_E; +#elif defined(WOLFSSL_ECIES_GEN_IV) + if (*outSz < (pubKeySz + ivSz + msgSz + digestSz)) + return BUFFER_E; +#else + if (*outSz < (pubKeySz + msgSz + digestSz)) + return BUFFER_E; +#endif + } #ifdef ECC_TIMING_RESISTANT if (ctx->rng != NULL && privKey->rng == NULL) @@ -15295,22 +15445,47 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } if (ret == 0) { + if (ecc_is_gcm(ctx->encAlgo)) { + /* GCM nonce follows the ECIES IV build mode, matching CBC/CTR: + * OLD -> from the KDF output; GEN_IV -> fresh random, embedded; + * default -> fixed zero. No MAC key. */ + encKey = keys + offset; + macKey = NULL; +#ifdef WOLFSSL_ECIES_OLD + encIv = encKey + encKeySz; +#elif defined(WOLFSSL_ECIES_GEN_IV) + { + WC_RNG* rng = (privKey->rng != NULL) ? privKey->rng : ctx->rng; + encIv = out; + out += ivSz; + if (rng == NULL) + ret = MISSING_RNG_E; + else + ret = wc_RNG_GenerateBlock(rng, encIv, (word32)ivSz); + } +#else + XMEMSET(iv, 0, (size_t)ivSz); + encIv = iv; +#endif + } + else { #ifdef WOLFSSL_ECIES_OLD - encKey = keys + offset; - encIv = encKey + encKeySz; - macKey = encKey + encKeySz + ivSz; + encKey = keys + offset; + encIv = encKey + encKeySz; + macKey = encKey + encKeySz + ivSz; #elif defined(WOLFSSL_ECIES_GEN_IV) - encKey = keys + offset; - encIv = out; - out += ivSz; - macKey = encKey + encKeySz; - ret = wc_RNG_GenerateBlock(privKey->rng, encIv, ivSz); + encKey = keys + offset; + encIv = out; + out += ivSz; + macKey = encKey + encKeySz; + ret = wc_RNG_GenerateBlock(privKey->rng, encIv, ivSz); #else - XMEMSET(iv, 0, (size_t)ivSz); - encKey = keys + offset; - encIv = iv; - macKey = encKey + encKeySz; + XMEMSET(iv, 0, (size_t)ivSz); + encKey = keys + offset; + encIv = iv; + macKey = encKey + encKeySz; #endif + } } if (ret == 0) { @@ -15329,7 +15504,7 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, NULL, INVALID_DEVID); + ret = wc_AesInit(aes, privKey->heap, eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_ENCRYPTION); @@ -15370,7 +15545,7 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, XMEMSET(ctr_iv + WOLFSSL_ECIES_GEN_IV_SIZE, 0, WC_AES_BLOCK_SIZE - WOLFSSL_ECIES_GEN_IV_SIZE); - ret = wc_AesInit(aes, NULL, INVALID_DEVID); + ret = wc_AesInit(aes, privKey->heap, eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, ctr_iv, AES_ENCRYPTION); @@ -15390,13 +15565,50 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #endif break; } + #if !defined(NO_AES) && defined(HAVE_AESGCM) + case ecAES_128_GCM: + case ecAES_256_GCM: + { + #ifdef WOLFSSL_SMALL_STACK + Aes *aes = (Aes *)XMALLOC(sizeof *aes, ctx->heap, + DYNAMIC_TYPE_AES); + if (aes == NULL) { + ret = MEMORY_E; + break; + } + #else + Aes aes[1]; + #endif + ret = wc_AesInit(aes, privKey->heap, eciesDevId); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); + if (ret == 0) { + /* tag is written directly after the ciphertext; the + * mac salt (if any) is bound in as AAD, mirroring the + * HMAC salt used by the CBC/CTR paths. */ + ret = wc_AesGcmEncrypt(aes, out, msg, msgSz, + encIv, (word32)ivSz, + out + msgSz, digestSz, + ctx->macSalt, ctx->macSaltSz); + #if defined(WOLFSSL_ASYNC_CRYPT) && \ + defined(WC_ASYNC_ENABLE_AES) + ret = wc_AsyncWait(ret, &aes->asyncDev, + WC_ASYNC_FLAG_NONE); + #endif + } + wc_AesFree(aes); + } + WC_FREE_VAR_EX(aes, ctx->heap, DYNAMIC_TYPE_AES); + break; + } + #endif default: ret = BAD_FUNC_ARG; break; } } - if (ret == 0) { + if (ret == 0 && !ecc_is_gcm(ctx->encAlgo)) { switch (ctx->macAlgo) { case ecHMAC_SHA256: { @@ -15410,7 +15622,7 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Hmac hmac[1]; #endif - ret = wc_HmacInit(hmac, NULL, INVALID_DEVID); + ret = wc_HmacInit(hmac, privKey->heap, eciesDevId); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -15439,13 +15651,25 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } if (ret == 0) { + if (ecc_is_gcm(ctx->encAlgo)) { + /* Nonce is only in the output in GEN_IV mode. */ +#ifdef WOLFSSL_ECIES_OLD + *outSz = msgSz + digestSz; +#elif defined(WOLFSSL_ECIES_GEN_IV) + *outSz = pubKeySz + (word32)ivSz + msgSz + digestSz; +#else + *outSz = pubKeySz + msgSz + digestSz; +#endif + } + else { #ifdef WOLFSSL_ECIES_OLD - *outSz = msgSz + digestSz; + *outSz = msgSz + digestSz; #elif defined(WOLFSSL_ECIES_GEN_IV) - *outSz = pubKeySz + ivSz + msgSz + digestSz; + *outSz = pubKeySz + ivSz + msgSz + digestSz; #else - *outSz = pubKeySz + msgSz + digestSz; + *outSz = pubKeySz + msgSz + digestSz; #endif + } } ForceZero(sharedSecret, sharedSz); @@ -15508,6 +15732,9 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, byte* encKey = NULL; const byte* encIv = NULL; byte* macKey = NULL; + /* devId to hand the DEM AES/HMAC primitives; ecc_key only carries a devId + * field with PLUTON_CRYPTO_ECC or WOLF_CRYPTO_CB, so default to INVALID. */ + int eciesDevId = INVALID_DEVID; if (privKey == NULL || msg == NULL || out == NULL || outSz == NULL) @@ -15517,6 +15744,23 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, return BAD_FUNC_ARG; #endif +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + eciesDevId = privKey->devId; +#endif + +#ifdef WOLF_CRYPTO_CB + #ifndef WOLF_CRYPTO_CB_FIND + if (privKey->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_EciesDecrypt(privKey, pubKey, msg, msgSz, out, outSz, + ctx); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return ret; + ret = 0; /* fall-through to software */ + } +#endif + if (ctx == NULL) { /* use defaults */ ecc_ctx_init(&localCtx, 0, NULL); ctx = &localCtx; @@ -15527,6 +15771,15 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (ret != 0) return ret; +#if !defined(WOLFSSL_ECIES_OLD) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + !defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) + /* Default IV mode gives the GCM DEM a fixed (zero) nonce, which is only safe + * with a fresh ephemeral key per message. Require explicit opt-in so it is + * never selected by accident; see WOLFSSL_ECIES_STATIC_GCM_NONCE. */ + if (ecc_is_gcm(ctx->encAlgo)) + return NOT_COMPILED_IN; +#endif + #ifndef WOLFSSL_ECIES_OLD ret = ecc_public_key_size(privKey, &pubKeySz); if (ret != 0) @@ -15557,6 +15810,27 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (keysLen > ECC_BUFSIZE) /* keys size */ return BUFFER_E; + if (ecc_is_gcm(ctx->encAlgo)) { + /* GCM has a trailing tag (digestSz) and no block padding. The nonce is + * only present in the input in GEN_IV mode. */ +#ifdef WOLFSSL_ECIES_OLD + if (msgSz < digestSz) + return BAD_FUNC_ARG; + if (*outSz < (msgSz - digestSz)) + return BUFFER_E; +#elif defined(WOLFSSL_ECIES_GEN_IV) + if (msgSz < pubKeySz + (word32)ivSz + digestSz) + return BAD_FUNC_ARG; + if (*outSz < (msgSz - (word32)ivSz - digestSz - pubKeySz)) + return BUFFER_E; +#else + if (msgSz < pubKeySz + digestSz) + return BAD_FUNC_ARG; + if (*outSz < (msgSz - digestSz - pubKeySz)) + return BUFFER_E; +#endif + } + else { #ifdef WOLFSSL_ECIES_OLD if (((msgSz - digestSz) % blockSz) != 0) return BAD_PADDING_E; @@ -15580,6 +15854,7 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, if (*outSz < (msgSz - digestSz - pubKeySz)) return BUFFER_E; #endif + } #ifdef ECC_TIMING_RESISTANT if (ctx->rng != NULL && privKey->rng == NULL) @@ -15694,7 +15969,23 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } } - if (ret == 0) { + if (ret == 0 && ecc_is_gcm(ctx->encAlgo)) { + /* GCM nonce follows the ECIES IV build mode (matching CBC/CTR); no MAC + * key and no separate HMAC verify (GCM authenticates on decrypt). */ + encKey = keys + offset; + macKey = NULL; +#ifdef WOLFSSL_ECIES_OLD + encIv = encKey + encKeySz; +#elif defined(WOLFSSL_ECIES_GEN_IV) + encIv = msg; + msg += ivSz; + msgSz -= (word32)ivSz; +#else + XMEMSET(iv, 0, (size_t)ivSz); + encIv = iv; +#endif + } + else if (ret == 0) { #ifdef WOLFSSL_ECIES_OLD encKey = keys + offset; encIv = encKey + encKeySz; @@ -15726,7 +16017,7 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Hmac hmac[1]; #endif - ret = wc_HmacInit(hmac, NULL, INVALID_DEVID); + ret = wc_HmacInit(hmac, privKey->heap, eciesDevId); if (ret == 0) { ret = wc_HmacSetKey(hmac, WC_SHA256, macKey, WC_SHA256_DIGEST_SIZE); @@ -15776,7 +16067,7 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, NULL, INVALID_DEVID); + ret = wc_AesInit(aes, privKey->heap, eciesDevId); if (ret == 0) { ret = wc_AesSetKey(aes, encKey, (word32)encKeySz, encIv, AES_DECRYPTION); @@ -15808,7 +16099,7 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else Aes aes[1]; #endif - ret = wc_AesInit(aes, NULL, INVALID_DEVID); + ret = wc_AesInit(aes, privKey->heap, eciesDevId); if (ret == 0) { byte ctr_iv[WC_AES_BLOCK_SIZE]; /* Make a 16 byte IV from the bytes passed in. */ @@ -15830,6 +16121,42 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, WC_FREE_VAR_EX(aes, ctx->heap, DYNAMIC_TYPE_AES); break; } + #endif + #if !defined(NO_AES) && defined(HAVE_AESGCM) + case ecAES_128_GCM: + case ecAES_256_GCM: + { + #ifdef WOLFSSL_SMALL_STACK + Aes *aes = (Aes *)XMALLOC(sizeof *aes, ctx->heap, + DYNAMIC_TYPE_AES); + if (aes == NULL) { + ret = MEMORY_E; + break; + } + #else + Aes aes[1]; + #endif + ret = wc_AesInit(aes, privKey->heap, eciesDevId); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, encKey, (word32)encKeySz); + if (ret == 0) { + /* tag trails the ciphertext; mac salt (if any) is the + * AAD. A tag mismatch returns AES_GCM_AUTH_E. */ + ret = wc_AesGcmDecrypt(aes, out, msg, msgSz - digestSz, + encIv, (word32)ivSz, + msg + msgSz - digestSz, digestSz, + ctx->macSalt, ctx->macSaltSz); + #if defined(WOLFSSL_ASYNC_CRYPT) && \ + defined(WC_ASYNC_ENABLE_AES) + ret = wc_AsyncWait(ret, &aes->asyncDev, + WC_ASYNC_FLAG_NONE); + #endif + } + wc_AesFree(aes); + } + WC_FREE_VAR_EX(aes, ctx->heap, DYNAMIC_TYPE_AES); + break; + } #endif default: ret = BAD_FUNC_ARG; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 25193ba8b7..59b216ee20 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -44247,6 +44247,36 @@ static wc_test_ret_t ecc_encrypt_combos_test(WC_RNG* rng, ecc_key* userA, } #endif #endif /* !NO_AES && WOLFSSL_AES_COUNTER */ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) && !defined(WOLFSSL_NO_MALLOC) +#ifdef WOLFSSL_AES_128 + if (ret == 0) { + ret = ecc_encrypt_e2e_test(rng, userA, userB, ecAES_128_GCM, + ecHKDF_SHA256, ecHMAC_SHA256); + if (ret != 0) { + printf("ECIES: AES_128_GCM, HKDF_SHA256\n"); + } + } +#ifdef HAVE_X963_KDF + if (ret == 0) { + ret = ecc_encrypt_e2e_test(rng, userA, userB, ecAES_128_GCM, + ecKDF_X963_SHA256, ecHMAC_SHA256); + if (ret != 0) { + printf("ECIES: AES_128_GCM, KDF_X963_SHA256\n"); + } + } +#endif +#endif +#ifdef WOLFSSL_AES_256 + if (ret == 0) { + ret = ecc_encrypt_e2e_test(rng, userA, userB, ecAES_256_GCM, + ecHKDF_SHA256, ecHMAC_SHA256); + if (ret != 0) { + printf("ECIES: AES_256_GCM, HKDF_SHA256\n"); + } + } +#endif +#endif /* !NO_AES && HAVE_AESGCM */ #if !defined(NO_AES) && defined(HAVE_AES_CBC) && !defined(WOLFSSL_NO_MALLOC) if (ret == 0) { ret = ecc_ctx_kdf_salt_test(rng, userA, userB); @@ -44258,6 +44288,483 @@ static wc_test_ret_t ecc_encrypt_combos_test(WC_RNG* rng, ecc_key* userA, (void)userB; return ret; } + +/* Fixed-vector (decrypt-only) KAT + negative tests for ECIES with an AES-GCM + * DEM. ECIES is non-deterministic (random ephemeral key + random GCM nonce), + * so each ciphertext is captured once and decrypted here with a FIXED recipient + * private key and FIXED exchange salts. The salt mixing in + * wc_ecc_ctx_set_peer_salt() is symmetric, so decrypt reproduces the KDF/MAC + * salts from the blob alone. Only valid for the default SEC1 wire format. + * + * ecc_encrypt_gcm_kat_vec() is parameterized over (encAlgo, kdfAlgo, blob) so + * the same body validates every captured vector below. */ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_HKDF) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(WOLFSSL_ECIES_ISO18033) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) && !defined(WOLFSSL_NO_MALLOC) + +/* recipient private key (SECP256R1 raw scalar) - shared by every GCM vector. + * File-scope so all vectors share them; must be 'static' (internal linkage) + * because WOLFSSL_SMALL_STACK_STATIC expands to nothing in non-small-stack + * builds, which at file scope would create external globals. */ +static const byte ecc_gcm_kat_privKey[] = { + 0x04, 0x80, 0xef, 0x1d, 0xbe, 0x02, 0x0c, 0x20, + 0x5b, 0xab, 0x80, 0x35, 0x5b, 0x2a, 0x0f, 0x6d, + 0xd3, 0xb0, 0x7f, 0x7e, 0x7f, 0x86, 0x8a, 0x49, + 0xee, 0xb4, 0xaa, 0x09, 0x2d, 0x1e, 0x1d, 0x02 +}; +/* fixed exchange salts (client's own, server's own) */ +static const byte ecc_gcm_kat_cliSalt[EXCHANGE_SALT_SZ] = { + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f +}; +static const byte ecc_gcm_kat_srvSalt[EXCHANGE_SALT_SZ] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f +}; +/* the fixed 48-byte plaintext every vector encrypts */ +static const byte ecc_gcm_kat_msg[] = { + 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, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f +}; + +/* Decrypt-side KAT for one captured vector, plus tamper passes: + * i==0 valid blob -> success + plaintext matches + * i==1 corrupt GCM tag -> auth failure + * i==2 corrupt ciphertext -> auth failure + * (Buffer-size and truncated-input error paths are covered by the API unit + * test test_wc_ecc_ecies_gcm().) A fresh server ctx is built each pass since + * the exchange ctx is single-use. */ +static wc_test_ret_t ecc_encrypt_gcm_kat_vec(WC_RNG* rng, byte encAlgo, + byte kdfAlgo, const byte* enc_msg, word32 enc_msgSz) +{ + wc_test_ret_t ret = 0; + ecc_key* userB = NULL; + byte* bad = NULL; + byte plain[sizeof(ecc_gcm_kat_msg)]; + word32 plainSz; + int i; + int userBInit = 0; + + userB = (ecc_key*)XMALLOC(sizeof(*userB), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + bad = (byte*)XMALLOC(enc_msgSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (userB == NULL || bad == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto vec_done; + } + + for (i = 0; i <= 2 && ret == 0; i++) { + ecEncCtx* srvCtx = NULL; + const byte* in = enc_msg; + + if (userBInit) { + wc_ecc_free(userB); + userBInit = 0; + } + ret = wc_ecc_init_ex(userB, HEAP_HINT, devId); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); break; } + userBInit = 1; + + ret = wc_ecc_import_private_key_ex(ecc_gcm_kat_privKey, + (word32)sizeof(ecc_gcm_kat_privKey), NULL, 0, userB, + ECC_SECP256R1); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); break; } +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ret = wc_ecc_set_rng(userB, rng); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); break; } +#endif + + srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); + if (srvCtx == NULL) { ret = WC_TEST_RET_ENC_ERRNO; break; } + ret = wc_ecc_ctx_set_algo(srvCtx, encAlgo, kdfAlgo, ecHMAC_SHA256); + if (ret == 0) { + /* force our fixed own salt, then set the peer's fixed salt */ + byte* ownSalt = (byte*)wc_ecc_ctx_get_own_salt(srvCtx); + if (ownSalt == NULL) { + ret = WC_TEST_RET_ENC_NC; + } + else { + XMEMCPY(ownSalt, ecc_gcm_kat_srvSalt, EXCHANGE_SALT_SZ); + ret = wc_ecc_ctx_set_peer_salt(srvCtx, ecc_gcm_kat_cliSalt); + } + } + if (ret != 0) { + wc_ecc_ctx_free(srvCtx); + ret = WC_TEST_RET_ENC_EC(ret); + break; + } + + plainSz = sizeof(plain); + if (i == 1) { /* corrupt the trailing GCM tag */ + XMEMCPY(bad, enc_msg, enc_msgSz); + bad[enc_msgSz - 1] ^= 0x01; + in = bad; + } + else if (i == 2) { /* corrupt a ciphertext byte */ + XMEMCPY(bad, enc_msg, enc_msgSz); + bad[enc_msgSz - (word32)sizeof(ecc_gcm_kat_msg)] ^= 0x01; + in = bad; + } + + ret = wc_ecc_decrypt(userB, NULL, in, enc_msgSz, plain, &plainSz, + srvCtx); + wc_ecc_ctx_free(srvCtx); + + if (i == 0) { + /* valid blob must decrypt and match */ + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); break; } + if (plainSz != sizeof(ecc_gcm_kat_msg) || + XMEMCMP(plain, ecc_gcm_kat_msg, + sizeof(ecc_gcm_kat_msg)) != 0) { + ret = WC_TEST_RET_ENC_NC; + break; + } + } + else { + /* corrupted blob must be rejected by GCM authentication */ + if (ret == 0) { ret = WC_TEST_RET_ENC_NC; break; } + ret = 0; /* expected failure -> continue */ + } + } + +vec_done: + if (userBInit) + wc_ecc_free(userB); + XFREE(userB, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(bad, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* Run every fixed GCM KAT vector available in this build. */ +static wc_test_ret_t ecc_encrypt_gcm_kat(WC_RNG* rng) +{ + wc_test_ret_t ret = 0; + +#ifdef WOLFSSL_AES_256 + /* AES-256-GCM, HKDF-SHA256, SEC1 default mode (fixed zero nonce, not sent): + * 0x04||point(65)||ciphertext(48)||tag(16). Generated with OpenSSL's + * (NIST SP 800-38D) AES-GCM so wolfSSL decrypting it validates the DEM. */ + WOLFSSL_SMALL_STACK_STATIC const byte enc_msg_256_hkdf[] = { + 0x04, 0x2a, 0x15, 0xd9, 0x15, 0xec, 0x17, 0xf0, + 0xcb, 0xa0, 0x16, 0xcb, 0x87, 0x8b, 0xb5, 0x0a, + 0x71, 0x88, 0xd9, 0x62, 0xdc, 0x66, 0x08, 0x03, + 0x37, 0xbf, 0xff, 0x04, 0x9f, 0xae, 0x32, 0xa5, + 0x1f, 0xa2, 0x30, 0xbc, 0x18, 0x01, 0x01, 0xc3, + 0x87, 0x37, 0xd4, 0xd5, 0xbc, 0xa2, 0x72, 0xe7, + 0x91, 0xc7, 0xeb, 0x3d, 0x52, 0x2e, 0xe1, 0x12, + 0x07, 0x77, 0x56, 0x35, 0xed, 0x57, 0xf8, 0x9e, + 0xae, 0xb4, 0xb8, 0x29, 0x89, 0xdf, 0x6f, 0xda, + 0xbf, 0xdf, 0xd4, 0x08, 0x67, 0x9d, 0x76, 0x11, + 0x55, 0x44, 0x86, 0x56, 0xc2, 0x62, 0x70, 0x1e, + 0xd7, 0x30, 0xec, 0x61, 0x91, 0xb6, 0x22, 0x57, + 0x41, 0x71, 0x6e, 0x5c, 0xbd, 0x91, 0x63, 0x14, + 0x81, 0x12, 0x17, 0x98, 0x67, 0x8a, 0xfb, 0xa7, + 0xba, 0x2f, 0x36, 0xd9, 0xba, 0xa8, 0xda, 0xda, + 0xd9, 0x89, 0xbd, 0xf8, 0x21, 0x55, 0x43, 0x88, + 0x19 + }; + if (ret == 0) { + ret = ecc_encrypt_gcm_kat_vec(rng, ecAES_256_GCM, ecHKDF_SHA256, + enc_msg_256_hkdf, (word32)sizeof(enc_msg_256_hkdf)); + } +#endif /* WOLFSSL_AES_256 */ + +#ifdef WOLFSSL_AES_128 + /* AES-128-GCM, HKDF-SHA256, SEC1 format */ + WOLFSSL_SMALL_STACK_STATIC const byte enc_msg_128_hkdf[] = { + 0x04,0x33,0xb1,0x00,0x18,0x4a,0x91,0x6e, + 0x4d,0x4b,0xad,0x90,0xfb,0x25,0xaa,0xe7, + 0x1b,0x82,0x38,0xda,0xab,0x85,0x51,0x91, + 0xb8,0xd9,0xf8,0xa4,0x7b,0x7d,0xca,0x4d, + 0x03,0xe4,0xe1,0x44,0xcf,0xf6,0x9e,0x70, + 0x6f,0x23,0x2a,0xaf,0xcd,0x16,0xe4,0x02, + 0x14,0x0c,0xf7,0x0b,0x1d,0x24,0x1a,0xf3, + 0xc1,0xa8,0xe3,0x8e,0x96,0xa1,0x63,0xe0, + 0x0d,0xa5,0xdd,0x32,0x27,0xdb,0x01,0x57, + 0x59,0x65,0x2b,0xa8,0x32,0xdc,0x03,0x59, + 0x5a,0xc7,0xb2,0xb7,0x1c,0xef,0x03,0xc5, + 0x05,0x84,0x03,0x9d,0x6b,0x7c,0x59,0x65, + 0xeb,0xa1,0x67,0xfb,0x94,0xd6,0x7d,0xa1, + 0x22,0x58,0xf5,0x35,0x55,0xd7,0x5c,0x36, + 0xb6,0x48,0x01,0x0a,0x8c,0x24,0xac,0x85, + 0x20,0xab,0x13,0xac,0xe9,0xcd,0x6d,0x89, + 0xb0 + }; + if (ret == 0) { + ret = ecc_encrypt_gcm_kat_vec(rng, ecAES_128_GCM, ecHKDF_SHA256, + enc_msg_128_hkdf, (word32)sizeof(enc_msg_128_hkdf)); + } +#ifdef HAVE_X963_KDF + /* AES-128-GCM, X9.63-KDF-SHA256, SEC1 format */ + { + WOLFSSL_SMALL_STACK_STATIC const byte enc_msg_128_x963[] = { + 0x04,0x7b,0x1a,0x46,0x41,0x7c,0x5e,0x64, + 0xb4,0x55,0x73,0x78,0xf1,0x7f,0xe9,0xbf, + 0xd9,0x01,0xdb,0x1a,0x5d,0xef,0x96,0x1c, + 0x78,0x58,0x1b,0x68,0x5f,0x5d,0xe4,0x40, + 0xcd,0x8f,0xdf,0x88,0xc5,0x06,0xc1,0x64, + 0x00,0x37,0x45,0xe0,0x6c,0xf0,0x61,0x70, + 0xda,0x2d,0x65,0xff,0xd2,0x59,0x5b,0x73, + 0x9d,0x4f,0x72,0x15,0x21,0xa5,0x54,0xbc, + 0x80,0x2f,0x0d,0x83,0x64,0x0e,0x41,0x2b, + 0x1e,0x61,0xbb,0x0c,0x98,0x37,0x0e,0xbe, + 0xf4,0xb2,0xf3,0xae,0x07,0x20,0x88,0x79, + 0xb3,0x08,0xca,0x73,0x2b,0xb3,0x26,0x8f, + 0x2b,0x22,0x05,0x57,0x4f,0x50,0x04,0xc1, + 0x51,0x9c,0xbb,0xb5,0xd2,0xad,0x3b,0x19, + 0x1c,0x14,0xc0,0xd5,0x81,0x60,0x34,0x79, + 0x04,0x26,0x18,0x20,0x2d,0xd7,0xda,0xa2, + 0x31 + }; + if (ret == 0) { + ret = ecc_encrypt_gcm_kat_vec(rng, ecAES_128_GCM, + ecKDF_X963_SHA256, enc_msg_128_x963, + (word32)sizeof(enc_msg_128_x963)); + } + } +#endif /* HAVE_X963_KDF */ +#endif /* WOLFSSL_AES_128 */ + + return ret; +} +#endif /* GCM KAT guards */ + +#if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) +/* Minimal ECIES CryptoCb: with mode==1 it services the operation (forwarding to + * software after clearing devId) and records that it was invoked; with mode==0 + * it returns CRYPTOCB_UNAVAILABLE so ECIES falls back to software. */ +typedef struct EciesCbCtx { + int mode; /* 0 = force fallback, 1 = handle in callback */ + int encryptInvoked; /* set when the callback services an ECIES encrypt */ + int decryptInvoked; /* set when the callback services an ECIES decrypt */ +} EciesCbCtx; + +static int myEciesCryptoCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + EciesCbCtx* cbCtx = (EciesCbCtx*)ctx; + + if (info->algo_type == WC_ALGO_TYPE_PK) { + if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + if (cbCtx->mode == 0) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + cbCtx->encryptInvoked = 1; + info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, + info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, + info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, + info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, + info->pk.eciesencrypt.compressed); + info->pk.eciesencrypt.privKey->devId = devIdArg; + } + else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + if (cbCtx->mode == 0) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + cbCtx->decryptInvoked = 1; + info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, + info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, + info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, + info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); + info->pk.eciesdecrypt.privKey->devId = devIdArg; + } + } + + return ret; +} + +#define ECIES_CB_TEST_DEVID 0x45434945 /* 'ECIE' */ + +/* Every ECIES DEM cipher available in this build. The CryptoCb dispatch is + * whole-operation (cipher-agnostic), but we route each mode through the + * callback to prove the round-trip works for all of them. */ +static const byte eciesCbModes[] = { +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + #ifdef WOLFSSL_AES_128 + ecAES_128_CBC, + #endif + #ifdef WOLFSSL_AES_256 + ecAES_256_CBC, + #endif +#endif +#if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) + #ifdef WOLFSSL_AES_128 + ecAES_128_CTR, + #endif + #ifdef WOLFSSL_AES_256 + ecAES_256_CTR, + #endif +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) + #ifdef WOLFSSL_AES_128 + ecAES_128_GCM, + #endif + #ifdef WOLFSSL_AES_256 + ecAES_256_GCM, + #endif +#endif + 0 /* sentinel so the array is never zero-sized; skipped at runtime */ +}; + +/* One callback round-trip for a specific DEM cipher. handleInCb selects whether + * the callback services the op (mode 1) or declines so software runs (mode 0). + * Verifies the plaintext round-trips and that the callback was/ wasn't used. */ +static wc_test_ret_t ecies_cryptocb_roundtrip(WC_RNG* rng, EciesCbCtx* cbCtx, + ecc_key* userA, ecc_key* userB, byte encAlgo, int handleInCb) +{ + wc_test_ret_t ret; + ecEncCtx* cliCtx = NULL; + ecEncCtx* srvCtx = NULL; + byte msg[32]; + byte out[256]; + byte plain[64]; + word32 outSz = (word32)sizeof(out); + word32 plainSz = (word32)sizeof(plain); + byte cliSalt[EXCHANGE_SALT_SZ]; + byte srvSalt[EXCHANGE_SALT_SZ]; + const byte* tmpSalt; + ecc_key* decPub = NULL; + int i; + +#ifdef WOLFSSL_ECIES_OLD + decPub = userA; /* OLD needs the sender's public key to decrypt */ +#endif + for (i = 0; i < (int)sizeof(msg); i++) + msg[i] = (byte)i; + + cliCtx = wc_ecc_ctx_new(REQ_RESP_CLIENT, rng); + srvCtx = wc_ecc_ctx_new(REQ_RESP_SERVER, rng); + if (cliCtx == NULL || srvCtx == NULL) { ret = WC_TEST_RET_ENC_ERRNO; goto rt_done; } + + ret = wc_ecc_ctx_set_algo(cliCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); + if (ret == 0) + ret = wc_ecc_ctx_set_algo(srvCtx, encAlgo, ecHKDF_SHA256, ecHMAC_SHA256); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + + tmpSalt = wc_ecc_ctx_get_own_salt(cliCtx); + if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto rt_done; } + XMEMCPY(cliSalt, tmpSalt, EXCHANGE_SALT_SZ); + tmpSalt = wc_ecc_ctx_get_own_salt(srvCtx); + if (tmpSalt == NULL) { ret = WC_TEST_RET_ENC_NC; goto rt_done; } + XMEMCPY(srvSalt, tmpSalt, EXCHANGE_SALT_SZ); + ret = wc_ecc_ctx_set_peer_salt(cliCtx, srvSalt); + if (ret == 0) + ret = wc_ecc_ctx_set_peer_salt(srvCtx, cliSalt); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + + cbCtx->mode = handleInCb; + cbCtx->encryptInvoked = 0; + cbCtx->decryptInvoked = 0; + + ret = wc_ecc_encrypt(userA, userB, msg, sizeof(msg), out, &outSz, cliCtx); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + ret = wc_ecc_decrypt(userB, decPub, out, outSz, plain, &plainSz, srvCtx); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto rt_done; } + + if (plainSz != sizeof(msg) || XMEMCMP(plain, msg, sizeof(msg)) != 0) { + ret = WC_TEST_RET_ENC_NC; goto rt_done; + } + /* Both directions must be serviced by the callback when handleInCb==1, and + * neither when it's 0 (software ran). Checking them separately catches a + * silently-broken dispatch in one direction that a single flag would hide. */ + if (cbCtx->encryptInvoked != (handleInCb ? 1 : 0) || + cbCtx->decryptInvoked != (handleInCb ? 1 : 0)) + ret = WC_TEST_RET_ENC_NC; + +rt_done: + wc_ecc_ctx_free(cliCtx); + wc_ecc_ctx_free(srvCtx); + return ret; +} + +/* Exercises the ECIES CryptoCb dispatch for every DEM cipher in the build, and + * for each: pass with the callback servicing the op, and a pass verifying + * software fallback on CRYPTOCB_UNAVAILABLE. */ +static wc_test_ret_t ecc_encrypt_cryptocb_test(WC_RNG* rng) +{ + wc_test_ret_t ret = 0; + ecc_key* userA = NULL; + ecc_key* userB = NULL; + int registered = 0; + int userAInit = 0, userBInit = 0; + int m, pass; + EciesCbCtx cbCtx; + + XMEMSET(&cbCtx, 0, sizeof(cbCtx)); + ret = wc_CryptoCb_RegisterDevice(ECIES_CB_TEST_DEVID, myEciesCryptoCb, + &cbCtx); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + registered = 1; + + userA = (ecc_key*)XMALLOC(sizeof(*userA), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + userB = (ecc_key*)XMALLOC(sizeof(*userB), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (userA == NULL || userB == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto cb_done; + } + + /* Fresh keys owned by this test, routed to our callback via devId. */ + ret = wc_ecc_init_ex(userA, HEAP_HINT, ECIES_CB_TEST_DEVID); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } + userAInit = 1; + ret = wc_ecc_init_ex(userB, HEAP_HINT, ECIES_CB_TEST_DEVID); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } + userBInit = 1; + /* Our callback only handles ECIES, so keygen falls back to software. + * Clear devId during make so keygen uses software directly, then restore + * for the ECIES ops. */ + userA->devId = INVALID_DEVID; + userB->devId = INVALID_DEVID; + ret = wc_ecc_make_key(rng, ECC_KEYGEN_SIZE, userA); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } + ret = wc_ecc_make_key(rng, ECC_KEYGEN_SIZE, userB); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } +#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \ + !defined(HAVE_SELFTEST) + ret = wc_ecc_set_rng(userA, rng); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } + ret = wc_ecc_set_rng(userB, rng); + if (ret != 0) { ret = WC_TEST_RET_ENC_EC(ret); goto cb_done; } +#endif + userA->devId = ECIES_CB_TEST_DEVID; + userB->devId = ECIES_CB_TEST_DEVID; + + /* For each DEM cipher in the build: pass 1 = callback services the op, + * pass 0 = callback declines and software fallback runs. */ + for (m = 0; + m < (int)(sizeof(eciesCbModes) / sizeof(eciesCbModes[0])) && ret == 0; + m++) { + if (eciesCbModes[m] == 0) /* sentinel entry */ + continue; + for (pass = 1; pass >= 0 && ret == 0; pass--) { + ret = ecies_cryptocb_roundtrip(rng, &cbCtx, userA, userB, + eciesCbModes[m], pass); + } + } + +cb_done: + if (userAInit) + wc_ecc_free(userA); + if (userBInit) + wc_ecc_free(userB); + XFREE(userA, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(userB, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (registered) + wc_CryptoCb_UnRegisterDevice(ECIES_CB_TEST_DEVID); + + return ret; +} +#endif /* WOLF_CRYPTO_CB && !WOLFSSL_NO_MALLOC */ #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) @@ -44333,6 +44840,17 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_encrypt_test(void) #if !defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)) if (ret == 0) ret = ecc_encrypt_combos_test(&rng, userA, userB); +#if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + defined(HAVE_HKDF) && !defined(WOLFSSL_ECIES_OLD) && \ + !defined(WOLFSSL_ECIES_ISO18033) && !defined(WOLFSSL_ECIES_GEN_IV) && \ + defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) && !defined(WOLFSSL_NO_MALLOC) + if (ret == 0) + ret = ecc_encrypt_gcm_kat(&rng); +#endif +#if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_NO_MALLOC) + if (ret == 0) + ret = ecc_encrypt_cryptocb_test(&rng); +#endif #endif /* !HAVE_FIPS || FIPS_VERSION_GE(5,3) */ done: @@ -75916,6 +76434,27 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) info->pk.ecdh.private_key->devId = devIdArg; #endif } + #ifdef HAVE_ECC_ENCRYPT + else if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + /* set devId to invalid so the software path runs */ + info->pk.eciesencrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_encrypt_ex(info->pk.eciesencrypt.privKey, + info->pk.eciesencrypt.pubKey, info->pk.eciesencrypt.msg, + info->pk.eciesencrypt.msgSz, info->pk.eciesencrypt.out, + info->pk.eciesencrypt.outSz, info->pk.eciesencrypt.ctx, + info->pk.eciesencrypt.compressed); + /* reset devId */ + info->pk.eciesencrypt.privKey->devId = devIdArg; + } + else if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + info->pk.eciesdecrypt.privKey->devId = INVALID_DEVID; + ret = wc_ecc_decrypt(info->pk.eciesdecrypt.privKey, + info->pk.eciesdecrypt.pubKey, info->pk.eciesdecrypt.msg, + info->pk.eciesdecrypt.msgSz, info->pk.eciesdecrypt.out, + info->pk.eciesdecrypt.outSz, info->pk.eciesdecrypt.ctx); + info->pk.eciesdecrypt.privKey->devId = devIdArg; + } + #endif /* HAVE_ECC_ENCRYPT */ else if (info->pk.type == WC_PK_TYPE_EC_GET_SIZE) { WC_DECLARE_VAR(tmpEcc, ecc_key, 1, NULL); WC_ALLOC_VAR(tmpEcc, ecc_key, 1, NULL); diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 3de89680e6..753af89b51 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -264,6 +264,27 @@ typedef struct wc_CryptoInfo { * public point) */ } ecc_check_pub; /* distinct from ecc_check (priv-key cmp) */ #endif + #ifdef HAVE_ECC_ENCRYPT + struct { + ecc_key* privKey; + ecc_key* pubKey; + const byte* msg; + word32 msgSz; + byte* out; + word32* outSz; + ecEncCtx* ctx; + int compressed; + } eciesencrypt; + struct { + ecc_key* privKey; + ecc_key* pubKey; + const byte* msg; + word32 msgSz; + byte* out; + word32* outSz; + ecEncCtx* ctx; + } eciesdecrypt; + #endif /* HAVE_ECC_ENCRYPT */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 struct { @@ -795,6 +816,13 @@ WOLFSSL_LOCAL int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut); WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv); #endif +#ifdef HAVE_ECC_ENCRYPT +WOLFSSL_LOCAL int wc_CryptoCb_EciesEncrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx, + int compressed); +WOLFSSL_LOCAL int wc_CryptoCb_EciesDecrypt(ecc_key* privKey, ecc_key* pubKey, + const byte* msg, word32 msgSz, byte* out, word32* outSz, ecEncCtx* ctx); +#endif #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index 763b3e603b..0793e79dcd 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1036,7 +1036,9 @@ enum ecEncAlgo { ecAES_128_CBC = 1, /* default */ ecAES_256_CBC = 2, ecAES_128_CTR = 3, - ecAES_256_CTR = 4 + ecAES_256_CTR = 4, + ecAES_128_GCM = 5, + ecAES_256_GCM = 6 }; enum ecKdfAlgo { @@ -1059,6 +1061,8 @@ enum { IV_SIZE_64 = 8, IV_SIZE_128 = 16, ECC_MAX_IV_SIZE = 16, + AES_GCM_NONCE_SZ = 12, /* GCM IV/nonce length for ECIES DEM */ + AES_GCM_TAG_SZ = 16, /* GCM authentication tag length */ EXCHANGE_SALT_SZ = 16, EXCHANGE_INFO_SZ = 23 }; @@ -1087,6 +1091,16 @@ int wc_ecc_ctx_reset(ecEncCtx* ctx, WC_RNG* rng); /* reset for use again w/o al WOLFSSL_API int wc_ecc_ctx_set_algo(ecEncCtx* ctx, byte encAlgo, byte kdfAlgo, byte macAlgo); +#ifdef WOLF_CRYPTO_CB +/* Accessors for crypto-callback backends; only built with WOLF_CRYPTO_CB. */ +WOLFSSL_API +int wc_ecc_ctx_get_algo(ecEncCtx* ctx, byte* encAlgo, byte* kdfAlgo, + byte* macAlgo); +WOLFSSL_API +int wc_ecc_ctx_get_kdf_salt(ecEncCtx* ctx, const byte** salt, word32* sz); +WOLFSSL_API +int wc_ecc_ctx_get_info(ecEncCtx* ctx, const byte** info, word32* sz); +#endif /* WOLF_CRYPTO_CB */ WOLFSSL_API const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx); WOLFSSL_API diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index aad0ad68b4..95aae100cd 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1623,6 +1623,12 @@ enum wc_PkType { WC_PK_TYPE_EC_CHECK_PUB_KEY = 35, #undef _WC_PK_TYPE_MAX #define _WC_PK_TYPE_MAX WC_PK_TYPE_EC_CHECK_PUB_KEY +#if defined(HAVE_ECC) && defined(HAVE_ECC_ENCRYPT) + WC_PK_TYPE_ECIES_ENCRYPT = 36, + WC_PK_TYPE_ECIES_DECRYPT = 37, + #undef _WC_PK_TYPE_MAX + #define _WC_PK_TYPE_MAX WC_PK_TYPE_ECIES_DECRYPT +#endif WC_PK_TYPE_MAX = _WC_PK_TYPE_MAX };