Skip to content

Commit

Permalink
Add an API for TLS 1.3 exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark-Simulacrum committed Oct 3, 2023
1 parent ab7bfce commit e169f5a
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,
"label",
sizeof("label") - 1,
"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 e169f5a

Please sign in to comment.