Add TLSv1.3 client side external PSK support Reviewed-by: Rich Salz <rsalz@openssl.org> (Merged from https://github.com/openssl/openssl/pull/3670)
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index eccc5af..0b82c0e 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c
@@ -594,6 +594,7 @@ {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PACKET_LENGTH), "bad packet length"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PROTOCOL_VERSION_NUMBER), "bad protocol version number"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PSK), "bad psk"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PSK_IDENTITY), "bad psk identity"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_RECORD_TYPE), "bad record type"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_RSA_ENCRYPT), "bad rsa encrypt"},
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index a46ede1..4bcbfa8 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c
@@ -419,6 +419,8 @@ SSL_SESSION_free(s->session); s->session = NULL; } + SSL_SESSION_free(s->psksession); + s->psksession = NULL; s->error = 0; s->hit = 0; @@ -969,6 +971,7 @@ ssl_clear_bad_session(s); SSL_SESSION_free(s->session); } + SSL_SESSION_free(s->psksession); clear_ciphers(s);
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index b26db6a..7889ab5 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h
@@ -485,6 +485,8 @@ * in here? */ size_t master_key_length; + /* TLSv1.3 early_secret used for external PSKs */ + unsigned char early_secret[EVP_MAX_MD_SIZE]; /* * For <=TLS1.2 this is the master_key. For TLS1.3 this is the resumption * master secret @@ -953,6 +955,7 @@ SSL_psk_server_cb_func psk_server_callback; # endif SSL_psk_find_session_cb_func psk_find_session_cb; + SSL_psk_use_session_cb_func psk_use_session_cb; # ifndef OPENSSL_NO_SRP SRP_CTX srp_ctx; /* ctx for SRP authentication */ @@ -1103,6 +1106,8 @@ unsigned char sid_ctx[SSL_MAX_SID_CTX_LENGTH]; /* This can also be in the session once a session is established */ SSL_SESSION *session; + /* TLSv1.3 PSK session */ + SSL_SESSION *psksession; /* Default generate session ID callback. */ GEN_SESSION_CB generate_session_id; /* Used in SSL3 */ @@ -1124,6 +1129,7 @@ SSL_psk_server_cb_func psk_server_callback; # endif SSL_psk_find_session_cb_func psk_find_session_cb; + SSL_psk_use_session_cb_func psk_use_session_cb; SSL_CTX *ctx; /* Verified chain of peer */ STACK_OF(X509) *verified_chain;
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c index 7defbb1..4965231 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c
@@ -1234,6 +1234,7 @@ EVP_MD_CTX *mctx = NULL; unsigned char hash[EVP_MAX_MD_SIZE], binderkey[EVP_MAX_MD_SIZE]; unsigned char finishedkey[EVP_MAX_MD_SIZE], tmpbinder[EVP_MAX_MD_SIZE]; + unsigned char *early_secret; const char resumption_label[] = "res binder"; const char external_label[] = "ext binder"; const char *label; @@ -1248,10 +1249,19 @@ labelsize = sizeof(resumption_label) - 1; } - /* Generate the early_secret */ + /* + * Generate the early_secret. On the server side we've selected a PSK to + * resume with (internal or external) so we always do this. On the client + * side we do this for a non-external (i.e. resumption) PSK so that it + * is in place for sending early data. For client side external PSK we + * generate it but store it away for later use. + */ + if (s->server || !external) + early_secret = (unsigned char *)s->early_secret; + else + early_secret = (unsigned char *)sess->early_secret; if (!tls13_generate_secret(s, md, NULL, sess->master_key, - sess->master_key_length, - (unsigned char *)&s->early_secret)) { + sess->master_key_length, early_secret)) { SSLerr(SSL_F_TLS_PSK_DO_BINDER, ERR_R_INTERNAL_ERROR); goto err; } @@ -1269,7 +1279,7 @@ } /* Generate the binder key */ - if (!tls13_hkdf_expand(s, md, s->early_secret, (unsigned char *)label, + if (!tls13_hkdf_expand(s, md, early_secret, (unsigned char *)label, labelsize, hash, binderkey, hashsize)) { SSLerr(SSL_F_TLS_PSK_DO_BINDER, ERR_R_INTERNAL_ERROR); goto err;
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c index 2f6d16e..5733a11 100644 --- a/ssl/statem/extensions_clnt.c +++ b/ssl/statem/extensions_clnt.c
@@ -791,10 +791,13 @@ { #ifndef OPENSSL_NO_TLS1_3 uint32_t now, agesec, agems; - size_t hashsize, binderoffset, msglen; - unsigned char *binder = NULL, *msgstart = NULL; - const EVP_MD *md; + size_t reshashsize, pskhashsize, binderoffset, msglen, idlen; + unsigned char *resbinder = NULL, *pskbinder = NULL, *msgstart = NULL; + const unsigned char *id; + const EVP_MD *handmd = NULL, *mdres, *mdpsk; EXT_RETURN ret = EXT_RETURN_FAIL; + SSL_SESSION *psksess = NULL; + int dores = 0; s->session->ext.tick_identity = TLSEXT_PSK_BAD_IDENTITY; @@ -809,76 +812,136 @@ * so don't add this extension. */ if (s->session->ssl_version != TLS1_3_VERSION - || s->session->ext.ticklen == 0) + || (s->session->ext.ticklen == 0 && s->psk_use_session_cb == NULL)) return EXT_RETURN_NOT_SENT; - if (s->session->cipher == NULL) { - SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + if (s->hello_retry_request) + handmd = ssl_handshake_md(s); + + if (s->psk_use_session_cb != NULL + && !s->psk_use_session_cb(s, handmd, &id, &idlen, &psksess)) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK); goto err; } - md = ssl_md(s->session->cipher->algorithm2); - if (md == NULL) { - /* Don't recognize this cipher so we can't use the session. Ignore it */ - return EXT_RETURN_NOT_SENT; - } + if (s->session->ext.ticklen != 0) { + if (s->session->cipher == NULL) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + goto err; + } - if (s->hello_retry_request && md != ssl_handshake_md(s)) { + mdres = ssl_md(s->session->cipher->algorithm2); + if (mdres == NULL) { + /* Don't recognize this cipher so we can't use the session. Ignore it */ + goto dopsksess; + } + + if (s->hello_retry_request && mdres != handmd) { + /* + * Selected ciphersuite hash does not match the hash for the session so + * we can't use it. + */ + goto dopsksess; + } + /* - * Selected ciphersuite hash does not match the hash for the session so - * we can't use it. + * Technically the C standard just says time() returns a time_t and says + * nothing about the encoding of that type. In practice most implementations + * follow POSIX which holds it as an integral type in seconds since epoch. + * We've already made the assumption that we can do this in multiple places + * in the code, so portability shouldn't be an issue. */ - return EXT_RETURN_NOT_SENT; - } + now = (uint32_t)time(NULL); + agesec = now - (uint32_t)s->session->time; - /* - * Technically the C standard just says time() returns a time_t and says - * nothing about the encoding of that type. In practice most implementations - * follow POSIX which holds it as an integral type in seconds since epoch. - * We've already made the assumption that we can do this in multiple places - * in the code, so portability shouldn't be an issue. - */ - now = (uint32_t)time(NULL); - agesec = now - (uint32_t)s->session->time; + if (s->session->ext.tick_lifetime_hint < agesec) { + /* Ticket is too old. Ignore it. */ + goto dopsksess; + } - if (s->session->ext.tick_lifetime_hint < agesec) { - /* Ticket is too old. Ignore it. */ - return EXT_RETURN_NOT_SENT; - } - - /* - * Calculate age in ms. We're just doing it to nearest second. Should be - * good enough. - */ - agems = agesec * (uint32_t)1000; - - if (agesec != 0 && agems / (uint32_t)1000 != agesec) { /* - * Overflow. Shouldn't happen unless this is a *really* old session. If - * so we just ignore it. + * Calculate age in ms. We're just doing it to nearest second. Should be + * good enough. */ - return EXT_RETURN_NOT_SENT; + agems = agesec * (uint32_t)1000; + + if (agesec != 0 && agems / (uint32_t)1000 != agesec) { + /* + * Overflow. Shouldn't happen unless this is a *really* old session. If + * so we just ignore it. + */ + goto dopsksess; + } + + /* + * Obfuscate the age. Overflow here is fine, this addition is supposed to + * be mod 2^32. + */ + agems += s->session->ext.tick_age_add; + + reshashsize = EVP_MD_size(mdres); + dores = 1; } - /* - * Obfuscate the age. Overflow here is fine, this addition is supposed to - * be mod 2^32. - */ - agems += s->session->ext.tick_age_add; + dopsksess: + if (!dores && psksess == NULL) + return EXT_RETURN_NOT_SENT; - hashsize = EVP_MD_size(md); + if (psksess != NULL) { + mdpsk = ssl_md(psksess->cipher->algorithm2); + if (mdpsk == NULL) { + /* + * Don't recognize this cipher so we can't use the session. + * If this happens it's an application bug. + */ + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK); + goto err; + } + + if (s->hello_retry_request && mdres != handmd) { + /* + * Selected ciphersuite hash does not match the hash for the PSK + * session. This is an application bug. + */ + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK); + goto err; + } + + pskhashsize = EVP_MD_size(mdpsk); + } /* Create the extension, but skip over the binder for now */ if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_psk) || !WPACKET_start_sub_packet_u16(pkt) - || !WPACKET_start_sub_packet_u16(pkt) - || !WPACKET_sub_memcpy_u16(pkt, s->session->ext.tick, - s->session->ext.ticklen) - || !WPACKET_put_bytes_u32(pkt, agems) - || !WPACKET_close(pkt) + || !WPACKET_start_sub_packet_u16(pkt)) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + goto err; + } + + if (dores) { + if (!WPACKET_sub_memcpy_u16(pkt, s->session->ext.tick, + s->session->ext.ticklen) + || !WPACKET_put_bytes_u32(pkt, agems)) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + goto err; + } + } + + if (psksess != NULL) { + if (!WPACKET_sub_memcpy_u16(pkt, id, idlen) + || !WPACKET_put_bytes_u32(pkt, 0)) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + goto err; + } + } + + if (!WPACKET_close(pkt) || !WPACKET_get_total_written(pkt, &binderoffset) || !WPACKET_start_sub_packet_u16(pkt) - || !WPACKET_sub_allocate_bytes_u8(pkt, hashsize, &binder) + || (dores + && !WPACKET_sub_allocate_bytes_u8(pkt, reshashsize, &resbinder)) + || (psksess != NULL + && !WPACKET_sub_allocate_bytes_u8(pkt, pskhashsize, &pskbinder)) || !WPACKET_close(pkt) || !WPACKET_close(pkt) || !WPACKET_get_total_written(pkt, &msglen) @@ -893,13 +956,24 @@ msgstart = WPACKET_get_curr(pkt) - msglen; - if (tls_psk_do_binder(s, md, msgstart, binderoffset, NULL, binder, - s->session, 1, 0) != 1) { + if (dores && tls_psk_do_binder(s, mdres, msgstart, binderoffset, NULL, + resbinder, s->session, 1, 0) != 1) { SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); goto err; } - s->session->ext.tick_identity = 0; + if (psksess != NULL && tls_psk_do_binder(s, mdpsk, msgstart, + binderoffset, NULL, pskbinder, + psksess, 1, 1) != 1) { + SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR); + goto err; + } + + if (dores) + s->session->ext.tick_identity = 0; + s->psksession = psksess; + if (psksess != NULL) + s->psksession->ext.tick_identity = (dores ? 1 : 0); ret = EXT_RETURN_SENT; err: @@ -1508,12 +1582,24 @@ return 0; } - if (s->session->ext.tick_identity != (int)identity) { + if (s->session->ext.tick_identity == (int)identity) { + s->hit = 1; + SSL_SESSION_free(s->psksession); + s->psksession = NULL; + return 1; + } + + if (s->psksession == NULL + || s->psksession->ext.tick_identity != (int)identity) { *al = SSL_AD_ILLEGAL_PARAMETER; SSLerr(SSL_F_TLS_PARSE_STOC_PSK, SSL_R_BAD_PSK_IDENTITY); return 0; } + SSL_SESSION_free(s->session); + s->session = s->psksession; + s->psksession = NULL; + memcpy(s->early_secret, s->session->early_secret, EVP_MAX_MD_SIZE); s->hit = 1; #endif