Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an API for TLS 1.3 exporter #4230

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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],
Mark-Simulacrum marked this conversation as resolved.
Show resolved Hide resolved
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");
Mark-Simulacrum marked this conversation as resolved.
Show resolved Hide resolved
lrstewart marked this conversation as resolved.
Show resolved Hide resolved
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
Loading