diff --git a/CMakeLists.txt b/CMakeLists.txt index f3092326b72..420831736ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4044,6 +4044,18 @@ if(WOLFSSL_SRP) ) endif() +add_option("WOLFSSL_ECJPAKE" + "Enable EC-JPAKE (default: disabled)" + "no" "yes;no") +if(WOLFSSL_ECJPAKE) + if(NOT WOLFSSL_ECC) + message(FATAL_ERROR "WOLFSSL_ECJPAKE requires ECC.") + endif() + list(APPEND WOLFSSL_DEFINITIONS + "-DWOLFCRYPT_HAVE_ECJPAKE" + ) +endif() + add_option("WOLFSSL_MOSQUITTO" "Enable Mosquitto support (default: disabled)" "no" "yes;no") @@ -5226,6 +5238,7 @@ if(WOLFSSL_EXAMPLES) tests/api/test_cmac.c tests/api/test_siphash.c tests/api/test_srp.c + tests/api/test_ecjpake.c tests/api/test_eccsi.c tests/api/test_sakke.c tests/api/test_hpke.c diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 70c9163e167..db17d8f1d3f 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -396,6 +396,9 @@ function(generate_build_flags) if(WOLFSSL_SRP OR WOLFSSL_USER_SETTINGS) set(BUILD_SRP "yes" PARENT_SCOPE) endif() + if(WOLFSSL_ECJPAKE OR WOLFSSL_USER_SETTINGS) + set(BUILD_ECJPAKE "yes" PARENT_SCOPE) + endif() if(WOLFSSL_RNG_BANK OR WOLFSSL_USER_SETTINGS) set(BUILD_RNG_BANK "yes" PARENT_SCOPE) endif() @@ -1383,6 +1386,10 @@ function(generate_lib_src_list LIB_SOURCES) list(APPEND LIB_SOURCES wolfcrypt/src/srp.c) endif() + if(BUILD_ECJPAKE) + list(APPEND LIB_SOURCES wolfcrypt/src/ecjpake.c) + endif() + # WOLFSSL_RNG_BANK defined WC_RNG_BANK_SUPPORT without ever adding # the file that implements it, so the option only linked once # something else pulled rng_bank.c in. Matches BUILD_RNG_BANK in diff --git a/cmake/options.h.in b/cmake/options.h.in index b59a84629ac..a1a8cad4474 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -581,6 +581,8 @@ extern "C" { #cmakedefine WOLFSSL_TEST_CERT #undef WOLFCRYPT_HAVE_SRP #cmakedefine WOLFCRYPT_HAVE_SRP +#undef WOLFCRYPT_HAVE_ECJPAKE +#cmakedefine WOLFCRYPT_HAVE_ECJPAKE #undef HAVE_MOSQUITTO #cmakedefine HAVE_MOSQUITTO #undef HAVE_WEBSERVER diff --git a/configure.ac b/configure.ac index 4f95dcd6ce3..1dc71afc4a1 100644 --- a/configure.ac +++ b/configure.ac @@ -10297,6 +10297,27 @@ then fi +# EC-JPAKE password authenticated key exchange +AC_ARG_ENABLE([ecjpake], + [AS_HELP_STRING([--enable-ecjpake],[Enable EC-JPAKE (default: disabled)])], + [ ENABLED_ECJPAKE=$enableval ], + [ ENABLED_ECJPAKE=no ] + ) + +if test "x$ENABLED_ECJPAKE" = "xyes" +then + if test "$ENABLED_ECC" = "no" || test "$ENABLED_SHA256" = "no" + then + AC_MSG_ERROR([--enable-ecjpake requires ECC and SHA-256.]) + fi + if test "$ENABLED_SP_MATH" = "yes" + then + AC_MSG_ERROR([--enable-ecjpake is not supported with --enable-sp-math.]) + fi + AM_CFLAGS="$AM_CFLAGS -DWOLFCRYPT_HAVE_ECJPAKE" +fi + + # Indefinite length encoded BER message support AC_ARG_ENABLE([indef], [AS_HELP_STRING([--enable-indef],[Enable parsing of indefinite length encoded msgs (default: disabled)])], @@ -14669,6 +14690,7 @@ AM_CONDITIONAL([BUILD_OCSP_STAPLING_V2],[test "x$ENABLED_CERTIFICATE_STATUS_REQU AM_CONDITIONAL([BUILD_CRL],[test "x$ENABLED_CRL" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_CRL_MONITOR],[test "x$ENABLED_CRL_MONITOR" = "xyes"]) AM_CONDITIONAL([BUILD_SRP],[test "x$ENABLED_SRP" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ECJPAKE],[test "x$ENABLED_ECJPAKE" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([USE_VALGRIND],[test "x$ENABLED_VALGRIND" = "xyes"]) AM_CONDITIONAL([BUILD_MD4],[test "x$ENABLED_MD4" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_PWDBASED],[test "x$ENABLED_PWDBASED" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) @@ -15311,6 +15333,7 @@ echo " * wolfTPM: $ENABLED_WOLFTPM" echo " * wolfCLU: $ENABLED_WOLFCLU" echo " * wolfSCEP: $ENABLED_WOLFSCEP" echo " * Secure Remote Password: $ENABLED_SRP" +echo " * EC-JPAKE: $ENABLED_ECJPAKE" echo " * Small Stack: $ENABLED_SMALL_STACK" echo " * Linux Kernel Module: $ENABLED_LINUXKM" diff --git a/src/include.am b/src/include.am index 1de3c928828..98826734ce6 100644 --- a/src/include.am +++ b/src/include.am @@ -2402,6 +2402,10 @@ if BUILD_SRP src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/srp.c endif +if BUILD_ECJPAKE +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ecjpake.c +endif + if BUILD_AFALG src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/af_alg/wc_afalg.c endif diff --git a/src/internal.c b/src/internal.c index cd897621c54..4d9fbe79e9b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3542,6 +3542,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #endif ctx->method = NULL; +#ifdef WOLFSSL_DTLS_ECJPAKE + EcJpakeFreePassword(ctx->heap, &ctx->ecJpakePw, &ctx->ecJpakePwSz); +#endif + XFREE(ctx->suites, ctx->heap, DYNAMIC_TYPE_SUITES); ctx->suites = NULL; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) || \ @@ -7977,10 +7981,12 @@ int InitSSL_Suites(WOLFSSL* ssl) byte haveRSA = 0; byte haveMcast = 0; byte haveCertSetupCb = 0; + byte haveEcJpake = 0; (void)haveAnon; /* Squash unused var warnings */ (void)haveMcast; (void)haveCertSetupCb; + (void)haveEcJpake; if (!ssl) return BAD_FUNC_ARG; @@ -8003,6 +8009,10 @@ int InitSSL_Suites(WOLFSSL* ssl) if (ssl->ctx->certSetupCb != NULL) haveCertSetupCb = 1; #endif /* WOLFSSL_TLS13 && !NO_CERTS && OPENSSL_EXTRA */ +#ifdef WOLFSSL_DTLS_ECJPAKE + /* EC-JPAKE authenticates with a password, not a certificate. */ + haveEcJpake = (ssl->ecJpakePw != NULL); +#endif #ifdef WOLFSSL_EARLY_DATA if (ssl->options.side == WOLFSSL_SERVER_END) @@ -8034,7 +8044,8 @@ int InitSSL_Suites(WOLFSSL* ssl) * Multicast or cert setup callback. This should be true even if just * switching ssl ctx */ if (ssl->options.side == WOLFSSL_SERVER_END && - !havePSK && !haveAnon && !haveMcast && !haveCertSetupCb) { + !havePSK && !haveAnon && !haveMcast && !haveCertSetupCb && + !haveEcJpake) { /* server certificate must be loaded */ if ((!ssl->buffers.certificate || !ssl->buffers.certificate->buffer) @@ -8465,6 +8476,16 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) if (ssl->options.side == WOLFSSL_SERVER_END) ssl->options.maxEarlyDataSz = ctx->maxEarlyDataSz; #endif +#ifdef WOLFSSL_DTLS_ECJPAKE + if (ctx->ecJpakePw != NULL) { + ret = EcJpakeSetPassword(ssl->heap, &ssl->ecJpakePw, + &ssl->ecJpakePwSz, ctx->ecJpakePw, + ctx->ecJpakePwSz); + if (ret != 0) + return ret; + ret = WOLFSSL_SUCCESS; + } +#endif #ifdef HAVE_ANON ssl->options.useAnon = ctx->useAnon; @@ -10343,6 +10364,11 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) WOLFSSL_MSG("Free'ing client ssl"); } +#ifdef WOLFSSL_DTLS_ECJPAKE + FreeEcJpake(ssl); + EcJpakeFreePassword(ssl->heap, &ssl->ecJpakePw, &ssl->ecJpakePwSz); +#endif + #ifdef HAVE_EX_DATA_CLEANUP_HOOKS wolfSSL_CRYPTO_cleanup_ex_data(&ssl->ex_data); #endif @@ -10652,6 +10678,10 @@ void FreeHandshakeResources(WOLFSSL* ssl) { WOLFSSL_ENTER("FreeHandshakeResources"); +#ifdef WOLFSSL_DTLS_ECJPAKE + FreeEcJpake(ssl); +#endif + #ifdef WOLFSSL_DTLS if (ssl->options.dtls) { /* DTLS_POOL (DTLSv1.3 flushes the queue autonomously) */ @@ -14614,6 +14644,13 @@ int CipherRequires(byte first, byte second, int requirement) return 1; break; + #ifdef WOLFSSL_DTLS_ECJPAKE + case TLS_ECJPAKE_WITH_AES_128_CCM_8 : + if (requirement == REQUIRES_AEAD) + return 1; + break; + #endif + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 : case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 : if (requirement == REQUIRES_ECC) @@ -19130,7 +19167,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (args->count == 0) { /* Empty certificate message. Only mutualAuth fails here * before TLS 1.2; the later check catches the rest. */ - if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher) && + if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher || + IsEcJpakeKea(ssl)) && (ssl->options.mutualAuth || IsAtLeastTLSv1_2(ssl))) { WOLFSSL_MSG("No peer cert from Client"); ret = NO_PEER_CERT; @@ -21044,6 +21082,7 @@ static int SanityCheckMsgReceived(WOLFSSL* ssl, byte type) if (ssl->specs.kea != psk_kea && ssl->specs.kea != dhe_psk_kea && ssl->specs.kea != ecdhe_psk_kea && + !IsEcJpakeKea(ssl) && !ssl->options.usingAnon_cipher) { WOLFSSL_MSG("No Certificate before ServerKeyExchange"); WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E); @@ -21128,6 +21167,7 @@ static int SanityCheckMsgReceived(WOLFSSL* ssl, byte type) if (ssl->specs.kea == psk_kea || ssl->specs.kea == dhe_psk_kea || ssl->specs.kea == ecdhe_psk_kea || + IsEcJpakeKea(ssl) || ssl->options.usingAnon_cipher) { WOLFSSL_MSG("No Cert required"); } @@ -21271,6 +21311,7 @@ static int SanityCheckMsgReceived(WOLFSSL* ssl, byte type) if (!ssl->options.resuming && ssl->options.verifyPeer && !ssl->options.usingPSK_cipher && !ssl->options.usingAnon_cipher && + !IsEcJpakeKea(ssl) && !ssl->msgsReceived.got_certificate) { return OUT_OF_ORDER_E; } @@ -29080,8 +29121,10 @@ int SendCertificate(WOLFSSL* ssl) WOLFSSL_START(WC_FUNC_CERTIFICATE_SEND); WOLFSSL_ENTER("SendCertificate"); - if (ssl->options.usingPSK_cipher || ssl->options.usingAnon_cipher) { - WOLFSSL_MSG("Not sending certificate msg. Using PSK or ANON cipher."); + if (ssl->options.usingPSK_cipher || ssl->options.usingAnon_cipher || + IsEcJpakeKea(ssl)) { + WOLFSSL_MSG("Not sending certificate msg. Using PSK, ANON or EC-JPAKE " + "cipher."); return 0; /* not needed */ } @@ -29580,7 +29623,8 @@ int SendCertificateRequest(WOLFSSL* ssl) reqSz += dnLen; #endif - if (ssl->options.usingPSK_cipher || ssl->options.usingAnon_cipher) + if (ssl->options.usingPSK_cipher || ssl->options.usingAnon_cipher || + IsEcJpakeKea(ssl)) return 0; /* not needed */ sendSz = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + reqSz; @@ -32259,6 +32303,11 @@ static const CipherSuiteInfo cipher_names[] = SUITE_ALIAS("ECDHE-ECDSA-AES128-CCM8",ECC_BYTE,TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLSv1_2_MINOR, SSLv3_MAJOR) #endif +#ifdef BUILD_TLS_ECJPAKE_WITH_AES_128_CCM_8 + SUITE_INFO("ECJPAKE-AES128-CCM-8","TLS_ECJPAKE_WITH_AES_128_CCM_8",ECC_BYTE,TLS_ECJPAKE_WITH_AES_128_CCM_8, TLSv1_2_MINOR, SSLv3_MAJOR), + SUITE_ALIAS("ECJPAKE-AES128-CCM8",ECC_BYTE,TLS_ECJPAKE_WITH_AES_128_CCM_8, TLSv1_2_MINOR, SSLv3_MAJOR) +#endif + #ifdef BUILD_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 SUITE_INFO("ECDHE-ECDSA-AES256-CCM-8","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",ECC_BYTE,TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, TLSv1_2_MINOR, SSLv3_MAJOR), SUITE_ALIAS("ECDHE-ECDSA-AES256-CCM8",ECC_BYTE,TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, TLSv1_2_MINOR, SSLv3_MAJOR) @@ -37688,6 +37737,25 @@ static int DoServerKeyExchange(WOLFSSL* ssl, const byte* input, break; } #endif /* (HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448) && !NO_PSK */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + if (ssl->ecJpake == NULL) { + ERROR_OUT(MISSING_HANDSHAKE_DATA, exit_dske); + } + /* ServerECJPAKEParams fill the message */ + ret = wc_EcJpakeReadRound2(ssl->ecJpake, + input + args->idx, + size - (args->idx - args->begin)); + if (ret != 0) { + WOLFSSL_MSG("EC-JPAKE round two failed"); + SendAlert(ssl, alert_fatal, handshake_failure); + break; + } + args->idx = args->begin + size; + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: ret = BAD_KEA_TYPE_E; } /* switch(ssl->specs.kea) */ @@ -37709,6 +37777,9 @@ static int DoServerKeyExchange(WOLFSSL* ssl, const byte* input, case psk_kea: case dhe_psk_kea: case ecdhe_psk_kea: + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + #endif { /* Nothing to do in this sub-state */ break; @@ -37901,6 +37972,9 @@ static int DoServerKeyExchange(WOLFSSL* ssl, const byte* input, case psk_kea: case dhe_psk_kea: case ecdhe_psk_kea: + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + #endif { /* Nothing to do in this sub-state */ break; @@ -38120,6 +38194,9 @@ static int DoServerKeyExchange(WOLFSSL* ssl, const byte* input, case psk_kea: case dhe_psk_kea: case ecdhe_psk_kea: + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + #endif { /* Nothing to do in this sub-state */ break; @@ -38594,6 +38671,14 @@ int SendClientKeyExchange(WOLFSSL* ssl) } #endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + if (ssl->ecJpake == NULL) { + ERROR_OUT(MISSING_HANDSHAKE_DATA, exit_scke); + } + break; + #endif /* WOLFSSL_DTLS_ECJPAKE */ + default: ret = BAD_KEA_TYPE_E; } /* switch(ssl->specs.kea) */ @@ -38835,6 +38920,25 @@ int SendClientKeyExchange(WOLFSSL* ssl) } #endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + /* ClientECJPAKEParams, then the premaster secret */ + args->encSz = MAX_ENCRYPT_SZ; + ret = wc_EcJpakeWriteRound2(ssl->ecJpake, ssl->rng, + args->encSecret, &args->encSz); + if (ret == 0) { + ret = wc_EcJpakeDeriveSecret(ssl->ecJpake, ssl->rng, + ssl->arrays->preMasterSecret, + &ssl->arrays->preMasterSz); + } + if (ret != 0) { + SendAlert(ssl, alert_fatal, handshake_failure); + } + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ + default: ret = BAD_KEA_TYPE_E; } /* switch(ssl->specs.kea) */ @@ -39060,6 +39164,11 @@ int SendClientKeyExchange(WOLFSSL* ssl) } #endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + break; + #endif /* WOLFSSL_DTLS_ECJPAKE */ + default: ret = BAD_KEA_TYPE_E; } /* switch(ssl->specs.kea) */ @@ -39147,6 +39256,11 @@ int SendClientKeyExchange(WOLFSSL* ssl) } #endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + break; + #endif /* WOLFSSL_DTLS_ECJPAKE */ + default: ret = BAD_KEA_TYPE_E; } /* switch(ssl->specs.kea) */ @@ -39172,7 +39286,8 @@ int SendClientKeyExchange(WOLFSSL* ssl) if (ssl->specs.kea == ecc_diffie_hellman_kea || ssl->specs.kea == dhe_psk_kea || - ssl->specs.kea == ecdhe_psk_kea) { /* always off */ + ssl->specs.kea == ecdhe_psk_kea || + IsEcJpakeKea(ssl)) { /* always off */ tlsSz = 0; } @@ -41368,6 +41483,15 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) break; } #endif /* HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448 */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + if (ssl->ecJpake == NULL) { + ERROR_OUT(MISSING_HANDSHAKE_DATA, exit_sske); + } + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: /* Skip ServerKeyExchange */ goto exit_sske; @@ -41653,6 +41777,29 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) break; } #endif /* !defined(NO_DH) && !defined(NO_RSA) */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + /* ServerECJPAKEParams */ + word32 sz = WC_ECJPAKE_ROUND2_SZ; + + args->idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + args->length = WC_ECJPAKE_ROUND2_SZ; + SetSendSzWithDtlsExtra(ssl, args); + CHECK_RET(ret, AllocArgsInput(ssl, args), exit_sske); + ret = wc_EcJpakeWriteRound2(ssl->ecJpake, ssl->rng, + args->output + args->idx, &sz); + if (ret != 0) { + SendAlert(ssl, alert_fatal, handshake_failure); + goto exit_sske; + } + args->length = sz; + args->idx += sz; + AddHeaders(args->output, args->length, + server_key_exchange, ssl); + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: break; } /* switch(ssl->specs.kea) */ @@ -42257,6 +42404,17 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif +#ifdef WOLFSSL_DTLS_ECJPAKE + if (first == ECC_BYTE && second == TLS_ECJPAKE_WITH_AES_128_CCM_8) { + if (!ssl->options.dtls || ssl->version.minor != DTLSv1_2_MINOR || + ssl->ecJpake == NULL || + TLSX_Find(extensions, TLSX_ECJPAKE_KKPP) == NULL) { + WOLFSSL_MSG("EC-JPAKE needs DTLS 1.2 and the peer's round one"); + return 0; + } + } +#endif + #if (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ defined(HAVE_CURVE448)) && defined(HAVE_SUPPORTED_CURVES) if (!TLSX_ValidateSupportedCurves(ssl, first, second, @@ -43712,6 +43870,14 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (ret == 0) ret = MatchSuite(ssl, ssl->clSuites); +#ifdef WOLFSSL_DTLS_ECJPAKE + /* draft-cragie-tls-ecjpake-01 Section 6 */ + if (ret == WC_NO_ERR_TRACE(MATCH_SUITE_ERROR) && + ssl->ecJpakePw != NULL) { + SendAlert(ssl, alert_fatal, handshake_failure); + } +#endif + #if defined(HAVE_TLS_EXTENSIONS) && defined(HAVE_SUPPORTED_CURVES) /* RFC 8422 Section 5.1.2: abort only when an ECC suite was actually * negotiated and the client's ec_point_formats omitted the uncompressed @@ -43721,7 +43887,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (ret == 0 && ssl->options.peerNoUncompPF && (ssl->specs.kea == ecc_diffie_hellman_kea || ssl->specs.kea == ecc_static_diffie_hellman_kea || - ssl->specs.kea == ecdhe_psk_kea)) { + ssl->specs.kea == ecdhe_psk_kea || + IsEcJpakeKea(ssl))) { WOLFSSL_MSG("Client ec_point_formats extension missing " "uncompressed format for negotiated ECC suite"); SendAlert(ssl, alert_fatal, illegal_parameter); @@ -46702,7 +46869,8 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], } #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) - if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher) && + if (ClientAuthRequired(ssl, ssl->options.usingPSK_cipher || + IsEcJpakeKea(ssl)) && !ssl->options.havePeerCert) { WOLFSSL_MSG("client didn't present peer cert"); ERROR_OUT(NO_PEER_CERT, exit_dcke); @@ -46777,6 +46945,15 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], break; } #endif /* (HAVE_ECC || CURVE25519 || CURVE448) && !NO_PSK */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + if (ssl->ecJpake == NULL) { + ERROR_OUT(MISSING_HANDSHAKE_DATA, exit_dcke); + } + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: WOLFSSL_MSG("Bad kea type"); ret = BAD_KEA_TYPE_E; @@ -47013,6 +47190,27 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], break; } #endif /* (HAVE_ECC || CURVE25519 || CURVE448) && !NO_PSK */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + { + /* ClientECJPAKEParams fill the message */ + ret = wc_EcJpakeReadRound2(ssl->ecJpake, + input + args->idx, + size - (args->idx - args->begin)); + if (ret == 0) { + ret = wc_EcJpakeDeriveSecret(ssl->ecJpake, + ssl->rng, ssl->arrays->preMasterSecret, + &ssl->arrays->preMasterSz); + } + if (ret != 0) { + WOLFSSL_MSG("EC-JPAKE round two failed"); + SendAlert(ssl, alert_fatal, handshake_failure); + break; + } + args->idx = args->begin + size; + break; + } + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: ret = BAD_KEA_TYPE_E; } /* switch (ssl->specs.kea) */ @@ -47235,6 +47433,10 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], break; } #endif /* (HAVE_ECC || CURVE25519 || CURVE448) && !NO_PSK */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + break; + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: ret = BAD_KEA_TYPE_E; } /* switch (ssl->specs.kea) */ @@ -47372,6 +47574,10 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], break; } #endif /* (HAVE_ECC || CURVE25519 || CURVE448) && !NO_PSK */ + #ifdef WOLFSSL_DTLS_ECJPAKE + case ecjpake_kea: + break; + #endif /* WOLFSSL_DTLS_ECJPAKE */ default: ret = BAD_KEA_TYPE_E; } /* switch (ssl->specs.kea) */ diff --git a/src/keys.c b/src/keys.c index 96f6920eafa..7ac1188b2e3 100644 --- a/src/keys.c +++ b/src/keys.c @@ -755,6 +755,24 @@ int GetCipherSpec(word16 side, byte cipherSuite0, byte cipherSuite, break; #endif +#ifdef BUILD_TLS_ECJPAKE_WITH_AES_128_CCM_8 + case TLS_ECJPAKE_WITH_AES_128_CCM_8 : + specs->bulk_cipher_algorithm = wolfssl_aes_ccm; + specs->cipher_type = aead; + specs->mac_algorithm = sha256_mac; + specs->kea = ecjpake_kea; + specs->sig_algo = anonymous_sa_algo; + specs->hash_size = WC_SHA256_DIGEST_SIZE; + specs->pad_size = PAD_SHA; + specs->static_ecdh = 0; + specs->key_size = AES_128_KEY_SIZE; + specs->block_size = WC_AES_BLOCK_SIZE; + specs->iv_size = AESCCM_IMP_IV_SZ; + specs->aead_mac_size = AES_CCM_8_AUTH_SZ; + + break; +#endif + #ifdef BUILD_TLS_ECDHE_ECDSA_WITH_NULL_SHA case TLS_ECDHE_ECDSA_WITH_NULL_SHA : specs->bulk_cipher_algorithm = wolfssl_cipher_null; diff --git a/src/ssl.c b/src/ssl.c index 1fe153e782d..863e2b73b6c 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -6007,6 +6007,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->ws_peer_ca_names = NULL; #endif + #ifdef WOLFSSL_DTLS_ECJPAKE + FreeEcJpake(ssl); + #endif #if defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) TLSX_FreeAll(ssl->extensions, ssl->heap); ssl->extensions = NULL; @@ -7034,6 +7037,9 @@ static WC_INLINE const char* wolfssl_kea_to_string(const CipherSpecs* specs) case ecdhe_psk_kea: keaStr = "ECDHEPSK"; break; + case ecjpake_kea: + keaStr = "ECJPAKE"; + break; case ecc_diffie_hellman_kea: /* Static ECDH is named after the signature of the CA, as in * OpenSSL 1.0.2. */ @@ -7066,6 +7072,9 @@ static WC_INLINE const char* wolfssl_sigalg_to_string( if ((specs->kea == psk_kea) || (specs->kea == dhe_psk_kea) || (specs->kea == ecdhe_psk_kea)) authStr = "PSK"; + /* The password authenticates the peer. */ + else if (specs->kea == ecjpake_kea) + authStr = "ECJPAKE"; else authStr = "None"; break; diff --git a/src/ssl_api_dtls.c b/src/ssl_api_dtls.c index 174aa97ff7b..b4f9befc3a7 100644 --- a/src/ssl_api_dtls.c +++ b/src/ssl_api_dtls.c @@ -55,6 +55,129 @@ int wolfSSL_set_dtls_fd_connected(WOLFSSL* ssl, int fd) } #endif +#if defined(WOLFSSL_DTLS) && defined(WOLFCRYPT_HAVE_ECJPAKE) +/* Set the password for EC-JPAKE (draft-cragie-tls-ecjpake-01) on a context + * and restrict it to TLS_ECJPAKE_WITH_AES_128_CCM_8. Objects created from the + * context get a copy of the password, and a server needs no certificate. + * Only supported with a DTLS 1.2 method; a client also offers only secp256r1 + * unless groups were already set. + * + * @param [in, out] ctx SSL/TLS context. + * @param [in] pw Password octets, for Thread the PSKd or PSKc. + * @param [in] pwSz Length of the password in octets. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx or pw is NULL or pwSz is zero. + * @return VERSION_ERROR when ctx is not using a DTLS 1.2 method. + * @return MEMORY_E when allocation fails. + * @return WOLFSSL_FAILURE when the cipher suite cannot be set. + * @return NOT_COMPILED_IN when the suite is not in this build. + */ +int wolfSSL_CTX_set_ecjpake_password(WOLFSSL_CTX* ctx, const unsigned char* pw, + unsigned int pwSz) +{ +#ifdef WOLFSSL_DTLS_ECJPAKE + WOLFSSL_ENTER("wolfSSL_CTX_set_ecjpake_password"); + + if (ctx == NULL || pw == NULL || pwSz == 0) { + return BAD_FUNC_ARG; + } + if (ctx->method->version.major != DTLS_MAJOR || ctx->method->downgrade || + ctx->method->version.minor != DTLSv1_2_MINOR) { + WOLFSSL_MSG("EC-JPAKE requires a DTLS 1.2 method"); + return VERSION_ERROR; + } + + if (wolfSSL_CTX_set_cipher_list(ctx, "ECJPAKE-AES128-CCM-8") != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } +#ifdef HAVE_SUPPORTED_CURVES + if (ctx->method->side == WOLFSSL_CLIENT_END && + TLSX_Find(ctx->extensions, TLSX_SUPPORTED_GROUPS) == NULL) { + int ret = wolfSSL_CTX_UseSupportedCurve(ctx, WOLFSSL_ECC_SECP256R1); + if (ret != WOLFSSL_SUCCESS) { + return ret; + } + } +#endif + + if (EcJpakeSetPassword(ctx->heap, &ctx->ecJpakePw, &ctx->ecJpakePwSz, pw, + pwSz) != 0) { + return MEMORY_E; + } + return WOLFSSL_SUCCESS; +#else + (void)ctx; + (void)pw; + (void)pwSz; + return NOT_COMPILED_IN; +#endif /* WOLFSSL_DTLS_ECJPAKE */ +} + +/* Set the password for EC-JPAKE on an object, replacing one inherited from + * its context, and restrict it to TLS_ECJPAKE_WITH_AES_128_CCM_8. A server + * without a certificate must set the password on its context instead, before + * creating the object. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] pw Password octets, for Thread the PSKd or PSKc. + * @param [in] pwSz Length of the password in octets. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl or pw is NULL or pwSz is zero. + * @return VERSION_ERROR when ssl is not using a DTLS 1.2 method. + * @return BAD_STATE_E when the handshake has started the exchange. + * @return MEMORY_E when allocation fails. + * @return WOLFSSL_FAILURE when the cipher suite cannot be set. + * @return NOT_COMPILED_IN when the suite is not in this build. + */ +int wolfSSL_set_ecjpake_password(WOLFSSL* ssl, const unsigned char* pw, + unsigned int pwSz) +{ +#ifdef WOLFSSL_DTLS_ECJPAKE + WOLFSSL_ENTER("wolfSSL_set_ecjpake_password"); + + if (ssl == NULL || pw == NULL || pwSz == 0) { + return BAD_FUNC_ARG; + } + if (!ssl->options.dtls || ssl->options.downgrade || + ssl->version.minor != DTLSv1_2_MINOR) { + WOLFSSL_MSG("EC-JPAKE requires a DTLS 1.2 method"); + return VERSION_ERROR; + } + if (ssl->ecJpake != NULL) { + WOLFSSL_MSG("EC-JPAKE exchange already started"); + return BAD_STATE_E; + } + + if (wolfSSL_set_cipher_list(ssl, "ECJPAKE-AES128-CCM-8") != + WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; + } +#ifdef HAVE_SUPPORTED_CURVES + if (ssl->options.side == WOLFSSL_CLIENT_END && + TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS) == NULL && + TLSX_Find(ssl->ctx->extensions, TLSX_SUPPORTED_GROUPS) == NULL) { + int ret = wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1); + if (ret != WOLFSSL_SUCCESS) { + return ret; + } + } +#endif + + if (EcJpakeSetPassword(ssl->heap, &ssl->ecJpakePw, &ssl->ecJpakePwSz, pw, + pwSz) != 0) { + return MEMORY_E; + } + return WOLFSSL_SUCCESS; +#else + (void)ssl; + (void)pw; + (void)pwSz; + return NOT_COMPILED_IN; +#endif /* WOLFSSL_DTLS_ECJPAKE */ +} +#endif /* WOLFSSL_DTLS && WOLFCRYPT_HAVE_ECJPAKE */ + /* Determine whether the object is configured for DTLS. * * @param [in] ssl SSL/TLS object. diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index 521bc38b8a5..f1fbcf1806b 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -778,6 +778,7 @@ static int wolfssl_accept_check_creds(WOLFSSL* ssl) word16 havePSK = 0; word16 haveAnon = 0; word16 haveMcast = 0; + word16 haveEcJpake = 0; #ifndef NO_PSK havePSK = ssl->options.havePSK; @@ -791,7 +792,11 @@ static int wolfssl_accept_check_creds(WOLFSSL* ssl) haveMcast = ssl->options.haveMcast; #endif - if ((!havePSK) && (!haveAnon) && (!haveMcast)) { + #ifdef WOLFSSL_DTLS_ECJPAKE + haveEcJpake = (ssl->ecJpakePw != NULL); + #endif + + if ((!havePSK) && (!haveAnon) && (!haveMcast) && (!haveEcJpake)) { #ifdef WOLFSSL_CERT_SETUP_CB if (ssl->ctx->certSetupCb != NULL) { WOLFSSL_MSG("CertSetupCb set. server cert and " @@ -1106,7 +1111,8 @@ int wolfSSL_accept(WOLFSSL* ssl) */ if ((!ssl->options.resuming) && (ssl->options.verifyPeer) && (!ssl->options.havePeerCert) && - (!ClientAuthRequired(ssl, ssl->options.usingPSK_cipher))) { + (!ClientAuthRequired(ssl, ssl->options.usingPSK_cipher || + IsEcJpakeKea(ssl)))) { ssl->options.peerAuthGood = 1; } #endif /* !NO_CERTS */ diff --git a/src/tls.c b/src/tls.c index 60980f51a53..d65400060b1 100644 --- a/src/tls.c +++ b/src/tls.c @@ -1590,7 +1590,8 @@ int wolfSSL_GetHmacType_ex(CipherSpecs* specs) /** Highest extension type that TLSX_ToSemaphore() maps directly onto its own * semaphore index. Higher types are either remapped into the remaining indices - * (renegotiation_info, QUIC, ECH, CKS) or fall outside the semaphore's range. + * (renegotiation_info, QUIC, ECH, CKS, EC-JPAKE) or fall outside the + * semaphore's range. * This boundary also drives duplicate-extension detection in TLSX_Parse(); keep * the two in sync. */ #define SEMAPHORE_MAX_DIRECT_TYPE 62 @@ -1634,6 +1635,10 @@ static WC_INLINE word16 TLSX_ToSemaphore(word16 type) #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DUAL_ALG_CERTS) case TLSX_CKS: return 66; +#endif +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLSX_ECJPAKE_KKPP: /* 0x0100 */ + return 67; #endif default: if (type > SEMAPHORE_MAX_DIRECT_TYPE) { @@ -6051,6 +6056,11 @@ int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, key |= ssl->ecdhCurveOID == oid; ephmSuite = 1; break; +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLS_ECJPAKE_WITH_AES_128_CCM_8: + key |= oid == ECC_SECP256R1_OID; + break; +#endif #ifdef WOLFSSL_STATIC_DH /* ECDH_ECDSA */ @@ -6154,6 +6164,13 @@ int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, if (!foundCurve) return 0; +#ifdef WOLFSSL_DTLS_ECJPAKE + if (first == ECC_BYTE && second == TLS_ECJPAKE_WITH_AES_128_CCM_8) { + *ecdhCurveOID = key ? ECC_SECP256R1_OID : 0; + return key; + } +#endif + *ecdhCurveOID = ssl->ecdhCurveOID; /* Choose the default if it is at the required strength. */ #ifdef HAVE_ECC @@ -13675,6 +13692,172 @@ static int TLSX_QuicTP_Parse(WOLFSSL *ssl, const byte *input, size_t len, int ex #define CID_FREE(a, b) 0 #endif /* defined(WOLFSSL_DTLS_CID) */ +#ifdef WOLFSSL_DTLS_ECJPAKE +/******************************************************************************/ +/* ecjpake_key_kp_pair extension (draft-cragie-tls-ecjpake-01 Section 7.2.2) */ +/******************************************************************************/ + +/* This side's encoded round one message (ECJPAKEKeyKPPairList). The optional + * identity field is elided, as for Thread. */ +typedef struct EcJpakeExt { + word16 sz; + byte data[WC_ECJPAKE_ROUND1_SZ]; +} EcJpakeExt; + +void FreeEcJpake(WOLFSSL* ssl) +{ + if (ssl->ecJpake != NULL) { + wc_EcJpakeFree(ssl->ecJpake); + XFREE(ssl->ecJpake, ssl->heap, DYNAMIC_TYPE_ECC); + ssl->ecJpake = NULL; + } +} + +/* Replaces *pw with a copy of in. */ +int EcJpakeSetPassword(void* heap, byte** pw, word32* pwSz, const byte* in, + word32 inSz) +{ + byte* copy = (byte*)XMALLOC(inSz, heap, DYNAMIC_TYPE_SECRET); + + if (copy == NULL) { + return MEMORY_E; + } + XMEMCPY(copy, in, inSz); + EcJpakeFreePassword(heap, pw, pwSz); + *pw = copy; + *pwSz = inSz; + return 0; +} + +void EcJpakeFreePassword(void* heap, byte** pw, word32* pwSz) +{ + if (*pw != NULL) { + ForceZero(*pw, *pwSz); + XFREE(*pw, heap, DYNAMIC_TYPE_SECRET); + *pw = NULL; + } + *pwSz = 0; + (void)heap; +} + +static void TLSX_EcJpake_Free(EcJpakeExt* ext, void* heap) +{ + XFREE(ext, heap, DYNAMIC_TYPE_TLSX); + (void)heap; +} + +static word16 TLSX_EcJpake_GetSize(const EcJpakeExt* ext) +{ + return ext->sz; +} + +static word16 TLSX_EcJpake_Write(const EcJpakeExt* ext, byte* output) +{ + XMEMCPY(output, ext->data, ext->sz); + return ext->sz; +} + +/* Starts a new exchange and stores this side's round one message in the + * extension. */ +static int TLSX_EcJpake_New(WOLFSSL* ssl, int role) +{ + int ret; + word32 sz = WC_ECJPAKE_ROUND1_SZ; + EcJpakeExt* ext; + + FreeEcJpake(ssl); + ssl->ecJpake = (EcJpake*)XMALLOC(sizeof(EcJpake), ssl->heap, + DYNAMIC_TYPE_ECC); + if (ssl->ecJpake == NULL) { + return MEMORY_E; + } + ret = wc_EcJpakeInit(ssl->ecJpake, role, ECC_SECP256R1, ssl->ecJpakePw, + ssl->ecJpakePwSz, ssl->heap); + if (ret != 0) { + XFREE(ssl->ecJpake, ssl->heap, DYNAMIC_TYPE_ECC); + ssl->ecJpake = NULL; + return ret; + } + + ext = (EcJpakeExt*)XMALLOC(sizeof(EcJpakeExt), ssl->heap, + DYNAMIC_TYPE_TLSX); + if (ext == NULL) { + ret = MEMORY_E; + } + if (ret == 0) { + ret = wc_EcJpakeWriteRound1(ssl->ecJpake, ssl->rng, ext->data, &sz); + } + if (ret == 0) { + ext->sz = (word16)sz; + ret = TLSX_Push(&ssl->extensions, TLSX_ECJPAKE_KKPP, ext, ssl->heap); + } + if (ret != 0) { + XFREE(ext, ssl->heap, DYNAMIC_TYPE_TLSX); + FreeEcJpake(ssl); + } + return ret; +} + +/* Adds the extension to a ClientHello. A ClientHello sent again after a + * HelloVerifyRequest carries the same round one message (Section 7.2.2). */ +int TLSX_EcJpake_Use(WOLFSSL* ssl) +{ + if (ssl->ecJpakePw == NULL) { + return 0; + } + if (ssl->ecJpake != NULL && + TLSX_Find(ssl->extensions, TLSX_ECJPAKE_KKPP) != NULL) { + return 0; + } + return TLSX_EcJpake_New(ssl, WC_ECJPAKE_CLIENT); +} + +static int TLSX_EcJpake_Parse(WOLFSSL* ssl, const byte* input, word16 length, + byte msgType) +{ + int ret; + + if (msgType == client_hello) { +#ifndef NO_WOLFSSL_SERVER + if (ssl->ecJpakePw == NULL) { + return 0; + } + ret = TLSX_EcJpake_New(ssl, WC_ECJPAKE_SERVER); + if (ret == 0) { + ret = wc_EcJpakeReadRound1(ssl->ecJpake, input, length); + } + if (ret == 0) { + TLSX_SetResponse(ssl, TLSX_ECJPAKE_KKPP); + } +#else + return EXT_NOT_ALLOWED; +#endif + } + else if (msgType == server_hello) { + if (ssl->ecJpake == NULL || + TLSX_Find(ssl->extensions, TLSX_ECJPAKE_KKPP) == NULL) { + return TLSX_HandleUnsupportedExtension(ssl); + } + ret = wc_EcJpakeReadRound1(ssl->ecJpake, input, length); + } + else { + return EXT_NOT_ALLOWED; + } + + if (ret != 0) { + WOLFSSL_MSG("EC-JPAKE round one failed"); + SendAlert(ssl, alert_fatal, handshake_failure); + FreeEcJpake(ssl); + } + return ret; +} + +#define ECJPAKE_FREE TLSX_EcJpake_Free +#define ECJPAKE_GET_SIZE TLSX_EcJpake_GetSize +#define ECJPAKE_WRITE TLSX_EcJpake_Write +#define ECJPAKE_PARSE TLSX_EcJpake_Parse +#endif /* WOLFSSL_DTLS_ECJPAKE */ + #if defined(HAVE_RPK) /******************************************************************************/ /* Client_Certificate_Type extension */ @@ -15712,6 +15895,11 @@ void TLSX_FreeAll(TLSX* list, void* heap) CID_FREE((byte*)extension->data, heap); break; #endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLSX_ECJPAKE_KKPP: + ECJPAKE_FREE((EcJpakeExt*)extension->data, heap); + break; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) case TLSX_ECH: WOLFSSL_MSG("ECH extension free"); @@ -15983,6 +16171,11 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, length += CID_GET_SIZE((byte*)extension->data); break; #endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLSX_ECJPAKE_KKPP: + length += ECJPAKE_GET_SIZE((EcJpakeExt*)extension->data); + break; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) case TLSX_ECH: length += ECH_GET_SIZE((WOLFSSL_ECH*)extension->data, msgType); @@ -16291,6 +16484,13 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, break; #endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLSX_ECJPAKE_KKPP: + WOLFSSL_MSG("EC-JPAKE extension to write"); + offset += ECJPAKE_WRITE((EcJpakeExt*)extension->data, + output + offset); + break; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) case TLSX_ECH: WOLFSSL_MSG("ECH extension to write"); @@ -16861,6 +17061,13 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) } #endif /* HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ +#ifdef WOLFSSL_DTLS_ECJPAKE + if (!isServer && ssl->options.dtls) { + ret = TLSX_EcJpake_Use(ssl); + if (ret != 0) + return ret; + } +#endif #ifdef WOLFSSL_SRTP if (ssl->options.dtls && ssl->dtlsSrtpProfiles != 0) { WOLFSSL_MSG("Adding DTLS SRTP extension"); @@ -17588,6 +17795,7 @@ static int TLSX_CustomExt_IsKnown(word16 ext_type) case TLSXT_SIGNATURE_ALGORITHMS_CERT: case TLSXT_KEY_SHARE: case TLSXT_CONNECTION_ID: + case TLSXT_ECJPAKE_KKPP: case TLSXT_KEY_QUIC_TP_PARAMS: case TLSXT_ECH: case TLSXT_ECH_OUTER_EXTENSIONS: @@ -18908,6 +19116,9 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #endif #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DUAL_ALG_CERTS) || (type == TLSX_CKS) + #endif + #ifdef WOLFSSL_DTLS_ECJPAKE + || (type == TLSX_ECJPAKE_KKPP) #endif ) { @@ -19578,6 +19789,12 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, break; #endif /* defined(WOLFSSL_DTLS_CID) */ +#ifdef WOLFSSL_DTLS_ECJPAKE + case TLSX_ECJPAKE_KKPP: + WOLFSSL_MSG("EC-JPAKE extension received"); + ret = ECJPAKE_PARSE(ssl, input + offset, size, msgType); + break; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #if defined(HAVE_RPK) case TLSX_CLIENT_CERTIFICATE_TYPE: WOLFSSL_MSG("Client Certificate Type extension received"); diff --git a/tests/api.c b/tests/api.c index 3874969255e..6d90dab8e66 100644 --- a/tests/api.c +++ b/tests/api.c @@ -261,6 +261,7 @@ #include #include #include +#include #include #include #include @@ -47301,6 +47302,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_certreq_sighash_algos), TEST_DECL(test_revoked_loaded_int_cert), TEST_DTLS_DECLS, + TEST_ECJPAKE_DECLS, TEST_DTLS13_DECLS, TEST_SSL_CERT_DECLS, TEST_SSL_CRL_OCSP_DECLS, diff --git a/tests/api/include.am b/tests/api/include.am index c67ed918039..dcc95d0f021 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -20,6 +20,7 @@ tests_unit_test_SOURCES += tests/api/test_hmac.c tests_unit_test_SOURCES += tests/api/test_cmac.c tests_unit_test_SOURCES += tests/api/test_siphash.c tests_unit_test_SOURCES += tests/api/test_srp.c +tests_unit_test_SOURCES += tests/api/test_ecjpake.c tests_unit_test_SOURCES += tests/api/test_eccsi.c tests_unit_test_SOURCES += tests/api/test_sakke.c tests_unit_test_SOURCES += tests/api/test_hpke.c @@ -168,6 +169,7 @@ EXTRA_DIST += tests/api/test_hmac.h EXTRA_DIST += tests/api/test_cmac.h EXTRA_DIST += tests/api/test_siphash.h EXTRA_DIST += tests/api/test_srp.h +EXTRA_DIST += tests/api/test_ecjpake.h EXTRA_DIST += tests/api/test_eccsi.h EXTRA_DIST += tests/api/test_sakke.h EXTRA_DIST += tests/api/test_hpke.h diff --git a/tests/api/test_ecjpake.c b/tests/api/test_ecjpake.c new file mode 100644 index 00000000000..c41f90ee142 --- /dev/null +++ b/tests/api/test_ecjpake.c @@ -0,0 +1,502 @@ +/* test_ecjpake.c + * + * Copyright (C) 2006-2026 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 3 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 + */ + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#include +#include +#include +#include +#include + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + +#define ECJPAKE_SUITE "ECJPAKE-AES128-CCM-8" + +static const unsigned char ecjpakePw[] = "J01NME"; +static const unsigned char ecjpakeOtherPw[] = "J01NMF"; + +/* ecjpake_key_kp_pair extension header: type 0x0100, length 330 */ +static const byte ecjpakeExtHdr[] = { 0x01, 0x00, 0x01, 0x4a }; +/* ServerECJPAKEParams: secp256r1 ECParameters, then ECPoint X */ +static const byte ecjpakeSkeHdr[] = { 0x03, 0x00, 0x17, 0x41, 0x04 }; +/* supported_groups with secp256r1 only */ +static const byte ecjpakeGroups[] = { + 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x17 +}; +/* ec_point_formats with uncompressed only */ +static const byte ecjpakePointFormats[] = { 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00 }; +/* ClientKeyExchange handshake header: type, 24-bit length 165 */ +static const byte ecjpakeCkeHdr[] = { client_key_exchange, 0x00, 0x00, 0xa5 }; + +/* Index of the last octet of the first proof's r in an ECJPAKEKeyKP. */ +#define ECJPAKE_KKP_R_LAST (WC_ECJPAKE_KKP_SZ - 1) + +static int ecjpake_find(const byte* buf, int len, const byte* pat, int patSz) +{ + int i; + + for (i = 0; i + patSz <= len; i++) { + if (XMEMCMP(buf + i, pat, (size_t)patSz) == 0) + return i; + } + return -1; +} + +/* DTLS 1.2 client, given a password when cPw is not NULL, and a + * certificate-less server with a password on its context. */ +static int ecjpake_setup(struct test_memio_ctx* test_ctx, WOLFSSL_CTX** ctx_c, + WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + const unsigned char* cPw, const unsigned char* sPw) +{ + EXPECT_DECLS; + + XMEMSET(test_ctx, 0, sizeof(*test_ctx)); + ExpectNotNull(*ctx_s = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + if (*ctx_s != NULL) { + wolfSSL_SetIORecv(*ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(*ctx_s, test_memio_write_cb); + } + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(*ctx_s, sPw, + (unsigned int)XSTRLEN((const char*)sPw)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + if (cPw != NULL) { + ExpectIntEQ(wolfSSL_set_ecjpake_password(*ssl_c, cPw, + (unsigned int)XSTRLEN((const char*)cPw)), + WOLFSSL_SUCCESS); + } + + return EXPECT_RESULT(); +} + +static void ecjpake_cleanup(WOLFSSL_CTX* ctx_c, WOLFSSL_CTX* ctx_s, + WOLFSSL* ssl_c, WOLFSSL* ssl_s) +{ + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +} + +/* Runs the handshake up to the server's first flight after the cookie + * exchange: ServerHello, ServerKeyExchange and ServerHelloDone are left in + * the client's buffer. */ +static int ecjpake_to_server_flight(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_c, WOLFSSL* ssl_s) +{ + EXPECT_DECLS; + + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(test_ctx->c_len, 0); + + return EXPECT_RESULT(); +} + +/* Has the client read the server's pending alert and checks that it was a + * fatal handshake_failure. */ +static int ecjpake_client_got_handshake_failure(WOLFSSL* ssl_c) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.level, alert_fatal); + ExpectIntEQ(h.last_rx.code, handshake_failure); + + return EXPECT_RESULT(); +} + +/* Checks that ssl received a fatal handshake_failure alert. */ +static int ecjpake_got_handshake_failure(WOLFSSL* ssl) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + ExpectIntEQ(wolfSSL_get_alert_history(ssl, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.level, alert_fatal); + ExpectIntEQ(h.last_rx.code, handshake_failure); + + return EXPECT_RESULT(); +} +#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES && WOLFSSL_DTLS_ECJPAKE */ + +/* Full DTLS 1.2 handshake with a cookie exchange, the ClientHello sent again + * carrying the same round one message, the hello extensions of Section 7.2.1, + * and application data both ways. */ +int test_wolfSSL_ecjpake_dtls12(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + byte ch1[TEST_MEMIO_BUF_SZ]; + int ch1Sz = 0; + int off1 = -1, off2 = -1; + char buf[16]; + + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakePw), TEST_SUCCESS); + + /* ClientHello, HelloVerifyRequest, ClientHello with cookie */ + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + if (EXPECT_SUCCESS()) { + ch1Sz = test_ctx.s_len; + XMEMCPY(ch1, test_ctx.s_buff, (size_t)ch1Sz); + } + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + + ExpectIntGE(off1 = ecjpake_find(ch1, ch1Sz, ecjpakeExtHdr, + sizeof(ecjpakeExtHdr)), 0); + ExpectIntGE(off2 = ecjpake_find(test_ctx.s_buff, test_ctx.s_len, + ecjpakeExtHdr, sizeof(ecjpakeExtHdr)), 0); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(XMEMCMP(ch1 + off1, test_ctx.s_buff + off2, + sizeof(ecjpakeExtHdr) + WC_ECJPAKE_ROUND1_SZ), 0); + } + ExpectIntGE(ecjpake_find(test_ctx.s_buff, test_ctx.s_len, ecjpakeGroups, + sizeof(ecjpakeGroups)), 0); + ExpectIntGE(ecjpake_find(test_ctx.s_buff, test_ctx.s_len, + ecjpakePointFormats, sizeof(ecjpakePointFormats)), 0); + + /* ServerHello */ + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntGE(ecjpake_find(test_ctx.c_buff, test_ctx.c_len, ecjpakeExtHdr, + sizeof(ecjpakeExtHdr)), 0); + ExpectIntGE(ecjpake_find(test_ctx.c_buff, test_ctx.c_len, + ecjpakePointFormats, sizeof(ecjpakePointFormats)), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c), ECJPAKE_SUITE); + ExpectStrEQ(wolfSSL_get_cipher_name(ssl_s), ECJPAKE_SUITE); + ExpectIntEQ(wolfSSL_write(ssl_c, "ping", 4), 4); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), 4); + ExpectIntEQ(XMEMCMP(buf, "ping", 4), 0); + ExpectIntEQ(wolfSSL_write(ssl_s, "pong", 4), 4); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), 4); + ExpectIntEQ(XMEMCMP(buf, "pong", 4), 0); + ExpectNull(ssl_c == NULL ? NULL : ssl_c->ecJpake); + ExpectNull(ssl_s == NULL ? NULL : ssl_s->ecJpake); + + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); +#endif + return EXPECT_RESULT(); +} + +/* Different passwords complete the key exchange with different secrets, so + * the Finished messages never verify and neither side finishes. */ +int test_wolfSSL_ecjpake_wrong_password(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakeOtherPw), TEST_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectFalse(wolfSSL_is_init_finished(ssl_c)); + ExpectFalse(wolfSSL_is_init_finished(ssl_s)); + + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); +#endif + return EXPECT_RESULT(); +} + +/* A client offering the suite without the extension or without secp256r1, + * or a server without a password, fails with handshake_failure (Section 6). */ +int test_wolfSSL_ecjpake_negotiation(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + + /* Suite offered without the extension. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + NULL, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, ECJPAKE_SUITE), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ecjpake_client_got_handshake_failure(ssl_c), + TEST_SUCCESS); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; + +#if defined(HAVE_SUPPORTED_CURVES) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + /* Client supported groups without secp256r1. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + NULL, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl_c, WOLFSSL_ECC_SECP384R1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl_c, ecjpakePw, + (unsigned int)XSTRLEN((const char*)ecjpakePw)), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ecjpake_client_got_handshake_failure(ssl_c), + TEST_SUCCESS); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; +#endif + + /* Certificate server without a password. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + 0); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl_c, ecjpakePw, + (unsigned int)XSTRLEN((const char*)ecjpakePw)), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#ifdef WOLFSSL_EXTRA_ALERTS + ExpectIntEQ(ecjpake_client_got_handshake_failure(ssl_c), + TEST_SUCCESS); +#endif + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; + +#ifndef WOLFSSL_NO_TLS12 + /* The suite is not selected over TLS even when both sides list it. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_ciphers = ECJPAKE_SUITE; + test_ctx.s_ciphers = ECJPAKE_SUITE; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#ifdef WOLFSSL_EXTRA_ALERTS + ExpectIntEQ(ecjpake_client_got_handshake_failure(ssl_c), + TEST_SUCCESS); +#endif + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); +#endif +#endif + return EXPECT_RESULT(); +} + +/* A corrupted proof in any of the three EC-JPAKE messages ends the handshake + * with handshake_failure from the side that checked it. */ +int test_wolfSSL_ecjpake_bad_proof(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + int off = -1; + + /* Server round one in ServerHello. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(ecjpake_to_server_flight(&test_ctx, ssl_c, ssl_s), + TEST_SUCCESS); + ExpectIntGE(off = ecjpake_find(test_ctx.c_buff, test_ctx.c_len, + ecjpakeExtHdr, sizeof(ecjpakeExtHdr)), 0); + if (EXPECT_SUCCESS()) + test_ctx.c_buff[off + sizeof(ecjpakeExtHdr) + ECJPAKE_KKP_R_LAST] ^= 1; + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(ecjpake_got_handshake_failure(ssl_s), TEST_SUCCESS); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; + + /* Server round two in ServerKeyExchange. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(ecjpake_to_server_flight(&test_ctx, ssl_c, ssl_s), + TEST_SUCCESS); + ExpectIntGE(off = ecjpake_find(test_ctx.c_buff, test_ctx.c_len, + ecjpakeSkeHdr, sizeof(ecjpakeSkeHdr)), 0); + if (EXPECT_SUCCESS()) { + test_ctx.c_buff[off + WC_ECJPAKE_ECPARAMS_SZ + + ECJPAKE_KKP_R_LAST] ^= 1; + } + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(ecjpake_got_handshake_failure(ssl_s), TEST_SUCCESS); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; + + /* Client round two in ClientKeyExchange. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(ecjpake_to_server_flight(&test_ctx, ssl_c, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntGE(off = ecjpake_find(test_ctx.s_buff, test_ctx.s_len, + ecjpakeCkeHdr, sizeof(ecjpakeCkeHdr)), 0); + if (EXPECT_SUCCESS()) { + test_ctx.s_buff[off + DTLS_HANDSHAKE_HEADER_SZ + + ECJPAKE_KKP_R_LAST] ^= 1; + } + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(ecjpake_got_handshake_failure(ssl_c), TEST_SUCCESS); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); +#endif + return EXPECT_RESULT(); +} + +/* Arguments, the DTLS 1.2 method requirement, and objects inheriting the + * context's password. */ +int test_wolfSSL_ecjpake_set_password(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx = NULL, *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl = NULL, *ssl_c = NULL, *ssl_s = NULL; + + ExpectIntEQ(wolfSSL_set_ecjpake_password(NULL, ecjpakePw, 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(NULL, ecjpakePw, 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(ctx, NULL, 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(ctx, ecjpakePw, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* A server with no certificate is created from a password context. */ + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(ctx, ecjpakePw, 1), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectNotNull(ssl == NULL ? NULL : ssl->ecJpakePw); + ExpectIntEQ(ssl == NULL ? 0 : (int)ssl->ecJpakePwSz, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, NULL, 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, ecjpakePw, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, ecjpakePw, 1), + WOLFSSL_SUCCESS); + /* Setting it again replaces the password. */ + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, ecjpakeOtherPw, 2), + WOLFSSL_SUCCESS); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* The password cannot change once the ClientHello carries round one. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakePw, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl_c, ecjpakeOtherPw, + (unsigned int)XSTRLEN((const char*)ecjpakeOtherPw)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + ctx_c = NULL; ctx_s = NULL; ssl_c = NULL; ssl_s = NULL; + + /* Clearing an unfinished handshake allows a new password. */ + ExpectIntEQ(ecjpake_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + ecjpakeOtherPw, ecjpakePw), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + ExpectNull(ssl_c == NULL ? NULL : ssl_c->ecJpake); + test_memio_clear_buffer(&test_ctx, 0); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl_c, ecjpakePw, + (unsigned int)XSTRLEN((const char*)ecjpakePw)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ecjpake_cleanup(ctx_c, ctx_s, ssl_c, ssl_s); + + /* A version-flexible DTLS method could negotiate DTLS 1.3. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(ctx, ecjpakePw, 1), + WC_NO_ERR_TRACE(VERSION_ERROR)); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, ecjpakePw, 1), + WC_NO_ERR_TRACE(VERSION_ERROR)); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#ifndef WOLFSSL_NO_TLS12 + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_ecjpake_password(ctx, ecjpakePw, 1), + WC_NO_ERR_TRACE(VERSION_ERROR)); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_ecjpake_password(ssl, ecjpakePw, 1), + WC_NO_ERR_TRACE(VERSION_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ecjpake.h b/tests/api/test_ecjpake.h new file mode 100644 index 00000000000..e0eb8ed9b38 --- /dev/null +++ b/tests/api/test_ecjpake.h @@ -0,0 +1,40 @@ +/* test_ecjpake.h + * + * Copyright (C) 2006-2026 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 3 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 + */ + +#ifndef WOLFSSL_TEST_ECJPAKE_H +#define WOLFSSL_TEST_ECJPAKE_H + +#include + +int test_wolfSSL_ecjpake_dtls12(void); +int test_wolfSSL_ecjpake_wrong_password(void); +int test_wolfSSL_ecjpake_negotiation(void); +int test_wolfSSL_ecjpake_bad_proof(void); +int test_wolfSSL_ecjpake_set_password(void); + +#define TEST_ECJPAKE_DECLS \ + TEST_DECL_GROUP("ecjpake", test_wolfSSL_ecjpake_dtls12), \ + TEST_DECL_GROUP("ecjpake", test_wolfSSL_ecjpake_wrong_password), \ + TEST_DECL_GROUP("ecjpake", test_wolfSSL_ecjpake_negotiation), \ + TEST_DECL_GROUP("ecjpake", test_wolfSSL_ecjpake_bad_proof), \ + TEST_DECL_GROUP("ecjpake", test_wolfSSL_ecjpake_set_password) + +#endif /* WOLFSSL_TEST_ECJPAKE_H */ diff --git a/tests/api/test_ossl_sk.c b/tests/api/test_ossl_sk.c index db7ad9ec66e..f05332834b5 100644 --- a/tests/api/test_ossl_sk.c +++ b/tests/api/test_ossl_sk.c @@ -1120,6 +1120,10 @@ static const test_cipher_desc test_cipher_descs[] = { { ECC_BYTE, TLS_ECDHE_ECDSA_WITH_AES_128_CCM, "ECDHE-ECDSA-AES128-CCM", " Kx=ECDH Au=ECDSA Enc=AESCCM(128) Mac=AEAD", 128, 128 }, #endif +#ifdef BUILD_TLS_ECJPAKE_WITH_AES_128_CCM_8 + { ECC_BYTE, TLS_ECJPAKE_WITH_AES_128_CCM_8, "ECJPAKE-AES128-CCM-8", + " Kx=ECJPAKE Au=ECJPAKE Enc=AESCCM8(128) Mac=AEAD", 128, 128 }, +#endif #ifdef BUILD_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA { CIPHER_BYTE, TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA, "DHE-RSA-CAMELLIA128-SHA", diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index f092088bd10..1701548912f 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -6756,7 +6756,7 @@ static int record_size_skip_cipher(const char *name) * and not ECDHE-* because of the trailing '-'. RENEGOTIATION-INFO is the * TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling value, not a real cipher. */ static const char* const deny[] = { - "PSK", "SRP", "ANON", "NULL", "ECDSA", "ECDH-", "SM", + "PSK", "SRP", "ANON", "NULL", "ECDSA", "ECDH-", "SM", "ECJPAKE", "RENEGOTIATION-INFO" }; size_t i; diff --git a/wolfcrypt/src/ecjpake.c b/wolfcrypt/src/ecjpake.c new file mode 100644 index 00000000000..e8191728ab0 --- /dev/null +++ b/wolfcrypt/src/ecjpake.c @@ -0,0 +1,890 @@ +/* ecjpake.c + * + * Copyright (C) 2006-2026 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 3 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 + */ + +#include + +#ifdef WOLFCRYPT_HAVE_ECJPAKE + +#include +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#define ECJPAKE_FLAG_KEYS 0x01 +#define ECJPAKE_FLAG_ROUND1_WRITTEN 0x02 +#define ECJPAKE_FLAG_ROUND1_READ 0x04 +#define ECJPAKE_FLAG_ROUND2_READ 0x08 + +#define ECJPAKE_ID_SZ 6 + +static const byte ecjpakeIdClient[ECJPAKE_ID_SZ] = { + 'c', 'l', 'i', 'e', 'n', 't' +}; +static const byte ecjpakeIdServer[ECJPAKE_ID_SZ] = { + 's', 'e', 'r', 'v', 'e', 'r' +}; + +/* ECParameters: curve_type named_curve(3), NamedCurve secp256r1(23) */ +static const byte ecjpakeEcParams[WC_ECJPAKE_ECPARAMS_SZ] = { + 0x03, 0x00, 0x17 +}; + +static const byte* ecjpake_id(int role) +{ + return (role == WC_ECJPAKE_CLIENT) ? ecjpakeIdClient : ecjpakeIdServer; +} + +static void ecjpake_free_points(EcJpake* jp) +{ + wc_ecc_del_point_h(jp->Xm1, jp->heap); + wc_ecc_del_point_h(jp->Xm2, jp->heap); + wc_ecc_del_point_h(jp->Xp1, jp->heap); + wc_ecc_del_point_h(jp->Xp2, jp->heap); + wc_ecc_del_point_h(jp->Xp, jp->heap); + jp->Xm1 = jp->Xm2 = jp->Xp1 = jp->Xp2 = jp->Xp = NULL; +} + +/* Loads the curve base point G into an allocated point. */ +static int ecjpake_load_base(EcJpake* jp, ecc_point* G) +{ + int err = MP_OKAY; + const ecc_set_type* dp = wc_ecc_get_curve_params(jp->curveIdx); + + if (dp == NULL) { + err = ECC_CURVE_OID_E; + } + if (err == MP_OKAY) { + err = mp_read_radix(G->x, dp->Gx, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_read_radix(G->y, dp->Gy, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_set(G->z, 1); + } + return err; +} + +/* Writes the SEC1 uncompressed encoding of an affine point. */ +static int ecjpake_point_encode(ecc_point* p, byte* out) +{ + int err; + + out[0] = ECC_POINT_UNCOMP; + err = mp_to_unsigned_bin_len(p->x, out + 1, WC_ECJPAKE_FIELD_SZ); + if (err == MP_OKAY) { + err = mp_to_unsigned_bin_len(p->y, out + 1 + WC_ECJPAKE_FIELD_SZ, + WC_ECJPAKE_FIELD_SZ); + } + return err; +} + +/* Writes a TLS ECPoint: opaque point <1..2^8-1>. */ +static int ecjpake_write_point(ecc_point* p, byte* out, word32* idx) +{ + int err; + + out[*idx] = WC_ECJPAKE_POINT_SZ; + err = ecjpake_point_encode(p, out + *idx + 1); + if (err == MP_OKAY) { + *idx += 1 + WC_ECJPAKE_POINT_SZ; + } + return err; +} + +/* Reads a TLS ECPoint and validates it as a point on the curve. P-256 has a + * cofactor of one and no affine encoding of infinity, so this is full public + * key validation. */ +static int ecjpake_read_point(EcJpake* jp, const byte* in, word32 inSz, + word32* idx, ecc_point* p) +{ + int err; + const byte* pt; + + if (inSz - *idx < 1 + WC_ECJPAKE_POINT_SZ) { + return BUFFER_E; + } + pt = in + *idx + 1; + if (in[*idx] != WC_ECJPAKE_POINT_SZ || pt[0] != ECC_POINT_UNCOMP) { + return ECC_BAD_ARG_E; + } + + err = mp_read_unsigned_bin(p->x, pt + 1, WC_ECJPAKE_FIELD_SZ); + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(p->y, pt + 1 + WC_ECJPAKE_FIELD_SZ, + WC_ECJPAKE_FIELD_SZ); + } + if (err == MP_OKAY) { + err = mp_set(p->z, 1); + } + if (err == MP_OKAY) { + err = wc_ecc_point_is_on_curve(p, jp->curveIdx); + } + if (err == MP_OKAY) { + *idx += 1 + WC_ECJPAKE_POINT_SZ; + } + return err; +} + +/* R = k * P, affine, mapped with a constant time inversion. */ +static int ecjpake_mul(EcJpake* jp, mp_int* k, ecc_point* P, ecc_point* R, + WC_RNG* rng) +{ + int err; + + err = wc_ecc_mulmod_ex2(k, P, R, &jp->a, &jp->prime, &jp->order, rng, 0, + jp->heap); + if (err == MP_OKAY) { + err = ecc_map_ex(R, &jp->prime, jp->mp, 1); + } + return err; +} + +static int ecjpake_to_mont(EcJpake* jp, mp_int* mu, ecc_point* p, + ecc_point* r) +{ + int err; + + err = mp_mulmod(p->x, mu, &jp->prime, r->x); + if (err == MP_OKAY) { + err = mp_mulmod(p->y, mu, &jp->prime, r->y); + } + if (err == MP_OKAY) { + err = mp_copy(mu, r->z); + } + return err; +} + +/* R = A + B for affine A and B, affine. ECC_INF_E when the sum is the point + * at infinity. */ +static int ecjpake_add(EcJpake* jp, ecc_point* A, ecc_point* B, ecc_point* R) +{ + int err = MP_OKAY; + int inf = 0; + int muInit = 0; + ecc_point* tA; + ecc_point* tB; + WC_DECLARE_VAR(mu, mp_int, 1, jp->heap); + + WC_ALLOC_VAR_EX(mu, mp_int, 1, jp->heap, DYNAMIC_TYPE_ECC, + err = MEMORY_E); + tA = wc_ecc_new_point_h(jp->heap); + tB = wc_ecc_new_point_h(jp->heap); + if (tA == NULL || tB == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + err = mp_init(mu); + muInit = (err == MP_OKAY); + } + /* Montgomery normalizer R mod p */ + if (err == MP_OKAY) { + err = mp_montgomery_calc_normalization(mu, &jp->prime); + } + if (err == MP_OKAY) { + err = ecjpake_to_mont(jp, mu, A, tA); + } + if (err == MP_OKAY) { + err = ecjpake_to_mont(jp, mu, B, tB); + } + if (err == MP_OKAY) { + err = ecc_projective_add_point_safe(tA, tB, R, &jp->a, &jp->prime, + jp->mp, &inf); + } + if (err == MP_OKAY && (inf || wc_ecc_point_is_at_infinity(R))) { + err = ECC_INF_E; + } + if (err == MP_OKAY) { + err = ecc_map_ex(R, &jp->prime, jp->mp, 1); + } + + if (muInit) { + mp_free(mu); + } + WC_FREE_VAR_EX(mu, jp->heap, DYNAMIC_TYPE_ECC); + wc_ecc_del_point_h(tB, jp->heap); + wc_ecc_del_point_h(tA, jp->heap); + return err; +} + +/* R = A + B + C */ +static int ecjpake_add3(EcJpake* jp, ecc_point* A, ecc_point* B, + ecc_point* C, ecc_point* R) +{ + int err = ecjpake_add(jp, A, B, R); + if (err == MP_OKAY) { + err = ecjpake_add(jp, R, C, R); + } + return err; +} + +/* h = int(SHA-256(lenG || G || lenV || V || lenX || X || lenID || ID)) mod n + * (draft-cragie-tls-ecjpake-01 Section 8.2, RFC 8235 Section 3.3). */ +static int ecjpake_zkp_hash(EcJpake* jp, ecc_point* gen, ecc_point* V, + ecc_point* X, const byte* id, mp_int* h) +{ + int err; + int i; + wc_Sha256 sha; + byte len[4]; + byte pt[WC_ECJPAKE_POINT_SZ]; + byte digest[WC_SHA256_DIGEST_SIZE]; + ecc_point* pts[3]; + + pts[0] = gen; + pts[1] = V; + pts[2] = X; + + err = wc_InitSha256_ex(&sha, jp->heap, INVALID_DEVID); + if (err != 0) { + return err; + } + + c32toa(WC_ECJPAKE_POINT_SZ, len); + for (i = 0; i < 3 && err == 0; i++) { + err = ecjpake_point_encode(pts[i], pt); + if (err == 0) { + err = wc_Sha256Update(&sha, len, sizeof(len)); + } + if (err == 0) { + err = wc_Sha256Update(&sha, pt, sizeof(pt)); + } + } + if (err == 0) { + c32toa(ECJPAKE_ID_SZ, len); + err = wc_Sha256Update(&sha, len, sizeof(len)); + } + if (err == 0) { + err = wc_Sha256Update(&sha, id, ECJPAKE_ID_SZ); + } + if (err == 0) { + err = wc_Sha256Final(&sha, digest); + } + wc_Sha256Free(&sha); + + if (err == 0) { + err = mp_read_unsigned_bin(h, digest, sizeof(digest)); + } + if (err == 0) { + err = mp_mod(h, &jp->order, h); + } + return err; +} + +/* Writes ECSchnorrZKP proving knowledge of x where X = gen * x. */ +static int ecjpake_zkp_write(EcJpake* jp, ecc_point* gen, mp_int* x, + ecc_point* X, WC_RNG* rng, byte* out, + word32* idx) +{ + int err = MP_OKAY; + int scInit = 0; + ecc_point* V; + mp_int* v = NULL; + mp_int* h = NULL; + WC_DECLARE_VAR(sc, mp_int, 2, jp->heap); + + WC_ALLOC_VAR_EX(sc, mp_int, 2, jp->heap, DYNAMIC_TYPE_ECC, + err = MEMORY_E); + V = wc_ecc_new_point_h(jp->heap); + if (V == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + v = &sc[0]; + h = &sc[1]; + err = mp_init_multi(v, h, NULL, NULL, NULL, NULL); + scInit = (err == MP_OKAY); + } + if (err == MP_OKAY) { + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, v, &jp->order); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, v, gen, V, rng); + } + if (err == MP_OKAY) { + err = ecjpake_zkp_hash(jp, gen, V, X, ecjpake_id(jp->role), h); + } + /* r = v - x*h mod n */ + if (err == MP_OKAY) { + err = mp_mulmod(x, h, &jp->order, h); + } + if (err == MP_OKAY) { + err = mp_submod_ct(v, h, &jp->order, v); + } + if (err == MP_OKAY) { + err = ecjpake_write_point(V, out, idx); + } + if (err == MP_OKAY) { + out[*idx] = WC_ECJPAKE_FIELD_SZ; + err = mp_to_unsigned_bin_len(v, out + *idx + 1, WC_ECJPAKE_FIELD_SZ); + } + if (err == MP_OKAY) { + *idx += 1 + WC_ECJPAKE_FIELD_SZ; + } + + if (scInit) { + mp_forcezero(v); + mp_forcezero(h); + } + WC_FREE_VAR_EX(sc, jp->heap, DYNAMIC_TYPE_ECC); + wc_ecc_del_point_h(V, jp->heap); + return err; +} + +/* Reads ECSchnorrZKP and verifies V == X*h + gen*r. */ +static int ecjpake_zkp_read(EcJpake* jp, ecc_point* gen, ecc_point* X, + const byte* in, word32 inSz, word32* idx) +{ + int err = MP_OKAY; + int scInit = 0; + word32 rSz = 0; + ecc_point* V; + ecc_point* Xh; + ecc_point* Gr; + mp_int* r = NULL; + mp_int* h = NULL; + WC_DECLARE_VAR(sc, mp_int, 2, jp->heap); + + WC_ALLOC_VAR_EX(sc, mp_int, 2, jp->heap, DYNAMIC_TYPE_ECC, + err = MEMORY_E); + V = wc_ecc_new_point_h(jp->heap); + Xh = wc_ecc_new_point_h(jp->heap); + Gr = wc_ecc_new_point_h(jp->heap); + if (V == NULL || Xh == NULL || Gr == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + r = &sc[0]; + h = &sc[1]; + err = mp_init_multi(r, h, NULL, NULL, NULL, NULL); + scInit = (err == MP_OKAY); + } + if (err == MP_OKAY) { + err = ecjpake_read_point(jp, in, inSz, idx, V); + } + + /* opaque r<1..2^8-1>, leading zero octets permitted */ + if (err == MP_OKAY) { + if (inSz - *idx < 1) { + err = BUFFER_E; + } + else { + rSz = in[(*idx)++]; + if (rSz == 0 || inSz - *idx < rSz) { + err = BUFFER_E; + } + } + } + if (err == MP_OKAY) { + while (rSz > WC_ECJPAKE_FIELD_SZ && in[*idx] == 0) { + (*idx)++; + rSz--; + } + if (rSz > WC_ECJPAKE_FIELD_SZ) { + err = ECC_OUT_OF_RANGE_E; + } + } + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(r, in + *idx, rSz); + *idx += rSz; + } + if (err == MP_OKAY && (mp_cmp(r, &jp->order) != MP_LT || mp_iszero(r))) { + err = SIG_VERIFY_E; + } + + if (err == MP_OKAY) { + err = ecjpake_zkp_hash(jp, gen, V, X, ecjpake_id(!jp->role), h); + } + if (err == MP_OKAY && mp_iszero(h)) { + err = SIG_VERIFY_E; + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, h, X, Xh, NULL); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, r, gen, Gr, NULL); + } + if (err == MP_OKAY) { + err = ecjpake_add(jp, Xh, Gr, Xh); + if (err == WC_NO_ERR_TRACE(ECC_INF_E)) { + err = SIG_VERIFY_E; + } + } + if (err == MP_OKAY && wc_ecc_cmp_point(Xh, V) != MP_EQ) { + err = SIG_VERIFY_E; + } + + if (scInit) { + mp_free(r); + mp_free(h); + } + WC_FREE_VAR_EX(sc, jp->heap, DYNAMIC_TYPE_ECC); + wc_ecc_del_point_h(Gr, jp->heap); + wc_ecc_del_point_h(Xh, jp->heap); + wc_ecc_del_point_h(V, jp->heap); + return err; +} + +/* Writes ECJPAKEKeyKP for the key pair (x, X = gen * x). */ +static int ecjpake_kkp_write(EcJpake* jp, ecc_point* gen, mp_int* x, + ecc_point* X, WC_RNG* rng, byte* out, + word32* idx) +{ + int err = ecjpake_write_point(X, out, idx); + if (err == MP_OKAY) { + err = ecjpake_zkp_write(jp, gen, x, X, rng, out, idx); + } + return err; +} + +/* Reads ECJPAKEKeyKP into X, verifying its proof against gen. */ +static int ecjpake_kkp_read(EcJpake* jp, ecc_point* gen, ecc_point* X, + const byte* in, word32 inSz, word32* idx) +{ + int err = ecjpake_read_point(jp, in, inSz, idx, X); + if (err == MP_OKAY) { + err = ecjpake_zkp_read(jp, gen, X, in, inSz, idx); + } + return err; +} + +int wc_EcJpakeInit(EcJpake* jp, int role, int curveId, const byte* secret, + word32 secretSz, void* heap) +{ + int err; + const ecc_set_type* dp = NULL; + + if (jp == NULL || secret == NULL || secretSz == 0 || + (role != WC_ECJPAKE_CLIENT && role != WC_ECJPAKE_SERVER)) { + return BAD_FUNC_ARG; + } + if (curveId != ECC_SECP256R1) { + return ECC_CURVE_OID_E; + } + + XMEMSET(jp, 0, sizeof(*jp)); + jp->role = role; + jp->heap = heap; + jp->curveIdx = wc_ecc_get_curve_idx(curveId); + + err = mp_init_multi(&jp->prime, &jp->a, &jp->order, &jp->s, &jp->xm2, + NULL); + if (err != MP_OKAY) { + return err; + } + + jp->Xm1 = wc_ecc_new_point_h(heap); + jp->Xm2 = wc_ecc_new_point_h(heap); + jp->Xp1 = wc_ecc_new_point_h(heap); + jp->Xp2 = wc_ecc_new_point_h(heap); + jp->Xp = wc_ecc_new_point_h(heap); + if (jp->Xm1 == NULL || jp->Xm2 == NULL || jp->Xp1 == NULL || + jp->Xp2 == NULL || jp->Xp == NULL) { + err = MEMORY_E; + } + + if (err == MP_OKAY) { + dp = wc_ecc_get_curve_params(jp->curveIdx); + if (dp == NULL) { + err = ECC_CURVE_OID_E; + } + } + if (err == MP_OKAY) { + err = mp_read_radix(&jp->prime, dp->prime, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_read_radix(&jp->a, dp->Af, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_read_radix(&jp->order, dp->order, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_montgomery_setup(&jp->prime, &jp->mp); + } + + /* s = int(password) mod n, which RFC 8236 requires to be non-zero */ + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(&jp->s, secret, secretSz); + } + if (err == MP_OKAY) { + err = mp_mod(&jp->s, &jp->order, &jp->s); + } + if (err == MP_OKAY && mp_iszero(&jp->s)) { + err = ECC_OUT_OF_RANGE_E; + } + + if (err != MP_OKAY) { + wc_EcJpakeFree(jp); + } + return err; +} + +void wc_EcJpakeFree(EcJpake* jp) +{ + if (jp == NULL) { + return; + } + + mp_forcezero(&jp->s); + mp_forcezero(&jp->xm2); + mp_free(&jp->prime); + mp_free(&jp->a); + mp_free(&jp->order); + ecjpake_free_points(jp); + jp->flags = 0; +} + +int wc_EcJpakeWriteRound1(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) +{ + int err = MP_OKAY; + int xInit = 0; + word32 idx = 0; + ecc_point* G = NULL; + WC_DECLARE_VAR(xm1, mp_int, 1, NULL); + + if (jp == NULL || rng == NULL || out == NULL || outSz == NULL) { + return BAD_FUNC_ARG; + } + if (jp->flags & ECJPAKE_FLAG_KEYS) { + return BAD_STATE_E; + } + if (*outSz < WC_ECJPAKE_ROUND1_SZ) { + return BUFFER_E; + } + + WC_ALLOC_VAR_EX(xm1, mp_int, 1, jp->heap, DYNAMIC_TYPE_ECC, + err = MEMORY_E); + G = wc_ecc_new_point_h(jp->heap); + if (G == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + err = mp_init(xm1); + xInit = (err == MP_OKAY); + } + if (err == MP_OKAY) { + err = ecjpake_load_base(jp, G); + } + if (err == MP_OKAY) { + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, xm1, &jp->order); + } + if (err == MP_OKAY) { + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->xm2, &jp->order); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, xm1, G, jp->Xm1, rng); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->xm2, G, jp->Xm2, rng); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_write(jp, G, xm1, jp->Xm1, rng, out, &idx); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_write(jp, G, &jp->xm2, jp->Xm2, rng, out, &idx); + } + /* s = x2 * s mod n */ + if (err == MP_OKAY) { + err = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->s); + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_WRITTEN; + *outSz = idx; + } + else { + mp_forcezero(&jp->xm2); + } + if (xInit) { + mp_forcezero(xm1); + } + WC_FREE_VAR_EX(xm1, jp->heap, DYNAMIC_TYPE_ECC); + wc_ecc_del_point_h(G, jp->heap); + return err; +} + +int wc_EcJpakeReadRound1(EcJpake* jp, const byte* in, word32 inSz) +{ + int err = MP_OKAY; + word32 idx = 0; + ecc_point* G; + + if (jp == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + if (jp->flags & ECJPAKE_FLAG_ROUND1_READ) { + return BAD_STATE_E; + } + + G = wc_ecc_new_point_h(jp->heap); + if (G == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + err = ecjpake_load_base(jp, G); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_read(jp, G, jp->Xp1, in, inSz, &idx); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_read(jp, G, jp->Xp2, in, inSz, &idx); + } + if (err == MP_OKAY && idx != inSz) { + err = BUFFER_E; + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_ROUND1_READ; + } + wc_ecc_del_point_h(G, jp->heap); + return err; +} + +int wc_EcJpakeWriteRound2(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) +{ + int err = MP_OKAY; + word32 idx = 0; + word32 need; + ecc_point* gen; + ecc_point* Xm; + + if (jp == NULL || rng == NULL || out == NULL || outSz == NULL) { + return BAD_FUNC_ARG; + } + if ((jp->flags & (ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_READ)) != + (ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_READ)) { + return BAD_STATE_E; + } + need = WC_ECJPAKE_KKP_SZ; + if (jp->role == WC_ECJPAKE_SERVER) { + need += WC_ECJPAKE_ECPARAMS_SZ; + } + if (*outSz < need) { + return BUFFER_E; + } + + gen = wc_ecc_new_point_h(jp->heap); + Xm = wc_ecc_new_point_h(jp->heap); + if (gen == NULL || Xm == NULL) { + err = MEMORY_E; + } + + /* Client: GA = X1 + X3 + X4, xc = x2*s, Xc = GA*xc + * Server: GB = X3 + X1 + X2, xs = x4*s, Xs = GB*xs */ + if (err == MP_OKAY) { + err = ecjpake_add3(jp, jp->Xm1, jp->Xp1, jp->Xp2, gen); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->s, gen, Xm, rng); + } + + if (err == MP_OKAY && jp->role == WC_ECJPAKE_SERVER) { + XMEMCPY(out, ecjpakeEcParams, WC_ECJPAKE_ECPARAMS_SZ); + idx += WC_ECJPAKE_ECPARAMS_SZ; + } + if (err == MP_OKAY) { + err = ecjpake_kkp_write(jp, gen, &jp->s, Xm, rng, out, &idx); + } + if (err == MP_OKAY) { + *outSz = idx; + } + + wc_ecc_del_point_h(Xm, jp->heap); + wc_ecc_del_point_h(gen, jp->heap); + return err; +} + +int wc_EcJpakeReadRound2(EcJpake* jp, const byte* in, word32 inSz) +{ + int err = MP_OKAY; + word32 idx = 0; + ecc_point* gen; + + if (jp == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + if ((jp->flags & (ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_READ | + ECJPAKE_FLAG_ROUND2_READ)) != + (ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_READ)) { + return BAD_STATE_E; + } + + if (jp->role == WC_ECJPAKE_CLIENT) { + if (inSz < WC_ECJPAKE_ECPARAMS_SZ) { + return BUFFER_E; + } + if (XMEMCMP(in, ecjpakeEcParams, WC_ECJPAKE_ECPARAMS_SZ) != 0) { + return ECC_CURVE_OID_E; + } + idx += WC_ECJPAKE_ECPARAMS_SZ; + } + + gen = wc_ecc_new_point_h(jp->heap); + if (gen == NULL) { + err = MEMORY_E; + } + + /* Client: GB = X1 + X2 + X3; Server: GA = X3 + X4 + X1 */ + if (err == MP_OKAY) { + err = ecjpake_add3(jp, jp->Xm1, jp->Xm2, jp->Xp1, gen); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_read(jp, gen, jp->Xp, in, inSz, &idx); + } + if (err == MP_OKAY && idx != inSz) { + err = BUFFER_E; + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_ROUND2_READ; + } + wc_ecc_del_point_h(gen, jp->heap); + return err; +} + +int wc_EcJpakeDeriveSecret(EcJpake* jp, WC_RNG* rng, byte* out, + word32* outSz) +{ + int err = MP_OKAY; + ecc_point* K; + byte kx[WC_ECJPAKE_FIELD_SZ]; + + if (jp == NULL || rng == NULL || out == NULL || outSz == NULL) { + return BAD_FUNC_ARG; + } + if (!(jp->flags & ECJPAKE_FLAG_ROUND2_READ)) { + return BAD_STATE_E; + } + if (*outSz < WC_ECJPAKE_SECRET_SZ) { + return BUFFER_E; + } + + K = wc_ecc_new_point_h(jp->heap); + if (K == NULL) { + err = MEMORY_E; + } + + /* Client: K = (Xs - X4*(x2*s)) * x2 + * Server: K = (Xc - X2*(x4*s)) * x4 */ + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->s, jp->Xp2, K, rng); + } + /* -P = (x, p - y) */ + if (err == MP_OKAY) { + err = mp_sub(&jp->prime, K->y, K->y); + } + if (err == MP_OKAY) { + err = ecjpake_add(jp, jp->Xp, K, K); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->xm2, K, K, rng); + } + + /* PMS = SHA-256(str(32, K.x)) */ + if (err == MP_OKAY) { + err = mp_to_unsigned_bin_len(K->x, kx, WC_ECJPAKE_FIELD_SZ); + } + if (err == MP_OKAY) { + err = wc_Sha256Hash(kx, WC_ECJPAKE_FIELD_SZ, out); + } + if (err == MP_OKAY) { + *outSz = WC_ECJPAKE_SECRET_SZ; + } + + ForceZero(kx, sizeof(kx)); + if (K != NULL) { + wc_ecc_forcezero_point(K); + } + wc_ecc_del_point_h(K, jp->heap); + return err; +} + +/* Test only: loads fixed round one keys for known answer tests. */ +int wc_EcJpakeSetRound1Keys(EcJpake* jp, WC_RNG* rng, const byte* x1, + word32 x1Sz, const byte* x2, word32 x2Sz) +{ + int err = MP_OKAY; + int xInit = 0; + ecc_point* G = NULL; + WC_DECLARE_VAR(xm1, mp_int, 1, NULL); + + if (jp == NULL || x1 == NULL || x2 == NULL || x1Sz == 0 || x2Sz == 0) { + return BAD_FUNC_ARG; + } + if (jp->flags & ECJPAKE_FLAG_KEYS) { + return BAD_STATE_E; + } + + WC_ALLOC_VAR_EX(xm1, mp_int, 1, jp->heap, DYNAMIC_TYPE_ECC, + err = MEMORY_E); + G = wc_ecc_new_point_h(jp->heap); + if (G == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + err = mp_init(xm1); + xInit = (err == MP_OKAY); + } + if (err == MP_OKAY) { + err = ecjpake_load_base(jp, G); + } + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(xm1, x1, x1Sz); + } + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(&jp->xm2, x2, x2Sz); + } + if (err == MP_OKAY && (mp_iszero(xm1) || mp_iszero(&jp->xm2) || + mp_cmp(xm1, &jp->order) != MP_LT || + mp_cmp(&jp->xm2, &jp->order) != MP_LT)) { + err = ECC_OUT_OF_RANGE_E; + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, xm1, G, jp->Xm1, rng); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->xm2, G, jp->Xm2, rng); + } + /* s = x2 * s mod n */ + if (err == MP_OKAY) { + err = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->s); + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_KEYS; + } + else { + mp_forcezero(&jp->xm2); + } + if (xInit) { + mp_forcezero(xm1); + } + WC_FREE_VAR_EX(xm1, jp->heap, DYNAMIC_TYPE_ECC); + wc_ecc_del_point_h(G, jp->heap); + return err; +} + +#endif /* WOLFCRYPT_HAVE_ECJPAKE */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 8f9cd7acda9..770e976a2a0 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -410,6 +410,7 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #include #include #include +#include #include #include #ifdef HAVE_ASCON @@ -1010,6 +1011,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dsa_test(void); #ifdef WOLFCRYPT_HAVE_SRP WOLFSSL_TEST_SUBROUTINE wc_test_ret_t srp_test(void); #endif +#ifdef WOLFCRYPT_HAVE_ECJPAKE +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecjpake_test(void); +#endif #ifndef WC_NO_RNG WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); #ifdef WC_TEST_RNG_AUTOLOCK @@ -3431,6 +3435,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("SRP test passed!\n"); #endif +#ifdef WOLFCRYPT_HAVE_ECJPAKE + if ( (ret = ecjpake_test()) != 0) + TEST_FAIL("ECJPAKE test failed!\n", ret); + else + TEST_PASS("ECJPAKE test passed!\n"); +#endif + #ifndef NO_PWDBASED PRIVATE_KEY_UNLOCK(); ret = pwdbased_test(); @@ -45912,6 +45923,689 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t srp_test(void) #endif /* WOLFCRYPT_HAVE_SRP */ +#ifdef WOLFCRYPT_HAVE_ECJPAKE +/* Known answer vectors generated from an independent implementation of + * RFC 8236 Section 3 / draft-cragie-tls-ecjpake-01. */ +static const byte ecjpake_kat_pw[] = { + 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x6a, 0x70, 0x61, 0x6b, 0x65, 0x74, + 0x65, 0x73, 0x74 +}; +static const byte ecjpake_kat_x1[] = { + 0xe3, 0x54, 0x1d, 0xd2, 0xe8, 0xd1, 0x66, 0x5f, 0x3d, 0x6e, 0x96, 0xe1, + 0x53, 0x65, 0x8f, 0xd4, 0x36, 0x25, 0x5f, 0x1a, 0x18, 0xf4, 0x6a, 0xcb, + 0x6d, 0x9d, 0xd2, 0x9f, 0x24, 0x64, 0xa3, 0x5f +}; +static const byte ecjpake_kat_x2[] = { + 0x7f, 0x16, 0x2c, 0xee, 0x40, 0xe1, 0x0e, 0x63, 0x5a, 0x61, 0x82, 0xab, + 0xea, 0xf1, 0xa2, 0x1d, 0x60, 0x44, 0xb8, 0x55, 0xfd, 0x5a, 0xfd, 0xdc, + 0x62, 0xcc, 0xa2, 0x17, 0xfe, 0xe4, 0x75, 0x7b +}; +static const byte ecjpake_kat_x3[] = { + 0xb5, 0x29, 0xfa, 0xb0, 0x11, 0x37, 0x74, 0x61, 0x28, 0xee, 0x88, 0xea, + 0x19, 0x71, 0xa3, 0x97, 0x77, 0xd1, 0xc5, 0x2b, 0x4c, 0xb2, 0x0f, 0x80, + 0x64, 0x47, 0x84, 0xb5, 0x07, 0x73, 0xd0, 0x94 +}; +static const byte ecjpake_kat_x4[] = { + 0x75, 0xe6, 0x2e, 0x8d, 0x32, 0x1a, 0xce, 0x69, 0x8a, 0x82, 0xf7, 0xa2, + 0x3c, 0xb1, 0xe5, 0x2d, 0x50, 0x5e, 0x9c, 0xe9, 0xe6, 0x48, 0xfb, 0x7d, + 0x08, 0x16, 0x59, 0x04, 0x7f, 0xb0, 0x4f, 0xc5 +}; +static const byte ecjpake_kat_cli_one[] = { + 0x41, 0x04, 0x3b, 0xff, 0xfb, 0x33, 0x11, 0x14, 0x1a, 0x15, 0x4f, 0x85, + 0xb0, 0x10, 0x1d, 0x44, 0x45, 0xf2, 0xfd, 0xe4, 0xba, 0x07, 0x45, 0x90, + 0x86, 0x99, 0xb3, 0x8e, 0x0f, 0xc1, 0xd3, 0xbe, 0x07, 0x41, 0xd9, 0xdc, + 0x7f, 0xea, 0x4a, 0x17, 0xc2, 0xb3, 0xfd, 0x92, 0x42, 0x9d, 0x4a, 0x62, + 0x8f, 0xcf, 0x1a, 0x46, 0x71, 0x72, 0xa5, 0x45, 0xae, 0x4b, 0xcd, 0xaa, + 0x9c, 0xf1, 0x3a, 0x32, 0x83, 0xc5, 0x41, 0x04, 0xf0, 0x1a, 0x23, 0xf2, + 0x11, 0xa9, 0xf2, 0x32, 0xd1, 0x62, 0x9a, 0xbb, 0xe2, 0x1d, 0x76, 0x1d, + 0xea, 0x89, 0xbf, 0xa9, 0x22, 0x2a, 0x14, 0xe8, 0xdf, 0xcb, 0x09, 0x05, + 0x1a, 0xf0, 0xed, 0x91, 0x2a, 0x26, 0x09, 0x7b, 0x04, 0x3f, 0x9f, 0x92, + 0x53, 0x1a, 0xfd, 0xf1, 0xbe, 0x4b, 0xc9, 0xfc, 0xf1, 0x9f, 0x3a, 0x76, + 0xe2, 0x9c, 0x96, 0x55, 0x5b, 0xdb, 0x70, 0xff, 0x4e, 0x12, 0xf5, 0x6e, + 0x20, 0x8b, 0x1b, 0x73, 0x4a, 0x67, 0x67, 0x84, 0x9f, 0x74, 0x9e, 0x3d, + 0x12, 0xfa, 0x38, 0x64, 0xcc, 0x97, 0x52, 0x19, 0x5f, 0x58, 0x1e, 0x11, + 0x35, 0xb4, 0x21, 0xc2, 0x2f, 0x57, 0xfe, 0xb3, 0x6a, 0x41, 0x04, 0xd0, + 0x74, 0xb9, 0x03, 0x76, 0x0d, 0x7b, 0x53, 0x00, 0x33, 0x89, 0x1c, 0x11, + 0xb1, 0xb7, 0x6f, 0xde, 0xac, 0x5b, 0xce, 0xf8, 0xbb, 0x5c, 0xbe, 0x78, + 0x9a, 0x1b, 0x21, 0x9a, 0x43, 0x0e, 0x12, 0x91, 0xa2, 0x2e, 0x69, 0x86, + 0xba, 0x9d, 0x9b, 0x4e, 0x60, 0xc2, 0x56, 0x75, 0x89, 0xdf, 0xb9, 0x81, + 0x08, 0xda, 0xa9, 0xf6, 0xff, 0xba, 0xa1, 0xbf, 0x95, 0x52, 0xaa, 0xb7, + 0xe5, 0x91, 0x87, 0x41, 0x04, 0x72, 0x92, 0x5a, 0x6b, 0xd3, 0xbb, 0xdc, + 0x1f, 0xea, 0x27, 0xb3, 0x60, 0xf3, 0x2d, 0x89, 0xa4, 0xd9, 0x16, 0x0a, + 0x87, 0x42, 0x48, 0x8e, 0xba, 0x0d, 0x5d, 0x00, 0x54, 0x1f, 0x0d, 0xd1, + 0x80, 0xa4, 0xb5, 0x3c, 0x69, 0xab, 0x43, 0xba, 0xaa, 0x7e, 0xa5, 0x44, + 0xcb, 0x2b, 0xc9, 0x9b, 0xd9, 0x8e, 0x6c, 0x39, 0x55, 0x18, 0x12, 0x8e, + 0x01, 0x2f, 0x42, 0xa8, 0x12, 0xa6, 0xcb, 0xc3, 0x4b, 0x20, 0x81, 0xde, + 0x34, 0xcd, 0x1d, 0xc3, 0x04, 0x19, 0x47, 0x99, 0x20, 0xe8, 0x4f, 0xe2, + 0x17, 0x95, 0x1d, 0x52, 0xee, 0x9b, 0xbe, 0x2f, 0x43, 0x0d, 0x3c, 0x29, + 0xaf, 0xa1, 0x3b, 0x16, 0x26, 0x7e +}; +static const byte ecjpake_kat_srv_one[] = { + 0x41, 0x04, 0x29, 0x72, 0xdd, 0x97, 0x7f, 0x48, 0x4f, 0x02, 0x39, 0x55, + 0x63, 0x2e, 0x7a, 0xd2, 0x3f, 0x30, 0x3e, 0x18, 0xc0, 0xf8, 0x60, 0x7c, + 0xc3, 0xa9, 0x5a, 0x55, 0x3a, 0x70, 0xa0, 0xbe, 0x89, 0x0e, 0xbc, 0x95, + 0x77, 0x3d, 0xff, 0x31, 0x4c, 0x7a, 0x69, 0x0b, 0xdf, 0xa9, 0xf0, 0xf8, + 0x7c, 0x3a, 0xc6, 0xf0, 0x11, 0x5c, 0x08, 0x07, 0xe7, 0x0e, 0xc9, 0xc3, + 0xa3, 0x05, 0x86, 0x7b, 0x1d, 0xeb, 0x41, 0x04, 0xfd, 0xd9, 0x2f, 0x64, + 0x5c, 0xde, 0x4b, 0x1f, 0xaa, 0x75, 0xe4, 0x50, 0xac, 0xc0, 0x1c, 0xb1, + 0x4d, 0x1a, 0x0b, 0xca, 0x81, 0x20, 0xf7, 0x08, 0x24, 0x23, 0xb2, 0x97, + 0xf7, 0xf5, 0xf6, 0xa5, 0x55, 0xf6, 0xbe, 0xc7, 0x98, 0x36, 0x40, 0x5b, + 0xf0, 0x8f, 0x32, 0x81, 0xa0, 0x37, 0xec, 0x95, 0x24, 0xa9, 0x3f, 0x00, + 0x40, 0x3a, 0x52, 0xcf, 0xc0, 0xd4, 0xeb, 0x0c, 0x4d, 0x77, 0xd8, 0x14, + 0x20, 0xee, 0x1b, 0x4a, 0xb2, 0x2c, 0x10, 0x1e, 0x80, 0x2f, 0x9e, 0x22, + 0x3a, 0x2e, 0x5c, 0x40, 0xfe, 0x03, 0x12, 0xf5, 0x3f, 0x32, 0xd4, 0x87, + 0xc8, 0xf5, 0x2c, 0xd2, 0x8e, 0x82, 0x54, 0xa3, 0x59, 0x41, 0x04, 0xb2, + 0x26, 0x48, 0x2f, 0xa4, 0x47, 0x45, 0x8e, 0x2f, 0x6b, 0x95, 0x96, 0x8e, + 0x81, 0x4a, 0x9b, 0x5c, 0x35, 0xb9, 0x07, 0xe6, 0xb4, 0xc9, 0xcc, 0xb3, + 0x2a, 0x77, 0x71, 0xc9, 0xd1, 0xf4, 0x85, 0xeb, 0x20, 0xa5, 0x36, 0x48, + 0xb4, 0xd4, 0x84, 0x3a, 0xee, 0xbd, 0x15, 0x5f, 0x4c, 0x39, 0x32, 0x12, + 0x6b, 0xa9, 0x60, 0xa7, 0x6f, 0xc5, 0x6b, 0x4f, 0xc6, 0x74, 0x77, 0x3b, + 0x21, 0xa1, 0xce, 0x41, 0x04, 0xb7, 0x0b, 0x5e, 0xbd, 0xb2, 0xc8, 0x58, + 0x38, 0xe4, 0xa6, 0xc9, 0x9f, 0x85, 0xe2, 0x99, 0x68, 0xe6, 0xd5, 0x90, + 0xe1, 0xb9, 0x10, 0xc6, 0x7f, 0xc4, 0xdb, 0xd4, 0x7e, 0xd4, 0x2c, 0x54, + 0xdd, 0x35, 0xca, 0x02, 0x54, 0x9a, 0xbd, 0xc5, 0x28, 0xab, 0x01, 0x9a, + 0x4c, 0x00, 0xb2, 0xc0, 0x71, 0xb4, 0x6d, 0x1c, 0x07, 0xee, 0xc3, 0x37, + 0x55, 0x58, 0xc1, 0x56, 0xb0, 0x23, 0x5e, 0xd4, 0x00, 0x20, 0x87, 0x5b, + 0x2f, 0x98, 0xa4, 0x07, 0x21, 0xaa, 0x76, 0x4b, 0xec, 0x28, 0x53, 0x94, + 0xf5, 0xc3, 0xa3, 0x52, 0x0b, 0x57, 0x0c, 0x47, 0x5b, 0xc6, 0xc7, 0x88, + 0x51, 0xcb, 0x96, 0x1d, 0xef, 0xcb +}; +static const byte ecjpake_kat_srv_two[] = { + 0x03, 0x00, 0x17, 0x41, 0x04, 0x23, 0xb3, 0xd5, 0x23, 0x5e, 0xdd, 0x17, + 0x92, 0x15, 0xc9, 0x76, 0x97, 0x43, 0x79, 0x40, 0xc4, 0x58, 0x81, 0x9b, + 0x61, 0xa9, 0xd2, 0xe6, 0x10, 0xb6, 0xd2, 0x61, 0x63, 0x39, 0x84, 0x56, + 0x5c, 0x03, 0xff, 0x71, 0xbf, 0xb1, 0x93, 0x67, 0xad, 0x8b, 0x8d, 0xa9, + 0xb4, 0x77, 0x08, 0xca, 0xdf, 0x4c, 0xf0, 0x5a, 0x88, 0xc7, 0x25, 0xc8, + 0x93, 0x56, 0x2c, 0x91, 0xf9, 0x7d, 0xa5, 0xab, 0x6b, 0x41, 0x04, 0xdb, + 0x92, 0xa4, 0x27, 0x7c, 0x8a, 0xf1, 0x5c, 0x5d, 0x62, 0xaf, 0x33, 0x0c, + 0x46, 0xfc, 0xab, 0xab, 0xab, 0x04, 0x1e, 0x10, 0x00, 0x6e, 0xb8, 0x39, + 0xf6, 0x04, 0xd1, 0x61, 0xd6, 0xdd, 0xbb, 0x17, 0x46, 0x6e, 0x8a, 0xe8, + 0x5b, 0xd9, 0x8f, 0xd8, 0xf3, 0xae, 0x4f, 0xe9, 0xd4, 0x64, 0x48, 0x46, + 0x89, 0xdf, 0x58, 0xfa, 0x54, 0xb8, 0xba, 0x0e, 0x7f, 0x05, 0xde, 0x3f, + 0x79, 0x64, 0xbf, 0x20, 0x4c, 0x48, 0x65, 0x5c, 0x9b, 0xd3, 0x48, 0x75, + 0xce, 0x1b, 0xbd, 0x07, 0xd5, 0x92, 0xe6, 0x36, 0x91, 0x67, 0xff, 0xe9, + 0x65, 0xa0, 0x78, 0x0f, 0xaf, 0xc2, 0x3b, 0x31, 0xfa, 0x30, 0x36, 0x9c +}; +static const byte ecjpake_kat_cli_two[] = { + 0x41, 0x04, 0xbe, 0x3a, 0x68, 0x14, 0x33, 0xb6, 0x47, 0x28, 0x7d, 0x13, + 0xd6, 0xa8, 0x20, 0x15, 0xb6, 0xc2, 0x99, 0x84, 0x52, 0x11, 0xb8, 0x08, + 0x17, 0xd6, 0x31, 0x74, 0x18, 0x5c, 0xc4, 0x8f, 0x0c, 0x5f, 0xd6, 0x57, + 0x69, 0xfb, 0x89, 0xcb, 0x1e, 0xa2, 0x02, 0xe9, 0xed, 0xed, 0x2f, 0x14, + 0xb1, 0x28, 0x4e, 0x38, 0xd4, 0x44, 0xdf, 0x16, 0x30, 0x18, 0x3f, 0xf0, + 0xeb, 0x7a, 0x63, 0xb3, 0xfc, 0x2d, 0x41, 0x04, 0x8d, 0x6e, 0xd9, 0x46, + 0x8a, 0x36, 0x63, 0xc3, 0xbd, 0x5f, 0x85, 0x93, 0x97, 0x6d, 0xd7, 0x60, + 0x1c, 0xcd, 0xea, 0x45, 0xd9, 0x08, 0x48, 0xc0, 0x45, 0xd2, 0xbe, 0xcb, + 0x5e, 0x42, 0xd7, 0xdd, 0xd1, 0x96, 0xca, 0x82, 0xec, 0xbe, 0xae, 0x08, + 0x37, 0x03, 0xd8, 0x19, 0xeb, 0x58, 0x49, 0x78, 0x6c, 0x90, 0x8e, 0x2b, + 0xec, 0xcd, 0x12, 0x42, 0x43, 0xc3, 0x4f, 0x11, 0x33, 0xf0, 0x62, 0x2b, + 0x20, 0x73, 0x0b, 0x3a, 0x3b, 0x9b, 0xde, 0x40, 0x09, 0xf4, 0x48, 0xb8, + 0x6a, 0xd7, 0xfd, 0x75, 0x4e, 0x84, 0x85, 0xa5, 0xd4, 0x2f, 0x27, 0xc5, + 0xd7, 0x25, 0x35, 0x3b, 0x0d, 0x79, 0xd9, 0xce, 0x5b +}; +static const byte ecjpake_kat_pms[] = { + 0x0b, 0x75, 0xbb, 0xa6, 0x38, 0x5a, 0xd4, 0xa1, 0x97, 0x60, 0xbb, 0xf5, + 0x6d, 0x01, 0x04, 0x87, 0x62, 0xb8, 0x10, 0x4a, 0x1a, 0x8f, 0xdd, 0x5a, + 0x93, 0x0c, 0xc1, 0x07, 0xaf, 0xd7, 0x37, 0x1b +}; + +/* Runs one side of the known answer handshake from round one keys xa and xb + * and the peer's round one message r1, then, when r2 is not NULL, the peer's + * round two message r2 and derives the premaster secret into pms. Returns the + * first wolfCrypt error. */ +static int ecjpake_kat_run(int role, const byte* xa, const byte* xb, + const byte* r1, word32 r1Sz, + const byte* r2, word32 r2Sz, WC_RNG* rng, + byte* pms) +{ + int err; + EcJpake* jp; + word32 pmsSz = WC_ECJPAKE_SECRET_SZ; + + jp = (EcJpake*)XMALLOC(sizeof(*jp), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (jp == NULL) + return MEMORY_E; + + err = wc_EcJpakeInit(jp, role, ECC_SECP256R1, ecjpake_kat_pw, + sizeof(ecjpake_kat_pw), HEAP_HINT); + if (err != 0) { + XFREE(jp, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return err; + } + err = wc_EcJpakeSetRound1Keys(jp, rng, xa, WC_ECJPAKE_FIELD_SZ, xb, + WC_ECJPAKE_FIELD_SZ); + if (err == 0) + err = wc_EcJpakeReadRound1(jp, r1, r1Sz); + if (err == 0 && r2 != NULL) + err = wc_EcJpakeReadRound2(jp, r2, r2Sz); + if (err == 0 && r2 != NULL) + err = wc_EcJpakeDeriveSecret(jp, rng, pms, &pmsSz); + + wc_EcJpakeFree(jp); + XFREE(jp, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return err; +} + +/* Runs the known answer handshake for the given role and checks the derived + * premaster secret. */ +static wc_test_ret_t ecjpake_kat(int role, WC_RNG* rng) +{ + int err; + byte pms[WC_ECJPAKE_SECRET_SZ]; + + if (role == WC_ECJPAKE_CLIENT) { + err = ecjpake_kat_run(role, ecjpake_kat_x1, ecjpake_kat_x2, + ecjpake_kat_srv_one, sizeof(ecjpake_kat_srv_one), + ecjpake_kat_srv_two, sizeof(ecjpake_kat_srv_two), + rng, pms); + } + else { + err = ecjpake_kat_run(role, ecjpake_kat_x3, ecjpake_kat_x4, + ecjpake_kat_cli_one, sizeof(ecjpake_kat_cli_one), + ecjpake_kat_cli_two, sizeof(ecjpake_kat_cli_two), + rng, pms); + } + if (err != 0) + return WC_TEST_RET_ENC_EC(err); + if (XMEMCMP(pms, ecjpake_kat_pms, sizeof(pms)) != 0) + return WC_TEST_RET_ENC_NC; + return 0; +} + +/* Full handshake between a client and server with the given passwords. + * Returns 0 when both sides derive the same premaster secret. */ +static wc_test_ret_t ecjpake_handshake(const byte* cPw, word32 cPwSz, + const byte* sPw, word32 sPwSz, + WC_RNG* rng, int* match) +{ + wc_test_ret_t ret; + EcJpake* cli; + EcJpake* srv; + byte buf[WC_ECJPAKE_ROUND1_SZ]; + word32 bufSz; + byte cPms[WC_ECJPAKE_SECRET_SZ]; + byte sPms[WC_ECJPAKE_SECRET_SZ]; + word32 cPmsSz = sizeof(cPms); + word32 sPmsSz = sizeof(sPms); + + cli = (EcJpake*)XMALLOC(sizeof(*cli), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + srv = (EcJpake*)XMALLOC(sizeof(*srv), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (cli == NULL || srv == NULL) { + XFREE(srv, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cli, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WC_TEST_RET_ENC_ERRNO; + } + XMEMSET(cli, 0, sizeof(*cli)); + XMEMSET(srv, 0, sizeof(*srv)); + + ret = wc_EcJpakeInit(cli, WC_ECJPAKE_CLIENT, ECC_SECP256R1, cPw, cPwSz, + HEAP_HINT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_EcJpakeInit(srv, WC_ECJPAKE_SERVER, ECC_SECP256R1, sPw, sPwSz, + HEAP_HINT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + bufSz = sizeof(buf); + ret = wc_EcJpakeWriteRound1(cli, rng, buf, &bufSz); + if (ret == 0) + ret = wc_EcJpakeReadRound1(srv, buf, bufSz); + if (ret == 0) { + bufSz = sizeof(buf); + ret = wc_EcJpakeWriteRound1(srv, rng, buf, &bufSz); + } + if (ret == 0) + ret = wc_EcJpakeReadRound1(cli, buf, bufSz); + if (ret == 0) { + bufSz = sizeof(buf); + ret = wc_EcJpakeWriteRound2(srv, rng, buf, &bufSz); + } + if (ret == 0) + ret = wc_EcJpakeReadRound2(cli, buf, bufSz); + if (ret == 0) { + bufSz = sizeof(buf); + ret = wc_EcJpakeWriteRound2(cli, rng, buf, &bufSz); + } + if (ret == 0) + ret = wc_EcJpakeReadRound2(srv, buf, bufSz); + if (ret == 0) + ret = wc_EcJpakeDeriveSecret(cli, rng, cPms, &cPmsSz); + if (ret == 0) + ret = wc_EcJpakeDeriveSecret(srv, rng, sPms, &sPmsSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + *match = (cPmsSz == sPmsSz && XMEMCMP(cPms, sPms, cPmsSz) == 0); + +out: + wc_EcJpakeFree(srv); + wc_EcJpakeFree(cli); + XFREE(srv, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cli, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Offsets within an encoded ECJPAKEKeyKP. */ +#define ECJPAKE_KKP_X_OFF 0 +#define ECJPAKE_KKP_V_OFF (1 + WC_ECJPAKE_POINT_SZ) +#define ECJPAKE_KKP_R_OFF (2 * (1 + WC_ECJPAKE_POINT_SZ)) +/* Offset of the last octet of an ordinate starting after a length octet. */ +#define ECJPAKE_PT_X_LAST (1 + 1 + WC_ECJPAKE_FIELD_SZ - 1) +#define ECJPAKE_PT_Y_LAST (1 + 1 + 2 * WC_ECJPAKE_FIELD_SZ - 1) + +typedef struct ecjpake_mutation { + byte role; /* side receiving the message */ + byte round; + word16 off; + byte mask; /* XORed into the octet at off */ + int expected; +} ecjpake_mutation; + +/* Peer messages with a single octet changed. */ +static wc_test_ret_t ecjpake_mutation_test(WC_RNG* rng) +{ + static const ecjpake_mutation tests[] = { + /* Round one received by the server. */ + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_X_OFF, 0x01, + WC_NO_ERR_TRACE(ECC_BAD_ARG_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_X_OFF + 1, 0x06, + WC_NO_ERR_TRACE(ECC_BAD_ARG_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_X_OFF + ECJPAKE_PT_X_LAST, 0x01, + WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_X_OFF + ECJPAKE_PT_Y_LAST, 0x01, + WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_V_OFF, 0x01, + WC_NO_ERR_TRACE(ECC_BAD_ARG_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_V_OFF + ECJPAKE_PT_X_LAST, 0x01, + WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_R_OFF, 0x20, + WC_NO_ERR_TRACE(BUFFER_E) }, + { WC_ECJPAKE_SERVER, 1, ECJPAKE_KKP_R_OFF + WC_ECJPAKE_FIELD_SZ, 0x01, + WC_NO_ERR_TRACE(SIG_VERIFY_E) }, + { WC_ECJPAKE_SERVER, 1, WC_ECJPAKE_KKP_SZ + ECJPAKE_PT_X_LAST, 0x01, + WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_SERVER, 1, + WC_ECJPAKE_KKP_SZ + ECJPAKE_KKP_R_OFF + WC_ECJPAKE_FIELD_SZ, 0x01, + WC_NO_ERR_TRACE(SIG_VERIFY_E) }, + /* Round one received by the client. */ + { WC_ECJPAKE_CLIENT, 1, ECJPAKE_KKP_R_OFF + WC_ECJPAKE_FIELD_SZ, 0x01, + WC_NO_ERR_TRACE(SIG_VERIFY_E) }, + /* Round two received by the client: explicit_prime curve type, + * secp384r1, then the key pair. */ + { WC_ECJPAKE_CLIENT, 2, 0, 0x02, + WC_NO_ERR_TRACE(ECC_CURVE_OID_E) }, + { WC_ECJPAKE_CLIENT, 2, 2, 0x0f, + WC_NO_ERR_TRACE(ECC_CURVE_OID_E) }, + { WC_ECJPAKE_CLIENT, 2, WC_ECJPAKE_ECPARAMS_SZ + ECJPAKE_PT_X_LAST, + 0x01, WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_CLIENT, 2, + WC_ECJPAKE_ECPARAMS_SZ + ECJPAKE_KKP_V_OFF + ECJPAKE_PT_X_LAST, + 0x01, WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_CLIENT, 2, + WC_ECJPAKE_ECPARAMS_SZ + ECJPAKE_KKP_R_OFF + WC_ECJPAKE_FIELD_SZ, + 0x01, WC_NO_ERR_TRACE(SIG_VERIFY_E) }, + /* Round two received by the server. */ + { WC_ECJPAKE_SERVER, 2, ECJPAKE_PT_X_LAST, 0x01, + WC_NO_ERR_TRACE(IS_POINT_E) }, + { WC_ECJPAKE_SERVER, 2, ECJPAKE_KKP_R_OFF + WC_ECJPAKE_FIELD_SZ, 0x01, + WC_NO_ERR_TRACE(SIG_VERIFY_E) }, + }; + byte msg[WC_ECJPAKE_ROUND1_SZ]; + byte pms[WC_ECJPAKE_SECRET_SZ]; + word32 i; + int err; + + for (i = 0; i < (word32)(sizeof(tests) / sizeof(tests[0])); i++) { + const ecjpake_mutation* t = &tests[i]; + int cli = (t->role == WC_ECJPAKE_CLIENT); + const byte* xa = cli ? ecjpake_kat_x1 : ecjpake_kat_x3; + const byte* xb = cli ? ecjpake_kat_x2 : ecjpake_kat_x4; + const byte* r1 = cli ? ecjpake_kat_srv_one : ecjpake_kat_cli_one; + word32 r1Sz = cli ? sizeof(ecjpake_kat_srv_one) : + sizeof(ecjpake_kat_cli_one); + const byte* r2 = cli ? ecjpake_kat_srv_two : ecjpake_kat_cli_two; + word32 r2Sz = cli ? sizeof(ecjpake_kat_srv_two) : + sizeof(ecjpake_kat_cli_two); + + if (t->round == 1) { + XMEMCPY(msg, r1, r1Sz); + msg[t->off] ^= t->mask; + err = ecjpake_kat_run(t->role, xa, xb, msg, r1Sz, NULL, 0, rng, + pms); + } + else { + XMEMCPY(msg, r2, r2Sz); + msg[t->off] ^= t->mask; + err = ecjpake_kat_run(t->role, xa, xb, r1, r1Sz, msg, r2Sz, rng, + pms); + } + if (err != t->expected) + return WC_TEST_RET_ENC_I(i); + } + + return 0; +} + +#define ECJPAKE_EXPECT(call, expected) \ + do { \ + err = (call); \ + if (err != (expected)) \ + ERROR_OUT(WC_TEST_RET_ENC_EC(err), out); \ + } while (0) + +/* Malformed encodings, out of range values and messages that verify on their + * own but belong to a different context. */ +static wc_test_ret_t ecjpake_malformed_test(WC_RNG* rng) +{ + wc_test_ret_t ret = 0; + int err; + byte msg[WC_ECJPAKE_ROUND1_SZ + 2]; + byte pms[WC_ECJPAKE_SECRET_SZ]; + const word32 r1Sz = sizeof(ecjpake_kat_cli_one); + const word32 r2Sz = sizeof(ecjpake_kat_srv_two); + const word32 rOff = ECJPAKE_KKP_R_OFF + 1; + + /* x-coordinate not less than the field prime. */ + XMEMCPY(msg, ecjpake_kat_cli_one, r1Sz); + XMEMSET(msg + 2, 0xff, WC_ECJPAKE_FIELD_SZ); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz, NULL, 0, rng, pms), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + /* r of zero and r not less than the order. */ + XMEMCPY(msg, ecjpake_kat_cli_one, r1Sz); + XMEMSET(msg + rOff, 0x00, WC_ECJPAKE_FIELD_SZ); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz, NULL, 0, rng, pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + XMEMSET(msg + rOff, 0xff, WC_ECJPAKE_FIELD_SZ); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz, NULL, 0, rng, pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + + /* r encoded in 33 octets: accepted with a leading zero octet, rejected + * otherwise. */ + XMEMCPY(msg, ecjpake_kat_cli_one, rOff - 1); + msg[rOff - 1] = WC_ECJPAKE_FIELD_SZ + 1; + msg[rOff] = 0x00; + XMEMCPY(msg + rOff + 1, ecjpake_kat_cli_one + rOff, + r1Sz - rOff); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz + 1, NULL, 0, rng, pms), + 0); + msg[rOff] = 0x01; + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz + 1, NULL, 0, rng, pms), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + /* Round one: empty, truncated, cut at the pair boundary, trailing data. */ + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, ecjpake_kat_cli_one, 0, NULL, 0, rng, + pms), + WC_NO_ERR_TRACE(BUFFER_E)); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, ecjpake_kat_cli_one, r1Sz - 1, NULL, 0, + rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, ecjpake_kat_cli_one, WC_ECJPAKE_KKP_SZ, + NULL, 0, rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + XMEMCPY(msg, ecjpake_kat_cli_one, r1Sz); + msg[r1Sz] = 0x00; + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, msg, r1Sz + 1, NULL, 0, rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* Round two: shorter than ECParameters, truncated, trailing data. */ + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_CLIENT, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one), ecjpake_kat_srv_two, + WC_ECJPAKE_ECPARAMS_SZ - 1, rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_CLIENT, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one), ecjpake_kat_srv_two, + r2Sz - 1, rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + XMEMCPY(msg, ecjpake_kat_srv_two, r2Sz); + msg[r2Sz] = 0x00; + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_CLIENT, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one), msg, r2Sz + 1, rng, pms), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* Own messages reflected back: proofs are bound to the sender's + * identity. */ + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one), NULL, 0, rng, pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_CLIENT, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_cli_one, r1Sz, NULL, 0, rng, + pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + /* Server round two sent to the server; client round two, with + * ECParameters added, sent to the client. */ + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x3, + ecjpake_kat_x4, ecjpake_kat_cli_one, r1Sz, + ecjpake_kat_srv_two, r2Sz, rng, pms), + WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); + XMEMCPY(msg, ecjpake_kat_srv_two, WC_ECJPAKE_ECPARAMS_SZ); + XMEMCPY(msg + WC_ECJPAKE_ECPARAMS_SZ, ecjpake_kat_cli_two, + sizeof(ecjpake_kat_cli_two)); + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_CLIENT, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one), msg, + WC_ECJPAKE_ECPARAMS_SZ + sizeof(ecjpake_kat_cli_two), + rng, pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + + /* Round two proof is bound to the round one keys: a server holding + * different round one keys derives a different generator. */ + ECJPAKE_EXPECT(ecjpake_kat_run(WC_ECJPAKE_SERVER, ecjpake_kat_x1, + ecjpake_kat_x2, ecjpake_kat_cli_one, r1Sz, + ecjpake_kat_cli_two, sizeof(ecjpake_kat_cli_two), rng, + pms), + WC_NO_ERR_TRACE(SIG_VERIFY_E)); + +out: + return ret; +} + +/* Bad arguments, buffer sizes, invalid secrets and keys, and calls out of + * order. */ +static wc_test_ret_t ecjpake_api_test(WC_RNG* rng) +{ + wc_test_ret_t ret = 0; + int err; + EcJpake* jp; + byte buf[WC_ECJPAKE_ROUND1_SZ]; + word32 bufSz; + byte zero[WC_ECJPAKE_FIELD_SZ]; + /* Order of the P-256 base point. */ + static const byte order[WC_ECJPAKE_FIELD_SZ] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51 + }; + + jp = (EcJpake*)XMALLOC(sizeof(*jp), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (jp == NULL) + return WC_TEST_RET_ENC_ERRNO; + XMEMSET(zero, 0, sizeof(zero)); + + /* Init: arguments, curve and secret values that reduce to zero. */ + ECJPAKE_EXPECT(wc_EcJpakeInit(NULL, WC_ECJPAKE_CLIENT, ECC_SECP256R1, + ecjpake_kat_pw, sizeof(ecjpake_kat_pw), HEAP_HINT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP256R1, NULL, + sizeof(ecjpake_kat_pw), HEAP_HINT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP256R1, + ecjpake_kat_pw, 0, HEAP_HINT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, 2, ECC_SECP256R1, ecjpake_kat_pw, + sizeof(ecjpake_kat_pw), HEAP_HINT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP384R1, + ecjpake_kat_pw, sizeof(ecjpake_kat_pw), HEAP_HINT), + WC_NO_ERR_TRACE(ECC_CURVE_OID_E)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP256R1, zero, + sizeof(zero), HEAP_HINT), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP256R1, + order, sizeof(order), HEAP_HINT), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + wc_EcJpakeFree(NULL); + + /* Fixed round one keys must be in [1, n-1]. */ + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_CLIENT, ECC_SECP256R1, + ecjpake_kat_pw, sizeof(ecjpake_kat_pw), HEAP_HINT), 0); + ECJPAKE_EXPECT(wc_EcJpakeSetRound1Keys(jp, rng, NULL, WC_ECJPAKE_FIELD_SZ, + ecjpake_kat_x2, WC_ECJPAKE_FIELD_SZ), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeSetRound1Keys(jp, rng, zero, sizeof(zero), + ecjpake_kat_x2, WC_ECJPAKE_FIELD_SZ), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + ECJPAKE_EXPECT(wc_EcJpakeSetRound1Keys(jp, rng, ecjpake_kat_x1, + WC_ECJPAKE_FIELD_SZ, order, sizeof(order)), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + /* Nothing past round one is possible before both round one messages. */ + bufSz = sizeof(buf); + ECJPAKE_EXPECT(wc_EcJpakeWriteRound2(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BAD_STATE_E)); + ECJPAKE_EXPECT(wc_EcJpakeReadRound2(jp, ecjpake_kat_srv_two, + sizeof(ecjpake_kat_srv_two)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ECJPAKE_EXPECT(wc_EcJpakeDeriveSecret(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* Round one write: arguments and output size. */ + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, NULL, buf, &bufSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, rng, NULL, &bufSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, rng, buf, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + bufSz = WC_ECJPAKE_ROUND1_SZ - 1; + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BUFFER_E)); + + /* Round one keys are generated or loaded once. */ + ECJPAKE_EXPECT(wc_EcJpakeSetRound1Keys(jp, rng, ecjpake_kat_x1, + WC_ECJPAKE_FIELD_SZ, ecjpake_kat_x2, + WC_ECJPAKE_FIELD_SZ), 0); + bufSz = sizeof(buf); + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BAD_STATE_E)); + ECJPAKE_EXPECT(wc_EcJpakeSetRound1Keys(jp, rng, ecjpake_kat_x1, + WC_ECJPAKE_FIELD_SZ, ecjpake_kat_x2, + WC_ECJPAKE_FIELD_SZ), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* Round one read once only. */ + ECJPAKE_EXPECT(wc_EcJpakeReadRound1(jp, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeReadRound1(jp, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one)), 0); + ECJPAKE_EXPECT(wc_EcJpakeReadRound1(jp, ecjpake_kat_srv_one, + sizeof(ecjpake_kat_srv_one)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* Round two write: arguments and output size. */ + ECJPAKE_EXPECT(wc_EcJpakeWriteRound2(jp, NULL, buf, &bufSz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + bufSz = WC_ECJPAKE_KKP_SZ - 1; + ECJPAKE_EXPECT(wc_EcJpakeWriteRound2(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BUFFER_E)); + + /* Secret only after round two is read; round two read once only. */ + bufSz = sizeof(buf); + ECJPAKE_EXPECT(wc_EcJpakeDeriveSecret(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BAD_STATE_E)); + ECJPAKE_EXPECT(wc_EcJpakeReadRound2(jp, NULL, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ECJPAKE_EXPECT(wc_EcJpakeReadRound2(jp, ecjpake_kat_srv_two, + sizeof(ecjpake_kat_srv_two)), 0); + ECJPAKE_EXPECT(wc_EcJpakeReadRound2(jp, ecjpake_kat_srv_two, + sizeof(ecjpake_kat_srv_two)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* Derive: arguments and output size. */ + ECJPAKE_EXPECT(wc_EcJpakeDeriveSecret(jp, NULL, buf, &bufSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + bufSz = WC_ECJPAKE_SECRET_SZ - 1; + ECJPAKE_EXPECT(wc_EcJpakeDeriveSecret(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BUFFER_E)); + bufSz = sizeof(buf); + ECJPAKE_EXPECT(wc_EcJpakeDeriveSecret(jp, rng, buf, &bufSz), 0); + if (bufSz != WC_ECJPAKE_SECRET_SZ || + XMEMCMP(buf, ecjpake_kat_pms, bufSz) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + wc_EcJpakeFree(jp); + + /* A server's round two output also carries ECParameters. */ + ECJPAKE_EXPECT(wc_EcJpakeInit(jp, WC_ECJPAKE_SERVER, ECC_SECP256R1, + ecjpake_kat_pw, sizeof(ecjpake_kat_pw), HEAP_HINT), 0); + bufSz = sizeof(buf); + ECJPAKE_EXPECT(wc_EcJpakeWriteRound1(jp, rng, buf, &bufSz), 0); + ECJPAKE_EXPECT(wc_EcJpakeReadRound1(jp, ecjpake_kat_cli_one, + sizeof(ecjpake_kat_cli_one)), 0); + bufSz = WC_ECJPAKE_KKP_SZ; + ECJPAKE_EXPECT(wc_EcJpakeWriteRound2(jp, rng, buf, &bufSz), WC_NO_ERR_TRACE(BUFFER_E)); + bufSz = WC_ECJPAKE_ROUND2_SZ; + ECJPAKE_EXPECT(wc_EcJpakeWriteRound2(jp, rng, buf, &bufSz), 0); + if (bufSz != WC_ECJPAKE_ROUND2_SZ) + ERROR_OUT(WC_TEST_RET_ENC_I(bufSz), out); + +out: + wc_EcJpakeFree(jp); + XFREE(jp, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#undef ECJPAKE_EXPECT + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecjpake_test(void) +{ + wc_test_ret_t ret; + WC_RNG rng; + int match = 0; + static const byte otherPw[] = { 't', 'h', 'r', 'e', 'a', 'd' }; + + WOLFSSL_ENTER("ecjpake_test"); + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + ret = ecjpake_kat(WC_ECJPAKE_CLIENT, &rng); + if (ret != 0) + goto out; + ret = ecjpake_kat(WC_ECJPAKE_SERVER, &rng); + if (ret != 0) + goto out; + + ret = ecjpake_handshake(ecjpake_kat_pw, sizeof(ecjpake_kat_pw), + ecjpake_kat_pw, sizeof(ecjpake_kat_pw), &rng, + &match); + if (ret != 0) + goto out; + if (!match) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* Different passwords complete the exchange with different secrets. */ + ret = ecjpake_handshake(ecjpake_kat_pw, sizeof(ecjpake_kat_pw), + otherPw, sizeof(otherPw), &rng, &match); + if (ret != 0) + goto out; + if (match) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = ecjpake_mutation_test(&rng); + if (ret != 0) + goto out; + ret = ecjpake_malformed_test(&rng); + if (ret != 0) + goto out; + ret = ecjpake_api_test(&rng); + +out: + wc_FreeRng(&rng); + return ret; +} +#endif /* WOLFCRYPT_HAVE_ECJPAKE */ + #if defined(OPENSSL_EXTRA) && !defined(WOLFCRYPT_ONLY) #if !defined(NO_AES) && !defined(WOLFCRYPT_ONLY) diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index b63b5810bd6..6058cce9544 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -291,6 +291,9 @@ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dsa_test(void); #ifdef WOLFCRYPT_HAVE_SRP extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t srp_test(void); #endif +#ifdef WOLFCRYPT_HAVE_ECJPAKE +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecjpake_test(void); +#endif #ifndef WC_NO_RNG extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); #ifdef WC_TEST_RNG_AUTOLOCK diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 3ea829c0b4e..a9801bea83e 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -184,6 +184,9 @@ #include #endif #endif +#ifdef WOLFCRYPT_HAVE_ECJPAKE + #include +#endif #ifdef _MSC_VER /* 4996 warning to use MS extensions e.g., strcpy_s instead of strncpy */ @@ -918,6 +921,14 @@ #define NO_AESGCM_AEAD #endif +/* EC-JPAKE (draft-cragie-tls-ecjpake-01) is used over DTLS 1.2 only. */ +#if defined(WOLFCRYPT_HAVE_ECJPAKE) && defined(WOLFSSL_DTLS) && \ + defined(HAVE_TLS_EXTENSIONS) && defined(HAVE_AESCCM) && \ + defined(WOLFSSL_AES_128) && !defined(WOLFSSL_NO_TLS12) + #define WOLFSSL_DTLS_ECJPAKE + #define BUILD_TLS_ECJPAKE_WITH_AES_128_CCM_8 +#endif + #if defined(BUILD_TLS_RSA_WITH_AES_128_CCM_8) || \ defined(BUILD_TLS_RSA_WITH_AES_256_CCM_8) || \ defined(BUILD_TLS_PSK_WITH_AES_128_CCM_8) || \ @@ -929,6 +940,7 @@ defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_CCM) || \ defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) || \ defined(BUILD_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8) || \ + defined(BUILD_TLS_ECJPAKE_WITH_AES_128_CCM_8) || \ defined(BUILD_TLS_AES_128_CCM_SHA256) || \ defined(BUILD_TLS_AES_128_CCM_8_SHA256) #define BUILD_AESCCM @@ -1152,6 +1164,8 @@ enum { TLS_PSK_WITH_AES_256_CCM_8 = 0xa9, TLS_DHE_PSK_WITH_AES_128_CCM = 0xa6, TLS_DHE_PSK_WITH_AES_256_CCM = 0xa7, + /* Assigned by the Thread specification, not registered with IANA. */ + TLS_ECJPAKE_WITH_AES_128_CCM_8 = 0xff, /* Camellia */ TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x41, @@ -1397,6 +1411,13 @@ enum { #define ENCRYPT_BASE_BITS ((MAX_PSK_KEY_LEN + 2) * 8) #endif +/* ClientECJPAKEParams are written into the encrypted secret buffer. */ +#if defined(WOLFSSL_DTLS_ECJPAKE) && \ + ENCRYPT_BASE_BITS < (WC_ECJPAKE_KKP_SZ * 8) + #undef ENCRYPT_BASE_BITS + #define ENCRYPT_BASE_BITS (WC_ECJPAKE_KKP_SZ * 8) +#endif + #ifdef WOLFSSL_DTLS_CID #ifndef DTLS_CID_MAX_SIZE /* DTLS parsing code copies the record header in a static buffer to decrypt @@ -3369,6 +3390,8 @@ typedef struct Options Options; #define TLSXT_KEY_SHARE 0x0033 #define TLSXT_CONNECTION_ID 0x0036 #define TLSXT_KEY_QUIC_TP_PARAMS 0x0039 /* RFC 9001, ch. 8.2 */ +/* Assigned by the Thread specification, not registered with IANA. */ +#define TLSXT_ECJPAKE_KKPP 0x0100 #define TLSXT_ECH 0xfe0d /* RFC 9849 */ #define TLSXT_ECH_OUTER_EXTENSIONS 0xfd00 /* RFC 9849 */ /* The 0xFF section is experimental/custom/personal use */ @@ -3435,6 +3458,9 @@ typedef enum { #if defined(WOLFSSL_DTLS_CID) TLSX_CONNECTION_ID = TLSXT_CONNECTION_ID, #endif /* defined(WOLFSSL_DTLS_CID) */ +#ifdef WOLFSSL_DTLS_ECJPAKE + TLSX_ECJPAKE_KKPP = TLSXT_ECJPAKE_KKPP, +#endif #if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS) #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) TLSX_PRE_SHARED_KEY = TLSXT_PRE_SHARED_KEY, @@ -4395,6 +4421,13 @@ WOLFSSL_LOCAL byte DtlsCIDCheck(WOLFSSL* ssl, const byte* input, WOLFSSL_LOCAL int DtlsCidReplaceTx(WOLFSSL* ssl, const byte* cid, byte size); WOLFSSL_LOCAL int Dtls13UnifiedHeaderCIDPresent(byte flags); #endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_ECJPAKE +WOLFSSL_LOCAL int TLSX_EcJpake_Use(WOLFSSL* ssl); +WOLFSSL_LOCAL void FreeEcJpake(WOLFSSL* ssl); +WOLFSSL_LOCAL int EcJpakeSetPassword(void* heap, byte** pw, word32* pwSz, + const byte* in, word32 inSz); +WOLFSSL_LOCAL void EcJpakeFreePassword(void* heap, byte** pw, word32* pwSz); +#endif /* WOLFSSL_DTLS_ECJPAKE */ WOLFSSL_LOCAL byte DtlsGetCidTxSize(WOLFSSL* ssl); WOLFSSL_LOCAL byte DtlsGetCidRxSize(WOLFSSL* ssl); #ifdef WOLFSSL_SESSION_EXPORT @@ -4663,6 +4696,10 @@ struct WOLFSSL_CTX { #ifdef WOLFSSL_DTLS CallbackGenCookie CBIOCookie; /* gen cookie callback */ #endif /* WOLFSSL_DTLS */ +#ifdef WOLFSSL_DTLS_ECJPAKE + byte* ecJpakePw; + word32 ecJpakePwSz; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #ifdef WOLFSSL_SESSION_EXPORT #ifdef WOLFSSL_DTLS wc_dtls_export dtls_export; /* export function for DTLS session */ @@ -5022,9 +5059,16 @@ enum KeyExchangeAlgorithm { ecdhe_psk_kea, ecc_diffie_hellman_kea, ecc_static_diffie_hellman_kea, /* for verify suite only */ - any_kea + any_kea, + ecjpake_kea }; +#ifdef WOLFSSL_DTLS_ECJPAKE + #define IsEcJpakeKea(ssl) ((ssl)->specs.kea == ecjpake_kea) +#else + #define IsEcJpakeKea(ssl) 0 +#endif + /* Used with InitSuitesHashSigAlgo */ #define SIG_ECDSA 0x01 #define SIG_RSA 0x02 @@ -7208,6 +7252,11 @@ struct WOLFSSL { #ifdef WOLFSSL_DTLS_CID CIDInfo *dtlsCidInfo; #endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_ECJPAKE + EcJpake* ecJpake; /* handshake state */ + byte* ecJpakePw; + word32 ecJpakePwSz; +#endif /* WOLFSSL_DTLS_ECJPAKE */ #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_CALLBACKS diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 24ebab8000d..b7a70746243 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1997,6 +1997,15 @@ WOLFSSL_API int wolfSSL_dtls_get0_peer(WOLFSSL* ssl, const void** peer, WOLFSSL_API byte wolfSSL_is_stateful(WOLFSSL* ssl); +#if defined(WOLFCRYPT_HAVE_ECJPAKE) && defined(WOLFSSL_DTLS) +WOLFSSL_API int wolfSSL_CTX_set_ecjpake_password(WOLFSSL_CTX* ctx, + const unsigned char* pw, + unsigned int pwSz); +WOLFSSL_API int wolfSSL_set_ecjpake_password(WOLFSSL* ssl, + const unsigned char* pw, + unsigned int pwSz); +#endif + #if defined(WOLFSSL_SCTP) && defined(WOLFSSL_DTLS) WOLFSSL_API int wolfSSL_CTX_dtls_set_sctp(WOLFSSL_CTX* ctx); WOLFSSL_API int wolfSSL_dtls_set_sctp(WOLFSSL* ssl); diff --git a/wolfssl/wolfcrypt/ecjpake.h b/wolfssl/wolfcrypt/ecjpake.h new file mode 100644 index 00000000000..e1acc71cbeb --- /dev/null +++ b/wolfssl/wolfcrypt/ecjpake.h @@ -0,0 +1,120 @@ +/* ecjpake.h + * + * Copyright (C) 2006-2026 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 3 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/ecjpake.h +*/ + +/* EC-JPAKE password authenticated key exchange (RFC 8236 Section 3) with + * Schnorr NIZK proofs (RFC 8235 Section 3), profiled and encoded as in + * draft-cragie-tls-ecjpake-01: P-256, SHA-256, fixed "client"/"server" + * identities and the TLS ECJPAKEKeyKP wire structures. */ + +#ifndef WOLFCRYPT_ECJPAKE_H +#define WOLFCRYPT_ECJPAKE_H + +#include + +#ifdef WOLFCRYPT_HAVE_ECJPAKE + +#include +#include +#include + +#if !defined(HAVE_ECC) || defined(NO_SHA256) + #error "EC-JPAKE requires ECC and SHA-256" +#endif +#if defined(WOLFSSL_SP_MATH) || defined(WOLFSSL_NO_ECC_SW) + #error "EC-JPAKE requires generic ECC point arithmetic" +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Size of a P-256 field element / scalar in bytes. */ +#define WC_ECJPAKE_FIELD_SZ 32 +/* SEC1 uncompressed point: 0x04 || x || y */ +#define WC_ECJPAKE_POINT_SZ (1 + 2 * WC_ECJPAKE_FIELD_SZ) +/* ECJPAKEKeyKP: ECPoint X, ECPoint V, opaque r<1..2^8-1> */ +#define WC_ECJPAKE_KKP_SZ (2 * (1 + WC_ECJPAKE_POINT_SZ) + \ + 1 + WC_ECJPAKE_FIELD_SZ) +/* ECParameters: named_curve, NamedCurve */ +#define WC_ECJPAKE_ECPARAMS_SZ 3 + +/* Encoded size of a round one message (ECJPAKEKeyKPPairList). */ +#define WC_ECJPAKE_ROUND1_SZ (2 * WC_ECJPAKE_KKP_SZ) +/* Maximum encoded size of a round two message. */ +#define WC_ECJPAKE_ROUND2_SZ (WC_ECJPAKE_ECPARAMS_SZ + WC_ECJPAKE_KKP_SZ) +/* Size of the derived premaster secret. */ +#define WC_ECJPAKE_SECRET_SZ WC_SHA256_DIGEST_SIZE + +enum { + WC_ECJPAKE_CLIENT = 0, + WC_ECJPAKE_SERVER = 1 +}; + +typedef struct EcJpake { + int role; + int curveIdx; + word32 flags; + void* heap; + mp_digit mp; /* Montgomery setup value for prime */ + mp_int prime; + mp_int a; + mp_int order; + mp_int s; /* password scalar, then x2*s once keys set */ + mp_int xm2; /* own second round one private key */ + ecc_point* Xm1; /* own round one public keys */ + ecc_point* Xm2; + ecc_point* Xp1; /* peer round one public keys */ + ecc_point* Xp2; + ecc_point* Xp; /* peer round two public key */ +} EcJpake; + +WOLFSSL_API int wc_EcJpakeInit(EcJpake* jp, int role, int curveId, + const byte* secret, word32 secretSz, + void* heap); +WOLFSSL_API void wc_EcJpakeFree(EcJpake* jp); + +WOLFSSL_API int wc_EcJpakeWriteRound1(EcJpake* jp, WC_RNG* rng, byte* out, + word32* outSz); +WOLFSSL_API int wc_EcJpakeReadRound1(EcJpake* jp, const byte* in, + word32 inSz); +WOLFSSL_API int wc_EcJpakeWriteRound2(EcJpake* jp, WC_RNG* rng, byte* out, + word32* outSz); +WOLFSSL_API int wc_EcJpakeReadRound2(EcJpake* jp, const byte* in, + word32 inSz); +WOLFSSL_API int wc_EcJpakeDeriveSecret(EcJpake* jp, WC_RNG* rng, byte* out, + word32* outSz); + +/* Loads fixed round one private keys in place of WriteRound1, for known + * answer tests only. */ +WOLFSSL_TEST_VIS int wc_EcJpakeSetRound1Keys(EcJpake* jp, WC_RNG* rng, + const byte* x1, word32 x1Sz, + const byte* x2, word32 x2Sz); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFCRYPT_HAVE_ECJPAKE */ +#endif /* WOLFCRYPT_ECJPAKE_H */ diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index f3bc453cc24..6461903247b 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -67,6 +67,7 @@ nobase_include_HEADERS+= \ wolfssl/wolfcrypt/blake2-impl.h \ wolfssl/wolfcrypt/tfm.h \ wolfssl/wolfcrypt/srp.h \ + wolfssl/wolfcrypt/ecjpake.h \ wolfssl/wolfcrypt/types.h \ wolfssl/wolfcrypt/visibility.h \ wolfssl/wolfcrypt/logging.h \