From abf653f65f472f56d05119ca4f2cbc7a92df9e37 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Fri, 30 Oct 2026 12:32:58 -0400 Subject: [PATCH 1/4] Add EC-JPAKE password authenticated key exchange to wolfCrypt Implement EC-JPAKE (RFC 8236 Section 3) with Schnorr NIZK proofs (RFC 8235 Section 3), profiled as in draft-cragie-tls-ecjpake-01, the profile used by Thread commissioning: P-256, SHA-256, "client"/"server" identities, PMS = SHA-256(K.x), and the TLS ECJPAKEKeyKP and ECSchnorrZKP wire structures. - New API in wolfssl/wolfcrypt/ecjpake.h: wc_EcJpakeInit/Free, wc_EcJpakeWrite/ReadRound1, wc_EcJpakeWrite/ReadRound2 and wc_EcJpakeDeriveSecret. Round messages use the TLS encoding so the (D)TLS layer can carry them directly. - Peer points are fully validated, every proof is verified against the peer identity, and a shared secret that reduces to zero is rejected. Secret scalars go through wc_ecc_mulmod_ex2 with RNG blinding. - Calls must follow the protocol order; repeated or out of order calls return BAD_STATE_E. - Enable with --enable-ecjpake or -DWOLFSSL_ECJPAKE=yes (defines WOLFCRYPT_HAVE_ECJPAKE). Requires ECC and SHA-256; not supported with --enable-sp-math. - wolfcrypt/test/test.c: known answer tests for both roles using vectors from an independent implementation of the specifications, full handshakes with matching and mismatched passwords, and negative tests covering malformed encodings, out of range values, corrupted and reflected proofs, round binding, buffer sizes and API misuse. --- CMakeLists.txt | 12 + cmake/functions.cmake | 7 + cmake/options.h.in | 2 + configure.ac | 23 ++ src/include.am | 4 + wolfcrypt/src/ecjpake.c | 782 +++++++++++++++++++++++++++++++++++ wolfcrypt/test/test.c | 694 +++++++++++++++++++++++++++++++ wolfcrypt/test/test.h | 3 + wolfssl/wolfcrypt/ecjpake.h | 126 ++++++ wolfssl/wolfcrypt/include.am | 1 + 10 files changed, 1654 insertions(+) create mode 100644 wolfcrypt/src/ecjpake.c create mode 100644 wolfssl/wolfcrypt/ecjpake.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f3092326b72..1dec4967da6 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") 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/wolfcrypt/src/ecjpake.c b/wolfcrypt/src/ecjpake.c new file mode 100644 index 00000000000..89785885232 --- /dev/null +++ b/wolfcrypt/src/ecjpake.c @@ -0,0 +1,782 @@ +/* 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->G, jp->heap); + 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->G = jp->Xm1 = jp->Xm2 = jp->Xp1 = jp->Xp2 = jp->Xp = NULL; +} + +/* 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. */ +static int ecjpake_mul(EcJpake* jp, mp_int* k, ecc_point* P, ecc_point* R, + WC_RNG* rng) +{ + return wc_ecc_mulmod_ex2(k, P, R, &jp->a, &jp->prime, &jp->order, rng, 1, + jp->heap); +} + +static int ecjpake_to_mont(EcJpake* jp, ecc_point* p, ecc_point* r) +{ + int err; + + err = mp_mulmod(p->x, &jp->mu, &jp->prime, r->x); + if (err == MP_OKAY) { + err = mp_mulmod(p->y, &jp->mu, &jp->prime, r->y); + } + if (err == MP_OKAY) { + err = mp_copy(&jp->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; + ecc_point* tA; + ecc_point* tB; + + 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 = ecjpake_to_mont(jp, A, tA); + } + if (err == MP_OKAY) { + err = ecjpake_to_mont(jp, 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); + } + + 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; + ecc_point* V; + + V = wc_ecc_new_point_h(jp->heap); + if (V == NULL) { + err = MEMORY_E; + } + if (err == MP_OKAY) { + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->k, &jp->order); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->k, gen, V, rng); + } + if (err == MP_OKAY) { + err = ecjpake_zkp_hash(jp, gen, V, X, ecjpake_id(jp->role), &jp->h); + } + /* r = v - x*h mod n */ + if (err == MP_OKAY) { + err = mp_mulmod(x, &jp->h, &jp->order, &jp->h); + } + if (err == MP_OKAY) { + err = mp_submod(&jp->k, &jp->h, &jp->order, &jp->k); + } + 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(&jp->k, out + *idx + 1, + WC_ECJPAKE_FIELD_SZ); + } + if (err == MP_OKAY) { + *idx += 1 + WC_ECJPAKE_FIELD_SZ; + } + + mp_forcezero(&jp->k); + mp_forcezero(&jp->h); + 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; + word32 rSz = 0; + ecc_point* V; + ecc_point* Xh; + ecc_point* Gr; + + 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; + } + else { + 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(&jp->k, in + *idx, rSz); + *idx += rSz; + } + if (err == MP_OKAY && (mp_cmp(&jp->k, &jp->order) != MP_LT || + mp_iszero(&jp->k))) { + err = SIG_VERIFY_E; + } + + if (err == MP_OKAY) { + err = ecjpake_zkp_hash(jp, gen, V, X, + ecjpake_id(!jp->role), &jp->h); + } + if (err == MP_OKAY && mp_iszero(&jp->h)) { + err = SIG_VERIFY_E; + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->h, X, Xh, NULL); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->k, 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; + } + + 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->mu, &jp->s, + &jp->xm1); + if (err == MP_OKAY) { + err = mp_init_multi(&jp->xm2, &jp->k, &jp->h, &jp->t, NULL, NULL); + } + if (err != MP_OKAY) { + return err; + } + + jp->G = wc_ecc_new_point_h(heap); + 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->G == NULL || 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_read_radix(jp->G->x, dp->Gx, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_read_radix(jp->G->y, dp->Gy, MP_RADIX_HEX); + } + if (err == MP_OKAY) { + err = mp_set(jp->G->z, 1); + } + if (err == MP_OKAY) { + err = mp_montgomery_setup(&jp->prime, &jp->mp); + } + if (err == MP_OKAY) { + err = mp_montgomery_calc_normalization(&jp->mu, &jp->prime); + } + + /* 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->xm1); + mp_forcezero(&jp->xm2); + mp_forcezero(&jp->k); + mp_forcezero(&jp->h); + mp_forcezero(&jp->t); + mp_free(&jp->prime); + mp_free(&jp->a); + mp_free(&jp->order); + mp_free(&jp->mu); + ecjpake_free_points(jp); + jp->flags = 0; +} + +int wc_EcJpakeWriteRound1(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) +{ + int err; + word32 idx = 0; + + 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; + } + + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->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, &jp->xm1, jp->G, jp->Xm1, rng); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->xm2, jp->G, jp->Xm2, rng); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_write(jp, jp->G, &jp->xm1, jp->Xm1, rng, out, &idx); + } + if (err == MP_OKAY) { + err = ecjpake_kkp_write(jp, jp->G, &jp->xm2, jp->Xm2, rng, out, &idx); + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_WRITTEN; + *outSz = idx; + } + else { + mp_forcezero(&jp->xm1); + mp_forcezero(&jp->xm2); + } + return err; +} + +int wc_EcJpakeSetRound1Keys(EcJpake* jp, WC_RNG* rng, const byte* x1, + word32 x1Sz, const byte* x2, word32 x2Sz) +{ + int err; + + 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; + } + + err = mp_read_unsigned_bin(&jp->xm1, x1, x1Sz); + if (err == MP_OKAY) { + err = mp_read_unsigned_bin(&jp->xm2, x2, x2Sz); + } + if (err == MP_OKAY && (mp_iszero(&jp->xm1) || mp_iszero(&jp->xm2) || + mp_cmp(&jp->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, &jp->xm1, jp->G, jp->Xm1, rng); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->xm2, jp->G, jp->Xm2, rng); + } + + if (err == MP_OKAY) { + jp->flags |= ECJPAKE_FLAG_KEYS; + } + else { + mp_forcezero(&jp->xm1); + mp_forcezero(&jp->xm2); + } + return err; +} + +int wc_EcJpakeReadRound1(EcJpake* jp, const byte* in, word32 inSz) +{ + int err; + word32 idx = 0; + + if (jp == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + if (jp->flags & ECJPAKE_FLAG_ROUND1_READ) { + return BAD_STATE_E; + } + + err = ecjpake_kkp_read(jp, jp->G, jp->Xp1, in, inSz, &idx); + if (err == MP_OKAY) { + err = ecjpake_kkp_read(jp, 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; + } + 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 = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->t); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->t, 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->t, Xm, rng, out, &idx); + } + if (err == MP_OKAY) { + *outSz = idx; + } + + mp_forcezero(&jp->t); + 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 = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->t); + } + if (err == MP_OKAY) { + err = mp_sub(&jp->order, &jp->t, &jp->t); + } + if (err == MP_OKAY) { + err = ecjpake_mul(jp, &jp->t, jp->Xp2, K, rng); + } + 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)); + mp_forcezero(&jp->t); + if (K != NULL) { + wc_ecc_forcezero_point(K); + } + wc_ecc_del_point_h(K, 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/wolfcrypt/ecjpake.h b/wolfssl/wolfcrypt/ecjpake.h new file mode 100644 index 00000000000..83a088bfd4e --- /dev/null +++ b/wolfssl/wolfcrypt/ecjpake.h @@ -0,0 +1,126 @@ +/* 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 mu; /* Montgomery normalizer for prime */ + mp_int s; /* shared secret scalar */ + mp_int xm1; /* own round one private keys */ + mp_int xm2; + mp_int k; /* scratch scalars */ + mp_int h; + mp_int t; + ecc_point* G; /* curve base point */ + 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 \ From 0626da856282ec6b139ff1faff26d285ce775cf9 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Sat, 31 Oct 2026 12:07:08 -0400 Subject: [PATCH 2/4] Add DTLS 1.2 EC-JPAKE cipher suite (Thread commissioning) Implement TLS_ECJPAKE_WITH_AES_128_CCM_8 from draft-cragie-tls-ecjpake-01, DTLS 1.2 only. - Round one in the ecjpake_key_kp_pair hello extension; kept across HVR. - Round two in ServerKeyExchange and ClientKeyExchange; no certificates. - EC-JPAKE failures send handshake_failure. - Add wolfSSL_CTX_set_ecjpake_password() and wolfSSL_set_ecjpake_password(). - Codepoints 0xC0,0xFF and 256 come from the Thread specification. - Add tests/api/test_ecjpake.c. --- CMakeLists.txt | 1 + src/internal.c | 222 +++++++++++++++++- src/keys.c | 18 ++ src/ssl.c | 6 + src/ssl_api_dtls.c | 118 ++++++++++ src/ssl_api_hs.c | 10 +- src/tls.c | 208 ++++++++++++++++- tests/api.c | 2 + tests/api/include.am | 2 + tests/api/test_ecjpake.c | 449 ++++++++++++++++++++++++++++++++++++ tests/api/test_ecjpake.h | 40 ++++ tests/api/test_ossl_sk.c | 4 + tests/api/test_tls.c | 2 +- wolfcrypt/src/ecjpake.c | 316 ++++++++++++++++--------- wolfssl/internal.h | 44 +++- wolfssl/ssl.h | 9 + wolfssl/wolfcrypt/ecjpake.h | 10 +- 17 files changed, 1333 insertions(+), 128 deletions(-) create mode 100644 tests/api/test_ecjpake.c create mode 100644 tests/api/test_ecjpake.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dec4967da6..420831736ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5238,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/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..b0c749297c7 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -7034,6 +7034,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 +7069,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..3b7e5000f18 100644 --- a/src/ssl_api_dtls.c +++ b/src/ssl_api_dtls.c @@ -55,6 +55,124 @@ 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 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 (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..c90d12390e8 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 */ @@ -13675,6 +13685,168 @@ 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) { + 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 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 +15884,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 +16160,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 +16473,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 +17050,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 +17784,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 +19105,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 +19778,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..1314d105e61 --- /dev/null +++ b/tests/api/test_ecjpake.c @@ -0,0 +1,449 @@ +/* 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) + +#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) + 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) + 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 server is never left without the suite's round one data: a client + * offering the suite without the extension, 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) + 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; + + /* 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) + 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) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = 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; + + /* 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 index 89785885232..fad9029ef92 100644 --- a/wolfcrypt/src/ecjpake.c +++ b/wolfcrypt/src/ecjpake.c @@ -59,13 +59,33 @@ static const byte* ecjpake_id(int role) static void ecjpake_free_points(EcJpake* jp) { - wc_ecc_del_point_h(jp->G, jp->heap); 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->G = jp->Xm1 = jp->Xm2 = jp->Xp1 = jp->Xp2 = jp->Xp = NULL; + 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. */ @@ -137,16 +157,17 @@ static int ecjpake_mul(EcJpake* jp, mp_int* k, ecc_point* P, ecc_point* R, jp->heap); } -static int ecjpake_to_mont(EcJpake* jp, ecc_point* p, ecc_point* r) +static int ecjpake_to_mont(EcJpake* jp, mp_int* mu, ecc_point* p, + ecc_point* r) { int err; - err = mp_mulmod(p->x, &jp->mu, &jp->prime, r->x); + err = mp_mulmod(p->x, mu, &jp->prime, r->x); if (err == MP_OKAY) { - err = mp_mulmod(p->y, &jp->mu, &jp->prime, r->y); + err = mp_mulmod(p->y, mu, &jp->prime, r->y); } if (err == MP_OKAY) { - err = mp_copy(&jp->mu, r->z); + err = mp_copy(mu, r->z); } return err; } @@ -157,19 +178,31 @@ 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 = ecjpake_to_mont(jp, A, tA); + 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, B, tB); + 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, @@ -182,6 +215,10 @@ static int ecjpake_add(EcJpake* jp, ecc_point* A, ecc_point* B, ecc_point* R) 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; @@ -257,42 +294,56 @@ static int ecjpake_zkp_write(EcJpake* jp, ecc_point* gen, mp_int* x, 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) { - err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->k, &jp->order); + 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, &jp->k, gen, V, rng); + err = ecjpake_mul(jp, v, gen, V, rng); } if (err == MP_OKAY) { - err = ecjpake_zkp_hash(jp, gen, V, X, ecjpake_id(jp->role), &jp->h); + 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, &jp->h, &jp->order, &jp->h); + err = mp_mulmod(x, h, &jp->order, h); } if (err == MP_OKAY) { - err = mp_submod(&jp->k, &jp->h, &jp->order, &jp->k); + err = mp_submod(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(&jp->k, out + *idx + 1, - 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; } - mp_forcezero(&jp->k); - mp_forcezero(&jp->h); + 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; } @@ -301,19 +352,31 @@ static int ecjpake_zkp_write(EcJpake* jp, ecc_point* gen, mp_int* x, static int ecjpake_zkp_read(EcJpake* jp, ecc_point* gen, ecc_point* X, const byte* in, word32 inSz, word32* idx) { - int err; + 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; } - else { + 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); } @@ -339,26 +402,24 @@ static int ecjpake_zkp_read(EcJpake* jp, ecc_point* gen, ecc_point* X, } } if (err == MP_OKAY) { - err = mp_read_unsigned_bin(&jp->k, in + *idx, rSz); + err = mp_read_unsigned_bin(r, in + *idx, rSz); *idx += rSz; } - if (err == MP_OKAY && (mp_cmp(&jp->k, &jp->order) != MP_LT || - mp_iszero(&jp->k))) { + 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), &jp->h); + err = ecjpake_zkp_hash(jp, gen, V, X, ecjpake_id(!jp->role), h); } - if (err == MP_OKAY && mp_iszero(&jp->h)) { + if (err == MP_OKAY && mp_iszero(h)) { err = SIG_VERIFY_E; } if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->h, X, Xh, NULL); + err = ecjpake_mul(jp, h, X, Xh, NULL); } if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->k, gen, Gr, NULL); + err = ecjpake_mul(jp, r, gen, Gr, NULL); } if (err == MP_OKAY) { err = ecjpake_add(jp, Xh, Gr, Xh); @@ -370,6 +431,11 @@ static int ecjpake_zkp_read(EcJpake* jp, ecc_point* gen, ecc_point* X, 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); @@ -418,23 +484,19 @@ int wc_EcJpakeInit(EcJpake* jp, int role, int curveId, const byte* secret, jp->heap = heap; jp->curveIdx = wc_ecc_get_curve_idx(curveId); - err = mp_init_multi(&jp->prime, &jp->a, &jp->order, &jp->mu, &jp->s, - &jp->xm1); - if (err == MP_OKAY) { - err = mp_init_multi(&jp->xm2, &jp->k, &jp->h, &jp->t, NULL, NULL); - } + err = mp_init_multi(&jp->prime, &jp->a, &jp->order, &jp->s, &jp->xm2, + NULL); if (err != MP_OKAY) { return err; } - jp->G = wc_ecc_new_point_h(heap); 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->G == NULL || jp->Xm1 == NULL || jp->Xm2 == NULL || - jp->Xp1 == NULL || jp->Xp2 == NULL || jp->Xp == NULL) { + if (jp->Xm1 == NULL || jp->Xm2 == NULL || jp->Xp1 == NULL || + jp->Xp2 == NULL || jp->Xp == NULL) { err = MEMORY_E; } @@ -453,21 +515,9 @@ int wc_EcJpakeInit(EcJpake* jp, int role, int curveId, const byte* secret, if (err == MP_OKAY) { err = mp_read_radix(&jp->order, dp->order, MP_RADIX_HEX); } - if (err == MP_OKAY) { - err = mp_read_radix(jp->G->x, dp->Gx, MP_RADIX_HEX); - } - if (err == MP_OKAY) { - err = mp_read_radix(jp->G->y, dp->Gy, MP_RADIX_HEX); - } - if (err == MP_OKAY) { - err = mp_set(jp->G->z, 1); - } if (err == MP_OKAY) { err = mp_montgomery_setup(&jp->prime, &jp->mp); } - if (err == MP_OKAY) { - err = mp_montgomery_calc_normalization(&jp->mu, &jp->prime); - } /* s = int(password) mod n, which RFC 8236 requires to be non-zero */ if (err == MP_OKAY) { @@ -493,23 +543,21 @@ void wc_EcJpakeFree(EcJpake* jp) } mp_forcezero(&jp->s); - mp_forcezero(&jp->xm1); mp_forcezero(&jp->xm2); - mp_forcezero(&jp->k); - mp_forcezero(&jp->h); - mp_forcezero(&jp->t); mp_free(&jp->prime); mp_free(&jp->a); mp_free(&jp->order); - mp_free(&jp->mu); ecjpake_free_points(jp); jp->flags = 0; } int wc_EcJpakeWriteRound1(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) { - int err; + 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; @@ -521,76 +569,62 @@ int wc_EcJpakeWriteRound1(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) return BUFFER_E; } - err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->xm1, &jp->order); - if (err == MP_OKAY) { - err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->xm2, &jp->order); + 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 = ecjpake_mul(jp, &jp->xm1, jp->G, jp->Xm1, rng); + err = mp_init(xm1); + xInit = (err == MP_OKAY); } if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->xm2, jp->G, jp->Xm2, rng); + err = ecjpake_load_base(jp, G); } if (err == MP_OKAY) { - err = ecjpake_kkp_write(jp, jp->G, &jp->xm1, jp->Xm1, rng, out, &idx); + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, xm1, &jp->order); } if (err == MP_OKAY) { - err = ecjpake_kkp_write(jp, jp->G, &jp->xm2, jp->Xm2, rng, out, &idx); + err = wc_ecc_gen_k(rng, WC_ECJPAKE_FIELD_SZ, &jp->xm2, &jp->order); } - if (err == MP_OKAY) { - jp->flags |= ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_WRITTEN; - *outSz = idx; - } - else { - mp_forcezero(&jp->xm1); - mp_forcezero(&jp->xm2); - } - return err; -} - -int wc_EcJpakeSetRound1Keys(EcJpake* jp, WC_RNG* rng, const byte* x1, - word32 x1Sz, const byte* x2, word32 x2Sz) -{ - int err; - - 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; + err = ecjpake_mul(jp, xm1, G, jp->Xm1, rng); } - - err = mp_read_unsigned_bin(&jp->xm1, x1, x1Sz); if (err == MP_OKAY) { - err = mp_read_unsigned_bin(&jp->xm2, x2, x2Sz); + err = ecjpake_mul(jp, &jp->xm2, G, jp->Xm2, rng); } - if (err == MP_OKAY && (mp_iszero(&jp->xm1) || mp_iszero(&jp->xm2) || - mp_cmp(&jp->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_kkp_write(jp, G, xm1, jp->Xm1, rng, out, &idx); } if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->xm1, jp->G, jp->Xm1, rng); + err = ecjpake_kkp_write(jp, G, &jp->xm2, jp->Xm2, rng, out, &idx); } + /* s = x2 * s mod n */ if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->xm2, jp->G, jp->Xm2, rng); + err = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->s); } if (err == MP_OKAY) { - jp->flags |= ECJPAKE_FLAG_KEYS; + jp->flags |= ECJPAKE_FLAG_KEYS | ECJPAKE_FLAG_ROUND1_WRITTEN; + *outSz = idx; } else { - mp_forcezero(&jp->xm1); 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; + int err = MP_OKAY; word32 idx = 0; + ecc_point* G; if (jp == NULL || in == NULL) { return BAD_FUNC_ARG; @@ -599,9 +633,18 @@ int wc_EcJpakeReadRound1(EcJpake* jp, const byte* in, word32 inSz) return BAD_STATE_E; } - err = ecjpake_kkp_read(jp, jp->G, jp->Xp1, in, inSz, &idx); + 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, jp->G, jp->Xp2, in, inSz, &idx); + err = ecjpake_kkp_read(jp, G, jp->Xp2, in, inSz, &idx); } if (err == MP_OKAY && idx != inSz) { err = BUFFER_E; @@ -610,6 +653,7 @@ int wc_EcJpakeReadRound1(EcJpake* jp, const byte* in, word32 inSz) if (err == MP_OKAY) { jp->flags |= ECJPAKE_FLAG_ROUND1_READ; } + wc_ecc_del_point_h(G, jp->heap); return err; } @@ -648,10 +692,7 @@ int wc_EcJpakeWriteRound2(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) err = ecjpake_add3(jp, jp->Xm1, jp->Xp1, jp->Xp2, gen); } if (err == MP_OKAY) { - err = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->t); - } - if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->t, gen, Xm, rng); + err = ecjpake_mul(jp, &jp->s, gen, Xm, rng); } if (err == MP_OKAY && jp->role == WC_ECJPAKE_SERVER) { @@ -659,13 +700,12 @@ int wc_EcJpakeWriteRound2(EcJpake* jp, WC_RNG* rng, byte* out, word32* outSz) idx += WC_ECJPAKE_ECPARAMS_SZ; } if (err == MP_OKAY) { - err = ecjpake_kkp_write(jp, gen, &jp->t, Xm, rng, out, &idx); + err = ecjpake_kkp_write(jp, gen, &jp->s, Xm, rng, out, &idx); } if (err == MP_OKAY) { *outSz = idx; } - mp_forcezero(&jp->t); wc_ecc_del_point_h(Xm, jp->heap); wc_ecc_del_point_h(gen, jp->heap); return err; @@ -744,13 +784,11 @@ int wc_EcJpakeDeriveSecret(EcJpake* jp, WC_RNG* rng, byte* out, /* Client: K = (Xs - X4*(x2*s)) * x2 * Server: K = (Xc - X2*(x4*s)) * x4 */ if (err == MP_OKAY) { - err = mp_mulmod(&jp->xm2, &jp->s, &jp->order, &jp->t); - } - if (err == MP_OKAY) { - err = mp_sub(&jp->order, &jp->t, &jp->t); + err = ecjpake_mul(jp, &jp->s, jp->Xp2, K, rng); } + /* -P = (x, p - y) */ if (err == MP_OKAY) { - err = ecjpake_mul(jp, &jp->t, jp->Xp2, K, rng); + err = mp_sub(&jp->prime, K->y, K->y); } if (err == MP_OKAY) { err = ecjpake_add(jp, jp->Xp, K, K); @@ -771,7 +809,6 @@ int wc_EcJpakeDeriveSecret(EcJpake* jp, WC_RNG* rng, byte* out, } ForceZero(kx, sizeof(kx)); - mp_forcezero(&jp->t); if (K != NULL) { wc_ecc_forcezero_point(K); } @@ -779,4 +816,69 @@ int wc_EcJpakeDeriveSecret(EcJpake* jp, WC_RNG* rng, byte* out, 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/wolfssl/internal.h b/wolfssl/internal.h index 3ea829c0b4e..f4177f911b1 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, @@ -3369,6 +3383,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 +3451,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 +4414,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 +4689,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 +5052,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 +7245,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 index 83a088bfd4e..e1acc71cbeb 100644 --- a/wolfssl/wolfcrypt/ecjpake.h +++ b/wolfssl/wolfcrypt/ecjpake.h @@ -81,14 +81,8 @@ typedef struct EcJpake { mp_int prime; mp_int a; mp_int order; - mp_int mu; /* Montgomery normalizer for prime */ - mp_int s; /* shared secret scalar */ - mp_int xm1; /* own round one private keys */ - mp_int xm2; - mp_int k; /* scratch scalars */ - mp_int h; - mp_int t; - ecc_point* G; /* curve base point */ + 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 */ From c783576f43612c5b3ecccad61d7cca0b6b555822 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 3 Nov 2026 13:46:22 -0500 Subject: [PATCH 3/4] Fix EC-JPAKE review findings - Use constant time mapping for EC-JPAKE point multiplication. - Fix client only build (TLSX_SetResponse not compiled). - Size MAX_ENCRYPT_SZ for the client round two message. - Reject password changes once the exchange has started. - Require secp256r1 in the client's groups for the suite. - Add explicit API test guards and new negotiation/password tests. --- src/ssl_api_dtls.c | 5 ++++ src/tls.c | 11 ++++++++ tests/api/test_ecjpake.c | 61 ++++++++++++++++++++++++++++++++-------- wolfcrypt/src/ecjpake.c | 12 ++++++-- wolfssl/internal.h | 7 +++++ 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src/ssl_api_dtls.c b/src/ssl_api_dtls.c index 3b7e5000f18..b4f9befc3a7 100644 --- a/src/ssl_api_dtls.c +++ b/src/ssl_api_dtls.c @@ -125,6 +125,7 @@ int wolfSSL_CTX_set_ecjpake_password(WOLFSSL_CTX* ctx, const unsigned char* pw, * @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. @@ -143,6 +144,10 @@ int wolfSSL_set_ecjpake_password(WOLFSSL* ssl, const unsigned char* pw, 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) { diff --git a/src/tls.c b/src/tls.c index c90d12390e8..d65400060b1 100644 --- a/src/tls.c +++ b/src/tls.c @@ -6164,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 @@ -13811,6 +13818,7 @@ static int TLSX_EcJpake_Parse(WOLFSSL* ssl, const byte* input, word16 length, int ret; if (msgType == client_hello) { +#ifndef NO_WOLFSSL_SERVER if (ssl->ecJpakePw == NULL) { return 0; } @@ -13821,6 +13829,9 @@ static int TLSX_EcJpake_Parse(WOLFSSL* ssl, const byte* input, word16 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 || diff --git a/tests/api/test_ecjpake.c b/tests/api/test_ecjpake.c index 1314d105e61..4490a663e8f 100644 --- a/tests/api/test_ecjpake.c +++ b/tests/api/test_ecjpake.c @@ -35,7 +35,9 @@ #include #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS_ECJPAKE) + 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" @@ -164,7 +166,9 @@ int test_wolfSSL_ecjpake_dtls12(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS_ECJPAKE) + 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; @@ -230,7 +234,9 @@ int test_wolfSSL_ecjpake_wrong_password(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS_ECJPAKE) + 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; @@ -246,14 +252,15 @@ int test_wolfSSL_ecjpake_wrong_password(void) return EXPECT_RESULT(); } -/* A server is never left without the suite's round one data: a client - * offering the suite without the extension, or a server without a password, - * fails with handshake_failure (Section 6). */ +/* 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_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; @@ -269,6 +276,23 @@ int test_wolfSSL_ecjpake_negotiation(void) 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, @@ -309,7 +333,9 @@ int test_wolfSSL_ecjpake_bad_proof(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS_ECJPAKE) + 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; @@ -381,9 +407,12 @@ int test_wolfSSL_ecjpake_set_password(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS_ECJPAKE) - WOLFSSL_CTX* ctx = NULL; - WOLFSSL* ssl = NULL; + 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)); @@ -422,6 +451,16 @@ int test_wolfSSL_ecjpake_set_password(void) 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); + /* 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), diff --git a/wolfcrypt/src/ecjpake.c b/wolfcrypt/src/ecjpake.c index fad9029ef92..4391180bd50 100644 --- a/wolfcrypt/src/ecjpake.c +++ b/wolfcrypt/src/ecjpake.c @@ -149,12 +149,18 @@ static int ecjpake_read_point(EcJpake* jp, const byte* in, word32 inSz, return err; } -/* R = k * P, affine. */ +/* 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) { - return wc_ecc_mulmod_ex2(k, P, R, &jp->a, &jp->prime, &jp->order, rng, 1, - jp->heap); + 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, diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f4177f911b1..a9801bea83e 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1411,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 From c4a95d6cae2ca058e65df97b3864be70577d590e Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Wed, 4 Nov 2026 15:43:32 -0500 Subject: [PATCH 4/4] Fix additional EC-JPAKE review findings - Use constant time subtraction for the Schnorr proof response. - Free EC-JPAKE state in wolfSSL_clear() so the password can change. --- src/ssl.c | 3 +++ tests/api/test_ecjpake.c | 14 ++++++++++++++ wolfcrypt/src/ecjpake.c | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/ssl.c b/src/ssl.c index b0c749297c7..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; diff --git a/tests/api/test_ecjpake.c b/tests/api/test_ecjpake.c index 4490a663e8f..c41f90ee142 100644 --- a/tests/api/test_ecjpake.c +++ b/tests/api/test_ecjpake.c @@ -460,6 +460,20 @@ int test_wolfSSL_ecjpake_set_password(void) 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())); diff --git a/wolfcrypt/src/ecjpake.c b/wolfcrypt/src/ecjpake.c index 4391180bd50..e8191728ab0 100644 --- a/wolfcrypt/src/ecjpake.c +++ b/wolfcrypt/src/ecjpake.c @@ -332,7 +332,7 @@ static int ecjpake_zkp_write(EcJpake* jp, ecc_point* gen, mp_int* x, err = mp_mulmod(x, h, &jp->order, h); } if (err == MP_OKAY) { - err = mp_submod(v, h, &jp->order, v); + err = mp_submod_ct(v, h, &jp->order, v); } if (err == MP_OKAY) { err = ecjpake_write_point(V, out, idx);