From b38ede8043439d99a3c6c174f17b91875cce66ac Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 4 Feb 2018 12:20:37 +0900 Subject: [PATCH] Export keying material using early exporter master secret This commit adds SSL_export_keying_material_early() which exports keying material using early exporter master secret. Reviewed-by: Rich Salz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/5252) --- doc/man3/SSL_export_keying_material.pod | 31 ++++++++-- include/openssl/tls1.h | 13 ++++ ssl/ssl_lib.c | 12 ++++ ssl/ssl_locl.h | 6 ++ ssl/statem/statem.c | 15 +++++ ssl/statem/statem.h | 1 + ssl/tls13_enc.c | 70 +++++++++++++++++++++ test/sslapitest.c | 82 +++++++++++++++++++++++++ test/tls13secretstest.c | 5 ++ util/libssl.num | 1 + 10 files changed, 232 insertions(+), 4 deletions(-) diff --git a/doc/man3/SSL_export_keying_material.pod b/doc/man3/SSL_export_keying_material.pod index 532b1446d9..586ad66fb2 100644 --- a/doc/man3/SSL_export_keying_material.pod +++ b/doc/man3/SSL_export_keying_material.pod @@ -2,7 +2,9 @@ =head1 NAME -SSL_export_keying_material - obtain keying material for application use +SSL_export_keying_material, +SSL_export_keying_material_early +- obtain keying material for application use =head1 SYNOPSIS @@ -13,14 +15,29 @@ SSL_export_keying_material - obtain keying material for application use const unsigned char *context, size_t contextlen, int use_context); + int SSL_export_keying_material_early(SSL *s, unsigned char *out, size_t olen, + const char *label, size_t llen, + const unsigned char *context, + size_t contextlen); + =head1 DESCRIPTION During the creation of a TLS or DTLS connection shared keying material is -established between the two endpoints. The function SSL_export_keying_material() -enables an application to use some of this keying material for its own purposes -in accordance with RFC5705 (for TLSv1.2 and below) or RFCXXXX (for TLSv1.3). +established between the two endpoints. The functions +SSL_export_keying_material() and SSL_export_keying_material_early() enable an +application to use some of this keying material for its own purposes in +accordance with RFC5705 (for TLSv1.2 and below) or RFCXXXX (for TLSv1.3). TODO(TLS1.3): Update the RFC number when the RFC is published. +SSL_export_keying_material() derives keying material using +the F established in the handshake. + +SSL_export_keying_material_early() is only usable with TLSv1.3, and derives +keying material using the F (as defined in the +TLS 1.3 RFC). For the client, the F is only +available when the client attempts to send 0-RTT data. For the server, it is +only available when the server accepts 0-RTT data. + An application may need to securely establish the context within which this keying material will be used. For example this may include identifiers for the application session, application algorithms or parameters, or the lifetime of @@ -52,6 +69,12 @@ above. Attempting to use it in SSLv3 will result in an error. SSL_export_keying_material() returns 0 or -1 on failure or 1 on success. +SSL_export_keying_material_early() returns 0 on failure or 1 on success. + +=head1 HISTORY + +SSL_export_keying_material_early() was first added in OpenSSL 1.1.1. + =head1 COPYRIGHT Copyright 2017 The OpenSSL Project Authors. All Rights Reserved. diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h index ed0380f84a..f16785663b 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h @@ -232,6 +232,19 @@ __owur int SSL_export_keying_material(SSL *s, unsigned char *out, size_t olen, const unsigned char *context, size_t contextlen, int use_context); +/* + * SSL_export_keying_material_early exports a value derived from the + * early exporter master secret, as specified in + * https://tools.ietf.org/html/draft-ietf-tls-tls13-23. It writes + * |olen| bytes to |out| given a label and optional context. It + * returns 1 on success and 0 otherwise. + */ +__owur int SSL_export_keying_material_early(SSL *s, unsigned char *out, + size_t olen, const char *label, + size_t llen, + const unsigned char *context, + size_t contextlen); + int SSL_get_peer_signature_type_nid(const SSL *s, int *pnid); int SSL_get_sigalgs(SSL *s, int idx, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 00e02f4dc7..59b507e788 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -2810,6 +2810,18 @@ int SSL_export_keying_material(SSL *s, unsigned char *out, size_t olen, contextlen, use_context); } +int SSL_export_keying_material_early(SSL *s, unsigned char *out, size_t olen, + const char *label, size_t llen, + const unsigned char *context, + size_t contextlen) +{ + if (s->version != TLS1_3_VERSION) + return 0; + + return tls13_export_keying_material_early(s, out, olen, label, llen, + context, contextlen); +} + static unsigned long ssl_session_hash(const SSL_SESSION *a) { const unsigned char *session_id = a->session_id; diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h index b590b53630..0dd2a7b727 100644 --- a/ssl/ssl_locl.h +++ b/ssl/ssl_locl.h @@ -1111,6 +1111,7 @@ struct ssl_st { unsigned char client_app_traffic_secret[EVP_MAX_MD_SIZE]; unsigned char server_app_traffic_secret[EVP_MAX_MD_SIZE]; unsigned char exporter_master_secret[EVP_MAX_MD_SIZE]; + unsigned char early_exporter_master_secret[EVP_MAX_MD_SIZE]; EVP_CIPHER_CTX *enc_read_ctx; /* cryptographic state */ unsigned char read_iv[EVP_MAX_IV_LENGTH]; /* TLSv1.3 static read IV */ EVP_MD_CTX *read_hash; /* used for mac generation */ @@ -2406,6 +2407,11 @@ __owur int tls13_export_keying_material(SSL *s, unsigned char *out, size_t olen, const char *label, size_t llen, const unsigned char *context, size_t contextlen, int use_context); +__owur int tls13_export_keying_material_early(SSL *s, unsigned char *out, + size_t olen, const char *label, + size_t llen, + const unsigned char *context, + size_t contextlen); __owur int tls1_alert_code(int code); __owur int tls13_alert_code(int code); __owur int ssl3_alert_code(int code); diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c index 818e648176..a574853487 100644 --- a/ssl/statem/statem.c +++ b/ssl/statem/statem.c @@ -951,3 +951,18 @@ int ossl_statem_export_allowed(SSL *s) return s->s3->previous_server_finished_len != 0 && s->statem.hand_state != TLS_ST_SW_FINISHED; } + +/* + * Return 1 if early TLS exporter is ready to export keying material, + * or 0 if otherwise. + */ +int ossl_statem_export_early_allowed(SSL *s) +{ + /* + * The early exporter secret is only present on the server if we + * have accepted early_data. It is present on the client as long + * as we have sent early_data. + */ + return s->ext.early_data == SSL_EARLY_DATA_ACCEPTED + || (!s->server && s->ext.early_data != SSL_EARLY_DATA_NOT_SENT); +} diff --git a/ssl/statem/statem.h b/ssl/statem/statem.h index 58cc4f4f85..193571878a 100644 --- a/ssl/statem/statem.h +++ b/ssl/statem/statem.h @@ -133,6 +133,7 @@ void ossl_statem_check_finish_init(SSL *s, int send); void ossl_statem_set_hello_verify_done(SSL *s); __owur int ossl_statem_app_data_allowed(SSL *s); __owur int ossl_statem_export_allowed(SSL *s); +__owur int ossl_statem_export_early_allowed(SSL *s); /* Flush the write BIO */ int statem_flush(SSL *s); diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c index 93118661d6..63328045cc 100644 --- a/ssl/tls13_enc.c +++ b/ssl/tls13_enc.c @@ -365,6 +365,7 @@ int tls13_change_cipher_state(SSL *s, int which) static const unsigned char server_application_traffic[] = "s ap traffic"; static const unsigned char exporter_master_secret[] = "exp master"; static const unsigned char resumption_master_secret[] = "res master"; + static const unsigned char early_exporter_master_secret[] = "e exp master"; unsigned char *iv; unsigned char secret[EVP_MAX_MD_SIZE]; unsigned char hashval[EVP_MAX_MD_SIZE]; @@ -481,6 +482,16 @@ int tls13_change_cipher_state(SSL *s, int which) } hashlen = hashlenui; EVP_MD_CTX_free(mdctx); + + if (!tls13_hkdf_expand(s, md, insecret, + early_exporter_master_secret, + sizeof(early_exporter_master_secret) - 1, + hashval, hashlen, + s->early_exporter_master_secret, hashlen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, + SSL_F_TLS13_CHANGE_CIPHER_STATE, ERR_R_INTERNAL_ERROR); + goto err; + } } else if (which & SSL3_CC_HANDSHAKE) { insecret = s->handshake_secret; finsecret = s->client_finished_secret; @@ -690,3 +701,62 @@ int tls13_export_keying_material(SSL *s, unsigned char *out, size_t olen, EVP_MD_CTX_free(ctx); return ret; } + +int tls13_export_keying_material_early(SSL *s, unsigned char *out, size_t olen, + const char *label, size_t llen, + const unsigned char *context, + size_t contextlen) +{ + static const unsigned char exporterlabel[] = "exporter"; + unsigned char exportsecret[EVP_MAX_MD_SIZE]; + unsigned char hash[EVP_MAX_MD_SIZE], data[EVP_MAX_MD_SIZE]; + const EVP_MD *md; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + unsigned int hashsize, datalen; + int ret = 0; + const SSL_CIPHER *sslcipher; + + if (ctx == NULL || !ossl_statem_export_early_allowed(s)) + goto err; + + if (!s->server && s->max_early_data > 0 + && s->session->ext.max_early_data == 0) + sslcipher = SSL_SESSION_get0_cipher(s->psksession); + else + sslcipher = SSL_SESSION_get0_cipher(s->session); + + md = ssl_md(sslcipher->algorithm2); + + /* + * Calculate the hash value and store it in |data|. The reason why + * the empty string is used is that the definition of TLS-Exporter + * is like so: + * + * TLS-Exporter(label, context_value, key_length) = + * HKDF-Expand-Label(Derive-Secret(Secret, label, ""), + * "exporter", Hash(context_value), key_length) + * + * Derive-Secret(Secret, Label, Messages) = + * HKDF-Expand-Label(Secret, Label, + * Transcript-Hash(Messages), Hash.length) + * + * Here Transcript-Hash is the cipher suite hash algorithm. + */ + if (EVP_DigestInit_ex(ctx, md, NULL) <= 0 + || EVP_DigestUpdate(ctx, context, contextlen) <= 0 + || EVP_DigestFinal_ex(ctx, hash, &hashsize) <= 0 + || EVP_DigestInit_ex(ctx, md, NULL) <= 0 + || EVP_DigestFinal_ex(ctx, data, &datalen) <= 0 + || !tls13_hkdf_expand(s, md, s->early_exporter_master_secret, + (const unsigned char *)label, llen, + data, datalen, exportsecret, hashsize) + || !tls13_hkdf_expand(s, md, exportsecret, exporterlabel, + sizeof(exporterlabel) - 1, hash, hashsize, + out, olen)) + goto err; + + ret = 1; + err: + EVP_MD_CTX_free(ctx); + return ret; +} diff --git a/test/sslapitest.c b/test/sslapitest.c index 0b6af66652..1cf5c4fc6c 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -3157,6 +3157,85 @@ static int test_export_key_mat(int tst) return testresult; } +#ifndef OPENSSL_NO_TLS1_3 +/* + * Test that SSL_export_keying_material_early() produces expected + * results. There are no test vectors so all we do is test that both + * sides of the communication produce the same results for different + * protocol versions. + */ +static int test_export_key_mat_early(int idx) +{ + static const char label[] = "test label"; + static const unsigned char context[] = "context"; + int testresult = 0; + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + SSL_SESSION *sess = NULL; + const unsigned char *emptycontext = NULL; + unsigned char ckeymat1[80], ckeymat2[80]; + unsigned char skeymat1[80], skeymat2[80]; + unsigned char buf[1]; + size_t readbytes, written; + + if (!TEST_true(setupearly_data_test(&cctx, &sctx, &clientssl, &serverssl, + &sess, idx))) + goto end; + + /* Here writing 0 length early data is enough. */ + if (!TEST_true(SSL_write_early_data(clientssl, NULL, 0, &written)) + || !TEST_int_eq(SSL_read_early_data(serverssl, buf, sizeof(buf), + &readbytes), + SSL_READ_EARLY_DATA_ERROR) + || !TEST_int_eq(SSL_get_early_data_status(serverssl), + SSL_EARLY_DATA_ACCEPTED)) + goto end; + + if (!TEST_int_eq(SSL_export_keying_material_early( + clientssl, ckeymat1, sizeof(ckeymat1), label, + sizeof(label) - 1, context, sizeof(context) - 1), 1) + || !TEST_int_eq(SSL_export_keying_material_early( + clientssl, ckeymat2, sizeof(ckeymat2), label, + sizeof(label) - 1, emptycontext, 0), 1) + || !TEST_int_eq(SSL_export_keying_material_early( + serverssl, skeymat1, sizeof(skeymat1), label, + sizeof(label) - 1, context, sizeof(context) - 1), 1) + || !TEST_int_eq(SSL_export_keying_material_early( + serverssl, skeymat2, sizeof(skeymat2), label, + sizeof(label) - 1, emptycontext, 0), 1) + /* + * Check that both sides created the same key material with the + * same context. + */ + || !TEST_mem_eq(ckeymat1, sizeof(ckeymat1), skeymat1, + sizeof(skeymat1)) + /* + * Check that both sides created the same key material with an + * empty context. + */ + || !TEST_mem_eq(ckeymat2, sizeof(ckeymat2), skeymat2, + sizeof(skeymat2)) + /* Different contexts should produce different results */ + || !TEST_mem_ne(ckeymat1, sizeof(ckeymat1), ckeymat2, + sizeof(ckeymat2))) + goto end; + + testresult = 1; + + end: + if (sess != clientpsk) + SSL_SESSION_free(sess); + SSL_SESSION_free(clientpsk); + SSL_SESSION_free(serverpsk); + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} +#endif /* OPENSSL_NO_TLS1_3 */ + static int test_ssl_clear(int idx) { SSL_CTX *cctx = NULL, *sctx = NULL; @@ -3431,6 +3510,9 @@ int setup_tests(void) #endif ADD_ALL_TESTS(test_serverinfo, 8); ADD_ALL_TESTS(test_export_key_mat, 4); +#ifndef OPENSSL_NO_TLS1_3 + ADD_ALL_TESTS(test_export_key_mat_early, 3); +#endif ADD_ALL_TESTS(test_ssl_clear, 2); ADD_ALL_TESTS(test_max_fragment_len_ext, OSSL_NELEM(max_fragment_len_test)); return 1; diff --git a/test/tls13secretstest.c b/test/tls13secretstest.c index c19bd2cb3e..8b775b8047 100644 --- a/test/tls13secretstest.c +++ b/test/tls13secretstest.c @@ -217,6 +217,11 @@ int ossl_statem_export_allowed(SSL *s) return 1; } +int ossl_statem_export_early_allowed(SSL *s) +{ + return 1; +} + /* End of mocked out code */ static int test_secret(SSL *s, unsigned char *prk, diff --git a/util/libssl.num b/util/libssl.num index 866ff537a5..48c5eca19d 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -476,3 +476,4 @@ SSL_SESSION_get_max_fragment_length 476 1_1_1 EXIST::FUNCTION: SSL_stateless 477 1_1_1 EXIST::FUNCTION: SSL_verify_client_post_handshake 478 1_1_1 EXIST::FUNCTION: SSL_force_post_handshake_auth 479 1_1_1 EXIST::FUNCTION: +SSL_export_keying_material_early 480 1_1_1 EXIST::FUNCTION: -- GitLab