diff --git a/configure.ac b/configure.ac index a7a9a93cfc3..890e437713f 100644 --- a/configure.ac +++ b/configure.ac @@ -742,6 +742,7 @@ then test "$enable_trusted_ca" = "" && enable_trusted_ca=yes test "$enable_session_ticket" = "" && enable_session_ticket=yes test "$enable_earlydata" = "" && enable_earlydata=yes + test "$enable_ech" = "" && enable_ech=yes if test "$ENABLED_32BIT" != "yes" then @@ -1120,6 +1121,25 @@ AC_ARG_ENABLE([cryptonly], AS_IF([test "x$FIPS_VERSION" = "xrand"],[ENABLED_CRYPTONLY="yes"]) +# ECH +AC_ARG_ENABLE([ech], + [AS_HELP_STRING([--enable-ech],[Enable ECH (default: disabled)])], + [ ENABLED_ECH=$enableval ], + [ ENABLED_ECH=no ] + ) +if test "$ENABLED_ECH" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_ECH" + + test "$enable_hpke" = "" && enable_hpke=yes + test "$enable_ecc" = "" && enable_ecc=yes + test "$enable_curve25519" = "" && enable_curve25519=yes + test "$enable_sha256" = "" && enable_sha256=yes + test "$enable_tlsx" = "" && enable_tlsx=yes + test "$enable_sni" = "" && enable_sni=yes + test "$enable_tls13" = "" && enable_tls13=yes +fi + # DTLS # DTLS is a prereq for the options mcast, sctp, and jni. Enabling any of those # without DTLS will also enable DTLS. @@ -2980,6 +3000,20 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF" fi + +# HPKE +AC_ARG_ENABLE([hpke], + [AS_HELP_STRING([--enable-hpke],[Enable HKPE support (default: disabled)])], + [ ENABLED_HPKE=$enableval ], + [ ENABLED_HPKE=no ] + ) +if test "$ENABLED_HPKE" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_HPKE" + + test "$enable_hkdf" = "" && enable_hkdf=yes +fi + # X9.63 KDF AC_ARG_ENABLE([x963kdf], [AS_HELP_STRING([--enable-x963kdf],[Enable X9.63 KDF support (default: disabled)])], @@ -8450,6 +8484,7 @@ AM_CONDITIONAL([BUILD_PSA],[test "x$ENABLED_PSA" = "xyes"]) AM_CONDITIONAL([BUILD_DTLS13],[test "x$ENABLED_DTLS13" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_QUIC],[test "x$ENABLED_QUIC" = "xyes"]) AM_CONDITIONAL([BUILD_DTLS_CID],[test "x$ENABLED_DTLS_CID" = "xyes"]) +AM_CONDITIONAL([BUILD_HPKE],[test "x$ENABLED_HPKE" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_DTLS],[test "x$ENABLED_DTLS" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_MAXQ10XX],[test "x$ENABLED_MAXQ10XX" = "xyes"]) @@ -8740,6 +8775,7 @@ echo " * PWDBASED: $ENABLED_PWDBASED" echo " * scrypt: $ENABLED_SCRYPT" echo " * wolfCrypt Only: $ENABLED_CRYPTONLY" echo " * HKDF: $ENABLED_HKDF" +echo " * HPKE: $ENABLED_HPKE" echo " * X9.63 KDF: $ENABLED_X963KDF" echo " * MD4: $ENABLED_MD4" echo " * PSK: $ENABLED_PSK" diff --git a/examples/configs/user_settings_all.h b/examples/configs/user_settings_all.h index 29d21756af2..c708a64e4f3 100644 --- a/examples/configs/user_settings_all.h +++ b/examples/configs/user_settings_all.h @@ -234,6 +234,9 @@ extern "C" { #define HAVE_KEYING_MATERIAL #define WOLFSSL_HAVE_PRF +/* Encrypted Client Hello */ +#define HAVE_HPKE +#define HAVE_ECH /* Non-Standard Algorithms (DG disabled) */ //#define HAVE_CAMELLIA diff --git a/src/include.am b/src/include.am index c11a4a27e47..dacd89f5647 100644 --- a/src/include.am +++ b/src/include.am @@ -509,6 +509,10 @@ if BUILD_ASN src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/asn.c endif +if BUILD_HPKE +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/hpke.c +endif + endif !BUILD_FIPS_RAND if BUILD_CODING diff --git a/src/internal.c b/src/internal.c index 219760e8ceb..10e8033459b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2398,6 +2398,36 @@ void wolfSSL_CRYPTO_cleanup_ex_data(WOLFSSL_CRYPTO_EX_DATA* ex_data) } #endif /* HAVE_EX_DATA_CLEANUP_HOOKS */ +#if defined(HAVE_ECH) +/* free all ech configs in the list */ +static void FreeEchConfigs(WOLFSSL_EchConfig* configs, void* heap) +{ + WOLFSSL_EchConfig* working_config = configs; + WOLFSSL_EchConfig* next_config; + + while (working_config != NULL) { + next_config = working_config->next; + + XFREE(working_config->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(working_config->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (working_config->raw != NULL) + XFREE(working_config->raw, heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (working_config->receiverPrivkey != NULL) { + wc_HpkeFreeKey(NULL, working_config->kemId, + working_config->receiverPrivkey, heap); + } + + XFREE(working_config, heap, DYNAMIC_TYPE_TMP_BUFFER); + + working_config = next_config; + } + + (void)heap; +} +#endif + /* In case contexts are held in array and don't want to free actual ctx. */ /* The allocations done in InitSSL_Ctx must be free'd with ctx->onHeapHint @@ -2554,6 +2584,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) ctx->staticKELockInit = 0; } #endif +#endif +#if defined(HAVE_ECH) + FreeEchConfigs(ctx->echConfigs, ctx->heap); + ctx->echConfigs = NULL; #endif (void)heapAtCTXInit; } @@ -6479,6 +6513,71 @@ void FreeHandshakeHashes(WOLFSSL* ssl) } } +/* copy the hashes from source to a newly made destination return status */ +int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source, + HS_Hashes** destination) +{ + int ret = 0; + HS_Hashes* tmpHashes; + + if (source == NULL) + return BAD_FUNC_ARG; + + /* save the original so we can put it back afterward */ + tmpHashes = ssl->hsHashes; + ssl->hsHashes = NULL; + + InitHandshakeHashes(ssl); + + *destination = ssl->hsHashes; + ssl->hsHashes = tmpHashes; + + /* now copy the source contents to the destination */ +#ifndef NO_OLD_TLS + #ifndef NO_SHA + ret = wc_ShaCopy(&source->hashSha, &(*destination)->hashSha); + #endif + #ifndef NO_MD5 + if (ret == 0) + ret = wc_Md5Copy(&source->hashMd5, &(*destination)->hashMd5); + #endif + #endif /* !NO_OLD_TLS */ + #ifndef NO_SHA256 + if (ret == 0) + ret = wc_Sha256Copy(&source->hashSha256, + &(*destination)->hashSha256); + #endif + #ifdef WOLFSSL_SHA384 + if (ret == 0) + ret = wc_Sha384Copy(&source->hashSha384, + &(*destination)->hashSha384); + #endif + #ifdef WOLFSSL_SHA512 + if (ret == 0) + ret = wc_Sha512Copy(&source->hashSha512, + &(*destination)->hashSha512); + #endif + #if (defined(HAVE_ED25519) || defined(HAVE_ED448)) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + if (ret == 0 && source->messages != NULL) { + (*destination)->messages = (byte*)XMALLOC(source->length, ssl->heap, + DYNAMIC_TYPE_HASHES); + (*destination)->length = source->length; + (*destination)->prevLen = source->prevLen; + + if ((*destination)->messages == NULL) { + ret = MEMORY_E; + } + else { + XMEMCPY((*destination)->messages, source->messages, + source->length); + } + } + #endif + + return ret; +} + /* called if user attempts to re-use WOLFSSL object for a new session. * For example wolfSSL_clear() is called then wolfSSL_connect or accept */ int ReinitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) @@ -7463,6 +7562,17 @@ void SSL_ResourceFree(WOLFSSL* ssl) ForceZero(&ssl->clientSecret, sizeof(ssl->clientSecret)); ForceZero(&ssl->serverSecret, sizeof(ssl->serverSecret)); } + +#if defined(HAVE_ECH) + if (ssl->options.useEch == 1) { + FreeEchConfigs(ssl->echConfigs, ssl->heap); + ssl->echConfigs = NULL; + /* free the ech specific hashes */ + ssl->hsHashes = ssl->hsHashesEch; + FreeHandshakeHashes(ssl); + ssl->options.useEch = 0; + } +#endif #endif #ifdef WOLFSSL_HAVE_TLS_UNIQUE ForceZero(&ssl->clientFinished, TLS_FINISHED_SZ_MAX); diff --git a/src/ssl.c b/src/ssl.c index 9438b04d3d3..5cb95a67305 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -222,6 +222,8 @@ #endif /* !WOLFSSL_NO_OPENSSL_RAND_CB */ #endif /* OPENSSL_EXTRA */ +#include + #if defined(OPENSSL_EXTRA) && defined(HAVE_ECC) const WOLF_EC_NIST_NAME kNistCurves[] = { {XSTR_SIZEOF("P-192"), "P-192", NID_X9_62_prime192v1}, @@ -260,6 +262,598 @@ const WOLF_EC_NIST_NAME kNistCurves[] = { }; #endif +#if defined(HAVE_ECH) +/* create the hpke key and ech config to send to clients */ +int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, + word16 kemId, word16 kdfId, word16 aeadId) +{ + int ret = 0; + word16 encLen = DHKEM_X25519_ENC_LEN; +#ifdef WOLFSSL_SMALL_STACK + Hpke* hpke = NULL; + WC_RNG* rng; +#else + Hpke hpke[1]; + WC_RNG rng[1]; +#endif + + if (ctx == NULL || publicName == NULL) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_SMALL_STACK + rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), ctx->heap, DYNAMIC_TYPE_RNG); + if (rng == NULL) + return MEMORY_E; +#endif + ret = wc_InitRng(rng); + if (ret != 0) { + #ifdef WOLFSSL_SMALL_STACK + XFREE(rng, ctx->heap, DYNAMIC_TYPE_RNG); + #endif + return ret; + } + + ctx->echConfigs = (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), + ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ctx->echConfigs == NULL) + ret = MEMORY_E; + else + XMEMSET(ctx->echConfigs, 0, sizeof(WOLFSSL_EchConfig)); + + /* set random config id */ + if (ret == 0) + ret = wc_RNG_GenerateByte(rng, &ctx->echConfigs->configId); + + /* if 0 is selected for algorithms use default, may change with draft */ + if (kemId == 0) + kemId = DHKEM_X25519_HKDF_SHA256; + + if (kdfId == 0) + kdfId = HKDF_SHA256; + + if (aeadId == 0) + aeadId = HPKE_AES_128_GCM; + + if (ret == 0) { + /* set the kem id */ + ctx->echConfigs->kemId = kemId; + + /* set the cipher suite, only 1 for now */ + ctx->echConfigs->numCipherSuites = 1; + ctx->echConfigs->cipherSuites = (EchCipherSuite*)XMALLOC( + sizeof(EchCipherSuite), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (ctx->echConfigs->cipherSuites == NULL) { + ret = MEMORY_E; + } + else { + ctx->echConfigs->cipherSuites[0].kdfId = kdfId; + ctx->echConfigs->cipherSuites[0].aeadId = aeadId; + } + } + +#ifdef WOLFSSL_SMALL_STACK + if (ret == 0) { + hpke = (Hpke*)XMALLOC(sizeof(Hpke), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (hpke == NULL) + ret = MEMORY_E; + } +#endif + + if (ret == 0) + ret = wc_HpkeInit(hpke, kemId, kdfId, aeadId, ctx->heap); + + /* generate the receiver private key */ + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(hpke, &ctx->echConfigs->receiverPrivkey, + rng); + + /* done with RNG */ + wc_FreeRng(rng); + + /* serialize the receiver key */ + if (ret == 0) + ret = wc_HpkeSerializePublicKey(hpke, ctx->echConfigs->receiverPrivkey, + ctx->echConfigs->receiverPubkey, &encLen); + + if (ret == 0) { + ctx->echConfigs->publicName = (char*)XMALLOC(XSTRLEN(publicName) + 1, + ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ctx->echConfigs->publicName == NULL) { + ret = MEMORY_E; + } + else { + XMEMCPY(ctx->echConfigs->publicName, publicName, + XSTRLEN(publicName) + 1); + } + } + + if (ret != 0) { + if (ctx->echConfigs) { + XFREE(ctx->echConfigs->cipherSuites, ctx->heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ctx->echConfigs->publicName, ctx->heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ctx->echConfigs, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + /* set to null to avoid double free in cleanup */ + ctx->echConfigs = NULL; + } + } + + if (ret == 0) + ret = WOLFSSL_SUCCESS; + +#ifdef WOLFSSL_SMALL_STACK + XFREE(hpke, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(rng, ctx->heap, DYNAMIC_TYPE_RNG); +#endif + + return ret; +} + +/* get the ech configs that the server context is using */ +int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, + word32* outputLen) { + if (ctx == NULL || outputLen == NULL) + return BAD_FUNC_ARG; + + /* if we don't have ech configs */ + if (ctx->echConfigs == NULL) { + return WOLFSSL_FATAL_ERROR; + } + + return GetEchConfigsEx(ctx->echConfigs, output, outputLen); +} + +/* set the ech config from base64 for our client ssl object, base64 is the + * format ech configs are sent using dns records */ +int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, + word32 echConfigs64Len) +{ + int ret = 0; + word32 decodedLen = echConfigs64Len * 3 / 4 + 1; + byte* decodedConfigs; + + if (ssl == NULL || echConfigs64 == NULL || echConfigs64Len == 0) + return BAD_FUNC_ARG; + + /* already have ech configs */ + if (ssl->options.useEch == 1) { + return WOLFSSL_FATAL_ERROR; + } + + decodedConfigs = (byte*)XMALLOC(decodedLen, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (decodedConfigs == NULL) + return MEMORY_E; + + decodedConfigs[decodedLen - 1] = 0; + + /* decode the echConfigs */ + ret = Base64_Decode((byte*)echConfigs64, echConfigs64Len, + decodedConfigs, &decodedLen); + + if (ret != 0) { + XFREE(decodedConfigs, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + return ret; + } + + ret = wolfSSL_SetEchConfigs(ssl, decodedConfigs, decodedLen); + + XFREE(decodedConfigs, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* set the ech config from a raw buffer, this is the format ech configs are + * sent using retry_configs from the ech server */ +int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, + word32 echConfigsLen) +{ + int ret = 0; + int i; + int j; + word16 totalLength; + word16 version; + word16 length; + word16 hpkePubkeyLen; + word16 cipherSuitesLen; + word16 publicNameLen; + WOLFSSL_EchConfig* configList = NULL; + WOLFSSL_EchConfig* workingConfig = NULL; + WOLFSSL_EchConfig* lastConfig = NULL; + byte* echConfig = NULL; + + if (ssl == NULL || echConfigs == NULL || echConfigsLen == 0) + return BAD_FUNC_ARG; + + /* already have ech configs */ + if (ssl->options.useEch == 1) { + return WOLFSSL_FATAL_ERROR; + } + + /* check that the total length is well formed */ + ato16(echConfigs, &totalLength); + + if (totalLength != echConfigsLen - 2) { + return WOLFSSL_FATAL_ERROR; + } + + /* skip the total length uint16_t */ + i = 2; + + do { + echConfig = (byte*)echConfigs + i; + ato16(echConfig, &version); + ato16(echConfig + 2, &length); + + /* if the version does not match */ + if (version != TLSX_ECH) { + /* we hit the end of the configs */ + if ( (word32)i + 2 >= echConfigsLen ) { + break; + } + + /* skip this config, +4 for version and length */ + i += length + 4; + continue; + } + + /* check if the length will overrun the buffer */ + if ((word32)i + length + 4 > echConfigsLen) { + break; + } + + if (workingConfig == NULL) { + workingConfig = + (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + configList = workingConfig; + workingConfig->next = NULL; + } + else { + lastConfig = workingConfig; + workingConfig->next = + (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + workingConfig = workingConfig->next; + } + + if (workingConfig == NULL) { + ret = MEMORY_E; + break; + } + + XMEMSET(workingConfig, 0, sizeof(WOLFSSL_EchConfig)); + + /* rawLen */ + workingConfig->rawLen = length + 4; + + /* raw body */ + workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->raw == NULL) { + ret = MEMORY_E; + break; + } + + XMEMCPY(workingConfig->raw, echConfig, workingConfig->rawLen); + + /* skip over version and length */ + echConfig += 4; + + /* configId, 1 byte */ + workingConfig->configId = *(echConfig); + echConfig++; + /* kemId, 2 bytes */ + ato16(echConfig, &workingConfig->kemId); + echConfig += 2; + /* hpke public_key length, 2 bytes */ + ato16(echConfig, &hpkePubkeyLen); + echConfig += 2; + /* hpke public_key */ + XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen); + echConfig += hpkePubkeyLen; + /* cipherSuitesLen */ + ato16(echConfig, &cipherSuitesLen); + + workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->cipherSuites == NULL) { + ret = MEMORY_E; + break; + } + + echConfig += 2; + workingConfig->numCipherSuites = cipherSuitesLen / 4; + /* cipherSuites */ + for (j = 0; j < workingConfig->numCipherSuites; j++) { + ato16(echConfig + j * 4, &workingConfig->cipherSuites[j].kdfId); + ato16(echConfig + j * 4 + 2, + &workingConfig->cipherSuites[j].aeadId); + } + echConfig += cipherSuitesLen; + /* publicNameLen */ + ato16(echConfig, &publicNameLen); + workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->publicName == NULL) { + ret = MEMORY_E; + break; + } + + echConfig += 2; + /* publicName */ + XMEMCPY(workingConfig->publicName, echConfig, publicNameLen); + /* null terminated */ + workingConfig->publicName[publicNameLen] = 0; + + /* add length to go to next config, +4 for version and length */ + i += length + 4; + + /* check that we support this config */ + for (j = 0; j < HPKE_SUPPORTED_KEM_LEN; j++) { + if (hpkeSupportedKem[j] == workingConfig->kemId) + break; + } + + /* if we don't support the kem or at least one cipher suite */ + if (j >= HPKE_SUPPORTED_KEM_LEN || + EchConfigGetSupportedCipherSuite(workingConfig) < 0) + { + XFREE(workingConfig->cipherSuites, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig->publicName, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + workingConfig = lastConfig; + } + } while ((word32)i < echConfigsLen); + + /* if we found valid configs */ + if (ret == 0 && configList != NULL) { + ssl->options.useEch = 1; + ssl->echConfigs = configList; + + return WOLFSSL_SUCCESS; + } + + workingConfig = configList; + + while (workingConfig != NULL) { + lastConfig = workingConfig; + workingConfig = workingConfig->next; + + XFREE(lastConfig->cipherSuites, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(lastConfig->publicName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(lastConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + XFREE(lastConfig, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret == 0) + return WOLFSSL_FATAL_ERROR; + + return ret; +} + +/* get the raw ech config from our struct */ +int GetEchConfig(WOLFSSL_EchConfig* config, byte* output, word32* outputLen) +{ + int i; + word16 totalLen = 0; + + if (config == NULL || (output == NULL && outputLen == NULL)) + return BAD_FUNC_ARG; + + /* 2 for version */ + totalLen += 2; + /* 2 for length */ + totalLen += 2; + /* 1 for configId */ + totalLen += 1; + /* 2 for kemId */ + totalLen += 2; + /* 2 for hpke_len */ + totalLen += 2; + + /* hpke_pub_key */ + switch (config->kemId) { + case DHKEM_P256_HKDF_SHA256: + totalLen += DHKEM_P256_ENC_LEN; + break; + case DHKEM_P384_HKDF_SHA384: + totalLen += DHKEM_P384_ENC_LEN; + break; + case DHKEM_P521_HKDF_SHA512: + totalLen += DHKEM_P521_ENC_LEN; + break; + case DHKEM_X25519_HKDF_SHA256: + totalLen += DHKEM_X25519_ENC_LEN; + break; + case DHKEM_X448_HKDF_SHA512: + totalLen += DHKEM_X448_ENC_LEN; + break; + } + + /* cipherSuitesLen */ + totalLen += 2; + /* cipherSuites */ + totalLen += config->numCipherSuites * 4; + /* public name len */ + totalLen += 2; + + /* public name */ + totalLen += XSTRLEN(config->publicName); + /* trailing zeros */ + totalLen += 2; + + if (output == NULL) { + *outputLen = totalLen; + return LENGTH_ONLY_E; + } + + if (totalLen > *outputLen) { + *outputLen = totalLen; + return INPUT_SIZE_E; + } + + /* version */ + c16toa(TLSX_ECH, output); + output += 2; + + /* length - 4 for version and length itself */ + c16toa(totalLen - 4, output); + output += 2; + + /* configId */ + *output = config->configId; + output++; + /* kemId */ + c16toa(config->kemId, output); + output += 2; + + /* length and key itself */ + switch (config->kemId) { + case DHKEM_P256_HKDF_SHA256: + c16toa(DHKEM_P256_ENC_LEN, output); + output += 2; + XMEMCPY(output, config->receiverPubkey, DHKEM_P256_ENC_LEN); + output += DHKEM_P256_ENC_LEN; + break; + case DHKEM_P384_HKDF_SHA384: + c16toa(DHKEM_P384_ENC_LEN, output); + output += 2; + XMEMCPY(output, config->receiverPubkey, DHKEM_P384_ENC_LEN); + output += DHKEM_P384_ENC_LEN; + break; + case DHKEM_P521_HKDF_SHA512: + c16toa(DHKEM_P521_ENC_LEN, output); + output += 2; + XMEMCPY(output, config->receiverPubkey, DHKEM_P521_ENC_LEN); + output += DHKEM_P521_ENC_LEN; + break; + case DHKEM_X25519_HKDF_SHA256: + c16toa(DHKEM_X25519_ENC_LEN, output); + output += 2; + XMEMCPY(output, config->receiverPubkey, DHKEM_X25519_ENC_LEN); + output += DHKEM_X25519_ENC_LEN; + break; + case DHKEM_X448_HKDF_SHA512: + c16toa(DHKEM_X448_ENC_LEN, output); + output += 2; + XMEMCPY(output, config->receiverPubkey, DHKEM_X448_ENC_LEN); + output += DHKEM_X448_ENC_LEN; + break; + } + + /* cipherSuites len */ + c16toa(config->numCipherSuites * 4, output); + output += 2; + + /* cipherSuites */ + for (i = 0; i < config->numCipherSuites; i++) { + c16toa(config->cipherSuites[i].kdfId, output); + output += 2; + c16toa(config->cipherSuites[i].aeadId, output); + output += 2; + } + + /* publicName len */ + c16toa(XSTRLEN(config->publicName), output); + output += 2; + + /* publicName */ + XMEMCPY(output, config->publicName, + XSTRLEN(config->publicName)); + output += XSTRLEN(config->publicName); + + /* terminating zeros */ + c16toa(0, output); + /* output += 2; */ + + *outputLen = totalLen; + + return 0; +} + +/* wrapper function to get ech configs from application code */ +int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* output, word32* outputLen) +{ + if (ssl == NULL || outputLen == NULL) + return BAD_FUNC_ARG; + + /* if we don't have ech configs */ + if (ssl->options.useEch != 1) { + return WOLFSSL_FATAL_ERROR; + } + + return GetEchConfigsEx(ssl->echConfigs, output, outputLen); +} + +/* get the raw ech configs from our linked list of ech config structs */ +int GetEchConfigsEx(WOLFSSL_EchConfig* configs, byte* output, word32* outputLen) +{ + int ret = 0; + WOLFSSL_EchConfig* workingConfig = NULL; + byte* outputStart = output; + word32 totalLen = 2; + word32 workingOutputLen; + + if (configs == NULL || outputLen == NULL) + return BAD_FUNC_ARG; + + workingOutputLen = *outputLen - totalLen; + + /* skip over total length which we fill in later */ + if (output != NULL) + output += 2; + + workingConfig = configs; + + while (workingConfig != NULL) { + /* get this config */ + ret = GetEchConfig(workingConfig, output, &workingOutputLen); + + if (output != NULL) + output += workingOutputLen; + + /* add this config's length to the total length */ + totalLen += workingOutputLen; + + if (totalLen > *outputLen) + workingOutputLen = 0; + else + workingOutputLen = *outputLen - totalLen; + + /* only error we break on, other 2 we need to keep finding length */ + if (ret == BAD_FUNC_ARG) + return BAD_FUNC_ARG; + + workingConfig = workingConfig->next; + } + + if (output == NULL) { + *outputLen = totalLen; + return LENGTH_ONLY_E; + } + + if (totalLen > *outputLen) { + *outputLen = totalLen; + return INPUT_SIZE_E; + } + + /* total size -2 for size itself */ + c16toa(totalLen - 2, outputStart); + + *outputLen = totalLen; + + return WOLFSSL_SUCCESS; +} +#endif /* HAVE_ECH */ + + #if defined(WOLFSSL_RENESAS_TSIP_TLS) || defined(WOLFSSL_RENESAS_SCEPROTECT) #include #endif diff --git a/src/tls.c b/src/tls.c index d02fdb19cec..25028542769 100644 --- a/src/tls.c +++ b/src/tls.c @@ -61,6 +61,8 @@ #include #endif +#include + #ifndef NO_TLS #if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) @@ -1258,6 +1260,10 @@ static WC_INLINE word16 TLSX_ToSemaphore(word16 type) #ifdef WOLFSSL_QUIC case TLSX_KEY_QUIC_TP_PARAMS_DRAFT: /* 0xffa5 */ return 64; +#endif +#if defined(HAVE_ECH) + case TLSX_ECH: /* 0xfe0d */ + return 65; #endif default: if (type > 62) { @@ -2020,8 +2026,12 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte type; int matchStat; byte matched; +#if defined(HAVE_ECH) + WOLFSSL_ECH* ech = NULL; + WOLFSSL_EchConfig* workingConfig; + TLSX* echX; #endif - +#endif /* !NO_WOLFSSL_SERVER */ TLSX *extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); if (!extension) @@ -2109,6 +2119,27 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size && XSTRNCMP(sni->data.host_name, (const char*)input + offset, size) == 0); +#if defined(HAVE_ECH) + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + if (echX != NULL) + ech = (WOLFSSL_ECH*)(echX->data); + + if (!matched && ech != NULL) { + workingConfig = ech->echConfig; + + while (workingConfig != NULL) { + matched = XSTRLEN(workingConfig->publicName) == size && + XSTRNCMP(workingConfig->publicName, + (const char*)input + offset, size) == 0; + + if (matched) + break; + + workingConfig = workingConfig->next; + } + } +#endif + if (matched || sni->options & WOLFSSL_SNI_ANSWER_ON_MISMATCH) { int r = TLSX_UseSNI(&ssl->extensions, type, input + offset, size, ssl->heap); @@ -2140,7 +2171,7 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, } #else (void)input; -#endif +#endif /* !NO_WOLFSSL_SERVER */ #if defined(NO_WOLFSSL_CLIENT) && defined(NO_WOLFSSL_SERVER) (void)length; @@ -10200,8 +10231,14 @@ TLSX* TLSX_Find(TLSX* list, TLSX_Type type) /** Remove an extension. */ void TLSX_Remove(TLSX** list, TLSX_Type type, void* heap) { - TLSX* extension = *list; - TLSX** next = list; + TLSX* extension; + TLSX** next; + + if (list == NULL) + return; + + extension = *list; + next = list; while (extension && extension->type != type) { next = &extension->next; @@ -10215,6 +10252,694 @@ void TLSX_Remove(TLSX** list, TLSX_Type type, void* heap) } } +#if defined(HAVE_ECH) +#define GREASE_ECH_SIZE 160 +#define MAX_PUBLIC_NAME_SZ 256 +#define TLS_INFO_CONST_STRING "tls ech" +#define TLS_INFO_CONST_STRING_SZ 7 + +/* return status after setting up ech to write a grease ech */ +static int TLSX_GreaseECH_Use(TLSX** extensions, void* heap, WC_RNG* rng) +{ + int ret = 0; + WOLFSSL_ECH* ech; + + if (extensions == NULL) + return BAD_FUNC_ARG; + + ech = (WOLFSSL_ECH*)XMALLOC(sizeof(WOLFSSL_ECH), heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (ech == NULL) + return MEMORY_E; + + ForceZero(ech, sizeof(WOLFSSL_ECH)); + + ech->state = ECH_WRITE_GREASE; + + /* 0 for outer */ + ech->type = ECH_TYPE_OUTER; + /* kemId */ + ech->kemId = DHKEM_X25519_HKDF_SHA256; + /* cipherSuite kdf */ + ech->cipherSuite.kdfId = HKDF_SHA256; + /* cipherSuite aead */ + ech->cipherSuite.aeadId = HPKE_AES_128_GCM; + + /* random configId */ + ret = wc_RNG_GenerateByte(rng, &(ech->configId)); + + /* curve25519 encLen */ + ech->encLen = DHKEM_X25519_ENC_LEN; + + if (ret == 0) + ret = TLSX_Push(extensions, TLSX_ECH, ech, heap); + + if (ret != 0) { + XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); + } + + return ret; +} + +/* return status after setting up ech to write real ech */ +static int TLSX_ECH_Use(WOLFSSL_EchConfig* echConfig, TLSX** extensions, + void* heap, WC_RNG* rng) +{ + int ret = 0; + int suiteIndex; + WOLFSSL_ECH* ech; + + if (extensions == NULL) + return BAD_FUNC_ARG; + + /* find a supported cipher suite */ + suiteIndex = EchConfigGetSupportedCipherSuite(echConfig); + + if (suiteIndex < 0) + return suiteIndex; + + ech = (WOLFSSL_ECH*)XMALLOC(sizeof(WOLFSSL_ECH), heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (ech == NULL) + return MEMORY_E; + + ForceZero(ech, sizeof(WOLFSSL_ECH)); + + ech->state = ECH_WRITE_REAL; + + ech->echConfig = echConfig; + + /* 0 for outer */ + ech->type = ECH_TYPE_OUTER; + /* kemId */ + ech->kemId = echConfig->kemId; + + /* cipherSuite kdf */ + ech->cipherSuite.kdfId = echConfig->cipherSuites[suiteIndex].kdfId; + /* cipherSuite aead */ + ech->cipherSuite.aeadId = echConfig->cipherSuites[suiteIndex].aeadId; + /* configId */ + ech->configId = echConfig->configId; + + /* encLen */ + switch (echConfig->kemId) + { + case DHKEM_P256_HKDF_SHA256: + ech->encLen = DHKEM_P256_ENC_LEN; + break; + case DHKEM_P384_HKDF_SHA384: + ech->encLen = DHKEM_P384_ENC_LEN; + break; + case DHKEM_P521_HKDF_SHA512: + ech->encLen = DHKEM_P521_ENC_LEN; + break; + case DHKEM_X25519_HKDF_SHA256: + ech->encLen = DHKEM_X25519_ENC_LEN; + break; + case DHKEM_X448_HKDF_SHA512: + ech->encLen = DHKEM_X448_ENC_LEN; + break; + } + + /* setup hpke */ + ech->hpke = (Hpke*)XMALLOC(sizeof(Hpke), heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (ech->hpke == NULL) { + XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } + + ret = wc_HpkeInit(ech->hpke, ech->kemId, ech->cipherSuite.kdfId, + ech->cipherSuite.aeadId, heap); + + /* setup the ephemeralKey */ + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(ech->hpke, &ech->ephemeralKey, rng); + + if (ret == 0) + ret = TLSX_Push(extensions, TLSX_ECH, ech, heap); + + if (ret != 0) { + XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); + } + + return ret; +} + +/* return status after setting up ech to read and decrypt */ +static int TLSX_ServerECH_Use(TLSX** extensions, void* heap, + WOLFSSL_EchConfig* configs) +{ + int ret; + WOLFSSL_ECH* ech; + TLSX* echX; + + /* if we already have ech don't override it */ + echX = TLSX_Find(*extensions, TLSX_ECH); + if (echX != NULL) + return 0; + + if (extensions == NULL) + return BAD_FUNC_ARG; + + ech = (WOLFSSL_ECH*)XMALLOC(sizeof(WOLFSSL_ECH), heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (ech == NULL) + return MEMORY_E; + + ForceZero(ech, sizeof(WOLFSSL_ECH)); + + ech->state = ECH_WRITE_NONE; + + /* 0 for outer */ + ech->type = ECH_TYPE_OUTER; + + ech->echConfig = configs; + + /* setup the rest of the settings when we receive ech from the client */ + ret = TLSX_Push(extensions, TLSX_ECH, ech, heap); + + if (ret != 0) + XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* return length after writing the ech */ +static int TLSX_ECH_Write(WOLFSSL_ECH* ech, byte* writeBuf, word16* offset) +{ + int ret = 0; + int rngRet = -1; + word32 configsLen = 0; + void* ephemeralKey = NULL; + byte* writeBuf_p = writeBuf; +#ifdef WOLFSSL_SMALL_STACK + Hpke* hpke = NULL; + WC_RNG* rng = NULL; +#else + Hpke hpke[1]; + WC_RNG rng[1]; +#endif + + WOLFSSL_MSG("TLSX_ECH_Write"); + + if (ech->state == ECH_WRITE_NONE || ech->state == ECH_PARSED_INTERNAL) + return 0; + + if (ech->state == ECH_WRITE_RETRY_CONFIGS) { + /* get size then write */ + ret = GetEchConfigsEx(ech->echConfig, NULL, &configsLen); + + if (ret != LENGTH_ONLY_E) + return ret; + + ret = GetEchConfigsEx(ech->echConfig, writeBuf, &configsLen); + + if (ret != WOLFSSL_SUCCESS) + return ret; + + return configsLen; + } + +#ifdef WOLFSSL_SMALL_STACK + hpke = (Hpke*)XMALLOC(sizeof(Hpke), NULL, DYNAMIC_TYPE_TMP_BUFFER); + + if (hpke == NULL) + return MEMORY_E; + + rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), NULL, DYNAMIC_TYPE_RNG); + + if (rng == NULL) { + XFREE(hpke, NULL, DYNAMIC_TYPE_RNG); + return MEMORY_E; + } +#endif + + /* type */ + *writeBuf_p = ech->type; + writeBuf_p += sizeof(ech->type); + + /* outer has body, inner does not */ + if (ech->type == ECH_TYPE_OUTER) { + /* kdfId */ + c16toa(ech->cipherSuite.kdfId, writeBuf_p); + writeBuf_p += sizeof(ech->cipherSuite.kdfId); + + /* aeadId */ + c16toa(ech->cipherSuite.aeadId, writeBuf_p); + writeBuf_p += sizeof(ech->cipherSuite.aeadId); + + /* configId */ + *writeBuf_p = ech->configId; + writeBuf_p += sizeof(ech->configId); + + /* encLen */ + c16toa(ech->encLen, writeBuf_p); + writeBuf_p += 2; + + if (ech->state == ECH_WRITE_GREASE) { + /* hpke init */ + ret = wc_HpkeInit(hpke, ech->kemId, ech->cipherSuite.kdfId, + ech->cipherSuite.aeadId, NULL); + + if (ret == 0) + rngRet = ret = wc_InitRng(rng); + + /* create the ephemeralKey */ + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(hpke, &ephemeralKey, rng); + + /* enc */ + if (ret == 0) { + ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey, writeBuf_p, + &ech->encLen); + writeBuf_p += ech->encLen; + } + + if (ret == 0) { + /* innerClientHelloLen */ + c16toa(GREASE_ECH_SIZE + ((writeBuf_p + 2 - writeBuf) % 32), + writeBuf_p); + writeBuf_p += 2; + + /* innerClientHello */ + ret = wc_RNG_GenerateBlock(rng, writeBuf_p, GREASE_ECH_SIZE + + ((writeBuf_p - writeBuf) % 32)); + writeBuf_p += GREASE_ECH_SIZE + ((writeBuf_p - writeBuf) % 32); + } + + if (rngRet == 0) + wc_FreeRng(rng); + + if (ephemeralKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, hpke->heap); + } + else { + /* write enc to writeBuf_p */ + ret = wc_HpkeSerializePublicKey(ech->hpke, ech->ephemeralKey, + writeBuf_p, &ech->encLen); + writeBuf_p += ech->encLen; + + /* innerClientHelloLen */ + c16toa(ech->innerClientHelloLen, writeBuf_p); + writeBuf_p += 2; + + /* set payload offset for when we finalize */ + ech->outerClientPayload = writeBuf_p; + + /* write zeros for payload */ + XMEMSET(writeBuf_p, 0, ech->innerClientHelloLen); + writeBuf_p += ech->innerClientHelloLen; + } + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(hpke, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(rng, NULL, DYNAMIC_TYPE_RNG); +#endif + + if (ret == 0) + *offset += (writeBuf_p - writeBuf); + + return ret; +} + +/* return the size needed for the ech extension */ +static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech) +{ + int ret; + word32 size; + + if (ech->state == ECH_WRITE_GREASE) { + size = sizeof(ech->type) + sizeof(ech->cipherSuite) + + sizeof(ech->configId) + sizeof(word16) + ech->encLen + + sizeof(word16); + + size += GREASE_ECH_SIZE + (size % 32); + } + else if (ech->state == ECH_WRITE_NONE || + ech->state == ECH_PARSED_INTERNAL) { + size = 0; + } + else if (ech->state == ECH_WRITE_RETRY_CONFIGS) { + /* get the size of the raw configs */ + ret = GetEchConfigsEx(ech->echConfig, NULL, &size); + + if (ret != LENGTH_ONLY_E) + return ret; + } + else if (ech->type == ECH_TYPE_INNER) + { + size = sizeof(ech->type); + } + else + { + size = sizeof(ech->type) + sizeof(ech->cipherSuite) + + sizeof(ech->configId) + sizeof(word16) + ech->encLen + + sizeof(word16) + ech->innerClientHelloLen; + } + + return (int)size; +} + +/* return status after attempting to open the hpke encrypted ech extension, if + * successful the inner client hello will be stored in + * ech->innerClientHelloLen */ +static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, + byte* aad, word32 aadLen, void* heap) +{ + int ret = 0; + int expectedEncLen; + int i; + word32 rawConfigLen = 0; + byte* info = NULL; + word32 infoLen = 0; + + if (ech == NULL || echConfig == NULL || aad == NULL) + return BAD_FUNC_ARG; + + /* verify the kem and key len */ + switch (echConfig->kemId) + { + case DHKEM_P256_HKDF_SHA256: + expectedEncLen = DHKEM_P256_ENC_LEN; + break; + case DHKEM_P384_HKDF_SHA384: + expectedEncLen = DHKEM_P384_ENC_LEN; + break; + case DHKEM_P521_HKDF_SHA512: + expectedEncLen = DHKEM_P521_ENC_LEN; + break; + case DHKEM_X25519_HKDF_SHA256: + expectedEncLen = DHKEM_X25519_ENC_LEN; + break; + case DHKEM_X448_HKDF_SHA512: + expectedEncLen = DHKEM_X448_ENC_LEN; + break; + default: + expectedEncLen = 0; + break; + } + + if (expectedEncLen != ech->encLen) + return BAD_FUNC_ARG; + + /* verify the cipher suite */ + for (i = 0; i < echConfig->numCipherSuites; i++) { + if (echConfig->cipherSuites[i].kdfId == ech->cipherSuite.kdfId && + echConfig->cipherSuites[i].aeadId == ech->cipherSuite.aeadId) { + break; + } + } + + if (i >= echConfig->numCipherSuites) { + return BAD_FUNC_ARG; + } + + ech->hpke = (Hpke*)XMALLOC(sizeof(Hpke), heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (ech->hpke == NULL) + return MEMORY_E; + + ret = wc_HpkeInit(ech->hpke, echConfig->kemId, ech->cipherSuite.kdfId, + ech->cipherSuite.aeadId, heap); + + /* get the rawConfigLen */ + if (ret == 0) + ret = GetEchConfig(echConfig, NULL, &rawConfigLen); + + if (ret == LENGTH_ONLY_E) + ret = 0; + + /* create info */ + if (ret == 0) { + infoLen = TLS_INFO_CONST_STRING_SZ + 1 + rawConfigLen; + info = (byte*)XMALLOC(infoLen, heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (info == NULL) + ret = MEMORY_E; + else { + XMEMCPY(info, (byte*)TLS_INFO_CONST_STRING, + TLS_INFO_CONST_STRING_SZ + 1); + ret = GetEchConfig(echConfig, info + + TLS_INFO_CONST_STRING_SZ + 1, &rawConfigLen); + } + } + + /* decrypt the ech payload */ + if (ret == 0) + ret = wc_HpkeOpenBase(ech->hpke, echConfig->receiverPrivkey, ech->enc, + ech->encLen, info, infoLen, aad, aadLen, ech->outerClientPayload, + ech->innerClientHelloLen, + ech->innerClientHello + HANDSHAKE_HEADER_SZ); + + if (ret != 0) { + XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->hpke = NULL; + } + + if (info != NULL) + XFREE(info, heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* parse the ech extension, if internal update ech->state and return, if + * external attempt to extract the inner client_hello, return the status */ +static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, + byte msgType) +{ + int ret = 0; + int i; + TLSX* echX; + WOLFSSL_ECH* ech; + WOLFSSL_EchConfig* echConfig; + byte* aadCopy; + byte* readBuf_p = (byte*)readBuf; + + WOLFSSL_MSG("TLSX_ECH_Parse"); + + if (size == 0) + return BAD_FUNC_ARG; + + if (msgType == encrypted_extensions) { + ret = wolfSSL_SetEchConfigs(ssl, readBuf, size); + + if (ret == WOLFSSL_SUCCESS) + ret = 0; + } + else if (msgType == client_hello && ssl->ctx->echConfigs != NULL) { + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + + if (echX == NULL) + return BAD_FUNC_ARG; + + ech = (WOLFSSL_ECH*)echX->data; + + /* read the ech parameters before the payload */ + ech->type = *readBuf_p; + readBuf_p++; + + if (ech->type == ECH_TYPE_INNER) { + ech->state = ECH_PARSED_INTERNAL; + return 0; + } + + /* technically the payload would only be 1 byte at this length */ + if (size < 11 + ech->encLen) + return BAD_FUNC_ARG; + + ato16(readBuf_p, &ech->cipherSuite.kdfId); + readBuf_p += 2; + + ato16(readBuf_p, &ech->cipherSuite.aeadId); + readBuf_p += 2; + + ech->configId = *readBuf_p; + readBuf_p++; + + ato16(readBuf_p, &ech->encLen); + readBuf_p += 2; + + if (ech->encLen > HPKE_Npk_MAX) + return BAD_FUNC_ARG; + + XMEMCPY(ech->enc, readBuf_p, ech->encLen); + readBuf_p += ech->encLen; + + ato16(readBuf_p, &ech->innerClientHelloLen); + ech->innerClientHelloLen -= AES_BLOCK_SIZE; + readBuf_p += 2; + + ech->outerClientPayload = readBuf_p; + + /* make a copy of the aad */ + aadCopy = (byte*)XMALLOC(ech->aadLen, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (aadCopy == NULL) + return MEMORY_E; + + XMEMCPY(aadCopy, ech->aad, ech->aadLen); + + /* set the ech payload of the copy to zeros */ + XMEMSET(aadCopy + (readBuf_p - ech->aad), 0, + ech->innerClientHelloLen + AES_BLOCK_SIZE); + + /* allocate the inner payload buffer */ + ech->innerClientHello = + (byte*)XMALLOC(ech->innerClientHelloLen + HANDSHAKE_HEADER_SZ, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (ech->innerClientHello == NULL) { + XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } + + /* first check if the config id matches */ + echConfig = ssl->ctx->echConfigs; + + while (echConfig != NULL) { + /* decrypt with this config */ + if (echConfig->configId == ech->configId) { + ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, + ssl->heap); + break; + } + + echConfig = echConfig->next; + } + + /* try to decrypt with all configs */ + if (echConfig == NULL || ret != 0) { + echConfig = ssl->ctx->echConfigs; + + while (echConfig != NULL) { + ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, + ssl->heap); + + if (ret== 0) + break; + + echConfig = echConfig->next; + } + } + + /* if we failed to extract */ + if (ret != 0) { + XFREE(ech->innerClientHello, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->innerClientHello = NULL; + ech->state = ECH_WRITE_RETRY_CONFIGS; + } + else { + i = 0; + + /* decrement until before the padding */ + while (ech->innerClientHello[ech->innerClientHelloLen + + HANDSHAKE_HEADER_SZ - i - 1] != ECH_TYPE_INNER) { + i++; + } + + /* subtract the length of the padding from the length */ + ech->innerClientHelloLen -= i; + } + + XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return 0; + } + + return ret; +} + +/* free the ech struct and the dynamic buffer it uses */ +static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap) +{ + if (ech->innerClientHello != NULL) + XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ech->ephemeralKey != NULL) + wc_HpkeFreeKey(ech->hpke, ech->hpke->kem, ech->ephemeralKey, + ech->hpke->heap); + if (ech->hpke != NULL) + XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER); + + XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); + (void)heap; +} + +/* encrypt the client hello and store it in ech->outerClientPayload, return + * status */ +int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen) +{ + int ret; + void* receiverPubkey = NULL; + byte* info; + int infoLen; + byte* aadCopy; + + /* import the server public key */ + ret = wc_HpkeDeserializePublicKey(ech->hpke, &receiverPubkey, + ech->echConfig->receiverPubkey, ech->encLen); + + if (ret == 0) { + /* create info */ + infoLen = TLS_INFO_CONST_STRING_SZ + 1 + ech->echConfig->rawLen; + info = (byte*)XMALLOC(infoLen, ech->hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (info == NULL) + ret = MEMORY_E; + + if (ret == 0) { + /* puts the null byte in for me */ + XMEMCPY(info, (byte*)TLS_INFO_CONST_STRING, TLS_INFO_CONST_STRING_SZ + + 1); + XMEMCPY(info + TLS_INFO_CONST_STRING_SZ + 1, ech->echConfig->raw, + ech->echConfig->rawLen); + + /* make a copy of the aad since we overwrite it */ + aadCopy = (byte*)XMALLOC(aadLen, ech->hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (aadCopy == NULL) { + XFREE(info, ech->hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + ret = MEMORY_E; + } + } + + if (ret == 0) { + XMEMCPY(aadCopy, aad, aadLen); + + /* seal the payload */ + ret = wc_HpkeSealBase(ech->hpke, ech->ephemeralKey, receiverPubkey, + info, infoLen, aadCopy, aadLen, ech->innerClientHello, + ech->innerClientHelloLen - ech->hpke->Nt, + ech->outerClientPayload); + + XFREE(info, ech->hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(aadCopy, ech->hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + + if (receiverPubkey != NULL) + wc_HpkeFreeKey(ech->hpke, ech->hpke->kem, receiverPubkey, + ech->hpke->heap); + + return ret; +} + +#define GREASE_ECH_USE TLSX_GreaseECH_Use +#define ECH_USE TLSX_ECH_Use +#define SERVER_ECH_USE TLSX_ServerECH_Use +#define ECH_WRITE TLSX_ECH_Write +#define ECH_GET_SIZE TLSX_ECH_GetSize +#define ECH_PARSE TLSX_ECH_Parse +#define ECH_FREE TLSX_ECH_Free + +#endif + /** Releases all extensions in the provided list. */ void TLSX_FreeAll(TLSX* list, void* heap) { @@ -10337,7 +11062,11 @@ void TLSX_FreeAll(TLSX* list, void* heap) CID_FREE((byte*)extension->data, heap); break; #endif /* WOLFSSL_DTLS_CID */ - +#if defined(HAVE_ECH) + case TLSX_ECH: + ECH_FREE((WOLFSSL_ECH*)extension->data, heap); + break; +#endif default: break; } @@ -10506,6 +11235,11 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, length += CID_GET_SIZE((byte*)extension->data); break; #endif /* WOLFSSL_DTLS_CID */ +#if defined(HAVE_ECH) + case TLSX_ECH: + length += ECH_GET_SIZE((WOLFSSL_ECH*)extension->data); + break; +#endif default: break; } @@ -10709,6 +11443,12 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, break; #endif /* WOLFSSL_DTLS_CID */ +#if defined(HAVE_ECH) + case TLSX_ECH: + ret = ECH_WRITE((WOLFSSL_ECH*)extension->data, + output + offset, &offset); + break; +#endif default: break; } @@ -10719,6 +11459,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, /* marks the extension as processed so ctx level */ /* extensions don't overlap with ssl level ones. */ TURN_ON(semaphore, TLSX_ToSemaphore(extension->type)); + + /* if we encountered an error propigate it */ + if (ret != 0) + break; } *pOffset += offset; @@ -11354,7 +12098,28 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) return ret; } #endif +#if defined(HAVE_ECH) + /* GREASE ECH */ + if (ssl->echConfigs == NULL) { + ret = GREASE_ECH_USE(&(ssl->extensions), ssl->heap, ssl->rng); + } + else if (ssl->echConfigs != NULL) { + ret = ECH_USE(ssl->echConfigs, &(ssl->extensions), ssl->heap, + ssl->rng); + } +#endif + } +#if defined(HAVE_ECH) + else if (IsAtLeastTLSv1_3(ssl->version)) { + if (ssl->ctx->echConfigs != NULL) { + ret = SERVER_ECH_USE(&(ssl->extensions), ssl->heap, + ssl->ctx->echConfigs); + + if (ret == 0) + TLSX_SetResponse(ssl, TLSX_ECH); + } } +#endif #endif @@ -11369,6 +12134,101 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT) +#if defined(HAVE_ECH) +/* because the size of ech depends on the size of other extensions we need to + * get the size with ech special and process ech last, return status */ +static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType, + word16* pLength) +{ + int ret = 0; + TLSX* echX = NULL; + TLSX* serverNameX = NULL; + TLSX** extensions = NULL; +#ifdef WOLFSSL_SMALL_STACK + char* tmpServerName = NULL; +#else + char tmpServerName[MAX_PUBLIC_NAME_SZ]; +#endif + + /* calculate the rest of the extensions length with inner ech */ + if (ssl->extensions) + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + + if (echX == NULL && ssl->ctx && ssl->ctx->extensions) + echX = TLSX_Find(ssl->ctx->extensions, TLSX_ECH); + + /* if type is outer change sni to public name */ + if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->type == ECH_TYPE_OUTER) { + if (ssl->extensions) { + serverNameX = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); + + if (serverNameX != NULL) + extensions = &ssl->extensions; + } + + if (serverNameX == NULL && ssl->ctx && ssl->ctx->extensions) { + serverNameX = TLSX_Find(ssl->ctx->extensions, TLSX_SERVER_NAME); + extensions = &ssl->ctx->extensions; + } + + /* store the inner server name */ + if (serverNameX != NULL) { + char* hostName = ((SNI*)serverNameX->data)->data.host_name; + word32 hostNameSz = (word32)XSTRLEN(hostName) + 1; + + #ifdef WOLFSSL_SMALL_STACK + tmpServerName = (char*)XMALLOC(hostNameSz, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmpServerName == NULL) + return MEMORY_E; + #else + /* truncate if too long */ + if (hostNameSz > MAX_PUBLIC_NAME_SZ) + hostNameSz = MAX_PUBLIC_NAME_SZ; + #endif + + XMEMCPY(tmpServerName, hostName, hostNameSz); + } + + /* remove the inner server name */ + TLSX_Remove(extensions, TLSX_SERVER_NAME, ssl->heap); + + ret = TLSX_UseSNI(extensions, WOLFSSL_SNI_HOST_NAME, + ((WOLFSSL_ECH*)echX->data)->echConfig->publicName, + XSTRLEN(((WOLFSSL_ECH*)echX->data)->echConfig->publicName), + ssl->heap); + + /* set the public name as the server name */ + if (ret == WOLFSSL_SUCCESS) + ret = 0; + } + + if (ret == 0 && ssl->extensions) + ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, pLength); + + if (ret == 0 && ssl->ctx && ssl->ctx->extensions) + ret = TLSX_GetSize(ssl->ctx->extensions, semaphore, msgType, pLength); + + if (serverNameX != NULL) { + /* remove the public name SNI */ + TLSX_Remove(extensions, TLSX_SERVER_NAME, ssl->heap); + + ret = TLSX_UseSNI(extensions, WOLFSSL_SNI_HOST_NAME, + tmpServerName, XSTRLEN(tmpServerName), ssl->heap); + + /* restore the inner server name */ + if (ret == WOLFSSL_SUCCESS) + ret = 0; + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(tmpServerName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} +#endif + /** Tells the buffered size of extensions to be sent into the client hello. */ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word16* pLength) { @@ -11433,15 +12293,26 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word16* pLength) } #endif #endif - if (ssl->extensions) { - ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, &length); +#if defined(HAVE_ECH) + if (ssl->options.useEch == 1 && msgType == client_hello) { + ret = TLSX_GetSizeWithEch(ssl, semaphore, msgType, &length); if (ret != 0) return ret; } - if (ssl->ctx && ssl->ctx->extensions) { - ret = TLSX_GetSize(ssl->ctx->extensions, semaphore, msgType, &length); - if (ret != 0) - return ret; + else +#endif + { + if (ssl->extensions) { + ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, &length); + if (ret != 0) + return ret; + } + if (ssl->ctx && ssl->ctx->extensions) { + ret = TLSX_GetSize(ssl->ctx->extensions, semaphore, msgType, + &length); + if (ret != 0) + return ret; + } } #ifdef HAVE_EXTENDED_MASTER @@ -11459,6 +12330,127 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word16* pLength) return ret; } +#if defined(HAVE_ECH) +/* return status after writing the extensions with ech written last */ +static int TLSX_WriteWithEch(WOLFSSL* ssl, byte* output, byte* semaphore, + byte msgType, word16* pOffset) +{ + int ret = 0; + TLSX* echX = NULL; + TLSX* serverNameX = NULL; + TLSX** extensions = NULL; +#ifdef WOLFSSL_SMALL_STACK + char* tmpServerName = NULL; +#else + char tmpServerName[MAX_PUBLIC_NAME_SZ]; +#endif + + /* get the echX from either extensions or ctx */ + if (ssl->extensions) + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + + if (echX == NULL && ssl->ctx && ssl->ctx->extensions) { + /* if not NULL the semaphore will stop it from being counted */ + if (echX == NULL) + echX = TLSX_Find(ssl->ctx->extensions, TLSX_ECH); + } + + /* if type is outer change sni to public name */ + if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->type == ECH_TYPE_OUTER) { + if (ssl->extensions) { + serverNameX = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); + + if (serverNameX != NULL) + extensions = &ssl->extensions; + } + + if (serverNameX == NULL && ssl->ctx && ssl->ctx->extensions) { + serverNameX = TLSX_Find(ssl->ctx->extensions, TLSX_SERVER_NAME); + extensions = &ssl->ctx->extensions; + } + + /* store the inner server name */ + if (serverNameX != NULL) { + char* hostName = ((SNI*)serverNameX->data)->data.host_name; + word32 hostNameSz = (word32)XSTRLEN(hostName) + 1; + + #ifdef WOLFSSL_SMALL_STACK + tmpServerName = (char*)XMALLOC(hostNameSz, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmpServerName == NULL) + return MEMORY_E; + #else + /* truncate if too long */ + if (hostNameSz > MAX_PUBLIC_NAME_SZ) + hostNameSz = MAX_PUBLIC_NAME_SZ; + #endif + + XMEMCPY(tmpServerName, hostName, hostNameSz); + } + + /* remove the inner server name */ + TLSX_Remove(extensions, TLSX_SERVER_NAME, ssl->heap); + + ret = TLSX_UseSNI(extensions, WOLFSSL_SNI_HOST_NAME, + ((WOLFSSL_ECH*)echX->data)->echConfig->publicName, + XSTRLEN(((WOLFSSL_ECH*)echX->data)->echConfig->publicName), + ssl->heap); + + /* set the public name as the server name */ + if (ret == WOLFSSL_SUCCESS) + ret = 0; + } + + if (echX != NULL) { + /* turn ech on so it doesn't write, then write it last */ + TURN_ON(semaphore, TLSX_ToSemaphore(echX->type)); + } + + if (ret == 0 && ssl->extensions) { + ret = TLSX_Write(ssl->extensions, output + *pOffset, semaphore, + msgType, pOffset); + } + + if (ret == 0 && ssl->ctx && ssl->ctx->extensions) { + ret = TLSX_Write(ssl->ctx->extensions, output + *pOffset, semaphore, + msgType, pOffset); + } + + if (echX != NULL) { + /* turn off and write it last */ + TURN_OFF(semaphore, TLSX_ToSemaphore(echX->type)); + } + + if (ret == 0 && ssl->extensions) { + ret = TLSX_Write(ssl->extensions, output + *pOffset, semaphore, + msgType, pOffset); + } + + if (ret == 0 && ssl->ctx && ssl->ctx->extensions) { + ret = TLSX_Write(ssl->ctx->extensions, output + *pOffset, semaphore, + msgType, pOffset); + } + + if (serverNameX != NULL) { + /* remove the public name SNI */ + TLSX_Remove(extensions, TLSX_SERVER_NAME, ssl->heap); + + ret = TLSX_UseSNI(extensions, WOLFSSL_SNI_HOST_NAME, tmpServerName, + XSTRLEN(tmpServerName), ssl->heap); + + /* restore the inner server name */ + if (ret == WOLFSSL_SUCCESS) + ret = 0; + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(tmpServerName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} +#endif + /** Writes the extensions to be sent into the client hello. */ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word16* pOffset) { @@ -11532,17 +12524,28 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word16* pOffset) } #endif #endif - if (ssl->extensions) { - ret = TLSX_Write(ssl->extensions, output + offset, semaphore, +#if defined(HAVE_ECH) + if (ssl->options.useEch == 1 && msgType == client_hello) { + ret = TLSX_WriteWithEch(ssl, output, semaphore, msgType, &offset); if (ret != 0) return ret; } - if (ssl->ctx && ssl->ctx->extensions) { - ret = TLSX_Write(ssl->ctx->extensions, output + offset, semaphore, - msgType, &offset); - if (ret != 0) - return ret; + else +#endif + { + if (ssl->extensions) { + ret = TLSX_Write(ssl->extensions, output + offset, semaphore, + msgType, &offset); + if (ret != 0) + return ret; + } + if (ssl->ctx && ssl->ctx->extensions) { + ret = TLSX_Write(ssl->ctx->extensions, output + offset, semaphore, + msgType, &offset); + if (ret != 0) + return ret; + } } #ifdef HAVE_EXTENDED_MASTER @@ -12471,6 +13474,11 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType, break; #endif /* defined(WOLFSSL_DTLS_CID) */ +#if defined(HAVE_ECH) + case TLSX_ECH: + ret = ECH_PARSE(ssl, input + offset, size, msgType); + break; +#endif default: WOLFSSL_MSG("Unknown TLS extension type"); } diff --git a/src/tls13.c b/src/tls13.c index 5f8be72aa45..b693a7af233 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -165,6 +165,23 @@ static const byte tls13ProtocolLabel[TLS13_PROTOCOL_LABEL_SZ + 1] = "tls13 "; static const byte dtls13ProtocolLabel[DTLS13_PROTOCOL_LABEL_SZ + 1] = "dtls13"; #endif /* WOLFSSL_DTLS13 */ +#if defined(HAVE_ECH) +#define ECH_ACCEPT_CONFIRMATION_SZ 8 +#define ECH_ACCEPT_CONFIRMATION_LABEL_SZ 23 +static const byte + echAcceptConfirmationLabel[ECH_ACCEPT_CONFIRMATION_LABEL_SZ + 1] = + "ech accept confirmation"; +#endif + +#ifndef NO_CERTS +#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ + defined(HAVE_ED448) || defined(HAVE_PQC) + +static WC_INLINE int GetMsgHash(WOLFSSL* ssl, byte* hash); + +#endif +#endif + /* Expand data using HMAC, salt and label and info. * TLS v1.3 defines this function. Use callback if available. * @@ -253,7 +270,6 @@ PRAGMA_GCC_DIAG_POP; } #endif /* !HAVE_FIPS || !wc_Tls13_HKDF_Expand_Label */ - /* Derive a key from a message. * * ssl The SSL/TLS object. @@ -3829,6 +3845,71 @@ static int WritePSKBinders(WOLFSSL* ssl, byte* output, word32 idx) } #endif +#if defined(HAVE_ECH) +/* returns the index of the first supported cipher suite, -1 if none */ +int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config) +{ + int i, j, supported = 0; + + for (i = 0; i < config->numCipherSuites; i++) { + supported = 0; + + for (j = 0; j < HPKE_SUPPORTED_KDF_LEN; j++) { + if (config->cipherSuites[i].kdfId == hpkeSupportedKdf[j]) + break; + } + + if (j < HPKE_SUPPORTED_KDF_LEN) + for (j = 0; j < HPKE_SUPPORTED_AEAD_LEN; j++) { + if (config->cipherSuites[i].aeadId == hpkeSupportedAead[j]) { + supported = 1; + break; + } + } + + if (supported) + return i; + } + + return -1; +} + +/* returns status after we hash the ech inner */ +static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) +{ + int ret; + HS_Hashes* tmpHashes; + byte falseHeader[HANDSHAKE_HEADER_SZ]; + + if (ssl == NULL || ech == NULL) + return BAD_FUNC_ARG; + + /* switch hsHashes to the ech version */ + InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, &ssl->hsHashesEch); + + /* swap hsHashes so the regular hash functions work */ + tmpHashes = ssl->hsHashes; + ssl->hsHashes = ssl->hsHashesEch; + + /* do the handshake header then the body */ + AddTls13HandShakeHeader(falseHeader, + ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt, 0, 0, + client_hello, ssl); + ret = HashRaw(ssl, falseHeader, HANDSHAKE_HEADER_SZ); + + /* hash the body */ + if (ret == 0) { + ret = HashRaw(ssl, ech->innerClientHello, + ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt); + } + + /* swap hsHashes back */ + ssl->hsHashes = tmpHashes; + + return ret; +} +#endif + /* handle generation of TLS 1.3 client_hello (1) */ /* Send a ClientHello message to the server. * Include the information required to start a handshake with servers using @@ -3844,6 +3925,11 @@ typedef struct Sch13Args { word32 idx; int sendSz; word16 length; +#if defined(HAVE_ECH) + int clientRandomOffset; + int preXLength; + WOLFSSL_ECH* ech; +#endif } Sch13Args; int SendTls13ClientHello(WOLFSSL* ssl) @@ -3857,7 +3943,6 @@ int SendTls13ClientHello(WOLFSSL* ssl) #endif byte major, tls12minor; - WOLFSSL_START(WC_FUNC_CLIENT_HELLO_SEND); WOLFSSL_ENTER("SendTls13ClientHello"); @@ -4015,6 +4100,38 @@ int SendTls13ClientHello(WOLFSSL* ssl) return ret; } #endif + + /* find length of outer and inner */ +#if defined(HAVE_ECH) + if (ssl->options.useEch == 1) { + TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); + if (echX == NULL) + return -1; + + args->ech = (WOLFSSL_ECH*)echX->data; + if (args->ech == NULL) + return -1; + + /* set the type to inner */ + args->ech->type = ECH_TYPE_INNER; + args->preXLength = args->length; + + /* get size for inner */ + ret = TLSX_GetRequestSize(ssl, client_hello, &args->length); + if (ret != 0) + return ret; + + /* set the type to outer */ + args->ech->type = 0; + /* set innerClientHelloLen to ClientHelloInner + padding + tag */ + args->ech->paddingLen = 31 - ((args->length - 1) % 32); + args->ech->innerClientHelloLen = args->length + + args->ech->paddingLen + args->ech->hpke->Nt; + /* set the length back to before we computed ClientHelloInner size */ + args->length = args->preXLength; + } +#endif + /* Include length of TLS extensions. */ ret = TLSX_GetRequestSize(ssl, client_hello, &args->length); if (ret != 0) @@ -4060,6 +4177,11 @@ int SendTls13ClientHello(WOLFSSL* ssl) } else XMEMCPY(args->output + args->idx, ssl->arrays->clientRandom, RAN_LEN); + +#if defined(HAVE_ECH) + args->clientRandomOffset = args->idx; +#endif + args->idx += RAN_LEN; if (ssl->session->sessionIDSz > 0) { @@ -4121,14 +4243,78 @@ int SendTls13ClientHello(WOLFSSL* ssl) args->output[args->idx++] = COMP_LEN; args->output[args->idx++] = NO_COMPRESSION; +#if defined(HAVE_ECH) + /* write inner then outer */ + if (ssl->options.useEch == 1) { + /* set the type to inner */ + args->ech->type = ECH_TYPE_INNER; + + /* allocate the inner */ + args->ech->innerClientHello = + (byte*)XMALLOC(args->ech->innerClientHelloLen - args->ech->hpke->Nt, + ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (args->ech->innerClientHello == NULL) + return MEMORY_E; + + /* set the padding bytes to 0 */ + XMEMSET(args->ech->innerClientHello + args->ech->innerClientHelloLen - + args->ech->hpke->Nt - args->ech->paddingLen, 0, + args->ech->paddingLen); + + /* copy the client hello to the ech innerClientHello, exclude record */ + /* and handshake headers */ + XMEMCPY(args->ech->innerClientHello, + args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, + args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ)); + + /* copy the client random to inner */ + XMEMCPY(ssl->arrays->clientRandomInner, ssl->arrays->clientRandom, + RAN_LEN); + + /* change the outer client random */ + ret = wc_RNG_GenerateBlock(ssl->rng, args->output + + args->clientRandomOffset, RAN_LEN); + if (ret != 0) + return ret; + + /* copy the new client random */ + XMEMCPY(ssl->arrays->clientRandom, args->output + + args->clientRandomOffset, RAN_LEN); + + /* write the extensions for inner */ + args->length = 0; + ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello + args->idx - + (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ), client_hello, + &args->length); + if (ret != 0) + return ret; + + /* set the type to outer */ + args->ech->type = 0; + } +#endif + /* Write out extensions for a request. */ args->length = 0; ret = TLSX_WriteRequest(ssl, args->output + args->idx, client_hello, &args->length); if (ret != 0) return ret; + args->idx += args->length; +#if defined(HAVE_ECH) + /* encrypt and pack the ech innerClientHello */ + if (ssl->options.useEch == 1) { + ret = TLSX_FinalizeEch(args->ech, + args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, + args->sendSz - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ)); + + if (ret != 0) + return ret; + } +#endif + #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) /* Resumption has a specific set of extensions and binder is calculated * for each identity. @@ -4138,7 +4324,7 @@ int SendTls13ClientHello(WOLFSSL* ssl) } else #endif - { + { #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) ret = Dtls13HashHandshake(ssl, @@ -4146,8 +4332,19 @@ int SendTls13ClientHello(WOLFSSL* ssl) (word16)args->idx - Dtls13GetRlHeaderLength(ssl, 0)); else #endif /* WOLFSSL_DTLS13 */ + { +#if defined(HAVE_ECH) + /* compute the inner hash */ + if (ssl->options.useEch == 1) { + ret = EchHashHelloInner(ssl, args->ech); + } +#endif + + /* compute the outer hash */ + if (ret == 0) ret = HashOutput(ssl, args->output, args->idx, 0); } + } if (ret != 0) return ret; @@ -4233,6 +4430,233 @@ static int Dtls13DoDowngrade(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 && !WOLFSSL_NO_CLIENT*/ +#if defined(HAVE_ECH) +/* check if the server accepted ech or not */ +static int EchCheckAcceptance(WOLFSSL* ssl, const byte* input, + int serverRandomOffset, int helloSz) +{ + int ret = 0; + int digestType; + int digestSize; + HS_Hashes* tmpHashes; + HS_Hashes* acceptHashes; + byte zeros[WC_MAX_DIGEST_SIZE] = {0}; + byte transcriptEchConf[WC_MAX_DIGEST_SIZE]; + byte expandLabelPrk[WC_MAX_DIGEST_SIZE]; + byte acceptConfirmation[ECH_ACCEPT_CONFIRMATION_SZ]; + + /* copy ech hashes to accept */ + ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashesEch, &acceptHashes); + + /* swap hsHashes to acceptHashes */ + tmpHashes = ssl->hsHashes; + ssl->hsHashes = acceptHashes; + + /* hash up to the last 8 bytes */ + if (ret == 0) + ret = HashRaw(ssl, input, serverRandomOffset + RAN_LEN - + ECH_ACCEPT_CONFIRMATION_SZ); + + /* hash 8 zeros */ + if (ret == 0) + ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ); + + /* hash the rest of the hello */ + if (ret == 0) + ret = HashRaw(ssl, input + serverRandomOffset + RAN_LEN, + helloSz + HANDSHAKE_HEADER_SZ - (serverRandomOffset + RAN_LEN)); + + /* get the modified transcript hash */ + if (ret == 0) + ret = GetMsgHash(ssl, transcriptEchConf); + + if (ret > 0) + ret = 0; + + /* pick the right type and size based on mac_algorithm */ + if (ret == 0) + switch (ssl->specs.mac_algorithm) { +#ifndef NO_SHA256 + case sha256_mac: + digestType = WC_SHA256; + digestSize = WC_SHA256_DIGEST_SIZE; + break; +#endif /* !NO_SHA256 */ +#ifdef WOLFSSL_SHA384 + case sha384_mac: + digestType = WC_SHA384; + digestSize = WC_SHA384_DIGEST_SIZE; + break; +#endif /* WOLFSSL_SHA384 */ +#ifdef WOLFSSL_TLS13_SHA512 + case sha512_mac: + digestType = WC_SHA512; + digestSize = WC_SHA512_DIGEST_SIZE; + break; +#endif /* WOLFSSL_TLS13_SHA512 */ + default: + ret = -1; + break; + } + + /* extract clientRandomInner with a key of all zeros */ + if (ret == 0) + ret = wc_HKDF_Extract(digestType, zeros, digestSize, + ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk); + + /* tls expand with the confirmation label */ + if (ret == 0) + ret = wc_Tls13_HKDF_Expand_Label(acceptConfirmation, + ECH_ACCEPT_CONFIRMATION_SZ, + expandLabelPrk, digestSize, tls13ProtocolLabel, + TLS13_PROTOCOL_LABEL_SZ, echAcceptConfirmationLabel, + ECH_ACCEPT_CONFIRMATION_LABEL_SZ, transcriptEchConf, digestSize, + digestType); + + if (ret == 0) { + /* last 8 bytes should match our expand output */ + ret = XMEMCMP(acceptConfirmation, + ssl->arrays->serverRandom + RAN_LEN - ECH_ACCEPT_CONFIRMATION_SZ, + ECH_ACCEPT_CONFIRMATION_SZ); + + /* ech accepted */ + if (ret == 0) { + /* use the inner random for client random */ + XMEMCPY(ssl->arrays->clientRandom, ssl->arrays->clientRandomInner, + RAN_LEN); + + /* switch back to original hsHashes */ + ssl->hsHashes = tmpHashes; + + /* free hsHashes */ + FreeHandshakeHashes(ssl); + + /* set the final hsHashes to the ech hashes */ + tmpHashes = ssl->hsHashesEch; + + /* set hsHashesEch to NULL to avoid double free */ + ssl->hsHashesEch = NULL; + } + /* ech rejected */ + else { + /* switch to hsHashesEch */ + ssl->hsHashes = ssl->hsHashesEch; + + /* free ech hashes */ + FreeHandshakeHashes(ssl); + } + + /* continue with outer if we failed to verify ech was accepted */ + ret = 0; + } + + /* switch to acceptHashes */ + ssl->hsHashes = acceptHashes; + + /* free acceptHashes */ + FreeHandshakeHashes(ssl); + + ssl->hsHashes = tmpHashes; + + return ret; +} + +/* replace the last 8 bytes of the server random with the ech acceptance + * parameter, return status */ +static int EchWriteAcceptance(WOLFSSL* ssl, byte* output, + int serverRandomOffset, int helloSz) +{ + int ret = 0; + int digestType; + int digestSize; + HS_Hashes* tmpHashes; + HS_Hashes* acceptHashes; + byte zeros[WC_MAX_DIGEST_SIZE] = {0}; + byte transcriptEchConf[WC_MAX_DIGEST_SIZE]; + byte expandLabelPrk[WC_MAX_DIGEST_SIZE]; + + /* copy ech hashes to accept */ + ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, &acceptHashes); + + /* swap hsHashes to acceptHashes */ + tmpHashes = ssl->hsHashes; + ssl->hsHashes = acceptHashes; + + /* hash up to the last 8 bytes */ + if (ret == 0) + ret = HashRaw(ssl, output, serverRandomOffset + RAN_LEN - + ECH_ACCEPT_CONFIRMATION_SZ); + + /* hash 8 zeros */ + if (ret == 0) + ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ); + + /* hash the rest of the hello */ + if (ret == 0) + ret = HashRaw(ssl, output + serverRandomOffset + RAN_LEN, + helloSz - (serverRandomOffset + RAN_LEN)); + + /* get the modified transcript hash */ + if (ret == 0) + ret = GetMsgHash(ssl, transcriptEchConf); + + if (ret > 0) + ret = 0; + + /* pick the right type and size based on mac_algorithm */ + if (ret == 0) + switch (ssl->specs.mac_algorithm) { +#ifndef NO_SHA256 + case sha256_mac: + digestType = WC_SHA256; + digestSize = WC_SHA256_DIGEST_SIZE; + break; +#endif /* !NO_SHA256 */ +#ifdef WOLFSSL_SHA384 + case sha384_mac: + digestType = WC_SHA384; + digestSize = WC_SHA384_DIGEST_SIZE; + break; +#endif /* WOLFSSL_SHA384 */ +#ifdef WOLFSSL_TLS13_SHA512 + case sha512_mac: + digestType = WC_SHA512; + digestSize = WC_SHA512_DIGEST_SIZE; + break; +#endif /* WOLFSSL_TLS13_SHA512 */ + default: + ret = -1; + break; + } + + /* extract clientRandom with a key of all zeros */ + if (ret == 0) + ret = wc_HKDF_Extract(digestType, zeros, digestSize, + ssl->arrays->clientRandom, RAN_LEN, expandLabelPrk); + + /* tls expand with the confirmation label */ + if (ret == 0) + ret = wc_Tls13_HKDF_Expand_Label( + output + serverRandomOffset + RAN_LEN - ECH_ACCEPT_CONFIRMATION_SZ, + ECH_ACCEPT_CONFIRMATION_SZ, + expandLabelPrk, digestSize, tls13ProtocolLabel, + TLS13_PROTOCOL_LABEL_SZ, echAcceptConfirmationLabel, + ECH_ACCEPT_CONFIRMATION_LABEL_SZ, transcriptEchConf, digestSize, + digestType); + + if (ret == 0) + XMEMCPY(ssl->arrays->serverRandom, output + serverRandomOffset, + RAN_LEN); + + /* free acceptHashes */ + FreeHandshakeHashes(ssl); + + ssl->hsHashes = tmpHashes; + + return ret; +} +#endif + /* handle processing of TLS 1.3 server_hello (2) and hello_retry_request (6) */ /* Handle the ServerHello message from the server. * Only a client will receive this message. @@ -4253,6 +4677,9 @@ typedef struct Dsh13Args { word16 totalExtSz; byte sessIdSz; byte extMsgType; +#if defined(HAVE_ECH) + int serverRandomOffset; +#endif } Dsh13Args; int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, @@ -4400,6 +4827,9 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* Server random - keep for debugging. */ XMEMCPY(ssl->arrays->serverRandom, input + args->idx, RAN_LEN); +#if defined(HAVE_ECH) + args->serverRandomOffset = args->idx; +#endif args->idx += RAN_LEN; /* Session id */ @@ -4682,6 +5112,16 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, ret = SetCipherSpecs(ssl); if (ret != 0) return ret; + +#if defined(HAVE_ECH) + /* check for acceptConfirmation and HashInput with 8 0 bytes */ + if (ssl->options.useEch == 1) { + ret = EchCheckAcceptance(ssl, input, args->serverRandomOffset, helloSz); + if (ret != 0) + return ret; + } +#endif + #ifdef HAVE_NULL_CIPHER if (ssl->options.cipherSuite0 == ECC_BYTE && (ssl->options.cipherSuite == TLS_SHA256_SHA256 || @@ -5814,6 +6254,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #else Dch13Args args[1]; #endif +#if defined(HAVE_ECH) + word32 echInOutIdx; + TLSX* echX = NULL; +#endif WOLFSSL_START(WC_FUNC_CLIENT_HELLO_DO); WOLFSSL_ENTER("DoTls13ClientHello"); @@ -6027,12 +6471,31 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, if ((ret = TLSX_PopulateExtensions(ssl, 1)) != 0) goto exit_dch; +#if defined(HAVE_ECH) + if (ssl->ctx->echConfigs != NULL) { + /* save the start of the buffer so we can use it when parsing ech */ + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + + if (echX == NULL) + return -1; + + ((WOLFSSL_ECH*)echX->data)->aad = input + HANDSHAKE_HEADER_SZ; + ((WOLFSSL_ECH*)echX->data)->aadLen = helloSz; + } +#endif + /* Parse extensions */ if ((ret = TLSX_Parse(ssl, input + args->idx, totalExtSz, client_hello, args->clSuites))) { goto exit_dch; } +#if defined(HAVE_ECH) + /* jump to the end to clean things up */ + if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) + goto exit_dch; +#endif + #ifdef HAVE_SNI if ((ret = SNI_Callback(ssl)) != 0) goto exit_dch; @@ -6267,6 +6730,27 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, WOLFSSL_ERROR_VERBOSE(ret); } +#if defined(HAVE_ECH) + /* do the hello again with the inner */ + if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) { + /* reset the idx */ + echInOutIdx = args->begin; + + /* add the header to the inner hello */ + AddTls13HandShakeHeader(((WOLFSSL_ECH*)echX->data)->innerClientHello, + ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen, 0, 0, + client_hello, ssl); + + ret = DoTls13ClientHello(ssl, + ((WOLFSSL_ECH*)echX->data)->innerClientHello, + &echInOutIdx, ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen); + + /* inner hello succeeded, consider this handshake message processed */ + if (ret == 0) + *inOutIdx = args->begin + helloSz; + } +#endif + return ret; } @@ -6284,6 +6768,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) word16 length; word32 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; int sendSz; +#if defined(HAVE_ECH) + TLSX* echX = NULL; + word32 serverRandomOffset; +#endif WOLFSSL_START(WC_FUNC_SERVER_HELLO_SEND); WOLFSSL_ENTER("SendTls13ServerHello"); @@ -6335,6 +6823,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) XMEMCPY(output + idx, helloRetryRequestRandom, RAN_LEN); } +#if defined(HAVE_ECH) + serverRandomOffset = idx; +#endif + /* Store in SSL for debugging. */ XMEMCPY(ssl->arrays->serverRandom, output + idx, RAN_LEN); idx += RAN_LEN; @@ -6386,7 +6878,26 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) else #endif /* WOLFSSL_DTLS13 */ { - ret = HashOutput(ssl, output, sendSz, 0); +#if defined(HAVE_ECH) + if (ssl->ctx->echConfigs != NULL) { + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + + if (echX == NULL) + return -1; + + /* replace the last 8 bytes of server random with the accept */ + if (((WOLFSSL_ECH*)echX->data)->state == ECH_PARSED_INTERNAL) { + ret = EchWriteAcceptance(ssl, output + RECORD_HEADER_SZ, + serverRandomOffset - RECORD_HEADER_SZ, + sendSz - RECORD_HEADER_SZ); + + /* remove ech so we don't keep sending it in write */ + TLSX_Remove(&ssl->extensions, TLSX_ECH, ssl->heap); + } + } +#endif + if (ret == 0) + ret = HashOutput(ssl, output, sendSz, 0); } } diff --git a/tests/api.c b/tests/api.c index 2488ec6bf0c..e711a19ed7e 100644 --- a/tests/api.c +++ b/tests/api.c @@ -340,7 +340,8 @@ #if (defined(SESSION_CERTS) && defined(TEST_PEER_CERT_CHAIN)) || \ defined(HAVE_SESSION_TICKET) || (defined(OPENSSL_EXTRA) && \ defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_CERT_GEN)) || \ - defined(WOLFSSL_TEST_STATIC_BUILD) || defined(WOLFSSL_DTLS) + defined(WOLFSSL_TEST_STATIC_BUILD) || defined(WOLFSSL_DTLS) || \ + defined(HAVE_ECH) /* for testing SSL_get_peer_cert_chain, or SESSION_TICKET_HINT_DEFAULT, * for setting authKeyIdSrc in WOLFSSL_X509, or testing DTLS sequence * number tracking */ @@ -8542,51 +8543,61 @@ static int test_wolfSSL_UseSNI_connection(void) server_cb.devId = testDevId; /* success case at ctx */ + printf("success case at ctx\n"); client_cb.ctx_ready = use_SNI_at_ctx; client_cb.ssl_ready = NULL; client_cb.on_result = NULL; server_cb.ctx_ready = use_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_real_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* success case at ssl */ + printf("success case at ssl\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = use_SNI_at_ssl; client_cb.on_result = verify_SNI_real_matching; server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_at_ssl; server_cb.on_result = verify_SNI_real_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* default mismatch behavior */ + printf("default mismatch behavior\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = verify_FATAL_ERROR_on_client; server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_at_ssl; server_cb.on_result = verify_UNKNOWN_SNI_on_server; test_wolfSSL_client_server(&client_cb, &server_cb); /* continue on mismatch */ + printf("continue on mismatch\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = NULL; server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_WITH_CONTINUE_at_ssl; server_cb.on_result = verify_SNI_no_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* fake answer on mismatch */ + printf("fake answer on mismatch\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = NULL; server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_WITH_FAKE_ANSWER_at_ssl; server_cb.on_result = verify_SNI_fake_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* sni abort - success */ + printf("sni abort - success\n"); client_cb.ctx_ready = use_SNI_at_ctx; client_cb.ssl_ready = NULL; client_cb.on_result = NULL; server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_real_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* sni abort - abort when absent (ctx) */ + printf("sni abort - abort when absent (ctx)\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = NULL; client_cb.on_result = verify_FATAL_ERROR_on_client; server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_ABSENT_on_server; test_wolfSSL_client_server(&client_cb, &server_cb); /* sni abort - abort when absent (ssl) */ + printf("sni abort - abort when absent (ssl)\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = NULL; client_cb.on_result = verify_FATAL_ERROR_on_client; server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_MANDATORY_SNI_at_ssl; server_cb.on_result = verify_SNI_ABSENT_on_server; test_wolfSSL_client_server(&client_cb, &server_cb); /* sni abort - success when overwritten */ + printf("sni abort - success when overwritten\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = NULL; client_cb.on_result = NULL; server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = use_SNI_at_ssl; server_cb.on_result = verify_SNI_no_matching; test_wolfSSL_client_server(&client_cb, &server_cb); /* sni abort - success when allowing mismatches */ + printf("sni abort - success when allowing mismatches\n"); client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = NULL; server_cb.ctx_ready = use_PSEUDO_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_fake_matching; test_wolfSSL_client_server(&client_cb, &server_cb); @@ -35256,6 +35267,89 @@ static int test_wolfSSL_CTX_add_client_CA(void) #endif /* OPENSSL_EXTRA && !NO_RSA && !NO_CERTS && !NO_WOLFSSL_CLIENT */ return res; } +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) +{ + callback_functions* callbacks = ((func_args*)args)->callbacks; + WOLFSSL_CTX* ctx = callbacks->ctx; + WOLFSSL* ssl = NULL; + SOCKET_T sfd = 0; + SOCKET_T cfd = 0; + word16 port; + char input[1024]; + int idx; + int ret, err = 0; + const char* privateName = "ech-private-name.com"; + int privateNameLen = (int)XSTRLEN(privateName); + + ((func_args*)args)->return_code = TEST_FAIL; + port = ((func_args*)args)->signal->port; + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_load_verify_locations(ctx, cliCertFile, 0)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + WOLFSSL_FILETYPE_PEM)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + WOLFSSL_FILETYPE_PEM)); + + if (callbacks->ctx_ready) + callbacks->ctx_ready(ctx); + + ssl = wolfSSL_new(ctx); + + /* set the sni for the server */ + wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, privateNameLen); + + tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1, NULL, NULL); + CloseSocket(sfd); + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_set_fd(ssl, cfd)); + + if (callbacks->ssl_ready) + callbacks->ssl_ready(ssl); + + do { + err = 0; /* Reset error */ + ret = wolfSSL_accept(ssl); + if (ret != WOLFSSL_SUCCESS) { + err = wolfSSL_get_error(ssl, 0); + } + } while (ret != WOLFSSL_SUCCESS && err == WC_PENDING_E); + + if (ret != WOLFSSL_SUCCESS) { + char buff[WOLFSSL_MAX_ERROR_SZ]; + printf("error = %d, %s\n", err, wolfSSL_ERR_error_string(err, buff)); + } + else { + if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) { + input[idx] = 0; + printf("Client message: %s\n", input); + } + + AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName, + privateNameLen)); + ((func_args*)args)->return_code = TEST_SUCCESS; + } + + if (callbacks->on_result) + callbacks->on_result(ssl); + + wolfSSL_shutdown(ssl); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + CloseSocket(cfd); + +#ifdef FP_ECC + wc_ecc_fp_free(); +#endif + + return 0; +} +#endif /* HAVE_ECH && WOLFSSL_TLS13 */ + #if defined(OPENSSL_EXTRA) && defined(HAVE_SECRET_CALLBACK) static THREAD_RETURN WOLFSSL_THREAD server_task(void* args) { @@ -35621,6 +35715,154 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) #endif /* OPENSSL_EXTRA && HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ return res; } +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +static int test_wolfSSL_Tls13_ECH_params(void) +{ +#if !defined(NO_WOLFSSL_CLIENT) + word32 outputLen = 0; + byte testBuf[72]; + WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL *ssl = wolfSSL_new(ctx); + + AssertNotNull(ctx); + AssertNotNull(ssl); + + /* invalid ctx */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(NULL, + "ech-public-name.com", 0, 0, 0)); + /* invalid public name */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, NULL, 0, + 0, 0)); + /* invalid algorithms */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 1000, 1000, 1000)); + + /* invalid ctx */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL, + &outputLen)); + /* invalid output len */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, NULL)); + + /* invalid ssl */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, + (char*)testBuf, sizeof(testBuf))); + /* invalid configs64 */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL, + sizeof(testBuf))); + /* invalid size */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + (char*)testBuf, 0)); + + /* invalid ssl */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(NULL, testBuf, + sizeof(testBuf))); + /* invalid configs */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, NULL, + sizeof(testBuf))); + /* invalid size */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, 0)); + + /* invalid ssl */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(NULL, NULL, &outputLen)); + /* invalid size */ + AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, NULL, NULL)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* !NO_WOLFSSL_CLIENT */ + + return TEST_SUCCESS; +} + +static int test_wolfSSL_Tls13_ECH(void) +{ + tcp_ready ready; + func_args client_args; + func_args server_args; + THREAD_TYPE serverThread; + callback_functions server_cbf; + callback_functions client_cbf; + SOCKET_T sockfd = 0; + WOLFSSL_CTX* ctx; + WOLFSSL* ssl; + const char* publicName = "ech-public-name.com"; + const char* privateName = "ech-private-name.com"; + int privateNameLen = 20; + char reply[1024]; + int replyLen = 0; + byte rawEchConfig[128]; + word32 rawEchConfigLen = sizeof(rawEchConfig); + + InitTcpReady(&ready); + ready.port = 22222; + + XMEMSET(&client_args, 0, sizeof(func_args)); + XMEMSET(&server_args, 0, sizeof(func_args)); + XMEMSET(&server_cbf, 0, sizeof(callback_functions)); + XMEMSET(&client_cbf, 0, sizeof(callback_functions)); + server_cbf.method = wolfTLSv1_3_server_method; /* TLS1.3 */ + + /* create the server context here so we can get the ech config */ + AssertNotNull(server_cbf.ctx = + wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + + /* generate ech config */ + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(server_cbf.ctx, + publicName, 0, 0, 0)); + + /* get the config for the client to use */ + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_GetEchConfigs(server_cbf.ctx, rawEchConfig, + &rawEchConfigLen)); + + server_args.callbacks = &server_cbf; + server_args.signal = &ready; + + /* start server task */ + start_thread(server_task_ech, &server_args, &serverThread); + wait_tcp_ready(&server_args); + + /* run as a TLS1.3 client */ + AssertNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0)); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_certificate_file(ctx, cliCertFile, SSL_FILETYPE_PEM)); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_PrivateKey_file(ctx, cliKeyFile, SSL_FILETYPE_PEM)); + + tcp_connect(&sockfd, wolfSSLIP, server_args.signal->port, 0, 0, NULL); + + /* get connected the server task */ + AssertNotNull(ssl = wolfSSL_new(ctx)); + + /* set the ech configs for the client */ + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, rawEchConfig, + rawEchConfigLen)); + + /* set the sni for the client */ + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, + privateName, privateNameLen)); + + AssertIntEQ(wolfSSL_set_fd(ssl, sockfd), WOLFSSL_SUCCESS); + AssertIntEQ(wolfSSL_connect(ssl), WOLFSSL_SUCCESS); + AssertIntEQ(wolfSSL_write(ssl, privateName, privateNameLen), + privateNameLen); + AssertIntGT((replyLen = wolfSSL_read(ssl, reply, sizeof(reply))), 0); + /* add th null terminator for string compare */ + reply[replyLen] = 0; + /* check that the server replied with the private name */ + AssertStrEQ(privateName, reply); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + + join_thread(serverThread); + + FreeTcpReady(&ready); + + return TEST_SUCCESS; +} +#endif /* HAVE_ECH && WOLFSSL_TLS13 */ #if defined(HAVE_IO_TESTS_DEPENDENCIES) && \ defined(OPENSSL_EXTRA) && !defined(NO_CERTS) && \ @@ -60131,6 +60373,10 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation), TEST_DECL(test_tls_ext_duplicate), +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + TEST_DECL(test_wolfSSL_Tls13_ECH_params), + TEST_DECL(test_wolfSSL_Tls13_ECH), +#endif /* X509 tests */ TEST_DECL(test_wolfSSL_X509_NAME_get_entry), diff --git a/wolfcrypt/src/hpke.c b/wolfcrypt/src/hpke.c new file mode 100644 index 00000000000..3219e59cd84 --- /dev/null +++ b/wolfcrypt/src/hpke.c @@ -0,0 +1,1163 @@ +/* hpke.c + * + * Copyright (C) 2006-2022 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* The HPKE supports ECC and X25519 with AES GCM only. + * TODO: Add X448 and ChaCha20 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(HAVE_HPKE) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \ + defined(HAVE_AESGCM) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int hpkeSupportedKem[HPKE_SUPPORTED_KEM_LEN] = { + DHKEM_P256_HKDF_SHA256, + DHKEM_P384_HKDF_SHA384, + DHKEM_P521_HKDF_SHA512, + DHKEM_X25519_HKDF_SHA256, +}; + +const int hpkeSupportedKdf[HPKE_SUPPORTED_KDF_LEN] = { + HKDF_SHA256, + HKDF_SHA384, + HKDF_SHA512, +}; + +const int hpkeSupportedAead[HPKE_SUPPORTED_AEAD_LEN] = { + HPKE_AES_128_GCM, + HPKE_AES_256_GCM, +}; + +static const char* KEM_STR = "KEM"; +static const int KEM_STR_LEN = 3; + +static const char* HPKE_STR = "HPKE"; +static const int HPKE_STR_LEN = 4; + +static const char* HPKE_VERSION_STR = "HPKE-v1"; +static const int HPKE_VERSION_STR_LEN = 7; + +static const char* EAE_PRK_LABEL_STR = "eae_prk"; +static const int EAE_PRK_LABEL_STR_LEN = 7; + +static const char* SHARED_SECRET_LABEL_STR = "shared_secret"; +static const int SHARED_SECRET_LABEL_STR_LEN = 13; + +static const char* PSK_ID_HASH_LABEL_STR = "psk_id_hash"; +static const int PSK_ID_HASH_LABEL_STR_LEN = 11; + +static const char* INFO_HASH_LABEL_STR = "info_hash"; +static const int INFO_HASH_LABEL_STR_LEN = 9; + +static const char* SECRET_LABEL_STR = "secret"; +static const int SECRET_LABEL_STR_LEN = 6; + +static const char* KEY_LABEL_STR = "key"; +static const int KEY_LABEL_STR_LEN = 3; + +static const char* BASE_NONCE_LABEL_STR = "base_nonce"; +static const int BASE_NONCE_LABEL_STR_LEN = 10; + +static const char* EXP_LABEL_STR = "exp"; +static const int EXP_LABEL_STR_LEN = 3; + +/* encode n as a byte string with length w, return 0 or error */ +static int I2OSP(int n, int w, byte* out) +{ + int i; + + if (w <= 0 || w > 32) { + return MP_VAL; + } + + /* if width is less than int max check that n is less than w bytes max */ + /* if width is greater than int max check that n is less than int max */ + if ((w < 4 && n > ((1 << (w * 8)) - 1)) || (w >= 4 && n > 0x7fffffff)) { + return MP_VAL; + } + + /* make sure the byte string is cleared */ + XMEMSET( out, 0, w ); + + for (i = 0; i < w && n > 0; i++) { + out[w-(i + 1)] = (byte)n; + n >>= 8; + } + + return 0; +} + +/* initialize the hpke struct with the desired ciphersuites, return 0 or error*/ +int wc_HpkeInit(Hpke* hpke, int kem, int kdf, int aead, void* heap) +{ + int ret; + byte* id; + + if (hpke == NULL || kem == 0 || kdf == 0 || aead == 0) { + return BAD_FUNC_ARG; + } + + XMEMSET(hpke, 0, sizeof(*hpke)); + hpke->kem = kem; + hpke->kdf = kdf; + hpke->aead = aead; + hpke->heap = heap; + + /* set kem_suite_id */ + id = hpke->kem_suite_id; + + XMEMCPY(id, KEM_STR, KEM_STR_LEN); + id += KEM_STR_LEN; + + ret = I2OSP(kem, 2, id); + + /* set hpke_suite_id */ + id = hpke->hpke_suite_id; + + XMEMCPY(id, HPKE_STR, HPKE_STR_LEN); + id += HPKE_STR_LEN; + + if (ret == 0) { + ret = I2OSP(kem, 2, id); + id += 2; + } + if (ret == 0) { + ret = I2OSP(kdf, 2, id); + id += 2; + } + if (ret == 0) { + ret = I2OSP(aead, 2, id); + } + + if (ret == 0) { + switch (kem) { +#if defined(HAVE_ECC) +#if defined(WOLFSSL_SHA224) || !defined(NO_SHA256) + case DHKEM_P256_HKDF_SHA256: + hpke->curve_id = ECC_SECP256R1; + hpke->Nsecret = WC_SHA256_DIGEST_SIZE; + hpke->Nh = WC_SHA256_DIGEST_SIZE; + hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id); + hpke->Npk = 1 + hpke->Ndh * 2; + break; +#endif + +#ifdef WOLFSSL_SHA384 + case DHKEM_P384_HKDF_SHA384: + hpke->curve_id = ECC_SECP384R1; + hpke->Nsecret = WC_SHA384_DIGEST_SIZE; + hpke->Nh = WC_SHA384_DIGEST_SIZE; + hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id); + hpke->Npk = 1 + hpke->Ndh * 2; + break; +#endif + +#if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) + case DHKEM_P521_HKDF_SHA512: + hpke->curve_id = ECC_SECP521R1; + hpke->Nsecret = WC_SHA512_DIGEST_SIZE; + hpke->Nh = WC_SHA512_DIGEST_SIZE; + hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id); + hpke->Npk = 1 + hpke->Ndh * 2; + break; +#endif +#endif + +#if defined(HAVE_CURVE25519) &&\ + (defined(WOLFSSL_SHA224) || !defined(NO_SHA256)) + case DHKEM_X25519_HKDF_SHA256: + hpke->Nsecret = WC_SHA256_DIGEST_SIZE; + hpke->Nh = WC_SHA256_DIGEST_SIZE; + hpke->Ndh = CURVE25519_KEYSIZE; + hpke->Npk = CURVE25519_PUB_KEY_SIZE; + break; +#endif + +#if defined(HAVE_CURVE448) &&\ + (defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512)) + case DHKEM_X448_HKDF_SHA512: + hpke->Nsecret = WC_SHA512_DIGEST_SIZE; + hpke->Nh = WC_SHA512_DIGEST_SIZE; + /* size of x448 shared secret */ + hpke->Ndh = 64; + hpke->Npk = CURVE448_PUB_KEY_SIZE; + ret = BAD_FUNC_ARG; /* TODO: Add X448 */ + break; +#endif + + default: + ret = BAD_FUNC_ARG; + break; + } + } + + if (ret == 0) { + switch (kdf) { + case HKDF_SHA256: + hpke->kdf_digest = WC_SHA256; + break; + + case HKDF_SHA384: + hpke->kdf_digest = WC_SHA384; + break; + + case HKDF_SHA512: + hpke->kdf_digest = WC_SHA512; + break; + + default: + ret = BAD_FUNC_ARG; + break; + } + } + + if (ret == 0) { + switch (aead) { + case HPKE_AES_128_GCM: + hpke->Nk = AES_128_KEY_SIZE; + hpke->Nn = GCM_NONCE_MID_SZ; + hpke->Nt = AES_BLOCK_SIZE; + break; + + case HPKE_AES_256_GCM: + hpke->Nk = AES_256_KEY_SIZE; + hpke->Nn = GCM_NONCE_MID_SZ; + hpke->Nt = AES_BLOCK_SIZE; + break; + + default: + ret = BAD_FUNC_ARG; + break; + } + } + + if ((int)hpke->Ndh < 0) { + return hpke->Ndh; + } + + return ret; +} + +/* generate a keypair for use with the supplied hpke kem method, return 0 or + * error */ +int wc_HpkeGenerateKeyPair(Hpke* hpke, void** keypair, WC_RNG* rng) +{ + int ret = 0; + + if (hpke == NULL || keypair == NULL || rng == NULL) + return BAD_FUNC_ARG; + + switch (hpke->kem) { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + *keypair = wc_ecc_key_new(hpke->heap); + if (*keypair != NULL) + ret = wc_ecc_make_key_ex(rng, 32, (ecc_key*)*keypair, + ECC_SECP256R1); + break; + case DHKEM_P384_HKDF_SHA384: + *keypair = wc_ecc_key_new(hpke->heap); + if (*keypair != NULL) + ret = wc_ecc_make_key_ex(rng, 48, (ecc_key*)*keypair, + ECC_SECP384R1); + break; + case DHKEM_P521_HKDF_SHA512: + *keypair = wc_ecc_key_new(hpke->heap); + if (*keypair != NULL) + ret = wc_ecc_make_key_ex(rng, 66, (ecc_key*)*keypair, + ECC_SECP521R1); + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + *keypair = XMALLOC(sizeof(curve25519_key), hpke->heap, + DYNAMIC_TYPE_CURVE25519); + if (*keypair != NULL) { + ret = wc_curve25519_init_ex((curve25519_key*)*keypair, + hpke->heap, INVALID_DEVID); + if (ret == 0) + ret = wc_curve25519_make_key(rng, 32, + (curve25519_key*)*keypair); + } + break; +#endif + case DHKEM_X448_HKDF_SHA512: + /* TODO: Add X448 */ + default: + ret = BAD_FUNC_ARG; + break; + } + + if (ret == 0 && *keypair == NULL) + ret = MEMORY_E; + + if (ret != 0 && *keypair != NULL) { + wc_HpkeFreeKey(hpke, hpke->kem, *keypair, hpke->heap); + *keypair = NULL; + } + + return ret; +} + +/* encode the provided kem key into a byte string, return 0 or error */ +int wc_HpkeSerializePublicKey(Hpke* hpke, void* key, byte* out, word16* outSz) +{ + int ret; + word32 tmpOutSz; + + if (hpke == NULL || key == NULL || out == NULL || outSz == NULL) { + return BAD_FUNC_ARG; + } + + tmpOutSz = *outSz; + + switch (hpke->kem) + { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + case DHKEM_P384_HKDF_SHA384: + case DHKEM_P521_HKDF_SHA512: + /* export x963 uncompressed */ + ret = wc_ecc_export_x963_ex((ecc_key*)key, out, &tmpOutSz, 0); + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + ret = wc_curve25519_export_public_ex((curve25519_key*)key, out, + &tmpOutSz, EC25519_LITTLE_ENDIAN); + break; +#endif + case DHKEM_X448_HKDF_SHA512: + default: + ret = -1; + break; + } + + *outSz = tmpOutSz; + + return ret; +} + +/* load a serialized kem key into a wolfcrypt key struct depending on the kem */ +int wc_HpkeDeserializePublicKey(Hpke* hpke, void** key, const byte* in, + word16 inSz) +{ + int ret = 0; + + if (hpke == NULL || key == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + + if (inSz < (word32)hpke->Npk) { + return BUFFER_E; + } + + switch (hpke->kem) + { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + case DHKEM_P384_HKDF_SHA384: + case DHKEM_P521_HKDF_SHA512: + /* init the ecc key */ + *key = wc_ecc_key_new(hpke->heap); + if (*key != NULL) { + /* import the x963 key */ + ret = wc_ecc_import_x963_ex(in, inSz, (ecc_key*)*key, + hpke->curve_id); + } + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + *key = XMALLOC(sizeof(curve25519_key), hpke->heap, + DYNAMIC_TYPE_CURVE25519); + if (*key != NULL) { + ret = wc_curve25519_init_ex((curve25519_key*)*key, hpke->heap, + INVALID_DEVID); + if (ret == 0) + ret = wc_curve25519_import_public_ex(in, inSz, + (curve25519_key*)*key, EC25519_LITTLE_ENDIAN); + } + break; +#endif + case DHKEM_X448_HKDF_SHA512: + default: + ret = -1; + break; + } + + if (ret == 0 && *key == NULL) + ret = MEMORY_E; + + if (ret != 0 && *key != NULL) { + wc_HpkeFreeKey(hpke, hpke->kem, *key, hpke->heap); + *key = NULL; + } + + return ret; +} + +/* free a kem key */ +void wc_HpkeFreeKey(Hpke* hpke, word16 kem, void* keypair, void* heap) +{ + switch (kem) + { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + case DHKEM_P384_HKDF_SHA384: + case DHKEM_P521_HKDF_SHA512: + wc_ecc_key_free((ecc_key*)keypair); + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + wc_curve25519_free((curve25519_key*)keypair); + XFREE(keypair, heap, DYNAMIC_TYPE_CURVE25519); + break; +#endif + case DHKEM_X448_HKDF_SHA512: + /* TODO: Add X448 */ + default: + break; + } + (void)hpke; + (void)heap; +} + +static int wc_HpkeLabeledExtract(Hpke* hpke, byte* suite_id, + word32 suite_id_len, byte* salt, word32 salt_len, byte* label, + word32 label_len, byte* ikm, word32 ikm_len, byte* out) +{ + int ret; + byte* labeled_ikm_p; +#ifndef WOLFSSL_SMALL_STACK + byte labeled_ikm[MAX_HPKE_LABEL_SZ]; +#else + byte* labeled_ikm; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + labeled_ikm = (byte*)XMALLOC(MAX_HPKE_LABEL_SZ, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (labeled_ikm == NULL) { + return MEMORY_E; + } +#endif + + /* concat the labeled_ikm */ + /* version */ + XMEMCPY(labeled_ikm, HPKE_VERSION_STR, HPKE_VERSION_STR_LEN); + labeled_ikm_p = labeled_ikm + HPKE_VERSION_STR_LEN; + + /* suite_id */ + XMEMCPY(labeled_ikm_p, suite_id, suite_id_len); + labeled_ikm_p += suite_id_len; + + /* label */ + XMEMCPY(labeled_ikm_p, label, label_len); + labeled_ikm_p += label_len; + + /* ikm */ + if (ikm_len != 0) { + XMEMCPY(labeled_ikm_p, ikm, ikm_len); + labeled_ikm_p += ikm_len; + } + + /* call extract */ + ret = wc_HKDF_Extract(hpke->kdf_digest, salt, salt_len, labeled_ikm, + (word32)(size_t)(labeled_ikm_p - labeled_ikm), out); + +#ifdef WOLFSSL_SMALL_STACK + XFREE(labeled_ikm, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* do hkdf expand with the format specified in the hpke rfc, return 0 or + * error */ +static int wc_HpkeLabeledExpand(Hpke* hpke, byte* suite_id, word32 suite_id_len, + byte* prk, word32 prk_len, byte* label, word32 label_len, byte* info, + word32 infoSz, word32 L, byte* out) +{ + int ret; + byte* labeled_info_p; +#ifndef WOLFSSL_SMALL_STACK + byte labeled_info[MAX_HPKE_LABEL_SZ]; +#else + byte* labeled_info; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + labeled_info = (byte*)XMALLOC(MAX_HPKE_LABEL_SZ, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (labeled_info == NULL) { + return MEMORY_E; + } +#endif + + /* copy length */ + ret = I2OSP(L, 2, labeled_info); + labeled_info_p = labeled_info + 2; + + if (ret == 0) { + /* version */ + XMEMCPY(labeled_info_p, HPKE_VERSION_STR, HPKE_VERSION_STR_LEN); + labeled_info_p += HPKE_VERSION_STR_LEN; + + /* suite_id */ + XMEMCPY(labeled_info_p, suite_id, suite_id_len); + labeled_info_p += suite_id_len; + + /* label */ + XMEMCPY(labeled_info_p, label, label_len); + labeled_info_p += label_len; + + /* info */ + XMEMCPY(labeled_info_p, info, infoSz); + labeled_info_p += infoSz; + + /* call expand */ + ret = wc_HKDF_Expand(hpke->kdf_digest, + prk, prk_len, + labeled_info, (word32)(size_t)(labeled_info_p - labeled_info), + out, L); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(labeled_info, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* compute the current nonce from the base nonce using the sequence value, + * return 0 or error */ +static int wc_HpkeContextComputeNonce(Hpke* hpke, HpkeBaseContext* context, + byte* out) +{ + int i; + int ret; + byte seq_bytes[HPKE_Nn_MAX]; + + /* convert the sequence into a byte string with the same length as the + * nonce */ + ret = I2OSP(context->seq, hpke->Nn, seq_bytes); + if (ret == 0) { + for (i = 0; i < (int)hpke->Nn; i++) { + out[i] = (context->base_nonce[i] ^ seq_bytes[i]); + } + } + + return ret; +} + +/* call extract and expand as specified in the hpke rfc, return 0 or error */ +static int wc_HpkeExtractAndExpand( Hpke* hpke, byte* dh, word32 dh_len, + byte* kemContext, word32 kem_context_length, byte* sharedSecret) +{ + int ret; + /* max length is the largest hmac digest possible */ +#ifndef WOLFSSL_SMALL_STACK + byte eae_prk[WC_MAX_DIGEST_SIZE]; +#else + byte* eae_prk; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + eae_prk = (byte*)XMALLOC(WC_MAX_DIGEST_SIZE, hpke->heap, + DYNAMIC_TYPE_DIGEST); + if (eae_prk == NULL) { + return MEMORY_E; + } +#endif + + /* extract */ + ret = wc_HpkeLabeledExtract(hpke, hpke->kem_suite_id, + sizeof( hpke->kem_suite_id ), NULL, 0, (byte*)EAE_PRK_LABEL_STR, + EAE_PRK_LABEL_STR_LEN, dh, dh_len, eae_prk); + + /* expand */ + if ( ret == 0 ) + ret = wc_HpkeLabeledExpand(hpke, hpke->kem_suite_id, + sizeof( hpke->kem_suite_id ), eae_prk, hpke->Nh, + (byte*)SHARED_SECRET_LABEL_STR, SHARED_SECRET_LABEL_STR_LEN, + kemContext, kem_context_length, hpke->Nsecret, sharedSecret); + +#ifdef WOLFSSL_SMALL_STACK + XFREE(eae_prk, hpke->heap, DYNAMIC_TYPE_DIGEST); +#endif + + return ret; +} + +/* derive the key, nonce and exporter secret and store them in the context + * struct, return 0 or error */ +static int wc_HpkeKeyScheduleBase(Hpke* hpke, HpkeBaseContext* context, + byte* sharedSecret, byte* info, word32 infoSz) +{ + int ret; +#ifndef WOLFSSL_SMALL_STACK + /* 1 for mode and WC_MAX_DIGEST_SIZE times 2 for psk_id_hash and */ + /* info_hash */ + byte key_schedule_context[1 + 2 * WC_MAX_DIGEST_SIZE]; + /* maximum size of secret is largest hash of extract */ + byte secret[WC_MAX_DIGEST_SIZE]; +#else + byte* key_schedule_context = NULL; + byte* secret = NULL; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + key_schedule_context = (byte*)XMALLOC((1 + 2 * WC_MAX_DIGEST_SIZE), + hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + secret = (byte*)XMALLOC(WC_MAX_DIGEST_SIZE, hpke->heap, + DYNAMIC_TYPE_DIGEST); + if (key_schedule_context == NULL || secret == NULL) { + XFREE(key_schedule_context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(secret, hpke->heap, DYNAMIC_TYPE_DIGEST); + return MEMORY_E; + } +#endif + + /* set the sequence to 0 */ + context->seq = 0; + + /* 0 for mode */ + key_schedule_context[0] = 0; + + /* extract psk_id, which for base is null */ + ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), NULL, 0, (byte*)PSK_ID_HASH_LABEL_STR, + PSK_ID_HASH_LABEL_STR_LEN, NULL, 0, key_schedule_context + 1); + + /* extract info */ + if (ret == 0) { + ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), NULL, 0, (byte*)INFO_HASH_LABEL_STR, + INFO_HASH_LABEL_STR_LEN, info, infoSz, + key_schedule_context + 1 + hpke->Nh); + } + + /* extract secret */ + if (ret == 0) { + ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), sharedSecret, hpke->Nsecret, + (byte*)SECRET_LABEL_STR, SECRET_LABEL_STR_LEN, NULL, 0, secret); + } + + /* expand key */ + if (ret == 0) + ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), secret, hpke->Nh, + (byte*)KEY_LABEL_STR, KEY_LABEL_STR_LEN, key_schedule_context, + 1 + 2 * hpke->Nh, hpke->Nk, context->key); + + /* expand nonce */ + if (ret == 0) { + ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), secret, hpke->Nh, + (byte*)BASE_NONCE_LABEL_STR, BASE_NONCE_LABEL_STR_LEN, + key_schedule_context, 1 + 2 * hpke->Nh, hpke->Nn, + context->base_nonce); + } + + /* expand exporter_secret */ + if (ret == 0) { + ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id, + sizeof( hpke->hpke_suite_id ), secret, hpke->Nh, + (byte*)EXP_LABEL_STR, EXP_LABEL_STR_LEN, key_schedule_context, + 1 + 2 * hpke->Nh, hpke->Nh, context->exporter_secret); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(key_schedule_context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(secret, hpke->heap, DYNAMIC_TYPE_DIGEST); +#endif + + return ret; +} + +/* compute the shared secret from the ephemeral and receiver kem keys */ +static int wc_HpkeEncap(Hpke* hpke, void* ephemeralKey, void* receiverKey, + byte* sharedSecret) +{ + int ret; + word32 dh_len; + word16 receiverPubKeySz = hpke->Npk; + word16 ephemeralPubKeySz = hpke->Npk; +#ifndef WOLFSSL_SMALL_STACK + byte dh[HPKE_Ndh_MAX]; + byte kemContext[HPKE_Npk_MAX * 2]; +#else + byte* dh = NULL; + byte* kemContext = NULL; +#endif + + if (hpke == NULL || ephemeralKey == NULL || receiverKey == NULL || + sharedSecret == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + dh = (byte*)XMALLOC(hpke->Ndh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + kemContext = (byte*)XMALLOC(hpke->Npk * 2, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (dh == NULL || kemContext == NULL) { + XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } +#endif + + /* generate dh */ + dh_len = hpke->Ndh; + + switch (hpke->kem) + { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + case DHKEM_P384_HKDF_SHA384: + case DHKEM_P521_HKDF_SHA512: + ((ecc_key*)ephemeralKey)->rng = wc_rng_new(NULL, 0, hpke->heap); + + ret = wc_ecc_shared_secret((ecc_key*)ephemeralKey, + (ecc_key*)receiverKey, dh, &dh_len); + + wc_rng_free(((ecc_key*)ephemeralKey)->rng); + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + ret = wc_curve25519_shared_secret_ex((curve25519_key*)ephemeralKey, + (curve25519_key*)receiverKey, dh, &dh_len, + EC25519_LITTLE_ENDIAN); + break; +#endif + case DHKEM_X448_HKDF_SHA512: + /* TODO: Add X448 */ + default: + ret = -1; + break; + } + + if (ret == 0) { + /* serialize ephemeralKey into kemContext */ + ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey, + kemContext, &ephemeralPubKeySz); + } + if (ret == 0) { + /* serialize pkR into kemContext */ + ret = wc_HpkeSerializePublicKey(hpke, receiverKey, + kemContext + ephemeralPubKeySz, &receiverPubKeySz); + } + if (ret == 0) { + /* compute the shared secret */ + ret = wc_HpkeExtractAndExpand(hpke, dh, dh_len, kemContext, + hpke->Npk * 2, sharedSecret); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* setup the sender context with shared key, nonce and exporter secret */ +static int wc_HpkeSetupBaseSender(Hpke* hpke, HpkeBaseContext* context, + void* ephemeralKey, void* receiverKey, byte* info, word32 infoSz) +{ + int ret; +#ifndef WOLFSSL_SMALL_STACK + byte sharedSecret[HPKE_Nsecret_MAX]; +#else + byte* sharedSecret; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + sharedSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); +#endif + + /* encap */ + ret = wc_HpkeEncap(hpke, ephemeralKey, receiverKey, sharedSecret); + + /* schedule */ + if (ret == 0) { + ret = wc_HpkeKeyScheduleBase(hpke, context, sharedSecret, info, + infoSz); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* encrypt a message using an hpke base context, return 0 or error */ +static int wc_HpkeContextSealBase(Hpke* hpke, HpkeBaseContext* context, + byte* aad, word32 aadSz, byte* plaintext, word32 ptSz, byte* out) +{ + int ret; + byte nonce[HPKE_Nn_MAX]; +#ifndef WOLFSSL_SMALL_STACK + Aes aes_key[1]; +#else + Aes* aes_key; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + aes_key = (Aes*)XMALLOC(sizeof(Aes), hpke->heap, DYNAMIC_TYPE_AES); + if (aes_key == NULL) { + return MEMORY_E; + } +#endif + + ret = wc_AesInit(aes_key, hpke->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_HpkeContextComputeNonce(hpke, context, nonce); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes_key, context->key, hpke->Nk); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(aes_key, out, plaintext, ptSz, nonce, + hpke->Nn, out + ptSz, hpke->Nt, aad, aadSz); + } + if (ret == 0) { + context->seq++; + } + wc_AesFree(aes_key); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(aes_key, hpke->heap, DYNAMIC_TYPE_AES); +#endif + + return ret; +} + +/* encrypt a message using the provided ephemeral and receiver kem keys */ +int wc_HpkeSealBase(Hpke* hpke, void* ephemeralKey, void* receiverKey, + byte* info, word32 infoSz, byte* aad, word32 aadSz, byte* plaintext, + word32 ptSz, byte* ciphertext) +{ + int ret; +#ifdef WOLFSSL_SMALL_STACK + HpkeBaseContext* context; +#else + HpkeBaseContext context[1]; +#endif + + /* check that all the buffers are non NULL or optional with 0 length */ + if (hpke == NULL || ephemeralKey == NULL || receiverKey == NULL || + (info == NULL && infoSz != 0) || (aad == NULL && aadSz != 0) || + plaintext == NULL || ciphertext == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + context = (HpkeBaseContext*)XMALLOC(sizeof(HpkeBaseContext), hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (context == NULL) { + return MEMORY_E; + } +#endif + + /* setup the context and pubKey */ + ret = wc_HpkeSetupBaseSender(hpke, context, ephemeralKey, receiverKey, info, + infoSz); + + /* run seal using the context */ + if (ret == 0) + ret = wc_HpkeContextSealBase(hpke, context, aad, aadSz, plaintext, + ptSz, ciphertext); + +#ifdef WOLFSSL_SMALL_STACK + XFREE(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* compute the shared secret from the ephemeral and receiver kem keys */ +static int wc_HpkeDecap(Hpke* hpke, void* receiverKey, const byte* pubKey, + word16 pubKeySz, byte* sharedSecret) +{ + int ret; + word32 dh_len; + word16 receiverPubKeySz = hpke->Npk; + void* ephemeralKey = NULL; +#ifndef WOLFSSL_SMALL_STACK + byte dh[HPKE_Ndh_MAX]; + byte kemContext[HPKE_Npk_MAX * 2]; +#else + byte* dh = NULL; + byte* kemContext = NULL; +#endif + + if (hpke == NULL || receiverKey == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + dh = (byte*)XMALLOC(hpke->Ndh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + kemContext = (byte*)XMALLOC(hpke->Npk * 2, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (dh == NULL || kemContext == NULL) { + XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + return MEMORY_E; + } +#endif + + /* deserialize ephemeralKey from pubKey */ + ret = wc_HpkeDeserializePublicKey(hpke, &ephemeralKey, pubKey, pubKeySz); + + /* generate dh */ + dh_len = hpke->Ndh; + + if (ret == 0) + switch (hpke->kem) + { +#if defined(HAVE_ECC) + case DHKEM_P256_HKDF_SHA256: + case DHKEM_P384_HKDF_SHA384: + case DHKEM_P521_HKDF_SHA512: + ((ecc_key*)receiverKey)->rng = wc_rng_new(NULL, 0, hpke->heap); + + ret = wc_ecc_shared_secret((ecc_key*)receiverKey, + (ecc_key*)ephemeralKey, dh, &dh_len); + + wc_rng_free(((ecc_key*)receiverKey)->rng); + break; +#endif +#if defined(HAVE_CURVE25519) + case DHKEM_X25519_HKDF_SHA256: + ret = wc_curve25519_shared_secret_ex( + (curve25519_key*)receiverKey, (curve25519_key*)ephemeralKey, + dh, &dh_len, EC25519_LITTLE_ENDIAN); + break; +#endif + case DHKEM_X448_HKDF_SHA512: + /* TODO: Add X448 */ + default: + ret = -1; + break; + } + + if (ephemeralKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, hpke->heap); + + if (ret == 0) { + /* copy pubKey into kemContext */ + XMEMCPY(kemContext, pubKey, hpke->Npk); + + /* serialize pkR into kemContext */ + ret = wc_HpkeSerializePublicKey(hpke, receiverKey, + kemContext + hpke->Npk, &receiverPubKeySz); + } + + /* compute the shared secret */ + if (ret == 0) { + ret = wc_HpkeExtractAndExpand(hpke, dh, dh_len, kemContext, + hpke->Npk * 2, sharedSecret); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* setup an hpke base context for decrypting messages, return 0 or error */ +static int wc_HpkeSetupBaseReceiver(Hpke* hpke, HpkeBaseContext* context, + void* receiverKey, const byte* pubKey, word16 pubKeySz, byte* info, + word32 infoSz) +{ + int ret; +#ifndef WOLFSSL_SMALL_STACK + byte sharedSecret[HPKE_Nsecret_MAX]; +#else + byte* sharedSecret; +#endif + +#ifdef WOLFSSL_SMALL_STACK + sharedSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (sharedSecret == NULL) { + return MEMORY_E; + } +#endif + + /* decap */ + ret = wc_HpkeDecap(hpke, receiverKey, pubKey, pubKeySz, sharedSecret); + + /* schedule */ + if (ret == 0) { + ret = wc_HpkeKeyScheduleBase(hpke, context, sharedSecret, info, + infoSz); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* decrypt a message using a setup hpke context, return 0 or error */ +static int wc_HpkeContextOpenBase(Hpke* hpke, HpkeBaseContext* context, + byte* aad, word32 aadSz, byte* ciphertext, word32 ctSz, byte* out) +{ + int ret; + byte nonce[HPKE_Nn_MAX]; +#ifndef WOLFSSL_SMALL_STACK + Aes aes_key[1]; +#else + Aes* aes_key; +#endif + + if (hpke == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + aes_key = (Aes*)XMALLOC(sizeof(Aes), hpke->heap, DYNAMIC_TYPE_AES); + if (aes_key == NULL) { + return MEMORY_E; + } +#endif + + ret = wc_HpkeContextComputeNonce(hpke, context, nonce); + if (ret == 0) + ret = wc_AesInit(aes_key, hpke->heap, INVALID_DEVID); + if (ret == 0) { + if (ret == 0) { + ret = wc_AesGcmSetKey(aes_key, context->key, hpke->Nk); + } + if (ret == 0) { + ret = wc_AesGcmDecrypt(aes_key, out, ciphertext, ctSz, nonce, + hpke->Nn, ciphertext + ctSz, hpke->Nt, aad, aadSz); + } + if (ret == 0) { + context->seq++; + } + wc_AesFree(aes_key); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(aes_key, hpke->heap, DYNAMIC_TYPE_AES); +#endif + + return ret; +} + +/* decrypt a message using the receiver and encoded ephemeral key, return 0 or + * error */ +int wc_HpkeOpenBase(Hpke* hpke, void* receiverKey, const byte* pubKey, + word16 pubKeySz, byte* info, word32 infoSz, byte* aad, word32 aadSz, + byte* ciphertext, word32 ctSz, byte* plaintext) +{ + int ret; +#ifndef WOLFSSL_SMALL_STACK + HpkeBaseContext context[1]; +#else + HpkeBaseContext* context; +#endif + + /* check that all the buffer are non NULL or optional with 0 length */ + if (hpke == NULL || receiverKey == NULL || pubKey == NULL || + pubKeySz == 0 || (info == NULL && infoSz != 0) || + (aad == NULL && aadSz != 0) || plaintext == NULL || + ciphertext == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_SMALL_STACK + context = (HpkeBaseContext*)XMALLOC(sizeof(HpkeBaseContext), hpke->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (context == NULL) { + return MEMORY_E; + } +#endif + + /* setup receiver */ + ret = wc_HpkeSetupBaseReceiver(hpke, context, receiverKey, pubKey, + pubKeySz, info, infoSz); + + if (ret == 0) { + /* open the ciphertext */ + ret = wc_HpkeContextOpenBase(hpke, context, aad, aadSz, ciphertext, + ctSz, plaintext); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +#endif /* HAVE_HPKE && (HAVE_ECC || HAVE_CURVE25519) && HAVE_AESGCM */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 08185e39199..ea6b44d40f5 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -253,6 +253,9 @@ #ifdef HAVE_ECC #include #endif +#ifdef HAVE_HPKE + #include +#endif #ifdef HAVE_CURVE25519 #include #endif @@ -434,6 +437,7 @@ WOLFSSL_TEST_SUBROUTINE int sshkdf_test(void); WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void); #endif WOLFSSL_TEST_SUBROUTINE int x963kdf_test(void); +WOLFSSL_TEST_SUBROUTINE int hpke_test(void); WOLFSSL_TEST_SUBROUTINE int arc4_test(void); #ifdef WC_RC2 WOLFSSL_TEST_SUBROUTINE int rc2_test(void); @@ -1064,6 +1068,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("X963-KDF test passed!\n"); #endif +#if defined(HAVE_HPKE) && defined(HAVE_ECC) && defined(HAVE_AESGCM) + if ( (ret = hpke_test()) != 0) + return err_sys("HPKE test failed!\n", ret); + else + TEST_PASS("HPKE test passed!\n"); +#endif + #if defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \ !defined(WOLFSSL_AFALG_XILINX_AES) && !defined(WOLFSSL_XILINX_CRYPT) if ( (ret = gmac_test()) != 0) @@ -22244,8 +22255,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)ceTrafficLabel, (word32)strlen(ceTrafficLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)ceTrafficLabel, (word32)XSTRLEN(ceTrafficLabel), tv->hashHello1, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22254,8 +22265,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)eExpMasterLabel, (word32)strlen(eExpMasterLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)eExpMasterLabel, (word32)XSTRLEN(eExpMasterLabel), tv->hashHello1, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22264,8 +22275,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(salt, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)derivedLabel, (word32)strlen(derivedLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)derivedLabel, (word32)XSTRLEN(derivedLabel), hashZero, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22276,8 +22287,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)cHsTrafficLabel, (word32)strlen(cHsTrafficLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)cHsTrafficLabel, (word32)XSTRLEN(cHsTrafficLabel), tv->hashHello2, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22287,8 +22298,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)sHsTrafficLabel, (word32)strlen(sHsTrafficLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)sHsTrafficLabel, (word32)XSTRLEN(sHsTrafficLabel), tv->hashHello2, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22297,8 +22308,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(salt, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)derivedLabel, (word32)strlen(derivedLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)derivedLabel, (word32)XSTRLEN(derivedLabel), hashZero, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22308,8 +22319,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)cAppTrafficLabel, (word32)strlen(cAppTrafficLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)cAppTrafficLabel, (word32)XSTRLEN(cAppTrafficLabel), tv->hashFinished1, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22318,8 +22329,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)sAppTrafficLabel, (word32)strlen(sAppTrafficLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)sAppTrafficLabel, (word32)XSTRLEN(sAppTrafficLabel), tv->hashFinished1, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22328,8 +22339,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)expMasterLabel, (word32)strlen(expMasterLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)expMasterLabel, (word32)XSTRLEN(expMasterLabel), tv->hashFinished1, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22338,8 +22349,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void) ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz, secret, hashAlgSz, - (byte*)protocolLabel, (word32)strlen(protocolLabel), - (byte*)resMasterLabel, (word32)strlen(resMasterLabel), + (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel), + (byte*)resMasterLabel, (word32)XSTRLEN(resMasterLabel), tv->hashFinished2, hashAlgSz, tv->hashAlg); if (ret != 0) break; @@ -22498,6 +22509,138 @@ WOLFSSL_TEST_SUBROUTINE int x963kdf_test(void) #endif /* HAVE_X963_KDF */ +#if defined(HAVE_HPKE) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \ + defined(HAVE_AESGCM) + +static int hpke_test_single(Hpke* hpke) +{ + int ret = 0; + int rngRet = 0; + WC_RNG rng[1]; + const char* start_text = "this is a test"; + const char* info_text = "info"; + const char* aad_text = "aad"; + byte ciphertext[MAX_HPKE_LABEL_SZ]; + byte plaintext[MAX_HPKE_LABEL_SZ]; + void* receiverKey = NULL; + void* ephemeralKey = NULL; + uint8_t pubKey[HPKE_Npk_MAX]; /* public key */ + word16 pubKeySz = (word16)sizeof(pubKey); + + rngRet = ret = wc_InitRng(rng); + + if (ret != 0) + return ret; + + /* generate the keys */ + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(hpke, &ephemeralKey, rng); + + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(hpke, &receiverKey, rng); + + /* seal */ + if (ret == 0) + ret = wc_HpkeSealBase(hpke, ephemeralKey, receiverKey, + (byte*)info_text, (word32)XSTRLEN(info_text), + (byte*)aad_text, (word32)XSTRLEN(aad_text), + (byte*)start_text, (word32)XSTRLEN(start_text), + ciphertext); + + /* export ephemeral key */ + if (ret == 0) + ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey, pubKey, &pubKeySz); + + /* open with exported ephemeral key */ + if (ret == 0) + ret = wc_HpkeOpenBase(hpke, receiverKey, pubKey, pubKeySz, + (byte*)info_text, (word32)XSTRLEN(info_text), + (byte*)aad_text, (word32)XSTRLEN(aad_text), + ciphertext, (word32)XSTRLEN(start_text), + plaintext); + + if (ret == 0) + ret = XMEMCMP(plaintext, start_text, XSTRLEN(start_text)); + + if (ephemeralKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, hpke->heap); + + if (receiverKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, receiverKey, hpke->heap); + + if (rngRet == 0) + wc_FreeRng(rng); + + return ret; +} + +WOLFSSL_TEST_SUBROUTINE int hpke_test(void) +{ + int ret = 0; + Hpke hpke[1]; + +#if defined(HAVE_ECC) + #if defined(WOLFSSL_SHA224) || !defined(NO_SHA256) + /* p256 */ + ret = wc_HpkeInit(hpke, DHKEM_P256_HKDF_SHA256, HKDF_SHA256, + HPKE_AES_128_GCM, NULL); + + if (ret != 0) + return ret; + + ret = hpke_test_single(hpke); + + if (ret != 0) + return ret; + #endif + + #ifdef WOLFSSL_SHA384 + /* p384 */ + ret = wc_HpkeInit(hpke, DHKEM_P384_HKDF_SHA384, HKDF_SHA384, + HPKE_AES_128_GCM, NULL); + + if (ret != 0) + return ret; + + ret = hpke_test_single(hpke); + + if (ret != 0) + return ret; + #endif + + #if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512) + /* p521 */ + ret = wc_HpkeInit(hpke, DHKEM_P521_HKDF_SHA512, HKDF_SHA512, + HPKE_AES_128_GCM, NULL); + + if (ret != 0) + return ret; + + ret = hpke_test_single(hpke); + + if (ret != 0) + return ret; + #endif +#endif + +#if defined(HAVE_CURVE25519) + /* test with curve25519 and aes256 */ + ret = wc_HpkeInit(hpke, DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, + HPKE_AES_256_GCM, NULL); + + if (ret != 0) + return ret; + + ret = hpke_test_single(hpke); + + if (ret != 0) + return ret; +#endif + + return ret; +/* x448 and chacha20 are unimplemented */ +} +#endif /* HAVE_HPKE && HAVE_ECC && HAVE_AESGCM */ #ifdef HAVE_ECC diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 857cb79a3de..2aa9d651c6e 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -265,6 +265,8 @@ #include #endif +#include + #ifdef __cplusplus extern "C" { #endif @@ -2592,8 +2594,74 @@ typedef enum { #ifdef WOLFSSL_QUIC TLSX_KEY_QUIC_TP_PARAMS_DRAFT = 0xffa5, /* from draft-ietf-quic-tls-27 */ #endif +#if defined(HAVE_ECH) + TLSX_ECH = 0xfe0d, /* from draft-ietf-tls-esni-13 */ +#endif } TLSX_Type; +#if defined(HAVE_ECH) + +typedef enum { + ECH_TYPE_OUTER = 0, + ECH_TYPE_INNER = 1 +} EchType; + +typedef enum { + ECH_WRITE_GREASE, + ECH_WRITE_REAL, + ECH_WRITE_RETRY_CONFIGS, + ECH_WRITE_NONE, + ECH_PARSED_INTERNAL, +} EchState; + +typedef struct EchCipherSuite { + word16 kdfId; + word16 aeadId; +} EchCipherSuite; + +typedef struct WOLFSSL_EchConfig { + byte* raw; + char* publicName; + void* receiverPrivkey; + struct WOLFSSL_EchConfig* next; + EchCipherSuite* cipherSuites; + word32 rawLen; + word16 kemId; + byte configId; + byte numCipherSuites; + byte receiverPubkey[HPKE_Npk_MAX]; +} WOLFSSL_EchConfig; + +typedef struct WOLFSSL_ECH { + Hpke* hpke; + const byte* aad; + void* ephemeralKey; + WOLFSSL_EchConfig* echConfig; + byte* innerClientHello; + byte* outerClientPayload; + EchCipherSuite cipherSuite; + word16 aadLen; + word16 paddingLen; + word16 innerClientHelloLen; + word16 kemId; + word16 encLen; + EchState state; + byte type; + byte configId; + byte enc[HPKE_Npk_MAX]; +} WOLFSSL_ECH; + +WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config); + +WOLFSSL_LOCAL int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen); + +WOLFSSL_LOCAL int GetEchConfig(WOLFSSL_EchConfig* config, byte* output, + word32* outputLen); + +WOLFSSL_LOCAL int GetEchConfigsEx(WOLFSSL_EchConfig* configs, + byte* output, word32* outputLen); +#endif + typedef struct TLSX { TLSX_Type type; /* Extension Type */ void* data; /* Extension Data */ @@ -3477,6 +3545,9 @@ struct WOLFSSL_CTX { const WOLFSSL_QUIC_METHOD *method; } quic; #endif +#if defined(HAVE_ECH) + WOLFSSL_EchConfig* echConfigs; +#endif }; WOLFSSL_LOCAL @@ -4240,6 +4311,12 @@ typedef struct Options { #ifdef WOLFSSL_TLS13 word16 tls13MiddleBoxCompat:1; /* TLSv1.3 middlebox compatibility */ #endif +#ifdef WOLFSSL_DTLS_CID + byte useDtlsCID:1; +#endif /* WOLFSSL_DTLS_CID */ +#if defined(HAVE_ECH) + byte useEch:1; +#endif /* need full byte values for this section */ byte processReply; /* nonblocking resume */ @@ -4283,9 +4360,6 @@ typedef struct Options { #ifdef WOLFSSL_TLS13 byte oldMinor; /* client preferred version < TLS 1.3 */ #endif -#ifdef WOLFSSL_DTLS_CID - byte useDtlsCID:1; -#endif /* WOLFSSL_DTLS_CID */ } Options; typedef struct Arrays { @@ -4301,6 +4375,9 @@ typedef struct Arrays { byte psk_key[MAX_PSK_KEY_LEN]; #endif byte clientRandom[RAN_LEN]; +#if defined(HAVE_ECH) + byte clientRandomInner[RAN_LEN]; +#endif byte serverRandom[RAN_LEN]; byte sessionID[ID_LEN]; byte sessionIDSz; @@ -4855,6 +4932,9 @@ struct WOLFSSL { byte serverSecret[SECRET_LEN]; #endif HS_Hashes* hsHashes; +#if defined(HAVE_ECH) + HS_Hashes* hsHashesEch; +#endif void* IOCB_ReadCtx; void* IOCB_WriteCtx; WC_RNG* rng; @@ -5312,6 +5392,9 @@ struct WOLFSSL { * content have not been handled yet by quic */ } quic; #endif /* WOLFSSL_QUIC */ +#if defined(HAVE_ECH) + WOLFSSL_EchConfig* echConfigs; +#endif }; /* @@ -5817,6 +5900,8 @@ WOLFSSL_LOCAL int SetDhExternal(WOLFSSL_DH *dh); WOLFSSL_LOCAL int InitHandshakeHashes(WOLFSSL* ssl); WOLFSSL_LOCAL void FreeHandshakeHashes(WOLFSSL* ssl); +WOLFSSL_LOCAL int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source, + HS_Hashes** destination); #ifndef WOLFSSL_NO_TLS12 diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 39bdee59a06..99aa78bacd1 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -992,6 +992,23 @@ WOLFSSL_API WOLFSSL_METHOD *wolfSSLv23_method(void); #endif /* WOLFSSL_DTLS */ +#if defined(HAVE_ECH) +WOLFSSL_API int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, + const char* publicName, word16 kemId, word16 kdfId, word16 aeadId); + +WOLFSSL_API int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, + word32* outputLen); + +WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, + word32 echConfigs64Len); + +WOLFSSL_API int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, + word32 echConfigsLen); + +WOLFSSL_API int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* echConfigs, + word32* echConfigsLen); +#endif /* HAVE_ECH */ + #ifdef HAVE_POLY1305 WOLFSSL_API int wolfSSL_use_old_poly(WOLFSSL* ssl, int value); #endif @@ -3702,7 +3719,8 @@ WOLFSSL_API void* wolfSSL_CTX_GetHeap(WOLFSSL_CTX* ctx, WOLFSSL* ssl); /* SNI types */ enum { - WOLFSSL_SNI_HOST_NAME = 0 + WOLFSSL_SNI_HOST_NAME = 0, + WOLFSSL_SNI_HOST_NAME_OUTER = 0, }; WOLFSSL_ABI WOLFSSL_API int wolfSSL_UseSNI(WOLFSSL* ssl, unsigned char type, diff --git a/wolfssl/wolfcrypt/hpke.h b/wolfssl/wolfcrypt/hpke.h new file mode 100644 index 00000000000..432f574da8f --- /dev/null +++ b/wolfssl/wolfcrypt/hpke.h @@ -0,0 +1,136 @@ +/* hpke.h + * + * Copyright (C) 2006-2022 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! + \file wolfssl/wolfcrypt/hpke.h +*/ + +#include +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(HAVE_HPKE) && defined(HAVE_ECC) + +#ifndef WOLFCRYPT_HPKE +#define WOLFCRYPT_HPKE + +/* KEM enum */ +enum { + DHKEM_P256_HKDF_SHA256 = 0x0010, + DHKEM_P384_HKDF_SHA384 = 0x0011, + DHKEM_P521_HKDF_SHA512 = 0x0012, + DHKEM_X25519_HKDF_SHA256 = 0x0020, + DHKEM_X448_HKDF_SHA512 = 0x0021, +}; + +#define DHKEM_P256_ENC_LEN 65 +#define DHKEM_P384_ENC_LEN 97 +#define DHKEM_P521_ENC_LEN 133 +#define DHKEM_X25519_ENC_LEN 32 +#define DHKEM_X448_ENC_LEN 56 + +/* KDF enum */ +enum { + HKDF_SHA256 = 0x0001, + HKDF_SHA384 = 0x0002, + HKDF_SHA512 = 0x0003, +}; + +/* AEAD enum */ +enum { + HPKE_AES_128_GCM = 0x0001, + HPKE_AES_256_GCM = 0x0002, +}; + +/* TODO better way of doing this */ +#define HPKE_SUPPORTED_KEM_LEN 4 +#define HPKE_SUPPORTED_KDF_LEN 3 +#define HPKE_SUPPORTED_AEAD_LEN 2 +extern const int hpkeSupportedKem[HPKE_SUPPORTED_KEM_LEN]; +extern const int hpkeSupportedKdf[HPKE_SUPPORTED_KDF_LEN]; +extern const int hpkeSupportedAead[HPKE_SUPPORTED_AEAD_LEN]; + +#define HPKE_Nh_MAX 64 +#define HPKE_Nk_MAX 32 +#define HPKE_Nn_MAX 12 +#define HPKE_Nt_MAX 16 +#define HPKE_Ndh_MAX 66 +#define HPKE_Npk_MAX 133 +#define HPKE_Nsecret_MAX 64 +#define KEM_SUITE_ID_LEN 5 +#define HPKE_SUITE_ID_LEN 10 + +#ifndef MAX_HPKE_LABEL_SZ +#define MAX_HPKE_LABEL_SZ 512 +#endif + +typedef struct { + void* heap; + word32 kem; + word32 kdf; + word32 aead; + word32 Nh; + word32 Nk; + word32 Nn; + word32 Nt; + word32 Ndh; + word32 Npk; + word32 Nsecret; + int kdf_digest; + int curve_id; + byte kem_suite_id[KEM_SUITE_ID_LEN]; + byte hpke_suite_id[HPKE_SUITE_ID_LEN]; +} Hpke; + +typedef struct { + int seq; + byte key[HPKE_Nk_MAX]; + byte base_nonce[HPKE_Nn_MAX]; + byte exporter_secret[HPKE_Nsecret_MAX]; +} HpkeBaseContext; + + +WOLFSSL_API int wc_HpkeInit(Hpke* hpke, int kem, int kdf, int aead, void* heap); +WOLFSSL_API int wc_HpkeGenerateKeyPair(Hpke* hpke, void** keypair, WC_RNG* rng); +WOLFSSL_API int wc_HpkeSerializePublicKey(Hpke* hpke, void* key, byte* out, + word16* outSz); +WOLFSSL_API int wc_HpkeDeserializePublicKey(Hpke* hpke, void** key, + const byte* in, word16 inSz); +WOLFSSL_API void wc_HpkeFreeKey(Hpke* hpke, word16 kem, void* keypair, + void* heap); +WOLFSSL_API int wc_HpkeSealBase(Hpke* hpke, void* ephemeralKey, + void* receiverKey, byte* info, word32 infoSz, byte* aad, word32 aadSz, + byte* plaintext, word32 ptSz, byte* ciphertext); +WOLFSSL_API int wc_HpkeOpenBase(Hpke* hpke, void* receiverKey, + const byte* pubKey, word16 pubKeySz, byte* info, word32 infoSz, byte* aad, + word32 aadSz, byte* ciphertext, word32 ctSz, byte* plaintext); + +#endif + +#endif /* HAVE_HPKE && HAVE_ECC */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index fff6bbec7b6..529fc171d34 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -32,6 +32,7 @@ nobase_include_HEADERS+= \ wolfssl/wolfcrypt/fips_test.h \ wolfssl/wolfcrypt/hash.h \ wolfssl/wolfcrypt/hmac.h \ + wolfssl/wolfcrypt/hpke.h \ wolfssl/wolfcrypt/kdf.h \ wolfssl/wolfcrypt/integer.h \ wolfssl/wolfcrypt/md2.h \ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 149e11633f0..a23e1acd8ff 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -2879,6 +2879,10 @@ extern void uITRON4_free(void *p) ; #define WOLFSSL_NO_SHAKE256 #endif +/* Encrypted Client Hello - requires HPKE */ +#if defined(HAVE_ECH) && !defined(HAVE_HPKE) + #define HAVE_HPKE +#endif