Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
89 changes: 89 additions & 0 deletions src/session_mbedtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <ctype.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -1923,3 +1924,91 @@ nc_tls_get_cert_exp_time_wrap(void *cert)

return timegm(&t);
}

/**
* @brief Convert the MbedTLS key export type to a label for the keylog file.
*
* @param[in] type MbedTLS key export type.
* @return Label for the keylog file or NULL if the type is not supported.
*/
static const char *
nc_tls_keylog_type2label(mbedtls_ssl_key_export_type type)
{
switch (type) {
case MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET:
return "CLIENT_RANDOM";
#ifdef MBEDTLS_SSL_PROTO_TLS1_3
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_CLIENT_HANDSHAKE_TRAFFIC_SECRET:
return "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_SERVER_HANDSHAKE_TRAFFIC_SECRET:
return "SERVER_HANDSHAKE_TRAFFIC_SECRET";
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_CLIENT_APPLICATION_TRAFFIC_SECRET:
return "CLIENT_TRAFFIC_SECRET_0";
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_SERVER_APPLICATION_TRAFFIC_SECRET:
return "SERVER_TRAFFIC_SECRET_0";
#endif
default:
return NULL;
}
}

/**
* @brief Callback for writing a line in the keylog file.
*/
static void
nc_tls_keylog_write_line(void *UNUSED(p_expkey), mbedtls_ssl_key_export_type type, const unsigned char *secret,
size_t secret_len, const unsigned char client_random[32],
const unsigned char UNUSED(server_random[32]), mbedtls_tls_prf_types UNUSED(tls_prf_type))
{
size_t linelen, len = 0, i, client_random_len;
char buf[256];
const char *label;

if (!server_opts.tls_keylog_file) {
return;
}

label = nc_tls_keylog_type2label(type);
if (!label) {
/* type not supported */
return;
}

/* <Label> <space> 0x<ClientRandom> <space> 0x<Secret> */
linelen = strlen(label) + 1 + 2 * 32 + 1 + 2 * secret_len + 1;
if (linelen > sizeof(buf)) {
/* sanity check, should not happen since the max len should be 196 bytes */
return;
}

/* write the label */
len += sprintf(buf + len, "%s ", label);

/* write the client random */
client_random_len = 32;
for (i = 0; i < client_random_len; i++) {
len += sprintf(buf + len, "%02x", client_random[i]);
}
len += sprintf(buf + len, " ");

/* write the secret */
for (i = 0; i < secret_len; i++) {
len += sprintf(buf + len, "%02x", secret[i]);
}

len += sprintf(buf + len, "\n");
buf[len] = '\0';

if (len != linelen) {
return;
}

fputs(buf, server_opts.tls_keylog_file);
fflush(server_opts.tls_keylog_file);
}

void
nc_tls_keylog_session_wrap(void *session)
{
mbedtls_ssl_set_export_keys_cb(session, nc_tls_keylog_write_line, NULL);
}
43 changes: 43 additions & 0 deletions src/session_openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <ctype.h>
#include <poll.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -1449,3 +1450,45 @@ nc_tls_get_cert_exp_time_wrap(void *cert)

return timegm(&t);
}

/**
* @brief Callback for writing a line in the keylog file.
*/
static void
nc_tls_keylog_write_line(const SSL *UNUSED(ssl), const char *line)
{
size_t linelen;
char buf[256];

if (!server_opts.tls_keylog_file || !line) {
return;
}

/* linelen should not exceed 196 bytes, so 256 should be enough */
linelen = strlen(line);
if (!linelen || (linelen > sizeof(buf) - 2)) {
return;
}

memcpy(buf, line, linelen);
if (line[linelen - 1] != '\n') {
buf[linelen++] = '\n';
}
buf[linelen] = '\0';

fputs(buf, server_opts.tls_keylog_file);
fflush(server_opts.tls_keylog_file);
}

void
nc_tls_keylog_session_wrap(void *session)
{
SSL_CTX *ctx;

ctx = SSL_get_SSL_CTX(session);
if (!ctx) {
return;
}

SSL_CTX_set_keylog_callback(ctx, nc_tls_keylog_write_line);
}
7 changes: 7 additions & 0 deletions src/session_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ struct nc_server_opts {
} *intervals;
int interval_count; /**< Number of intervals. */
} cert_exp_notif;

FILE *tls_keylog_file; /**< File to log TLS secrets to. */
#endif
};

Expand Down Expand Up @@ -685,6 +687,11 @@ struct nc_server_opts {
*/
#define NC_CLIENT_MONITORING_LOCK_TIMEOUT 500

/**
* TLS key log file environment variable name.
*/
#define NC_TLS_KEYLOGFILE_ENV "SSLKEYLOGFILE"

/**
* @brief Type of the session
*/
Expand Down
31 changes: 31 additions & 0 deletions src/session_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,29 @@ nc_server_init_cb_ctx(const struct ly_ctx *ctx)
}
}

#ifdef NC_ENABLED_SSH_TLS

/**
* @brief Open the keylog file for writing TLS secrets.
*/
static void
nc_server_keylog_file_open(void)
{
char *keylog_file_name;

keylog_file_name = getenv(NC_TLS_KEYLOGFILE_ENV);
if (!keylog_file_name) {
return;
}

server_opts.tls_keylog_file = fopen(keylog_file_name, "a");

Check failure

Code scanning / CodeQL

File created without restricting permissions

A file may be created here with mode 0666, which would make it world-writable.

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This argument to a file access function is derived from [user input (an environment variable)](1) and then passed to fopen(__filename).
if (!server_opts.tls_keylog_file) {
WRN(NULL, "Failed to open keylog file \"%s\".", keylog_file_name);
}
}

#endif

API int
nc_server_init(void)
{
Expand Down Expand Up @@ -858,6 +881,9 @@ nc_server_init(void)
ERR(NULL, "%s: failed to init certificate expiration notification thread condition(%s).", __func__, strerror(r));
goto error;
}

/* try to open the keylog file for writing TLS secrets */
nc_server_keylog_file_open();
#endif

return 0;
Expand Down Expand Up @@ -917,6 +943,11 @@ nc_server_destroy(void)
nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
curl_global_cleanup();
ssh_finalize();

/* close the TLS keylog file */
if (server_opts.tls_keylog_file) {
fclose(server_opts.tls_keylog_file);
}
#endif /* NC_ENABLED_SSH_TLS */
}

Expand Down
7 changes: 6 additions & 1 deletion src/session_server_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#define _GNU_SOURCE

#include <errno.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
Expand All @@ -32,7 +33,6 @@
#include "session_p.h"
#include "session_wrapper.h"

struct nc_server_tls_opts tls_ch_opts;
extern struct nc_server_opts server_opts;

static int
Expand Down Expand Up @@ -899,6 +899,11 @@ nc_accept_tls_session(struct nc_session *session, struct nc_server_tls_opts *opt
goto fail;
}

/* if keylog file is set, log the tls secrets there */
if (server_opts.tls_keylog_file) {
nc_tls_keylog_session_wrap(session->ti.tls.session);
}

/* set session fd */
nc_tls_set_fd_wrap(session->ti.tls.session, sock, &session->ti.tls.ctx);

Expand Down
9 changes: 9 additions & 0 deletions src/session_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ struct nc_tls_verify_cb_data {
void *chain; /**< Certificate chain used to verify the client cert. */
};

extern struct nc_server_opts server_opts;

/**
* @brief Creates a new TLS session from the given configuration.
*
Expand Down Expand Up @@ -732,4 +734,11 @@ void nc_server_tls_set_cipher_suites_wrap(void *tls_cfg, void *cipher_suites);
*/
time_t nc_tls_get_cert_exp_time_wrap(void *cert);

/**
* @brief Set the session to log TLS secrets for.
*
* @param[in] session Session to log secrets for.
*/
void nc_tls_keylog_session_wrap(void *session);

#endif
72 changes: 71 additions & 1 deletion tests/test_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#include "ln2_test.h"

#define KEYLOG_FILENAME "ln2_test_tls_keylog.txt"

int TEST_PORT = 10050;
const char *TEST_PORT_STR = "10050";

Expand Down Expand Up @@ -99,6 +101,64 @@ test_nc_tls_ec_key(void **state)
}
}

static void
check_keylog_file(const char *filename)
{
char buf[256];
FILE *f;
int cli_random, cli_hs, cli_traffic, srv_hs, srv_traffic;

cli_random = cli_hs = cli_traffic = srv_hs = srv_traffic = 0;

f = fopen(filename, "r");
assert_non_null(f);

while (fgets(buf, sizeof(buf), f)) {
if (!strncmp(buf, "CLIENT_RANDOM", 13)) {
cli_random++;
} else if (!strncmp(buf, "CLIENT_HANDSHAKE_TRAFFIC_SECRET", 31)) {
cli_hs++;
} else if (!strncmp(buf, "CLIENT_TRAFFIC_SECRET_0", 23)) {
cli_traffic++;
} else if (!strncmp(buf, "SERVER_HANDSHAKE_TRAFFIC_SECRET", 31)) {
srv_hs++;
} else if (!strncmp(buf, "SERVER_TRAFFIC_SECRET_0", 23)) {
srv_traffic++;
}
}

fclose(f);

if (cli_random) {
/* tls 1.2 */
assert_int_equal(cli_random, 1);
assert_int_equal(cli_hs + cli_traffic + srv_hs + srv_traffic, 0);
} else {
/* tls 1.3 */
assert_int_equal(cli_hs + cli_traffic + srv_hs + srv_traffic, 4);
}
}

static void
test_nc_tls_keylog(void **state)
{
int ret, i;
pthread_t tids[2];

assert_non_null(state);

ret = pthread_create(&tids[0], NULL, client_thread, *state);
assert_int_equal(ret, 0);
ret = pthread_create(&tids[1], NULL, ln2_glob_test_server_thread, *state);
assert_int_equal(ret, 0);

for (i = 0; i < 2; i++) {
pthread_join(tids[i], NULL);
}

check_keylog_file(KEYLOG_FILENAME);
}

static void
test_nc_tls_free_test_data(void *test_data)
{
Expand Down Expand Up @@ -149,12 +209,22 @@ setup_f(void **state)
return 0;
}

static int
keylog_setup_f(void **state)
{
unlink(KEYLOG_FILENAME);
setenv("SSLKEYLOGFILE", KEYLOG_FILENAME, 1);

return setup_f(state);
}

int
main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_nc_tls, setup_f, ln2_glob_test_teardown),
cmocka_unit_test_setup_teardown(test_nc_tls_ec_key, setup_f, ln2_glob_test_teardown)
cmocka_unit_test_setup_teardown(test_nc_tls_ec_key, setup_f, ln2_glob_test_teardown),
cmocka_unit_test_setup_teardown(test_nc_tls_keylog, keylog_setup_f, ln2_glob_test_teardown)
};

/* try to get ports from the environment, otherwise use the default */
Expand Down