From 9368f86526d9feb1f511fab9c0eee8e8dc869b87 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Mon, 12 Jun 2017 13:30:21 +0100 Subject: [PATCH] Add TLSv1.3 client side external PSK support Reviewed-by: Rich Salz (Merged from https://github.com/openssl/openssl/pull/3670) --- crypto/err/openssl.txt | 1 + include/openssl/ssl.h | 4 + include/openssl/sslerr.h | 1 + ssl/ssl_err.c | 1 + ssl/ssl_lib.c | 3 + ssl/ssl_locl.h | 6 ++ ssl/statem/extensions.c | 18 +++- ssl/statem/extensions_clnt.c | 194 +++++++++++++++++++++++++---------- 8 files changed, 170 insertions(+), 58 deletions(-) diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index e2cf2c561b..813ca74c00 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -2137,6 +2137,7 @@ SSL_R_BAD_KEY_UPDATE:122:bad key update SSL_R_BAD_LENGTH:271:bad length SSL_R_BAD_PACKET_LENGTH:115:bad packet length SSL_R_BAD_PROTOCOL_VERSION_NUMBER:116:bad protocol version number +SSL_R_BAD_PSK:219:bad psk SSL_R_BAD_PSK_IDENTITY:114:bad psk identity SSL_R_BAD_RECORD_TYPE:443:bad record type SSL_R_BAD_RSA_ENCRYPT:119:bad rsa encrypt diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 2dfa7f6f91..41edb380b8 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -767,6 +767,10 @@ typedef int (*SSL_psk_find_session_cb_func)(SSL *ssl, const unsigned char *identity, size_t identity_len, SSL_SESSION **sess); +typedef int (*SSL_psk_use_session_cb_func)(SSL *ssl, const EVP_MD *md, + const unsigned char **id, + size_t *idlen, + SSL_SESSION **sess); void SSL_CTX_set_psk_server_callback(SSL_CTX *ctx, SSL_psk_server_cb_func cb); void SSL_set_psk_server_callback(SSL *ssl, SSL_psk_server_cb_func cb); diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h index 8dfc974338..540bafff17 100644 --- a/include/openssl/sslerr.h +++ b/include/openssl/sslerr.h @@ -381,6 +381,7 @@ int ERR_load_SSL_strings(void); # define SSL_R_BAD_LENGTH 271 # define SSL_R_BAD_PACKET_LENGTH 115 # define SSL_R_BAD_PROTOCOL_VERSION_NUMBER 116 +# define SSL_R_BAD_PSK 219 # define SSL_R_BAD_PSK_IDENTITY 114 # define SSL_R_BAD_RECORD_TYPE 443 # define SSL_R_BAD_RSA_ENCRYPT 119 diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index eccc5af22b..0b82c0e945 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -594,6 +594,7 @@ static const ERR_STRING_DATA SSL_str_reasons[] = { {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 a46ede1414..4bcbfa8377 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -419,6 +419,8 @@ int SSL_clear(SSL *s) 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 @@ void SSL_free(SSL *s) 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 b26db6a9ca..7889ab5b0e 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -485,6 +485,8 @@ struct ssl_session_st { * 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 @@ struct ssl_ctx_st { 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 @@ struct ssl_st { 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 @@ struct ssl_st { 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 7defbb1e00..496523146c 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c @@ -1234,6 +1234,7 @@ int tls_psk_do_binder(SSL *s, const EVP_MD *md, const unsigned char *msgstart, 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 @@ int tls_psk_do_binder(SSL *s, const EVP_MD *md, const unsigned char *msgstart, 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 @@ int tls_psk_do_binder(SSL *s, const EVP_MD *md, const unsigned char *msgstart, } /* 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 2f6d16e61a..5733a114ff 100644 --- a/ssl/statem/extensions_clnt.c +++ b/ssl/statem/extensions_clnt.c @@ -791,10 +791,13 @@ EXT_RETURN tls_construct_ctos_psk(SSL *s, WPACKET *pkt, unsigned int context, { #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 @@ EXT_RETURN tls_construct_ctos_psk(SSL *s, WPACKET *pkt, unsigned int context, * 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; + } + + 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; + } - if (s->hello_retry_request && md != ssl_handshake_md(s)) { /* - * 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; - /* - * 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. + */ + goto dopsksess; + } - 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. + * Obfuscate the age. Overflow here is fine, this addition is supposed to + * be mod 2^32. */ - return EXT_RETURN_NOT_SENT; + 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 @@ EXT_RETURN tls_construct_ctos_psk(SSL *s, WPACKET *pkt, unsigned int context, 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 @@ int tls_parse_stoc_psk(SSL *s, PACKET *pkt, unsigned int context, X509 *x, 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 -- 2.34.1