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 <rsalz@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/5252)
This commit is contained in:
Tatsuhiro Tsujikawa 2018-02-04 12:20:37 +09:00 committed by Matt Caswell
parent e454f3add6
commit b38ede8043
10 changed files with 232 additions and 4 deletions

View file

@ -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<exporter_master_secret> established in the handshake.
SSL_export_keying_material_early() is only usable with TLSv1.3, and derives
keying material using the F<early_exporter_master_secret> (as defined in the
TLS 1.3 RFC). For the client, the F<early_exporter_master_secret> 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.

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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,

View file

@ -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: