diff --git a/CMakeLists.txt b/CMakeLists.txt index 772da1456d5..f08d722a289 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,6 +402,24 @@ if(WOLFSSL_CERT_WITH_EXTERN_PSK) endif() endif() +# External PSK Importer (RFC 9258) +add_option("WOLFSSL_EXTERNAL_PSK_IMPORTER" + "Enable importing external PSKs for TLS 1.3 per RFC 9258 (default: disabled)" + "no" "yes;no") + +if(WOLFSSL_EXTERNAL_PSK_IMPORTER) + if(NOT WOLFSSL_TLS13) + message(WARNING "TLS 1.3 is disabled - disabling psk-importer") + override_cache(WOLFSSL_EXTERNAL_PSK_IMPORTER "no") + elseif(NOT WOLFSSL_PSK) + message(WARNING "PSK is disabled - disabling psk-importer") + override_cache(WOLFSSL_EXTERNAL_PSK_IMPORTER "no") + else() + list(APPEND WOLFSSL_DEFINITIONS + "-DWOLFSSL_EXTERNAL_PSK_IMPORTER") + endif() +endif() + # Hello Retry Request Cookie add_option("WOLFSSL_HRR_COOKIE" "Enable the server to send Cookie Extension in HRR with state (default: disabled)" diff --git a/cmake/options.h.in b/cmake/options.h.in index dfdee642ca6..eb81710189d 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -315,6 +315,8 @@ extern "C" { #cmakedefine WOLFSSL_DTLS_CH_FRAG #undef WOLFSSL_CERT_WITH_EXTERN_PSK #cmakedefine WOLFSSL_CERT_WITH_EXTERN_PSK +#undef WOLFSSL_EXTERNAL_PSK_IMPORTER +#cmakedefine WOLFSSL_EXTERNAL_PSK_IMPORTER #undef WOLFSSL_EITHER_SIDE #cmakedefine WOLFSSL_EITHER_SIDE #undef WOLFSSL_ENCRYPTED_KEYS diff --git a/configure.ac b/configure.ac index c1d2dd089ef..f7240362fd9 100644 --- a/configure.ac +++ b/configure.ac @@ -5298,6 +5298,27 @@ then fi fi +# External PSK Importer (RFC 9258) +AC_ARG_ENABLE([psk-importer], + [AS_HELP_STRING([--enable-psk-importer],[Enable importing external PSKs for TLS 1.3 per RFC 9258 (default: disabled)])], + [ ENABLED_PSK_IMPORTER=$enableval ], + [ ENABLED_PSK_IMPORTER=no ] + ) +if test "$ENABLED_PSK_IMPORTER" = "yes" +then + if test "$ENABLED_TLS13" = "no" + then + AC_MSG_NOTICE([TLS 1.3 is disabled - disabling psk-importer]) + ENABLED_PSK_IMPORTER="no" + elif test "$ENABLED_PSK" = "no" + then + AC_MSG_NOTICE([PSK is disabled - disabling psk-importer]) + ENABLED_PSK_IMPORTER="no" + else + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_EXTERNAL_PSK_IMPORTER" + fi +fi + # ERROR STRINGS AC_ARG_ENABLE([errorstrings], [AS_HELP_STRING([--enable-errorstrings],[Enable error strings table (default: enabled)])], diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 1a56a4ae7c7..1913bf9a1c5 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -15221,6 +15221,118 @@ void wolfSSL_CTX_set_psk_server_tls13_callback(WOLFSSL_CTX* ctx, void wolfSSL_set_psk_server_tls13_callback(WOLFSSL* ssl, wc_psk_server_tls13_callback cb); +/*! + \ingroup Setup + + \brief This function sets, on the WOLFSSL_CTX, the client side callback used + to import an external Pre-Shared Key (PSK) for TLS v1.3 connections per + RFC 9258. The callback returns the external identity, an optional context + and the external PSK; wolfSSL derives the ImportedIdentity and the imported + PSK used in the handshake. The identity, context and key are opaque byte + buffers: on entry each length argument holds the buffer capacity and the + callback overwrites it with the actual length. The context is limited to + MAX_PSK_CTX_LEN bytes and the identity to MAX_PSK_ID_LEN bytes. The hashAlgo + argument is the hash associated with the external PSK; it is pre-set to + WC_SHA256 (the RFC 9258 default) and only needs to be changed for an EPSK + associated with a different hash. The callback must be deterministic: it is + invoked more than once per connection (when building the ClientHello and + again while computing the binders) and must return the same identity, + context, key and hash each time. Requires WOLFSSL_EXTERNAL_PSK_IMPORTER. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure. + \param [in] cb a client PSK importer callback. + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_set_psk_client_importer_callback(ctx, my_psk_importer_cb); + \endcode + + \sa wolfSSL_set_psk_client_importer_callback + \sa wolfSSL_CTX_set_psk_server_importer_callback +*/ +void wolfSSL_CTX_set_psk_client_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_client_importer_callback cb); + +/*! + \ingroup Setup + + \brief This function sets, on the WOLFSSL object, the client side callback + used to import an external Pre-Shared Key (PSK) for TLS v1.3 connections per + RFC 9258. Requires WOLFSSL_EXTERNAL_PSK_IMPORTER. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] cb a client PSK importer callback. + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_set_psk_client_importer_callback(ssl, my_psk_importer_cb); + \endcode + + \sa wolfSSL_CTX_set_psk_client_importer_callback + \sa wolfSSL_set_psk_server_importer_callback +*/ +void wolfSSL_set_psk_client_importer_callback(WOLFSSL* ssl, + wc_psk_client_importer_callback cb); + +/*! + \ingroup Setup + + \brief This function sets, on the WOLFSSL_CTX, the server side callback used + to import an external Pre-Shared Key (PSK) for TLS v1.3 connections per + RFC 9258. Given a received external identity and optional context (each an + opaque buffer with an explicit length), the callback returns the matching + external PSK. The hashAlgo argument is the hash associated with the external + PSK; it is pre-set to WC_SHA256 and must be set to the same value the client + used for that EPSK. The callback must be deterministic: it may be invoked + more than once per connection (once per candidate cipher suite) and must + return the same key and hash each time. Requires + WOLFSSL_EXTERNAL_PSK_IMPORTER. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure. + \param [in] cb a server PSK importer callback. + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_set_psk_server_importer_callback(ctx, my_psk_importer_cb); + \endcode + + \sa wolfSSL_set_psk_server_importer_callback + \sa wolfSSL_CTX_set_psk_client_importer_callback +*/ +void wolfSSL_CTX_set_psk_server_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_server_importer_callback cb); + +/*! + \ingroup Setup + + \brief This function sets, on the WOLFSSL object, the server side callback + used to import an external Pre-Shared Key (PSK) for TLS v1.3 connections per + RFC 9258. Requires WOLFSSL_EXTERNAL_PSK_IMPORTER. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] cb a server PSK importer callback. + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_set_psk_server_importer_callback(ssl, my_psk_importer_cb); + \endcode + + \sa wolfSSL_CTX_set_psk_server_importer_callback + \sa wolfSSL_set_psk_client_importer_callback +*/ +void wolfSSL_set_psk_server_importer_callback(WOLFSSL* ssl, + wc_psk_server_importer_callback cb); + /*! \ingroup Setup diff --git a/examples/client/client.c b/examples/client/client.c index 64958f2091e..23dae8a9808 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -1466,6 +1466,10 @@ static const char* client_usage_msg[][81] = { !defined(NO_PSK) "--psk-with-certs Use TLS 1.3 PSK with certificates\n", /* 74 */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + "--psk-importer Import an external PSK per RFC 9258\n", +#endif #ifdef HAVE_RPK "--rpk Use RPK for the defined certificates\n", /* 75 */ #endif @@ -1736,6 +1740,10 @@ static const char* client_usage_msg[][81] = { !defined(NO_PSK) "--psk-with-certs Use TLS 1.3 PSK with certificates\n", /* 74 */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + "--psk-importer Import an external PSK per RFC 9258\n", +#endif #ifdef HAVE_RPK "--rpk Use RPK for the defined certificates\n", /* 75 */ #endif @@ -1989,6 +1997,10 @@ static void Usage(void) !defined(NO_PSK) printf("%s", msg[++msgid]); /* --psk-with-certs */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + printf("%s", msg[++msgid]); /* --psk-importer */ +#endif #ifdef HAVE_RPK printf("%s", msg[++msgid]); /* --rpk */ #endif @@ -2192,6 +2204,10 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ !defined(NO_PSK) { "psk-with-certs", 0, 272 }, +#endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + { "psk-importer", 0, 273 }, #endif { 0, 0, 0 } }; @@ -2201,6 +2217,7 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) int usePsk = 0; int opensslPsk = 0; int usePskWithCerts = 0; + int usePskImporter = 0; int useAnon = 0; int sendGET = 0; int benchmark = 0; @@ -2441,6 +2458,7 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) (void)opensslPsk; (void)fileFormat; (void)usePskWithCerts; + (void)usePskImporter; StackTrap(); /* Reinitialize the global myVerifyAction. */ @@ -3103,6 +3121,15 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) break; #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + case 273: + /* Import an external PSK per RFC 9258. */ + usePskImporter = 1; + usePsk = 1; + break; +#endif + default: Usage(); XEXIT_T(MY_EX_USAGE); @@ -3518,6 +3545,15 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #ifndef NO_PSK const char *defaultCipherList = cipherList; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) + if (usePskImporter) { + /* RFC 9258 external PSK importer (TLS 1.3 only). */ + wolfSSL_CTX_set_psk_client_importer_callback(ctx, + my_psk_client_importer_cb); + } + else +#endif + { wolfSSL_CTX_set_psk_client_callback(ctx, my_psk_client_cb); #ifdef WOLFSSL_TLS13 #if !defined(WOLFSSL_PSK_TLS13_CB) && !defined(WOLFSSL_PSK_ONE_ID) @@ -3538,7 +3574,8 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) } } #endif -#endif +#endif /* WOLFSSL_TLS13 */ + } /* end of non-importer PSK setup */ if (defaultCipherList == NULL) { #if defined(HAVE_AESGCM) && !defined(NO_DH) #ifdef WOLFSSL_TLS13 diff --git a/examples/server/server.c b/examples/server/server.c index 2ac169dce57..3f23da1bcdd 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1132,6 +1132,10 @@ static const char* server_usage_msg[][71] = { !defined(NO_PSK) "--psk-with-certs Use TLS 1.3 PSK with certificates\n", /* 65 */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + "--psk-importer Import an external PSK per RFC 9258\n", +#endif #ifdef WOLFSSL_DUAL_ALG_CERTS "--altPrivKey Generate alternative signature with this key.\n", /* 66 */ #endif @@ -1358,6 +1362,10 @@ static const char* server_usage_msg[][71] = { !defined(NO_PSK) "--psk-with-certs Use TLS 1.3 PSK with certificates\n", /* 65 */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + "--psk-importer Import an external PSK per RFC 9258\n", +#endif #ifdef WOLFSSL_DUAL_ALG_CERTS "--altPrivKey Generate alternative signature with this key.\n", /* 66 */ #endif @@ -1537,6 +1545,10 @@ static void Usage(void) !defined(NO_PSK) printf("%s", msg[++msgId]); /* --psk-with-certs */ #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + printf("%s", msg[++msgId]); /* --psk-importer */ +#endif #ifdef WOLFSSL_DUAL_ALG_CERTS printf("%s", msg[++msgId]); /* --altPrivKey */ #endif @@ -1673,6 +1685,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ !defined(NO_PSK) { "psk-with-certs", 0, 271 }, +#endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + { "psk-importer", 0, 272 }, #endif { 0, 0, 0 } }; @@ -1691,6 +1707,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) int usePsk = 0; int usePskPlus = 0; int usePskWithCerts = 0; + int usePskImporter = 0; int useAnon = 0; int doDTLS = 0; int dtlsUDP = 0; @@ -1928,6 +1945,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) (void)usePqc; (void)altPrivKey; (void)usePskWithCerts; + (void)usePskImporter; #ifdef WOLFSSL_TIRTOS fdOpenSession(Task_self()); @@ -2625,6 +2643,15 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) break; #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) + case 272: + /* Import an external PSK per RFC 9258. */ + usePskImporter = 1; + usePsk = 1; + break; +#endif + case -1: default: Usage(); @@ -3057,6 +3084,15 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #ifndef NO_PSK const char *defaultCipherList = cipherList; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) + if (usePskImporter) { + /* RFC 9258 external PSK importer (TLS 1.3 only). */ + wolfSSL_CTX_set_psk_server_importer_callback(ctx, + my_psk_server_importer_cb); + } + else +#endif + { wolfSSL_CTX_set_psk_server_callback(ctx, my_psk_server_cb); #ifdef WOLFSSL_TLS13 wolfSSL_CTX_set_psk_server_tls13_callback(ctx, my_psk_server_tls13_cb); @@ -3068,7 +3104,8 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) } } #endif - #endif + #endif /* WOLFSSL_TLS13 */ + } /* end of non-importer PSK setup */ if (sendPskIdentityHint == 1) wolfSSL_CTX_use_psk_identity_hint(ctx, "cyassl server"); diff --git a/src/internal.c b/src/internal.c index fed9d370dea..60aa16157a5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -7234,7 +7234,11 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.client_psk_cs_cb = ctx->client_psk_cs_cb; ssl->options.client_psk_tls13_cb = ctx->client_psk_tls13_cb; ssl->options.server_psk_tls13_cb = ctx->server_psk_tls13_cb; +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + ssl->options.client_psk_importer_cb = ctx->client_psk_importer_cb; + ssl->options.server_psk_importer_cb = ctx->server_psk_importer_cb; #endif +#endif /* WOLFSSL_TLS13 */ #endif /* NO_PSK */ #ifdef WOLFSSL_EARLY_DATA if (ssl->options.side == WOLFSSL_SERVER_END) diff --git a/src/tls.c b/src/tls.c index ace0f7f979a..95b2807031d 100644 --- a/src/tls.c +++ b/src/tls.c @@ -12620,6 +12620,159 @@ int TLSX_PreSharedKey_Use(TLSX** extensions, const byte* identity, word16 len, return 0; } +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +/* Create an ImportedIdentity object based on RFC 9258. + * + * id The external identity. + * id_len Length of the external identity. + * ctx The optional external context. + * ctx_len Length of the external context. + * hmac The underlying HMAC algorithm to be used for the TLS HKDF. + * protocol The protocol version to be used for the connection. + * output The buffer to write the ImportedIdentity object into. + * out_len On entry, size of the output buffer. + * On exit, the number of bytes written into the buffer. + * + * When output is NULL and out_len is not NULL, the function will store the + * size of the buffer needed to write the ImportedIdentity object. + * Returns 0 on success and other values indicate failure. + */ +int TLSX_PreSharedKey_CreateImportedIdentity(const byte* id, word16 id_len, + const byte* ctx, word16 ctx_len, byte hmac, ProtocolVersion protocol, + byte* output, word16* out_len) +{ + int ret = 0; + word16 idx = 0; + + if ((output == NULL && out_len == NULL) || id == NULL || id_len == 0 || + !IsAtLeastTLSv1_3(protocol)) + return BAD_FUNC_ARG; + + if (output == NULL && out_len != NULL) { + /* Calculate the size of the ImportedIdentity object. Identity + * and context are both prefixed with their length as 16-bit. + * 'target_protocol' and 'target_kdf' are both 16-bit integers. + */ + *out_len = OPAQUE16_LEN + id_len + OPAQUE16_LEN + ctx_len + + OPAQUE16_LEN + OPAQUE16_LEN; + return 0; + } + + /* Check if output buffer is big enough */ + if (*out_len < (OPAQUE16_LEN + id_len + OPAQUE16_LEN + ctx_len + + OPAQUE16_LEN + OPAQUE16_LEN)) + return BUFFER_E; + + /* Write identity */ + c16toa(id_len, output + idx); + idx += OPAQUE16_LEN; + XMEMCPY(output + idx, id, id_len); + idx += id_len; + + /* Write context */ + if (ctx != NULL && ctx_len > 0) { + c16toa(ctx_len, output + idx); + idx += OPAQUE16_LEN; + XMEMCPY(output + idx, ctx, ctx_len); + idx += ctx_len; + } else { + c16toa(0, output + idx); + idx += OPAQUE16_LEN; + } + + /* Write target_protocol */ + output[idx++] = protocol.major; + output[idx++] = protocol.minor; + + /* Write target_kdf */ + if (hmac == sha256_mac) { + output[idx++] = 0x00; + output[idx++] = 0x01; + } else if (hmac == sha384_mac) { + output[idx++] = 0x00; + output[idx++] = 0x02; + } else + return BAD_FUNC_ARG; + + *out_len = idx; + + return ret; +} + +/* Parse an ImportedIdentity object received on the wire (RFC 9258). + * + * input The serialized ImportedIdentity. The returned id/ctx pointers + * alias into this buffer; the caller must not modify it. + * length Length of the serialized ImportedIdentity. + * id On exit, points to the external identity within input. + * id_len On exit, length of the external identity (always non-zero). + * ctx On exit, points to the optional context within input, or NULL. + * ctx_len On exit, length of the optional context (may be zero). + * hkdf On exit, the HMAC algorithm matching target_kdf. + * protocol On exit, the parsed target_protocol. + * + * Returns 0 on success and other values indicate failure. + */ +int TLSX_PreSharedKey_ParseImportedIdentity(byte* input, word16 length, + byte** id, word16* id_len, byte** ctx, word16* ctx_len, + byte* hkdf, ProtocolVersion* protocol) +{ + int ret = 0; + word16 idx = 0; + word16 target_kdf = 0; + + /* Validate input. The minimum length is 4 times a 16-bit integer for + * the two length fields (id and ctx), the hkdf and the protocol version. + */ + if (input == NULL || length < (4 * OPAQUE16_LEN)) + return BAD_FUNC_ARG; + + /* Get identity len. RFC 9258 defines external_identity<1..2^16-1>, so an + * empty identity is invalid and must be rejected (a zero-length identity + * would otherwise leave *id NULL for callers to dereference). */ + ato16(input + idx, id_len); + idx += OPAQUE16_LEN; + if (*id_len == 0) + return BAD_FUNC_ARG; + if (*id_len > length - idx - (3 * OPAQUE16_LEN)) + return BUFFER_E; + + /* Get identity */ + *id = input + idx; + idx += *id_len; + + /* Get context len */ + ato16(input + idx, ctx_len); + idx += OPAQUE16_LEN; + if (*ctx_len > length - idx - (2 * OPAQUE16_LEN)) + return BUFFER_E; + + /* Get context */ + if (*ctx_len > 0) { + *ctx = input + idx; + idx += *ctx_len; + } + else + *ctx = NULL; + + /* Get target_protocol */ + protocol->major = input[idx++]; + protocol->minor = input[idx++]; + + /* Get target_kdf */ + ato16(input + idx, &target_kdf); + idx += OPAQUE16_LEN; + if (target_kdf == 0x0001) + *hkdf = sha256_mac; + else if (target_kdf == 0x0002) + *hkdf = sha384_mac; + else + return BAD_FUNC_ARG; + + return ret; +} +#endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ + #define PSK_FREE_ALL TLSX_PreSharedKey_FreeAll #define PSK_GET_SIZE TLSX_PreSharedKey_GetSize #define PSK_WRITE TLSX_PreSharedKey_Write @@ -16487,6 +16640,160 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #endif #ifndef NO_PSK #ifndef WOLFSSL_PSK_ONE_ID + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (ssl->options.client_psk_importer_cb != NULL) { + int i; + /* On entry these hold the buffer capacities; the callback + * overwrites them with the actual opaque lengths. */ + word32 identitySz = MAX_PSK_ID_LEN; + word32 ctxSz = MAX_PSK_CTX_LEN; + /* EPSK hash (RFC 9258 default SHA-256). Only the identity is + * built here; the actual import derivation, which uses this + * hash, happens later in SetupPskKey(). */ + int importerHash = WC_SHA256; + byte* importedIdentity = NULL; + /* Track which target KDFs already produced an identity so a + * single KDF is not emitted more than once (RFC 9258 derives + * one IPSK per (protocol, KDF), independent of cipher suite). + * Index 0 = HKDF_SHA256, index 1 = HKDF_SHA384. */ + byte seenKdf[2]; + #ifndef WOLFSSL_SMALL_STACK + byte ctx[MAX_PSK_CTX_LEN]; + #else + byte* ctx = (byte*)XMALLOC(MAX_PSK_CTX_LEN, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) + return MEMORY_ERROR; + #endif + ssl->arrays->psk_keySz = MAX_PSK_KEY_LEN; + XMEMSET(seenKdf, 0, sizeof(seenKdf)); + + /* Get the external PSK data (identity, optional context and + * the actual key) by executing the user callback. The identity + * is an opaque, length-delimited blob (RFC 9258). */ + ret = ssl->options.client_psk_importer_cb(ssl, + (byte*)ssl->arrays->client_identity, &identitySz, ctx, + &ctxSz, ssl->arrays->psk_key, &ssl->arrays->psk_keySz, + &importerHash); + if (ret != 0) { + ret = PSK_KEY_ERROR; + goto importer_cleanup; + } + + if (identitySz > MAX_PSK_ID_LEN || + ctxSz > MAX_PSK_CTX_LEN || + ssl->arrays->psk_keySz > MAX_PSK_KEY_LEN) { + ret = PSK_KEY_ERROR; + goto importer_cleanup; + } + + if (ssl->arrays->psk_keySz > 0 && identitySz > 0) { + const Suites* suites = WOLFSSL_SUITES(ssl); + + for (i = 0; i < suites->suiteSz; i += 2) { + word16 importedIdentitySz = 0; + byte cipherSuite0 = suites->suites[i + 0]; + byte cipherSuite = suites->suites[i + 1]; + byte suiteMac; + PreSharedKey* psk = NULL; + + #ifdef HAVE_NULL_CIPHER + if (cipherSuite0 == ECC_BYTE || + cipherSuite0 == ECDHE_PSK_BYTE) { + if (cipherSuite != TLS_SHA256_SHA256 && + cipherSuite != TLS_SHA384_SHA384) { + continue; + } + } + else + #endif + #if (defined(WOLFSSL_SM4_GCM) || \ + defined(WOLFSSL_SM4_CCM)) && \ + defined(WOLFSSL_SM3) + if (cipherSuite0 == CIPHER_BYTE) { + if ((cipherSuite != TLS_SM4_GCM_SM3) && + (cipherSuite != TLS_SM4_CCM_SM3)) { + continue; + } + } + else + #endif + if (cipherSuite0 != TLS13_BYTE) + continue; + + /* RFC 9258 only defines the HKDF_SHA256 and + * HKDF_SHA384 target KDFs. Skip suites with any other + * MAC, and skip a KDF already handled. */ + suiteMac = SuiteMac(suites->suites + i); + if (suiteMac == sha256_mac) { + if (seenKdf[0]) + continue; + seenKdf[0] = 1; + } + else if (suiteMac == sha384_mac) { + if (seenKdf[1]) + continue; + seenKdf[1] = 1; + } + else { + continue; + } + + /* Get length of imported identity */ + ret = TLSX_PreSharedKey_CreateImportedIdentity( + (byte*)ssl->arrays->client_identity, + (word16)identitySz, ctx, (word16)ctxSz, + suiteMac, ssl->version, NULL, + &importedIdentitySz); + if (ret != 0) + goto importer_cleanup; + + importedIdentity = (byte*)XMALLOC(importedIdentitySz, + ssl->heap, + DYNAMIC_TYPE_TLSX); + if (importedIdentity == NULL) { + ret = MEMORY_ERROR; + goto importer_cleanup; + } + + /* Generate the actual imported identity */ + ret = TLSX_PreSharedKey_CreateImportedIdentity( + (byte*)ssl->arrays->client_identity, + (word16)identitySz, ctx, (word16)ctxSz, + suiteMac, ssl->version, importedIdentity, + &importedIdentitySz); + if (ret != 0) + goto importer_cleanup; + + /* Store the PSK */ + ret = TLSX_PreSharedKey_Use(&ssl->extensions, + importedIdentity, importedIdentitySz, + 0, suiteMac, cipherSuite0, cipherSuite, 0, &psk, + ssl->heap); + if (ret != 0) + goto importer_cleanup; + + /* Set flag in the PSK object to indicate that this + * one is imported. We use that flag to select the + * proper derive method for the binder key. */ + psk->imported = 1; + + XFREE(importedIdentity, ssl->heap, DYNAMIC_TYPE_TLSX); + importedIdentity = NULL; + usingPSK = 1; + } + } + ret = 0; + importer_cleanup: + XFREE(importedIdentity, ssl->heap, DYNAMIC_TYPE_TLSX); + #ifdef WOLFSSL_SMALL_STACK + XFREE(ctx, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + #endif + if (ret != 0) + return ret; + } + else + #endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ if (ssl->options.client_psk_cs_cb != NULL) { int i; const Suites* suites = WOLFSSL_SUITES(ssl); @@ -16554,7 +16861,7 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) usingPSK = 1; } else - #endif + #endif /* WOLFSSL_PSK_ONE_ID */ if (ssl->options.client_psk_cb != NULL || ssl->options.client_psk_tls13_cb != NULL) { /* Default cipher suite. */ diff --git a/src/tls13.c b/src/tls13.c index ecfd5946dd8..4713b64e5b8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -563,6 +563,31 @@ static int DeriveBinderKey(WOLFSSL* ssl, byte* key) binderKeyLabel, BINDER_KEY_LABEL_SZ, NULL, 0, ssl->specs.mac_algorithm); } + +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +/* The length of the binder key label. */ +#define BINDER_KEY_IMPORTED_LABEL_SZ 10 +/* The binder key imported label. */ +static const byte binderKeyImportedLabel[BINDER_KEY_IMPORTED_LABEL_SZ + 1] = + "imp binder"; + +/* Derive the binder key for imported PSKs. + * + * ssl The SSL/TLS object. + * key The derived key. + * returns 0 on success, otherwise failure. + */ +static int DeriveBinderKeyImported(WOLFSSL* ssl, byte* key) +{ + WOLFSSL_MSG("Derive Binder Key- Imported"); + if (ssl == NULL || ssl->arrays == NULL) { + return BAD_FUNC_ARG; + } + return DeriveKeyMsg(ssl, key, -1, ssl->arrays->secret, + binderKeyImportedLabel, BINDER_KEY_IMPORTED_LABEL_SZ, + NULL, 0, ssl->specs.mac_algorithm); +} +#endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ #endif /* !NO_PSK */ #if defined(HAVE_SESSION_TICKET) && \ @@ -1177,6 +1202,227 @@ static int Tls13_HKDF_Extract(WOLFSSL *ssl, byte* prk, const byte* salt, return ret; } +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +/* The length of the derived psk label. */ +#define DERIVED_PSK_LABEL_SZ 11 +/* The derived psk label. */ +static const byte derivedPskLabel[DERIVED_PSK_LABEL_SZ + 1] = + "derived psk"; + +/* Derive an imported PSK from an external PSK and its ImportedIdentity + * (RFC 9258, Section 3.1). This core routine is independent of WOLFSSL state so + * it can be exercised directly with known-answer test vectors. + * + * epsk/epskSz External PSK base key. + * importedIdentity Serialized ImportedIdentity (hashed as the context). + * importedIdentitySz Length of importedIdentity. + * importerHash Hash associated with the EPSK (e.g. WC_SHA256), used for + * Hash(ImportedIdentity) and the HKDF. Independent of the + * target KDF. + * targetKdfMac MAC algorithm of the target_kdf; sets the output length + * L (the digest size of that KDF's hash). + * protocolMinor (D)TLS minor version, selecting the protocol label. + * isDtls Non-zero for DTLS 1.3. + * out Receives ipskx; must hold at least L bytes. May alias + * epsk (the input is fully consumed before out is written). + * outSz On exit, set to L. + * + * Returns 0 on success, otherwise a negative error. + */ +WOLFSSL_LOCAL int DeriveImportedPsk(const byte* epsk, word32 epskSz, + const byte* importedIdentity, word32 importedIdentitySz, + int importerHash, byte targetKdfMac, byte protocolMinor, int isDtls, + byte* out, word32* outSz, void* heap, int devId) +{ + int ret; + const byte* protocol; + word32 protocolLen; + word32 outputLen; + int hashSz; + byte hash[WC_MAX_DIGEST_SIZE]; + byte prk[WC_MAX_DIGEST_SIZE]; + byte okm[MAX_PSK_KEY_LEN]; + word32 idx; +#ifdef WOLFSSL_SMALL_STACK + byte* hkdfLabel = NULL; + wc_HashAlg* hashAlg = NULL; +#else + byte hkdfLabel[MAX_TLS13_HKDF_LABEL_SZ]; + wc_HashAlg hashAlg[1]; +#endif + + WOLFSSL_MSG("Derive Imported Pre-shared Key"); + if (epsk == NULL || importedIdentity == NULL || out == NULL || + outSz == NULL) + return BAD_FUNC_ARG; + + /* The hash function used for the import (Hash(ImportedIdentity) and the + * HKDF below) is the one associated with the external PSK, defaulting to + * SHA-256 per RFC 9258. It is independent of the target_kdf, which only + * determines the imported-PSK output length L. Validate that the digest + * fits our buffers and is a supported hash. */ + hashSz = wc_HashGetDigestSize((enum wc_HashType)importerHash); + if (hashSz <= 0 || hashSz > (int)sizeof(hash)) + return BAD_FUNC_ARG; + + /* The output length L matches the digest size of the target_kdf. */ + ret = wc_HashGetDigestSize(mac2hash(targetKdfMac)); + if (ret < 0) + return ret; + outputLen = (word32)ret; + if (outputLen == 0 || outputLen > (word32)sizeof(okm)) + return BUFFER_E; + + switch (protocolMinor) { + case TLSv1_3_MINOR: + protocol = tls13ProtocolLabel; + protocolLen = TLS13_PROTOCOL_LABEL_SZ; + break; + #ifdef WOLFSSL_DTLS13 + case DTLSv1_3_MINOR: + if (!isDtls) + return VERSION_ERROR; + protocol = dtls13ProtocolLabel; + protocolLen = DTLS13_PROTOCOL_LABEL_SZ; + break; + #endif /* WOLFSSL_DTLS13 */ + default: + return VERSION_ERROR; + } + (void)isDtls; + +#ifdef WOLFSSL_SMALL_STACK + hashAlg = (wc_HashAlg*)XMALLOC(sizeof(wc_HashAlg), heap, + DYNAMIC_TYPE_HASHES); + if (hashAlg == NULL) + return MEMORY_E; +#endif + + /* Create the hash of the ImportedIdentity, offloading to devId if set. */ + ret = wc_HashInit_ex(hashAlg, (enum wc_HashType)importerHash, heap, devId); + if (ret == 0) { + ret = wc_HashUpdate(hashAlg, (enum wc_HashType)importerHash, + importedIdentity, importedIdentitySz); + if (ret == 0) + ret = wc_HashFinal(hashAlg, (enum wc_HashType)importerHash, hash); + wc_HashFree(hashAlg, (enum wc_HashType)importerHash); + } +#ifdef WOLFSSL_SMALL_STACK + XFREE(hashAlg, heap, DYNAMIC_TYPE_HASHES); +#endif + if (ret != 0) + return ret; + + /* Check HkdfLabel length: okmLen (2) + protocol|label len (1) + + * info len(1) + protocollen + labellen + infolen + */ + idx = 4 + protocolLen + DERIVED_PSK_LABEL_SZ + (word32)hashSz; + if (idx > MAX_TLS13_HKDF_LABEL_SZ) + return BUFFER_E; + +#ifdef WOLFSSL_SMALL_STACK + hkdfLabel = (byte*)XMALLOC(idx, heap, DYNAMIC_TYPE_TMP_BUFFER); + if (hkdfLabel == NULL) + ret = MEMORY_E; +#endif + idx = 0; + + /* Construct the label */ + if (ret == 0) { + /* Output length. */ + hkdfLabel[idx++] = (byte)(outputLen >> 8); + hkdfLabel[idx++] = (byte)outputLen; + /* Length of protocol | label. */ + hkdfLabel[idx++] = (byte)(protocolLen + DERIVED_PSK_LABEL_SZ); + /* Protocol */ + XMEMCPY(&hkdfLabel[idx], protocol, protocolLen); + idx += protocolLen; + /* Label */ + XMEMCPY(&hkdfLabel[idx], derivedPskLabel, DERIVED_PSK_LABEL_SZ); + idx += DERIVED_PSK_LABEL_SZ; + /* Length of hash */ + hkdfLabel[idx++] = (byte)hashSz; + /* Hash of messages */ + XMEMCPY(&hkdfLabel[idx], hash, (word32)hashSz); + idx += (word32)hashSz; + + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("DeriveImportedPsk hkdfLabel", hkdfLabel, idx); + wc_MemZero_Add("DeriveImportedPsk okm", okm, sizeof(okm)); + wc_MemZero_Add("DeriveImportedPsk prk", prk, sizeof(prk)); + #endif + + /* okm holds ipskx; prk holds epskx. Both are dedicated buffers so the + * HKDF input may safely alias the output buffer. */ + PRIVATE_KEY_UNLOCK(); + /* epskx = HKDF-Extract(0, epsk) */ + #if !defined(HAVE_FIPS) || \ + (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) + ret = wc_HKDF_Extract_ex(importerHash, NULL, 0, epsk, epskSz, prk, + heap, devId); + #else + ret = wc_HKDF_Extract(importerHash, NULL, 0, epsk, epskSz, prk); + #endif + if (ret == 0) { + /* ipskx = HKDF-Expand-Label(epskx, "derived psk", + * Hash(ImportedIdentity), L) */ + ret = wc_HKDF_Expand_ex(importerHash, prk, (word32)hashSz, + hkdfLabel, idx, okm, outputLen, heap, devId); + } + PRIVATE_KEY_LOCK(); + + if (ret == 0) { + XMEMCPY(out, okm, outputLen); + *outSz = outputLen; + } + + ForceZero(okm, sizeof(okm)); + ForceZero(prk, sizeof(prk)); + ForceZero(hkdfLabel, idx); + + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(hkdfLabel, idx); + wc_MemZero_Check(prk, sizeof(prk)); + wc_MemZero_Check(okm, sizeof(okm)); + #endif + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(hkdfLabel, heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} + +/* Derive the imported PSK for a connection, sourcing the inputs from the SSL + * object and writing the result back into the PSK key buffer. + * + * ssl The SSL/TLS object. + * psk The PSK whose serialized ImportedIdentity and target_kdf drive + * the derivation. + * importerHash The hash associated with the external PSK (default WC_SHA256). + */ +static int DeriveImportedPreSharedKey(WOLFSSL* ssl, PreSharedKey* psk, + int importerHash) +{ + int ret; + word32 keySz; + + if (ssl == NULL || ssl->arrays == NULL || psk == NULL) + return BAD_FUNC_ARG; + + keySz = ssl->arrays->psk_keySz; + ret = DeriveImportedPsk(ssl->arrays->psk_key, ssl->arrays->psk_keySz, + psk->identity, psk->identityLen, importerHash, psk->hmac, + ssl->version.minor, ssl->options.dtls, ssl->arrays->psk_key, &keySz, + ssl->heap, ssl->devId); + if (ret == 0) + ssl->arrays->psk_keySz = keySz; + + return ret; +} +#endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ + /* Derive the early secret using HKDF Extract. * * ssl The SSL/TLS object. @@ -4222,8 +4468,26 @@ static int SetupPskKey(WOLFSSL* ssl, PreSharedKey* psk, int clientHello) if (psk->identityLen > MAX_PSK_ID_LEN) return PSK_KEY_ERROR; XMEMSET(ssl->arrays->client_identity, 0, - sizeof(ssl->arrays->client_identity)); - XMEMCPY(ssl->arrays->client_identity, psk->identity, psk->identityLen); + sizeof(ssl->arrays->client_identity)); + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (psk->imported) { + word16 identitySz = 0; + /* The identity buffer in the PSK object contains the full + * ImportedIdentity. Hence, we have to extract the actual + * identity here. The length of the identity is stored in + * the first two bytes. This has also already been verified + * to be less than MAX_PSK_ID_LEN during creation of the + * ImportedIdentity. */ + ato16(psk->identity, &identitySz); + XMEMCPY(ssl->arrays->client_identity, psk->identity + OPAQUE16_LEN, + identitySz); + } + else + #endif + { + XMEMCPY(ssl->arrays->client_identity, psk->identity, + psk->identityLen); + } #ifdef WOLFSSL_DEBUG_TLS WOLFSSL_MSG("PSK cipher suite:"); @@ -4269,6 +4533,54 @@ static int SetupPskKey(WOLFSSL* ssl, PreSharedKey* psk, int clientHello) } else #endif /* OPENSSL_EXTRA */ + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (ssl->options.client_psk_importer_cb != NULL) { + word32 identitySz = MAX_PSK_ID_LEN; + /* Hash associated with the EPSK; defaults to SHA-256 (RFC 9258). */ + int importerHash = WC_SHA256; + ssl->arrays->psk_keySz = MAX_PSK_KEY_LEN; + + /* Lookup key again for next identity. The context is not needed + * here, so its buffer and length are passed as NULL. */ + ret = ssl->options.client_psk_importer_cb(ssl, + (byte*)ssl->arrays->client_identity, &identitySz, NULL, + NULL, ssl->arrays->psk_key, &ssl->arrays->psk_keySz, + &importerHash); + if (ret != 0) + return PSK_KEY_ERROR; + + if (identitySz > MAX_PSK_ID_LEN || + ssl->arrays->psk_keySz > MAX_PSK_KEY_LEN) + return PSK_KEY_ERROR; + + /* Use PSK cipher suite. */ + if (clientHello) { + /* Use PSK cipher suite. */ + ssl->options.cipherSuite0 = psk->cipherSuite0; + ssl->options.cipherSuite = psk->cipherSuite; + } + else { + byte pskCS[2]; + pskCS[0] = psk->cipherSuite0; + pskCS[1] = psk->cipherSuite; + + /* Ensure PSK and negotiated cipher suites have same hash. */ + if (SuiteMac(pskCS) != SuiteMac(suite)) { + WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR); + return PSK_KEY_ERROR; + } + /* Negotiated cipher suite is to be used - update PSK. */ + psk->cipherSuite0 = suite[0]; + psk->cipherSuite = suite[1]; + } + + /* Derive the imported PSK */ + ret = DeriveImportedPreSharedKey(ssl, psk, importerHash); + if (ret != 0) + return ret; + } + else + #endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ if (ssl->options.client_psk_cs_cb != NULL) { #ifdef WOLFSSL_PSK_MULTI_ID_PER_CS ssl->arrays->client_identity[0] = 0; @@ -4467,8 +4779,14 @@ static int WritePSKBinders(WOLFSSL* ssl, byte* output, word32 idx) ret = DeriveBinderKeyResume(ssl, binderKey); #endif #ifndef NO_PSK - if (!current->resumption) - ret = DeriveBinderKey(ssl, binderKey); + if (!current->resumption) { + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (current->imported) + ret = DeriveBinderKeyImported(ssl, binderKey); + else + #endif + ret = DeriveBinderKey(ssl, binderKey); + } #endif if (ret != 0) break; @@ -6264,21 +6582,74 @@ int FindPskSuite(const WOLFSSL* ssl, PreSharedKey* psk, byte* psk_key, *found = 0; (void)suite; - if (ssl->options.server_psk_tls13_cb != NULL) { - *psk_keySz = ssl->options.server_psk_tls13_cb((WOLFSSL*)ssl, - (char*)psk->identity, psk_key, MAX_PSK_KEY_LEN, &cipherName); - if (*psk_keySz != 0) { - int cipherSuiteFlags = WOLFSSL_CIPHER_SUITE_FLAG_NONE; - *found = (GetCipherSuiteFromName(cipherName, &cipherSuite0, - &cipherSuite, NULL, NULL, &cipherSuiteFlags) == 0); - (void)cipherSuiteFlags; - } +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (ssl->options.server_psk_importer_cb != NULL) { + byte* id = NULL; + byte* ctx = NULL; + word16 idSz = 0; + word16 ctxSz = 0; + ProtocolVersion targetProtocol; + byte targetKdf = 0; + /* Hash associated with the EPSK; defaults to SHA-256 (RFC 9258). */ + int importerHash = WC_SHA256; + + XMEMSET(&targetProtocol, 0, sizeof(targetProtocol)); + + /* Decode the received ImportedIdentity. If this call fails, the + * received identity may also be not an imported one. */ + if (TLSX_PreSharedKey_ParseImportedIdentity(psk->identity, + psk->identityLen, &id, &idSz, &ctx, &ctxSz, + &targetKdf, &targetProtocol) == 0 && + /* The KDF and protocol bound into the ImportedIdentity must match + * the ones being negotiated (RFC 9258, Section 3.1). */ + SuiteMac(suite) == targetKdf && + targetProtocol.major == ssl->version.major && + targetProtocol.minor == ssl->version.minor) { + *psk_keySz = MAX_PSK_KEY_LEN; + + /* Look for an external PSK for that identity and context. The + * identity and context are passed as opaque (ptr,len) pairs that + * alias into the received message; no copy/termination required. */ + ret = ssl->options.server_psk_importer_cb((WOLFSSL*)ssl, + id, idSz, ctx, ctxSz, psk_key, psk_keySz, &importerHash); + + if (ret == 0 && *psk_keySz > 0 && *psk_keySz <= MAX_PSK_KEY_LEN) { + /* Not yet set on the server-side */ + psk->hmac = targetKdf; + + /* Derive the actual imported PSK */ + ret = DeriveImportedPreSharedKey((WOLFSSL*)ssl, psk, + importerHash); + if (ret == 0) { + *found = 1; + + cipherSuite0 = suite[0]; + cipherSuite = suite[1]; + + /* Set flag in the PSK object to indicate that this + * one is imported. We use that flag to select the + * proper derive method for the binder key. */ + psk->imported = 1; + } + } + } + } +#endif + if (*found == 0 && (ssl->options.server_psk_tls13_cb != NULL)) { + *psk_keySz = ssl->options.server_psk_tls13_cb((WOLFSSL*)ssl, + (char*)psk->identity, psk_key, MAX_PSK_KEY_LEN, &cipherName); + if (*psk_keySz != 0) { + int cipherSuiteFlags = WOLFSSL_CIPHER_SUITE_FLAG_NONE; + *found = (GetCipherSuiteFromName(cipherName, &cipherSuite0, + &cipherSuite, NULL, NULL, &cipherSuiteFlags) == 0); + (void)cipherSuiteFlags; + } } if (*found == 0 && (ssl->options.server_psk_cb != NULL)) { - *psk_keySz = ssl->options.server_psk_cb((WOLFSSL*)ssl, - (char*)psk->identity, psk_key, - MAX_PSK_KEY_LEN); - *found = (*psk_keySz != 0); + *psk_keySz = ssl->options.server_psk_cb((WOLFSSL*)ssl, + (char*)psk->identity, psk_key, + MAX_PSK_KEY_LEN); + *found = (*psk_keySz != 0); } if (*found) { if (*psk_keySz > MAX_PSK_KEY_LEN && @@ -6581,7 +6952,13 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, goto cleanup; /* Derive the binder key to use with HMAC. */ - ret = DeriveBinderKey(ssl, binderKey); + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + if (current->imported) + ret = DeriveBinderKeyImported(ssl, binderKey); + else + #endif + ret = DeriveBinderKey(ssl, binderKey); + if (ret != 0) goto cleanup; } @@ -15307,6 +15684,104 @@ void wolfSSL_set_psk_server_tls13_callback(WOLFSSL* ssl, ssl->options.useAnon, TRUE, TRUE, TRUE, TRUE, ssl->options.side); } +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +/* Set the PSK callback that return the external identity, an optional context + * and the actual PSK to be imported for a client based on RFC 9258. + * + * @param [in, out] ssl SSL/TLS context object. + * @param [in] cb Client PSK importer callback. + */ +void wolfSSL_CTX_set_psk_client_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_client_importer_callback cb) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_psk_client_importer_callback"); + if (ctx == NULL) + return; + + ctx->havePSK = 1; + ctx->client_psk_importer_cb = cb; +} + +/* Set the PSK callback that return the external identity, an optional context + * and the actual PSK to be imported for a client based on RFC 9258. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] cb Client PSK importer callback. + */ +void wolfSSL_set_psk_client_importer_callback(WOLFSSL* ssl, + wc_psk_client_importer_callback cb) +{ + int keySz = 0; + + WOLFSSL_ENTER("wolfSSL_set_psk_client_importer_callback"); + + if (ssl == NULL) + return; + + ssl->options.havePSK = 1; + ssl->options.client_psk_importer_cb = cb; + +#ifndef NO_CERTS + keySz = ssl->buffers.keySz; +#endif + + if (AllocateSuites(ssl) != 0) + return; + + InitSuites(ssl->suites, ssl->version, keySz, ssl->options.haveRSA, TRUE, + ssl->options.haveDH, ssl->options.haveECDSAsig, + ssl->options.haveECC, TRUE, ssl->options.haveStaticECC, + ssl->options.useAnon, TRUE, TRUE, TRUE, TRUE, ssl->options.side); +} + +/* Set the PSK callback that return the external identity, an optional context + * and the actual PSK to be imported for a server based on RFC 9258. + * + * @param [in, out] ssl SSL/TLS context object. + * @param [in] cb Server PSK importer callback. + */ +void wolfSSL_CTX_set_psk_server_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_server_importer_callback cb) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_psk_server_importer_callback"); + if (ctx == NULL) + return; + + ctx->havePSK = 1; + ctx->server_psk_importer_cb = cb; +} + +/* Set the PSK callback that return the external identity, an optional context + * and the actual PSK to be imported for a server based on RFC 9258. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] cb Server PSK importer callback. + */ +void wolfSSL_set_psk_server_importer_callback(WOLFSSL* ssl, + wc_psk_server_importer_callback cb) +{ + int keySz = 0; + + WOLFSSL_ENTER("wolfSSL_set_psk_server_importer_callback"); + if (ssl == NULL) + return; + + ssl->options.havePSK = 1; + ssl->options.server_psk_importer_cb = cb; + +#ifndef NO_CERTS + keySz = ssl->buffers.keySz; +#endif + + if (AllocateSuites(ssl) != 0) + return; + InitSuites(ssl->suites, ssl->version, keySz, ssl->options.haveRSA, TRUE, + ssl->options.haveDH, ssl->options.haveECDSAsig, + ssl->options.haveECC, TRUE, ssl->options.haveStaticECC, + ssl->options.useAnon, TRUE, TRUE, TRUE, TRUE, ssl->options.side); +} +#endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ + /* Get name of first supported cipher suite that uses the hash indicated. * * @param [in] ssl SSL/TLS object. diff --git a/tests/api.c b/tests/api.c index 32b90b9a079..b75bab8712b 100644 --- a/tests/api.c +++ b/tests/api.c @@ -30398,6 +30398,406 @@ static int test_prioritize_psk(void) } #endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) && \ + !defined(NO_PSK) && !defined(WOLFSSL_PSK_ONE_ID) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES_BUILD) && \ + defined(HAVE_AESGCM) && !defined(NO_SHA256) && defined(WOLFSSL_AES_128) +/* Shared external PSK (EPSK) and ImportedIdentity inputs used by both the + * client and server importer callbacks (RFC 9258). */ +static const char test_psk_importer_identity[] = "9258 Client_identity"; +static const unsigned char test_psk_importer_epsk[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; +static const char test_psk_importer_context[] = "RFC 9258 test context"; +/* Toggle whether the EPSK carries an optional context. */ +static int test_psk_importer_use_context = 0; +/* When set, the server returns a different EPSK so the binder must not match. */ +static int test_psk_importer_server_wrong_key = 0; +/* Hash associated with the EPSK that both callbacks advertise (default SHA-256 + * per RFC 9258; set to e.g. WC_SHA384 to exercise a non-default importer). */ +static int test_psk_importer_hash = WC_SHA256; + +static int test_psk_importer_client_cb(WOLFSSL* ssl, unsigned char* id, + word32* idSz, unsigned char* ctx, word32* ctxSz, unsigned char* key, + word32* keySz, int* hashAlgo) +{ + word32 idLen = (word32)XSTRLEN(test_psk_importer_identity); + (void)ssl; + + if (idLen > *idSz) + return -1; + XMEMCPY(id, test_psk_importer_identity, idLen); + *idSz = idLen; + + if (ctx != NULL && ctxSz != NULL) { + if (test_psk_importer_use_context) { + word32 ctxLen = (word32)XSTRLEN(test_psk_importer_context); + if (ctxLen > *ctxSz) + return -1; + XMEMCPY(ctx, test_psk_importer_context, ctxLen); + *ctxSz = ctxLen; + } + else { + *ctxSz = 0; + } + } + + if ((word32)sizeof(test_psk_importer_epsk) > *keySz) + return -1; + XMEMCPY(key, test_psk_importer_epsk, sizeof(test_psk_importer_epsk)); + *keySz = (word32)sizeof(test_psk_importer_epsk); + + /* hashAlgo arrives pre-set to WC_SHA256; only override when testing a + * non-default EPSK hash. */ + *hashAlgo = test_psk_importer_hash; + + return 0; +} + +static int test_psk_importer_server_cb(WOLFSSL* ssl, const unsigned char* id, + word32 idSz, const unsigned char* ctx, word32 ctxSz, + unsigned char* key, word32* keySz, int* hashAlgo) +{ + word32 idLen = (word32)XSTRLEN(test_psk_importer_identity); + (void)ssl; + + /* Match the received external identity (opaque, length-delimited). */ + if (id == NULL || idSz != idLen || + XMEMCMP(id, test_psk_importer_identity, idLen) != 0) + return -1; + + /* Match the optional context exactly as advertised by the client. */ + if (test_psk_importer_use_context) { + word32 ctxLen = (word32)XSTRLEN(test_psk_importer_context); + if (ctxSz != ctxLen || ctx == NULL || + XMEMCMP(ctx, test_psk_importer_context, ctxLen) != 0) + return -1; + } + else if (ctxSz != 0) { + return -1; + } + + /* Advertise the same EPSK hash as the client (default SHA-256). */ + *hashAlgo = test_psk_importer_hash; + + if ((word32)sizeof(test_psk_importer_epsk) > *keySz) + return -1; + XMEMCPY(key, test_psk_importer_epsk, sizeof(test_psk_importer_epsk)); + if (test_psk_importer_server_wrong_key) { + /* Flip a byte so the derived PSK (and thus the binder) differs. */ + key[0] ^= 0xFF; + } + *keySz = (word32)sizeof(test_psk_importer_epsk); + + return 0; +} + +/* Run a single TLS 1.3 handshake that authenticates with an imported external + * PSK (RFC 9258), restricted to the given cipher suite. */ +static int test_tls13_external_psk_importer_one(const char* cipher, + int expectedSuite, int useContext, int expectFail) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + test_psk_importer_use_context = useContext; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* PSK authenticates the peers; no certificate is required. */ + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_NONE, NULL); + + wolfSSL_set_psk_client_importer_callback(ssl_c, + test_psk_importer_client_cb); + wolfSSL_set_psk_server_importer_callback(ssl_s, + test_psk_importer_server_cb); + + /* Restrict both sides to the suite under test (importer callback setup + * rebuilds the suite list, so set the cipher list afterwards). */ + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, cipher), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, cipher), WOLFSSL_SUCCESS); + + if (expectFail) { + /* A mismatched imported PSK must abort the handshake. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_current_cipher_suite(ssl_c), expectedSuite); + ExpectIntEQ(wolfSSL_get_current_cipher_suite(ssl_s), expectedSuite); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + + return EXPECT_RESULT(); +} + +static int test_tls13_external_psk_importer(void) +{ + EXPECT_DECLS; + + test_psk_importer_server_wrong_key = 0; + test_psk_importer_hash = WC_SHA256; + + /* HKDF_SHA256 target_kdf, without and with an optional context. */ + ExpectIntEQ(test_tls13_external_psk_importer_one("TLS13-AES128-GCM-SHA256", + 0x1301, 0, 0), TEST_SUCCESS); + ExpectIntEQ(test_tls13_external_psk_importer_one("TLS13-AES128-GCM-SHA256", + 0x1301, 1, 0), TEST_SUCCESS); +#if defined(WOLFSSL_SHA384) && defined(WOLFSSL_AES_256) + /* HKDF_SHA384 target_kdf exercises the L = 48 derived-PSK length. */ + ExpectIntEQ(test_tls13_external_psk_importer_one("TLS13-AES256-GCM-SHA384", + 0x1302, 1, 0), TEST_SUCCESS); +#endif + +#if defined(WOLFSSL_SHA384) + /* EPSK associated with SHA-384 (non-default importer hash), used with a + * SHA-256 target_kdf: exercises an importer hash that differs from the + * target KDF (RFC 9258 Section 3.1). */ + test_psk_importer_hash = WC_SHA384; + ExpectIntEQ(test_tls13_external_psk_importer_one("TLS13-AES128-GCM-SHA256", + 0x1301, 1, 0), TEST_SUCCESS); + test_psk_importer_hash = WC_SHA256; +#endif + + /* Negative: server derives a different imported PSK -> binder mismatch + * -> handshake must fail. */ + test_psk_importer_server_wrong_key = 1; + ExpectIntEQ(test_tls13_external_psk_importer_one("TLS13-AES128-GCM-SHA256", + 0x1301, 1, 1), TEST_SUCCESS); + test_psk_importer_server_wrong_key = 0; + + return EXPECT_RESULT(); +} + +/* Directly exercise ImportedIdentity (de)serialization, including the + * malformed inputs a peer could send (RFC 9258, Section 3.1). */ +static int test_tls13_external_psk_importer_parse(void) +{ + EXPECT_DECLS; + byte out[128]; + word16 outLen = (word16)sizeof(out); + const char* id = "abc"; + const char* ctx = "xy"; + ProtocolVersion pv; + byte* pId = NULL; + byte* pCtx = NULL; + word16 pIdLen = 0; + word16 pCtxLen = 0; + byte pHkdf = 0; + ProtocolVersion pProto; + + pv.major = SSLv3_MAJOR; + pv.minor = TLSv1_3_MINOR; + + /* Round-trip a well-formed ImportedIdentity. */ + ExpectIntEQ(TLSX_PreSharedKey_CreateImportedIdentity((const byte*)id, 3, + (const byte*)ctx, 2, sha256_mac, pv, out, &outLen), 0); + ExpectIntEQ(TLSX_PreSharedKey_ParseImportedIdentity(out, outLen, &pId, + &pIdLen, &pCtx, &pCtxLen, &pHkdf, &pProto), 0); + ExpectIntEQ(pIdLen, 3); + ExpectNotNull(pId); + ExpectIntEQ(XMEMCMP(pId, id, 3), 0); + ExpectIntEQ(pCtxLen, 2); + ExpectIntEQ(pHkdf, sha256_mac); + ExpectIntEQ(pProto.major, pv.major); + ExpectIntEQ(pProto.minor, pv.minor); + + /* Malformed: empty external_identity must be rejected (the historical + * NULL-deref case). */ + { + byte empty[] = { 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x01 }; + pId = NULL; + ExpectIntNE(TLSX_PreSharedKey_ParseImportedIdentity(empty, + (word16)sizeof(empty), &pId, &pIdLen, &pCtx, &pCtxLen, &pHkdf, + &pProto), 0); + } + + /* Malformed: total length below the minimum header is rejected. */ + ExpectIntNE(TLSX_PreSharedKey_ParseImportedIdentity(out, 7, &pId, &pIdLen, + &pCtx, &pCtxLen, &pHkdf, &pProto), 0); + + /* Malformed: identity length exceeds the buffer is rejected. */ + { + byte ovf[] = { 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x04, 0x00, 0x01 }; + ExpectIntNE(TLSX_PreSharedKey_ParseImportedIdentity(ovf, + (word16)sizeof(ovf), &pId, &pIdLen, &pCtx, &pCtxLen, &pHkdf, + &pProto), 0); + } + + /* Malformed: unsupported target_kdf is rejected. */ + { + byte badKdf[sizeof(out)]; + XMEMCPY(badKdf, out, outLen); + badKdf[outLen - 1] = 0x09; /* not HKDF_SHA256/HKDF_SHA384 */ + ExpectIntNE(TLSX_PreSharedKey_ParseImportedIdentity(badKdf, outLen, + &pId, &pIdLen, &pCtx, &pCtxLen, &pHkdf, &pProto), 0); + } + + return EXPECT_RESULT(); +} + +/* Known-answer vectors for the imported-PSK derivation, computed independently + * of wolfSSL (Python hashlib/hmac) from RFC 9258 Section 3.1. A symmetric + * derivation bug would still let the handshake tests pass, so these pin the + * exact ImportedIdentity serialization and ipskx output. + * + * Inputs: external_identity = "9258 Client_identity", epsk = 16 x 0x0b, + * target_protocol = TLS 1.3 (0x0304). */ +static const byte test_kat_id[] = "9258 Client_identity"; /* 20 bytes, no NUL */ +static const byte test_kat_ctx[] = "RFC 9258 test context"; /* 21 bytes */ +static const byte test_kat_epsk[16] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b +}; + +/* V1: no context, target_kdf = HKDF_SHA256 (L=32), importer hash SHA-256. */ +static const byte test_kat_ii1[] = { + 0x00, 0x14, 0x39, 0x32, 0x35, 0x38, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x00, 0x00, + 0x03, 0x04, 0x00, 0x01 +}; +static const byte test_kat_ipsk1[] = { + 0x03, 0xa2, 0x4c, 0xc5, 0xe7, 0x8a, 0xeb, 0xb0, 0x03, 0x22, 0x7b, 0x99, + 0x98, 0x3a, 0x66, 0x9c, 0x35, 0xa5, 0x98, 0x93, 0xb6, 0xd0, 0x57, 0x70, + 0x8d, 0xe3, 0x50, 0x50, 0x58, 0x1b, 0x59, 0xfe +}; +#if defined(WOLFSSL_SHA384) +/* V2: with context, target_kdf = HKDF_SHA384 (L=48), importer hash SHA-256. */ +static const byte test_kat_ii2[] = { + 0x00, 0x14, 0x39, 0x32, 0x35, 0x38, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x00, 0x15, + 0x52, 0x46, 0x43, 0x20, 0x39, 0x32, 0x35, 0x38, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x03, 0x04, 0x00, + 0x02 +}; +static const byte test_kat_ipsk2[] = { + 0x89, 0x27, 0x92, 0x54, 0xf5, 0x07, 0xa5, 0x5d, 0xeb, 0xff, 0x72, 0x4a, + 0xc1, 0xee, 0x14, 0x2a, 0x1f, 0x2d, 0xe7, 0x6d, 0x54, 0x18, 0xd7, 0x12, + 0xb4, 0xe9, 0x83, 0xdc, 0x4e, 0xd0, 0x71, 0x4e, 0x5b, 0x70, 0xa4, 0x77, + 0x78, 0x65, 0x09, 0x0c, 0x2e, 0x02, 0x61, 0x43, 0x6e, 0x2d, 0x75, 0x95 +}; +/* V3: with context, target_kdf = HKDF_SHA256 (L=32), importer hash SHA-384. */ +static const byte test_kat_ii3[] = { + 0x00, 0x14, 0x39, 0x32, 0x35, 0x38, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x00, 0x15, + 0x52, 0x46, 0x43, 0x20, 0x39, 0x32, 0x35, 0x38, 0x20, 0x74, 0x65, 0x73, + 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x03, 0x04, 0x00, + 0x01 +}; +static const byte test_kat_ipsk3[] = { + 0xf2, 0x32, 0xfc, 0xfe, 0xdc, 0x36, 0x8b, 0xda, 0xf6, 0xbc, 0x4c, 0xcf, + 0xff, 0x4e, 0x60, 0xee, 0x2c, 0xa0, 0xd2, 0x81, 0x0a, 0x3d, 0x8c, 0x17, + 0xba, 0x4e, 0xac, 0x25, 0x13, 0x18, 0x7d, 0xd8 +}; +#endif /* WOLFSSL_SHA384 */ + +/* V4: cross-implementation vector. The external_identity (0xCAFECAFE), context + * (0xDEADBEEF) and the expected ImportedIdentity below are exactly those used + * by GnuTLS's own test, tests/psk-importer.c (GnuTLS >= 3.8.1), confirming our + * ImportedIdentity serialization matches an independent RFC 9258 + * implementation. The ipskx value is the independently computed expectation. */ +static const byte test_kat_gnutls_id[] = { 0xca, 0xfe, 0xca, 0xfe }; +static const byte test_kat_gnutls_ctx[] = { 0xde, 0xad, 0xbe, 0xef }; +static const byte test_kat_gnutls_epsk[] = { 0xde, 0xad, 0xbe, 0xef }; +static const byte test_kat_gnutls_ii[] = { + 0x00, 0x04, 0xca, 0xfe, 0xca, 0xfe, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef, + 0x03, 0x04, 0x00, 0x01 +}; +static const byte test_kat_gnutls_ipsk[] = { + 0x77, 0x49, 0xf3, 0x1b, 0x70, 0xcf, 0xec, 0x8f, 0x97, 0x83, 0x41, 0x3f, + 0x71, 0xe7, 0x15, 0xfa, 0x61, 0x73, 0xa9, 0xa5, 0x20, 0xd6, 0x0b, 0x85, + 0x06, 0x6d, 0xe2, 0x92, 0x22, 0xb3, 0x79, 0xff +}; + +static int test_tls13_psk_importer_kat_one(const byte* id, word16 idLen, + const byte* ctx, word16 ctxLen, const byte* epsk, word32 epskSz, + byte targetKdfMac, int importerHash, const byte* expII, word16 expIISz, + const byte* expIpsk, word32 expIpskSz) +{ + EXPECT_DECLS; + byte ii[128]; + word16 iiSz = (word16)sizeof(ii); + byte out[MAX_PSK_KEY_LEN]; + word32 outSz = 0; + ProtocolVersion pv; + + pv.major = SSLv3_MAJOR; + pv.minor = TLSv1_3_MINOR; + + /* ImportedIdentity serialization matches the independent vector. */ + ExpectIntEQ(TLSX_PreSharedKey_CreateImportedIdentity(id, idLen, ctx, ctxLen, + targetKdfMac, pv, ii, &iiSz), 0); + ExpectIntEQ((int)iiSz, (int)expIISz); + ExpectIntEQ(XMEMCMP(ii, expII, expIISz), 0); + + /* Derived imported PSK (ipskx) matches the independent vector. */ + ExpectIntEQ(DeriveImportedPsk(epsk, epskSz, expII, expIISz, importerHash, + targetKdfMac, TLSv1_3_MINOR, 0, out, &outSz, NULL, INVALID_DEVID), 0); + ExpectIntEQ((int)outSz, (int)expIpskSz); + ExpectIntEQ(XMEMCMP(out, expIpsk, expIpskSz), 0); + + return EXPECT_RESULT(); +} + +static int test_tls13_external_psk_importer_kat(void) +{ + EXPECT_DECLS; + word16 idLen = (word16)XSTRLEN((const char*)test_kat_id); + + ExpectIntEQ(test_tls13_psk_importer_kat_one(test_kat_id, idLen, NULL, 0, + test_kat_epsk, (word32)sizeof(test_kat_epsk), sha256_mac, WC_SHA256, + test_kat_ii1, (word16)sizeof(test_kat_ii1), + test_kat_ipsk1, (word32)sizeof(test_kat_ipsk1)), TEST_SUCCESS); +#if defined(WOLFSSL_SHA384) + ExpectIntEQ(test_tls13_psk_importer_kat_one(test_kat_id, idLen, + test_kat_ctx, (word16)(sizeof(test_kat_ctx) - 1), + test_kat_epsk, (word32)sizeof(test_kat_epsk), sha384_mac, WC_SHA256, + test_kat_ii2, (word16)sizeof(test_kat_ii2), + test_kat_ipsk2, (word32)sizeof(test_kat_ipsk2)), TEST_SUCCESS); + ExpectIntEQ(test_tls13_psk_importer_kat_one(test_kat_id, idLen, + test_kat_ctx, (word16)(sizeof(test_kat_ctx) - 1), + test_kat_epsk, (word32)sizeof(test_kat_epsk), sha256_mac, WC_SHA384, + test_kat_ii3, (word16)sizeof(test_kat_ii3), + test_kat_ipsk3, (word32)sizeof(test_kat_ipsk3)), TEST_SUCCESS); +#endif + + /* Cross-checked against GnuTLS tests/psk-importer.c. */ + ExpectIntEQ(test_tls13_psk_importer_kat_one(test_kat_gnutls_id, + (word16)sizeof(test_kat_gnutls_id), test_kat_gnutls_ctx, + (word16)sizeof(test_kat_gnutls_ctx), test_kat_gnutls_epsk, + (word32)sizeof(test_kat_gnutls_epsk), sha256_mac, WC_SHA256, + test_kat_gnutls_ii, (word16)sizeof(test_kat_gnutls_ii), + test_kat_gnutls_ipsk, (word32)sizeof(test_kat_gnutls_ipsk)), + TEST_SUCCESS); + + return EXPECT_RESULT(); +} +#else +static int test_tls13_external_psk_importer(void) +{ + return TEST_SKIPPED; +} +static int test_tls13_external_psk_importer_parse(void) +{ + return TEST_SKIPPED; +} +static int test_tls13_external_psk_importer_kat(void) +{ + return TEST_SKIPPED; +} +#endif + #if defined(WOLFSSL_TLS13) && defined(OPENSSL_EXTRA) && \ defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_AESGCM) && \ !defined(NO_SHA256) && defined(WOLFSSL_AES_128) && \ @@ -35529,6 +35929,9 @@ TEST_CASE testCases[] = { TEST_DECL(test_ticket_and_psk_mixing), /* Can't memory test as client/server Asserts in thread. */ TEST_DECL(test_prioritize_psk), + TEST_DECL(test_tls13_external_psk_importer), + TEST_DECL(test_tls13_external_psk_importer_parse), + TEST_DECL(test_tls13_external_psk_importer_kat), /* Can't memory test as client/server hangs. */ TEST_DECL(test_wc_CryptoCb), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 3cd37c739bd..00e7725738a 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1279,6 +1279,21 @@ enum { #endif #endif /* NO_DH */ +#ifndef MAX_PSK_ID_LEN + /* max psk identity/hint supported */ + #if defined(WOLFSSL_TLS13) + /* OpenSSL has a 1472 byte session ticket */ + #define MAX_PSK_ID_LEN 1536 + #else + #define MAX_PSK_ID_LEN 128 + #endif +#endif + +#ifndef MAX_PSK_CTX_LEN + /* maximum psk importer context size */ + #define MAX_PSK_CTX_LEN 128 +#endif + #ifndef MAX_PSK_KEY_LEN #define MAX_PSK_KEY_LEN 64 #endif @@ -3767,8 +3782,8 @@ WOLFSSL_LOCAL int TLSX_CKS_Parse(WOLFSSL* ssl, byte* input, word16 length, TLSX** extensions); WOLFSSL_LOCAL int TLSX_CKS_Set(WOLFSSL* ssl, TLSX** extensions); #endif -#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) enum PskDecryptReturn { PSK_DECRYPT_NONE = 0, PSK_DECRYPT_OK, @@ -3809,6 +3824,9 @@ typedef struct PreSharedKey { byte resumption:1; /* Resumption PSK */ byte chosen:1; /* Server's choice */ byte decryptRet:3; /* Ticket decrypt return */ +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_EXTERNAL_PSK_IMPORTER) + byte imported:1; /* PSK is imported */ +#endif struct PreSharedKey* next; /* List pointer */ } PreSharedKey; @@ -3847,6 +3865,19 @@ WOLFSSL_LOCAL int TLSX_PskKeyModes_Parse_Modes(const byte* input, word16 length, #ifdef WOLFSSL_EARLY_DATA WOLFSSL_LOCAL int TLSX_EarlyData_Use(WOLFSSL* ssl, word32 max, int is_response); #endif + +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +WOLFSSL_LOCAL int TLSX_PreSharedKey_CreateImportedIdentity(const byte* id, + word16 id_len, const byte* ctx, word16 ctx_len, byte hmac, + ProtocolVersion protocol, byte* output, word16* out_len); +WOLFSSL_LOCAL int TLSX_PreSharedKey_ParseImportedIdentity(byte* input, + word16 length, byte** id, word16* id_len, byte** ctx,word16* ctx_len, + byte* hkdf, ProtocolVersion* protocol); +WOLFSSL_LOCAL int DeriveImportedPsk(const byte* epsk, word32 epskSz, + const byte* importedIdentity, word32 importedIdentitySz, + int importerHash, byte targetKdfMac, byte protocolMinor, int isDtls, + byte* out, word32* outSz, void* heap, int devId); +#endif #endif /* HAVE_SESSION_TICKET || !NO_PSK */ @@ -4163,7 +4194,11 @@ struct WOLFSSL_CTX { wc_psk_client_cs_callback client_psk_cs_cb; /* client callback */ wc_psk_client_tls13_callback client_psk_tls13_cb; /* client callback */ wc_psk_server_tls13_callback server_psk_tls13_cb; /* server callback */ +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + wc_psk_client_importer_callback client_psk_importer_cb; + wc_psk_server_importer_callback server_psk_importer_cb; #endif +#endif /* WOLFSSL_TLS13 */ void* psk_ctx; char server_hint[MAX_PSK_ID_LEN + NULL_TERM_LEN]; #endif /* HAVE_SESSION_TICKET || !NO_PSK */ @@ -5104,7 +5139,11 @@ struct Options { wc_psk_client_cs_callback client_psk_cs_cb; /* client callback */ wc_psk_client_tls13_callback client_psk_tls13_cb; /* client callback */ wc_psk_server_tls13_callback server_psk_tls13_cb; /* server callback */ +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + wc_psk_client_importer_callback client_psk_importer_cb; + wc_psk_server_importer_callback server_psk_importer_cb; #endif +#endif /* WOLFSSL_TLS13 */ void* psk_ctx; #endif /* NO_PSK */ unsigned long mask; /* store SSL_OP_ flags */ @@ -5377,7 +5416,7 @@ typedef struct Arrays { char client_identity[MAX_PSK_ID_LEN + NULL_TERM_LEN]; char server_hint[MAX_PSK_ID_LEN + NULL_TERM_LEN]; byte psk_key[MAX_PSK_KEY_LEN]; -#endif +#endif /* HAVE_SESSION_TICKET || !NO_PSK */ byte clientRandom[RAN_LEN]; #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) byte clientRandomInner[RAN_LEN]; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 738828b127b..3259ddb56b0 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3280,6 +3280,25 @@ enum { /* ssl Constants */ wc_psk_client_tls13_callback cb); WOLFSSL_API void wolfSSL_set_psk_client_tls13_callback(WOLFSSL* ssl, wc_psk_client_tls13_callback cb); + + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + /* On entry each *Sz holds the buffer capacity; on exit the actual length. + * identity, context and key are opaque byte buffers (RFC 9258); identity is + * capped at MAX_PSK_ID_LEN and context at MAX_PSK_CTX_LEN. + * hashAlgo is the hash function associated with the external PSK: it is + * pre-initialized to WC_SHA256 (the RFC 9258 default) and only needs to be + * changed if the EPSK is associated with a different hash. + * The callback must be deterministic: it is invoked more than once per + * connection and must return the same values each time. */ + typedef int (*wc_psk_client_importer_callback)(WOLFSSL* ssl, + unsigned char* identity, word32* identitySz, + unsigned char* context, word32* contextSz, + unsigned char* key, word32* keySz, int* hashAlgo); + WOLFSSL_API void wolfSSL_CTX_set_psk_client_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_client_importer_callback cb); + WOLFSSL_API void wolfSSL_set_psk_client_importer_callback(WOLFSSL* ssl, + wc_psk_client_importer_callback cb); + #endif #endif WOLFSSL_API const char* wolfSSL_get_psk_identity_hint(const WOLFSSL* ssl); @@ -3301,6 +3320,24 @@ enum { /* ssl Constants */ wc_psk_server_tls13_callback cb); WOLFSSL_API void wolfSSL_set_psk_server_tls13_callback(WOLFSSL* ssl, wc_psk_server_tls13_callback cb); + + #ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER + /* identity and context are opaque (ptr,len) pairs as received on the wire; + * on entry *keySz holds the key buffer capacity, on exit the actual key + * length (RFC 9258). hashAlgo is the hash associated with the external PSK, + * pre-initialized to WC_SHA256; change it only for an EPSK associated with + * a different hash. The callback must be deterministic: it may be invoked + * more than once per connection and must return the same values each + * time. */ + typedef int (*wc_psk_server_importer_callback)(WOLFSSL* ssl, + const unsigned char* identity, word32 identitySz, + const unsigned char* context, word32 contextSz, + unsigned char* key, word32* keySz, int* hashAlgo); + WOLFSSL_API void wolfSSL_CTX_set_psk_server_importer_callback(WOLFSSL_CTX* ctx, + wc_psk_server_importer_callback cb); + WOLFSSL_API void wolfSSL_set_psk_server_importer_callback(WOLFSSL* ssl, + wc_psk_server_importer_callback cb); + #endif #endif #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) WOLFSSL_API int wolfSSL_CTX_set_cert_with_extern_psk(WOLFSSL_CTX* ctx, diff --git a/wolfssl/test.h b/wolfssl/test.h index 97454e1702b..d3b47fd5ce1 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -2099,6 +2099,83 @@ static WC_INLINE unsigned int my_psk_server_tls13_cb(WOLFSSL* ssl, return ret; } + +#ifdef WOLFSSL_EXTERNAL_PSK_IMPORTER +/* Example RFC 9258 external PSK importer callbacks. Both sides use the same + * external identity (kIdentityStr), an example context string, the default + * SHA-256 importer hash, and the fixed 32-byte external PSK below. Configure an + * interop peer (e.g. GnuTLS >= 3.8.1) with matching values. */ +static const char* kImporterContextStr = "wolfSSL importer example context"; + +static WC_INLINE void my_psk_importer_fill_key(unsigned char* key) +{ + int i; + int b = 0x01; + for (i = 0; i < 32; i++, b += 0x22) { + if (b >= 0x100) + b = 0x01; + key[i] = (unsigned char)b; + } +} + +static WC_INLINE int my_psk_client_importer_cb(WOLFSSL* ssl, + unsigned char* identity, word32* identitySz, unsigned char* context, + word32* contextSz, unsigned char* key, word32* keySz, int* hashAlgo) +{ + word32 idLen = (word32)XSTRLEN(kIdentityStr); + word32 ctxLen = (word32)XSTRLEN(kImporterContextStr); + + (void)ssl; + (void)hashAlgo; /* leave the default WC_SHA256 importer hash */ + + if (idLen > *identitySz) + return -1; + XMEMCPY(identity, kIdentityStr, idLen); + *identitySz = idLen; + + /* Provide an example (non-empty) optional context. */ + if (context != NULL && contextSz != NULL) { + if (ctxLen > *contextSz) + return -1; + XMEMCPY(context, kImporterContextStr, ctxLen); + *contextSz = ctxLen; + } + + if (32 > *keySz) + return -1; + my_psk_importer_fill_key(key); + *keySz = 32; + + return 0; +} + +static WC_INLINE int my_psk_server_importer_cb(WOLFSSL* ssl, + const unsigned char* identity, word32 identitySz, + const unsigned char* context, word32 contextSz, unsigned char* key, + word32* keySz, int* hashAlgo) +{ + word32 idLen = (word32)XSTRLEN(kIdentityStr); + word32 ctxLen = (word32)XSTRLEN(kImporterContextStr); + + (void)ssl; + (void)hashAlgo; /* leave the default WC_SHA256 importer hash */ + + if (identity == NULL || identitySz != idLen || + XMEMCMP(identity, kIdentityStr, idLen) != 0) + return -1; + /* Verify the example context advertised by the client. */ + if (contextSz != ctxLen || context == NULL || + XMEMCMP(context, kImporterContextStr, ctxLen) != 0) + return -1; + + if (32 > *keySz) + return -1; + my_psk_importer_fill_key(key); + *keySz = 32; + + return 0; +} +#endif /* WOLFSSL_EXTERNAL_PSK_IMPORTER */ #endif #ifdef OPENSSL_EXTRA