Skip to content

Commit

Permalink
Add support for exporting symmetric keys from connections
Browse files Browse the repository at this point in the history
This is currently only supported for TLS 1.3 and is standard compliant,
using the TLS-Exporter function defined by RFC 8446:
https://www.rfc-editor.org/rfc/rfc8446#section-7.5
  • Loading branch information
Mark-Simulacrum committed Oct 4, 2023
1 parent ab7bfce commit e6c57d8
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 12 deletions.
14 changes: 14 additions & 0 deletions api/s2n.h
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,20 @@ S2N_API extern int s2n_connection_client_cert_used(struct s2n_connection *conn);
*/
S2N_API extern const char *s2n_connection_get_cipher(struct s2n_connection *conn);

/**
* Provides access to the TLS-Exporter functionality.
*
* See https://datatracker.ietf.org/doc/html/rfc5705 and https://www.rfc-editor.org/rfc/rfc8446.
*
* @note This is currently only available with TLS 1.3 connections which have finished a handshake.
*
* @param conn A pointer to the connection
* @returns A POSIX error signal. If an error was returned, the value contained in `output` should be considered invalid.
*/
S2N_API extern int s2n_connection_tls_exporter(struct s2n_connection *conn,
const uint8_t *label, uint32_t label_length, const uint8_t *context, uint32_t context_length,
uint8_t *output, uint32_t output_length);

/**
* Returns the IANA value for the connection's negotiated cipher suite.
*
Expand Down
26 changes: 26 additions & 0 deletions bindings/rust/s2n-tls/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,32 @@ impl Connection {
hash_alg => Some(hash_alg.try_into()?),
})
}

/// Provides access to the TLS-Exporter functionality.
///
/// See https://datatracker.ietf.org/doc/html/rfc5705 and https://www.rfc-editor.org/rfc/rfc8446.
///
/// This is currently only available with TLS 1.3 connections which have finished a handshake.
pub fn tls_exporter(
&self,
label: &[u8],
context: &[u8],
output: &mut [u8],
) -> Result<(), Error> {
unsafe {
s2n_connection_tls_exporter(
self.connection.as_ptr(),
label.as_ptr(),
label.len().try_into().map_err(|_| Error::INVALID_INPUT)?,
context.as_ptr(),
context.len().try_into().map_err(|_| Error::INVALID_INPUT)?,
output.as_mut_ptr(),
output.len().try_into().map_err(|_| Error::INVALID_INPUT)?,
)
.into_result()
.map(|_| ())
}
}
}

struct Context {
Expand Down
5 changes: 1 addition & 4 deletions crypto/s2n_hkdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,7 @@ int s2n_hkdf_expand_label(struct s2n_hmac_state *hmac, s2n_hmac_algorithm alg, c
struct s2n_blob hkdf_label_blob = { 0 };
struct s2n_stuffer hkdf_label = { 0 };

/* RFC8446 specifies that labels must be 12 characters or less, to avoid
** incurring two hash rounds.
*/
POSIX_ENSURE_LTE(label->size, 12);
POSIX_ENSURE_LTE(label->size, S2N_MAX_HKDF_EXPAND_LABEL_LENGTH);

POSIX_GUARD(s2n_blob_init(&hkdf_label_blob, hkdf_label_buf, sizeof(hkdf_label_buf)));
POSIX_GUARD(s2n_stuffer_init(&hkdf_label, &hkdf_label_blob));
Expand Down
10 changes: 10 additions & 0 deletions crypto/s2n_hkdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
#include "crypto/s2n_hmac.h"
#include "utils/s2n_blob.h"

/*
* Label structure is `opaque label<7..255> = "tls13 " + Label` per RFC8446.
* So, we have 255-sizeof("tls13 ") = 249, the maximum label length.
*
* Note that all labels defined by RFC 8446 are <12 characters, which
* avoids an extra hash iteration. However, the exporter functionality
* (s2n_connection_tls_exporter) allows for longer labels.
*/
#define S2N_MAX_HKDF_EXPAND_LABEL_LENGTH 249

int s2n_hkdf(struct s2n_hmac_state *hmac, s2n_hmac_algorithm alg, const struct s2n_blob *salt,
const struct s2n_blob *key, const struct s2n_blob *info, struct s2n_blob *output);

Expand Down
7 changes: 6 additions & 1 deletion crypto/s2n_tls13_keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* [x] server_handshake_traffic_secret
* [x] client_application_traffic_secret_0
* [x] server_application_traffic_secret_0
* [ ] exporter_master_secret
* [x] exporter_master_secret
* [x] resumption_master_secret
*
* The TLS 1.3 key generation can be divided into 3 phases
Expand Down Expand Up @@ -79,6 +79,11 @@ S2N_BLOB_LABEL(s2n_tls13_label_session_ticket_secret, "resumption")
S2N_BLOB_LABEL(s2n_tls13_label_traffic_secret_key, "key")
S2N_BLOB_LABEL(s2n_tls13_label_traffic_secret_iv, "iv")

/*
* TLS 1.3 Exporter label
*/
S2N_BLOB_LABEL(s2n_tls13_label_exporter, "exporter")

/*
* TLS 1.3 Finished label
*/
Expand Down
2 changes: 2 additions & 0 deletions crypto/s2n_tls13_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ extern const struct s2n_blob s2n_tls13_label_resumption_master_secret;

extern const struct s2n_blob s2n_tls13_label_finished;

extern const struct s2n_blob s2n_tls13_label_exporter;

/* Traffic secret labels */

extern const struct s2n_blob s2n_tls13_label_traffic_secret_key;
Expand Down
1 change: 1 addition & 0 deletions tests/unit/s2n_self_talk_key_log_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ S2N_RESULT s2n_test_check_tls13(struct s2n_stuffer *stuffer)
RESULT_ENSURE_REF(strstr(out, "SERVER_HANDSHAKE_TRAFFIC_SECRET "));
RESULT_ENSURE_REF(strstr(out, "CLIENT_TRAFFIC_SECRET_0 "));
RESULT_ENSURE_REF(strstr(out, "SERVER_TRAFFIC_SECRET_0 "));
RESULT_ENSURE_REF(strstr(out, "EXPORTER_SECRET "));
return S2N_RESULT_OK;
}

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/s2n_self_talk_quic_support_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include "tls/s2n_quic_support.h"

#define S2N_MODE_COUNT 2
#define S2N_SECRET_TYPE_COUNT 5
#define S2N_SECRET_TYPE_COUNT 6

static const uint8_t CLIENT_TRANSPORT_PARAMS[] = "client transport params";
static const uint8_t SERVER_TRANSPORT_PARAMS[] = "server transport params";
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/s2n_tls13_key_schedule_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include "tls/s2n_tls13_secrets.c"

#define NO_TRIGGER_MSG APPLICATION_DATA
#define TRAFFIC_SECRET_COUNT 5
#define TRAFFIC_SECRET_COUNT 6

static uint8_t empty_secret[S2N_TLS13_SECRET_MAX_LEN] = { 0 };

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/s2n_tls13_secrets_rfc8448_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,50 @@ int main(int argc, char **argv)
secret.data, secret.size);
}
};

/* Derive EXPORTER_MASTER_SECRET */
{
/**
*= https://www.rfc-editor.org/rfc/rfc8448.html#section-3
*= type=test
*# {client} derive secret "tls13 exp master" (same as server)
*
*= https://www.rfc-editor.org/rfc/rfc8448.html#section-3
*= type=test
*# {server} derive secret "tls13 exp master":
*#
*# PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
*# 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
*#
*# hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a
*# 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
*#
**
*= https://www.rfc-editor.org/rfc/rfc8448.html#section-3
*= type=test
*# info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73
*# 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00
*# 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13
*#
*# expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67
*# 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50
*/
S2N_BLOB_FROM_HEX(hash, "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a \
00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13");
S2N_BLOB_FROM_HEX(secret, "fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 \
92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50");

for (size_t i = 0; i < s2n_array_len(modes); i++) {
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(modes[i]), s2n_connection_ptr_free);
conn->secure->cipher_suite = cipher_suite;
EXPECT_OK(s2n_connection_set_test_master_secret(conn, &master_secret));
EXPECT_OK(s2n_connection_set_test_transcript_hash(conn, SERVER_FINISHED, &hash));

EXPECT_OK(s2n_derive_exporter_master_secret(conn, &derived_secret));
EXPECT_EQUAL(derived_secret.size, secret.size);
EXPECT_BYTEARRAY_EQUAL(derived_secret.data, secret.data, secret.size);
}
};
};

/* Resumed 0-RTT Handshake */
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/s2n_tls13_secrets_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ int main(int argc, char **argv)
EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.server_handshake_secret, test_secret.data, test_secret.size);
EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.client_app_secret, test_secret.data, test_secret.size);
EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.server_app_secret, test_secret.data, test_secret.size);
EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.exporter_master_secret, test_secret.data, test_secret.size);
EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.resumption_master_secret, test_secret.data, test_secret.size);

EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.extract_secret, empty_secret, sizeof(empty_secret));
Expand All @@ -289,6 +290,7 @@ int main(int argc, char **argv)
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_handshake_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.client_app_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_app_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.resumption_master_secret, empty_secret, sizeof(empty_secret));

EXPECT_OK(s2n_tls13_secrets_clean(conn));
Expand All @@ -299,6 +301,7 @@ int main(int argc, char **argv)
EXPECT_BYTEARRAY_EQUAL(conn->secrets.version.tls13.server_handshake_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.client_app_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_app_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret, empty_secret, sizeof(empty_secret));
EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.resumption_master_secret, empty_secret, sizeof(empty_secret));
};
};
Expand Down Expand Up @@ -488,5 +491,58 @@ int main(int argc, char **argv)
};
};

/* s2n_connection_export_secret */
{
/* Derives exporter secret on SERVER_FINISHED */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
conn->secure->cipher_suite = &s2n_tls13_aes_128_gcm_sha256;
conn->actual_protocol_version = S2N_TLS13;
EXPECT_OK(s2n_connection_set_test_master_secret(conn, &test_secret));
EXPECT_BYTEARRAY_EQUAL(conn->secrets.version.tls13.exporter_master_secret,
empty_secret, sizeof(empty_secret));

while (s2n_conn_get_current_message_type(conn) != SERVER_FINISHED) {
conn->handshake.message_number++;
}
EXPECT_OK(s2n_tls13_secrets_update(conn));

EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret,
empty_secret, sizeof(empty_secret));

/*
* s2n_connection_tls_exporter requires us to finish the handshake.
* The above is needed since s2n_tls13_secrets_update will only
* initialize when it sees the SERVER_FINISHED frame.
*/
EXPECT_OK(s2n_skip_handshake(conn));

uint8_t output[32] = { 0 };
int result = s2n_connection_tls_exporter(
conn,
(const uint8_t *) "label",
sizeof("label") - 1,
(const uint8_t *) "context",
sizeof("context") - 1,
output,
sizeof(output));
EXPECT_SUCCESS(result);
/*
* If updating this value, it's a good idea to make sure the update
* matches OpenSSL's SSL_export_keying_material. The easiest known
* way of doing that is building a simple client/server pair and
* calling the s2n and OpenSSL APIs after a handshake on both
* sides; you should get identical results with identical
* label/context parameters. This particular value though is not
* checked as its dependent on the s2n-specific test master secret.
*/
S2N_BLOB_FROM_HEX(expected, "3a 72 eb 08 10 a3 69 f3 06 f2 77 11 70 ad d5 76 bd 21 15 \
46 d4 c8 fb 80 1a 93 04 1e ac 59 aa 47");
EXPECT_EQUAL(sizeof(output), expected.size);
EXPECT_BYTEARRAY_EQUAL(output, expected.data, expected.size);
};
};

END_TEST();
}
5 changes: 5 additions & 0 deletions tls/s2n_key_log.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ S2N_RESULT s2n_key_log_tls13_secret(struct s2n_connection *conn, const struct s2
const uint8_t server_handshake_label[] = "SERVER_HANDSHAKE_TRAFFIC_SECRET ";
const uint8_t client_traffic_label[] = "CLIENT_TRAFFIC_SECRET_0 ";
const uint8_t server_traffic_label[] = "SERVER_TRAFFIC_SECRET_0 ";
const uint8_t exporter_secret_label[] = "EXPORTER_SECRET ";

const uint8_t *label = NULL;
uint8_t label_size = 0;
Expand All @@ -109,6 +110,10 @@ S2N_RESULT s2n_key_log_tls13_secret(struct s2n_connection *conn, const struct s2
label = server_traffic_label;
label_size = sizeof(server_traffic_label) - 1;
break;
case S2N_EXPORTER_SECRET:
label = exporter_secret_label;
label_size = sizeof(exporter_secret_label) - 1;
break;
default:
/* Ignore the secret types we don't understand */
return S2N_RESULT_OK;
Expand Down
1 change: 1 addition & 0 deletions tls/s2n_quic_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ typedef enum {
S2N_SERVER_HANDSHAKE_TRAFFIC_SECRET,
S2N_CLIENT_APPLICATION_TRAFFIC_SECRET,
S2N_SERVER_APPLICATION_TRAFFIC_SECRET,
S2N_EXPORTER_SECRET,
} s2n_secret_type_t;

/*
Expand Down
Loading

0 comments on commit e6c57d8

Please sign in to comment.