diff --git a/configure.ac b/configure.ac index 054a7b4e3..894a35461 100644 --- a/configure.ac +++ b/configure.ac @@ -3514,6 +3514,18 @@ then AM_CFLAGS="$AM_CFLAGS -DHAVE_FALLBACK_SCSV" fi +# Exporting Keying Material +AC_ARG_ENABLE([keying-material], + [AS_HELP_STRING([--enable-keying-material],[Enable Keying Material Exporters (default: disabled)])], + [ ENABLED_KEYING_MATERIAL=$enableval ], + [ ENABLED_KEYING_MATERIAL=no ] + ) + +if test "x$ENABLED_KEYING_MATERIAL" = "xyes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_KEYING_MATERIAL" +fi + # Supported Elliptic Curves Extensions AC_ARG_ENABLE([supportedcurves], [AS_HELP_STRING([--enable-supportedcurves],[Enable Supported Elliptic Curves (default: enabled)])], @@ -6415,6 +6427,7 @@ echo " * Extended Master Secret: $ENABLED_EXTENDED_MASTER" echo " * Renegotiation Indication: $ENABLED_RENEGOTIATION_INDICATION" echo " * Secure Renegotiation: $ENABLED_SECURE_RENEGOTIATION" echo " * Fallback SCSV: $ENABLED_FALLBACK_SCSV" +echo " * Keying Material Exporter: $ENABLED_KEYING_MATERIAL" echo " * All TLS Extensions: $ENABLED_TLSX" echo " * PKCS#7 $ENABLED_PKCS7" echo " * wolfSSH $ENABLED_WOLFSSH" diff --git a/src/internal.c b/src/internal.c index 5f521452c..0f6bdca45 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5766,6 +5766,10 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #endif #endif +#if defined(WOLFSSL_OPENVPN) && defined(HAVE_KEYING_MATERIAL) + /* Save arrays by default for OpenVPN */ + ssl->options.saveArrays = 1; +#endif ssl->cipher.ssl = ssl; diff --git a/src/ssl.c b/src/ssl.c index c8bf9fcf7..b8d4135de 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -11879,6 +11879,110 @@ int wolfSSL_set_cipher_list(WOLFSSL* ssl, const char* list) #endif } +#ifdef HAVE_KEYING_MATERIAL +static const struct ForbiddenLabels { + const char* label; + size_t labelLen; +} forbiddenLabels[] = { + {TLS_PRF_LABEL_CLIENT_FINISHED, XSTR_SIZEOF(TLS_PRF_LABEL_CLIENT_FINISHED)}, + {TLS_PRF_LABEL_SERVER_FINISHED, XSTR_SIZEOF(TLS_PRF_LABEL_SERVER_FINISHED)}, + {TLS_PRF_LABEL_MASTER_SECRET, XSTR_SIZEOF(TLS_PRF_LABEL_MASTER_SECRET)}, + {TLS_PRF_LABEL_EXT_MASTER_SECRET, XSTR_SIZEOF(TLS_PRF_LABEL_EXT_MASTER_SECRET)}, + {TLS_PRF_LABEL_KEY_EXPANSION, XSTR_SIZEOF(TLS_PRF_LABEL_KEY_EXPANSION)}, + {NULL, 0}, +}; + +/** + * Implement RFC 5705 + * TLS 1.3 uses a different exporter definition (section 7.5 of RFC 8446) + * @return WOLFSSL_SUCCESS on success and WOLFSSL_FAILURE on error + */ +int wolfSSL_export_keying_material(WOLFSSL *ssl, + unsigned char *out, size_t outLen, + const char *label, size_t labelLen, + const unsigned char *context, size_t contextLen, + int use_context) +{ + byte* seed = NULL; + /* clientRandom + serverRandom + * OR + * clientRandom + serverRandom + ctx len encoding + ctx */ + word32 seedLen = !use_context ? SEED_LEN : SEED_LEN + 2 + contextLen; + const struct ForbiddenLabels* fl; + + WOLFSSL_ENTER("wolfSSL_export_keying_material"); + + if (ssl == NULL || out == NULL || label == NULL || + (use_context && contextLen && context == NULL)) { + WOLFSSL_MSG("Bad argument"); + return WOLFSSL_FAILURE; + } + + if (ssl->options.saveArrays == 0 || ssl->arrays == NULL) { + WOLFSSL_MSG("To export keying material wolfSSL needs to keep handshake " + "data. Call wolfSSL_KeepArrays before attempting to " + "export keyig material."); + return WOLFSSL_FAILURE; + } + + /* check forbidden labels */ + for (fl = &forbiddenLabels[0]; fl->label != NULL; fl++) { + if (labelLen >= fl->labelLen && + XMEMCMP(label, fl->label, fl->labelLen) == 0) { + WOLFSSL_MSG("Forbidden label"); + return WOLFSSL_FAILURE; + } + } + +#ifdef WOLFSSL_TLS13 + if (IsAtLeastTLSv1_3(ssl->version)) { + /* Path for TLS 1.3 */ + if (!use_context) { + contextLen = 0; + context = (byte*)""; /* Give valid pointer for 0 length memcpy */ + } + + if (Tls13_Exporter(ssl, out, outLen, label, labelLen, + context, contextLen) != 0) { + WOLFSSL_MSG("Tls13_Exporter error"); + return WOLFSSL_FAILURE; + } + return WOLFSSL_SUCCESS; + } +#endif + + /* Path for <=TLS 1.2 */ + seed = (byte*)XMALLOC(seedLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (seed == NULL) { + WOLFSSL_MSG("malloc error"); + return WOLFSSL_FAILURE; + } + + XMEMCPY(seed, ssl->arrays->clientRandom, RAN_LEN); + XMEMCPY(seed + RAN_LEN, ssl->arrays->serverRandom, RAN_LEN); + + if (use_context) { + /* Encode len in big endian */ + seed[SEED_LEN ] = (contextLen >> 8) & 0xFF; + seed[SEED_LEN + 1] = (contextLen) & 0xFF; + if (contextLen) { + /* 0 length context is allowed */ + XMEMCPY(seed + SEED_LEN + 2, context, contextLen); + } + } + + if (wc_PRF_TLS(out, outLen, ssl->arrays->masterSecret, SECRET_LEN, + (byte*)label, labelLen, seed, seedLen, IsAtLeastTLSv1_2(ssl), + ssl->specs.mac_algorithm, ssl->heap, ssl->devId) != 0) { + WOLFSSL_MSG("wc_PRF_TLS error"); + XFREE(seed, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_FAILURE; + } + + XFREE(seed, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_SUCCESS; +} +#endif /* HAVE_KEYING_MATERIAL */ int wolfSSL_dtls_get_using_nonblock(WOLFSSL* ssl) { diff --git a/src/tls13.c b/src/tls13.c index 199189d3d..1ec927efe 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -29,9 +29,8 @@ * Enables session tickets - required for TLS 1.3 resumption. * NO_PSK * Do not enable Pre-Shared Keys. - * TLS13_SUPPORTS_EXPORTERS - * Guard to compile out any code for exporter keys. - * Feature not supported yet. + * HAVE_KEYING_MATERIAL + * Enables exporting keying material based on section 7.5 of RFC 8446. * WOLFSSL_ASYNC_CRYPT * Enables the use of asynchronous cryptographic operations. * This is available for ciphers and certificates. @@ -515,7 +514,7 @@ static int DeriveEarlyTrafficSecret(WOLFSSL* ssl, byte* key) return ret; } -#ifdef TLS13_SUPPORTS_EXPORTERS +#ifdef HAVE_KEYING_MATERIAL /* The length of the early exporter label. */ #define EARLY_EXPORTER_LABEL_SZ 12 /* The early exporter label. */ @@ -688,7 +687,7 @@ static int DeriveServerTrafficSecret(WOLFSSL* ssl, byte* key) return ret; } -#ifdef TLS13_SUPPORTS_EXPORTERS +#ifdef HAVE_KEYING_MATERIAL /* The length of the exporter master secret label. */ #define EXPORTER_MASTER_LABEL_SZ 10 /* The exporter master secret label. */ @@ -704,7 +703,7 @@ static const byte exporterMasterLabel[EXPORTER_MASTER_LABEL_SZ + 1] = static int DeriveExporterSecret(WOLFSSL* ssl, byte* key) { int ret; - WOLFSSL_MSG("Derive Exporter Secret"); + WOLFSSL_ENTER("Derive Exporter Secret"); if (ssl == NULL || ssl->arrays == NULL) { return BAD_FUNC_ARG; } @@ -722,6 +721,104 @@ static int DeriveExporterSecret(WOLFSSL* ssl, byte* key) #endif /* HAVE_SECRET_CALLBACK */ return ret; } + +/* The length of the exporter label. */ +#define EXPORTER_LABEL_SZ 8 +/* The exporter label. */ +static const byte exporterLabel[EXPORTER_LABEL_SZ + 1] = + "exporter"; +/* Hash("") */ +#ifndef NO_SHA256 +static const byte emptySHA256Hash[] = { + 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, + 0x99, 0x6F, 0xB9, 0x24, 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, + 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 +}; +#endif +#ifdef WOLFSSL_SHA384 +static const byte emptySHA384Hash[] = { + 0x38, 0xB0, 0x60, 0xA7, 0x51, 0xAC, 0x96, 0x38, 0x4C, 0xD9, 0x32, 0x7E, + 0xB1, 0xB1, 0xE3, 0x6A, 0x21, 0xFD, 0xB7, 0x11, 0x14, 0xBE, 0x07, 0x43, + 0x4C, 0x0C, 0xC7, 0xBF, 0x63, 0xF6, 0xE1, 0xDA, 0x27, 0x4E, 0xDE, 0xBF, + 0xE7, 0x6F, 0x65, 0xFB, 0xD5, 0x1A, 0xD2, 0xF1, 0x48, 0x98, 0xB9, 0x5B +}; +#endif +#ifdef WOLFSSL_TLS13_SHA512 +static const byte emptySHA512Hash[] = { + 0xCF, 0x83, 0xE1, 0x35, 0x7E, 0xEF, 0xB8, 0xBD, 0xF1, 0x54, 0x28, 0x50, + 0xD6, 0x6D, 0x80, 0x07, 0xD6, 0x20, 0xE4, 0x05, 0x0B, 0x57, 0x15, 0xDC, + 0x83, 0xF4, 0xA9, 0x21, 0xD3, 0x6C, 0xE9, 0xCE, 0x47, 0xD0, 0xD1, 0x3C, + 0x5D, 0x85, 0xF2, 0xB0, 0xFF, 0x83, 0x18, 0xD2, 0x87, 0x7E, 0xEC, 0x2F, + 0x63, 0xB9, 0x31, 0xBD, 0x47, 0x41, 0x7A, 0x81, 0xA5, 0x38, 0x32, 0x7A, + 0xF9, 0x27, 0xDA, 0x3E +}; +#endif +/** + * Implement section 7.5 of RFC 8446 + * @return 0 on success + * <0 on failure + */ +int Tls13_Exporter(WOLFSSL* ssl, unsigned char *out, size_t outLen, + const char *label, size_t labelLen, + const unsigned char *context, size_t contextLen) +{ + int ret; + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int hashLen = 0; + byte hashOut[WC_MAX_DIGEST_SIZE]; + const byte* emptyHash = NULL; + byte firstExpand[WC_MAX_DIGEST_SIZE]; + const byte* protocol = tls13ProtocolLabel; + word32 protocolLen = TLS13_PROTOCOL_LABEL_SZ; + + if (ssl->version.minor != TLSv1_3_MINOR) + return VERSION_ERROR; + + switch (ssl->specs.mac_algorithm) { + #ifndef NO_SHA256 + case sha256_mac: + hashType = WC_SHA256; + hashLen = WC_SHA256_DIGEST_SIZE; + emptyHash = emptySHA256Hash; + break; + #endif + + #ifdef WOLFSSL_SHA384 + case sha384_mac: + hashType = WC_SHA384; + hashLen = WC_SHA384_DIGEST_SIZE; + emptyHash = emptySHA384Hash; + break; + #endif + + #ifdef WOLFSSL_TLS13_SHA512 + case sha512_mac: + hashType = WC_SHA512; + hashLen = WC_SHA512_DIGEST_SIZE; + emptyHash = emptySHA512Hash; + break; + #endif + } + + /* Derive-Secret(Secret, label, "") */ + ret = HKDF_Expand_Label(firstExpand, hashLen, + ssl->arrays->exporterSecret, hashLen, + protocol, protocolLen, (byte*)label, labelLen, + emptyHash, hashLen, hashType); + if (ret != 0) + return ret; + + /* Hash(context_value) */ + ret = wc_Hash(hashType, context, contextLen, hashOut, WC_MAX_DIGEST_SIZE); + if (ret != 0) + return ret; + + ret = HKDF_Expand_Label(out, outLen, firstExpand, hashLen, + protocol, protocolLen, exporterLabel, EXPORTER_LABEL_SZ, + hashOut, hashLen, hashType); + + return ret; +} #endif #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) @@ -862,9 +959,19 @@ int DeriveMasterSecret(WOLFSSL* ssl) if (ret != 0) return ret; - return Tls13_HKDF_Extract(ssl->arrays->masterSecret, + ret = Tls13_HKDF_Extract(ssl->arrays->masterSecret, key, ssl->specs.hash_size, ssl->arrays->masterSecret, 0, ssl->specs.mac_algorithm); + +#ifdef HAVE_KEYING_MATERIAL + if (ret != 0) + return ret; + /* Calculate exporter secret only when saving arrays */ + if (ssl->options.saveArrays) + ret = DeriveExporterSecret(ssl, ssl->arrays->exporterSecret); +#endif + + return ret; } #if defined(HAVE_SESSION_TICKET) diff --git a/tests/api.c b/tests/api.c index 879293a50..dc80978bb 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39948,6 +39948,86 @@ static int test_various_pathlen_chains(void) } #endif /* !NO_RSA && !NO_SHA && !NO_FILESYSTEM && !NO_CERTS */ +#ifdef HAVE_KEYING_MATERIAL +static int test_export_keying_material_cb(WOLFSSL_CTX *ctx, WOLFSSL *ssl) +{ + byte ekm[100] = {0}; + + (void)ctx; + + /* Succes Cases */ + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "Test label", XSTR_SIZEOF("Test label"), NULL, 0, 0), 1); + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "Test label", XSTR_SIZEOF("Test label"), NULL, 0, 1), 1); + /* Use some random context */ + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "Test label", XSTR_SIZEOF("Test label"), ekm, 10, 1), 1); + /* Failure cases */ + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "client finished", XSTR_SIZEOF("client finished"), NULL, 0, 0), 0); + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "server finished", XSTR_SIZEOF("server finished"), NULL, 0, 0), 0); + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "master secret", XSTR_SIZEOF("master secret"), NULL, 0, 0), 0); + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "extended master secret", XSTR_SIZEOF("extended master secret"), NULL, 0, 0), 0); + AssertIntEQ(wolfSSL_export_keying_material(ssl, ekm, sizeof(ekm), + "key expansion", XSTR_SIZEOF("key expansion"), NULL, 0, 0), 0); + return 0; +} + +static void test_export_keying_material_ssl_cb(WOLFSSL* ssl) +{ + wolfSSL_KeepArrays(ssl); +} + +static void test_export_keying_material(void) +{ +#ifndef SINGLE_THREADED + tcp_ready ready; + callback_functions clientCb; + func_args client_args; + func_args server_args; + THREAD_TYPE serverThread; + + XMEMSET(&client_args, 0, sizeof(func_args)); + XMEMSET(&server_args, 0, sizeof(func_args)); + XMEMSET(&clientCb, 0, sizeof(callback_functions)); +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + + StartTCP(); + InitTcpReady(&ready); + +#if defined(USE_WINDOWS_API) + /* use RNG to get random port if using windows */ + ready.port = GetRandomPort(); +#endif + + server_args.signal = &ready; + client_args.signal = &ready; + clientCb.ssl_ready = test_export_keying_material_ssl_cb; + client_args.callbacks = &clientCb; + + start_thread(test_server_nofail, &server_args, &serverThread); + wait_tcp_ready(&server_args); + test_client_nofail(&client_args, test_export_keying_material_cb); + join_thread(serverThread); + + AssertTrue(client_args.return_code); + AssertTrue(server_args.return_code); + + FreeTcpReady(&ready); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif +#endif /* !SINGLE_THREADED */ +} +#endif /* HAVE_KEYING_MATERIAL */ + /*----------------------------------------------------------------------------* | Main *----------------------------------------------------------------------------*/ @@ -40351,6 +40431,11 @@ void ApiTest(void) test_DhCallbacks(); #endif + +#ifdef HAVE_KEYING_MATERIAL + test_export_keying_material(); +#endif /* HAVE_KEYING_MATERIAL */ + /*wolfcrypt */ printf("\n-----------------wolfcrypt unit tests------------------\n"); AssertFalse(test_wolfCrypt_Init()); @@ -40653,7 +40738,6 @@ void ApiTest(void) test_wc_PKCS7_SetOriDecryptCtx(); test_wc_PKCS7_DecodeCompressedData(); - test_wc_i2d_PKCS12(); test_wolfSSL_CTX_LoadCRL(); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f953a143c..c7874e354 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2625,6 +2625,10 @@ WOLFSSL_LOCAL int DeriveMasterSecret(WOLFSSL* ssl); WOLFSSL_LOCAL int DeriveResumptionPSK(WOLFSSL* ssl, byte* nonce, byte nonceLen, byte* secret); WOLFSSL_LOCAL int DeriveResumptionSecret(WOLFSSL* ssl, byte* key); +WOLFSSL_LOCAL int Tls13_Exporter(WOLFSSL* ssl, unsigned char *out, size_t outLen, + const char *label, size_t labelLen, + const unsigned char *context, size_t contextLen); + /* The key update request values for KeyUpdate message. */ enum KeyUpdateRequest { update_not_requested, @@ -3591,6 +3595,9 @@ typedef struct Arrays { byte sessionIDSz; #ifdef WOLFSSL_TLS13 byte secret[SECRET_LEN]; +#endif +#ifdef HAVE_KEYING_MATERIAL + byte exporterSecret[WC_MAX_DIGEST_SIZE]; #endif byte masterSecret[SECRET_LEN]; #if defined(WOLFSSL_RENESAS_TSIP_TLS) && \ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index f869c2619..6475f705a 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -301,7 +301,7 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; /* wolfSSL does not support security levels */ #define SSL_CTX_set_security_level(...) /* wolfSSL does not support exporting keying material */ -#define SSL_export_keying_material(...) 0 +#define SSL_export_keying_material wolfSSL_export_keying_material #define SSL_CTX_set1_groups wolfSSL_CTX_set_groups #define SSL_set1_groups wolfSSL_set_groups diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 1487897f8..c02f5d9f1 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1057,6 +1057,20 @@ WOLFSSL_API int wolfSSL_CTX_get_cert_cache_memsize(WOLFSSL_CTX*); WOLFSSL_API int wolfSSL_CTX_set_cipher_list(WOLFSSL_CTX*, const char*); WOLFSSL_API int wolfSSL_set_cipher_list(WOLFSSL*, const char*); +#ifdef HAVE_KEYING_MATERIAL +#define TLS_PRF_LABEL_CLIENT_FINISHED "client finished" +#define TLS_PRF_LABEL_SERVER_FINISHED "server finished" +#define TLS_PRF_LABEL_MASTER_SECRET "master secret" +#define TLS_PRF_LABEL_EXT_MASTER_SECRET "extended master secret" +#define TLS_PRF_LABEL_KEY_EXPANSION "key expansion" +/* Keying Material Exporter for TLS */ +WOLFSSL_API int wolfSSL_export_keying_material(WOLFSSL *ssl, + unsigned char *out, size_t outLen, + const char *label, size_t labelLen, + const unsigned char *context, size_t contextLen, + int use_context); +#endif /* HAVE_KEYING_MATERIAL */ + /* Nonblocking DTLS helper functions */ WOLFSSL_API void wolfSSL_dtls_set_using_nonblock(WOLFSSL*, int); WOLFSSL_API int wolfSSL_dtls_get_using_nonblock(WOLFSSL*);