From 7f6899567baa493e4866b0e51db8b44fdea2d47d Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:17:00 +0100 Subject: [PATCH] Allow TLS pre-master secrets to be recorded This allows for RDP sessions to be easily decrypted within Wireshark --- common/os_calls.c | 24 +++++++++++ common/os_calls.h | 1 + common/ssl_calls.c | 92 ++++++++++++++++++++++++++++++++++++++++++ common/ssl_calls.h | 8 ++++ docs/man/xrdp.ini.5.in | 13 ++++++ libxrdp/xrdp_rdp.c | 8 ++++ xrdp/xrdp.ini.in | 3 ++ 7 files changed, 149 insertions(+) diff --git a/common/os_calls.c b/common/os_calls.c index 2a997bbb..32708ae1 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -2311,6 +2311,30 @@ g_file_seek(int fd, int offset) #endif } +/*****************************************************************************/ +/* move file pointer to end of file, plus an offset */ +int +g_file_seek_end(int fd, int offset) +{ +#if defined(_WIN32) + int rv; + + rv = (int)SetFilePointer((HANDLE)fd, offset, 0, FILE_END); + + if (rv == (int)INVALID_SET_FILE_POINTER) + { + return -1; + } + else + { + return rv; + } + +#else + return (int)lseek(fd, offset, SEEK_END); +#endif +} + /*****************************************************************************/ /* do a write lock on a file */ /* return boolean */ diff --git a/common/os_calls.h b/common/os_calls.h index 34815033..80b1bcda 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -242,6 +242,7 @@ int g_file_is_open(int fd); int g_file_read(int fd, char *ptr, int len); int g_file_write(int fd, const char *ptr, int len); int g_file_seek(int fd, int offset); +int g_file_seek_end(int fd, int offset); int g_file_lock(int fd, int start, int len); int g_file_map(int fd, int aread, int awrite, size_t length, void **addr); diff --git a/common/ssl_calls.c b/common/ssl_calls.c index 42155a70..e1ef6e96 100644 --- a/common/ssl_calls.c +++ b/common/ssl_calls.c @@ -53,6 +53,11 @@ static EVP_CIPHER *g_cipher_des_ede3_cbc; /* DES3 CBC cipher */ static EVP_MAC *g_mac_hmac; /* HMAC MAC */ #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000L +#define HAS_KEYLOG_CALLBACK /* SSL_CTX_set_keylog_callback() is available */ +static char *g_keylog_filename = NULL; +#endif + /* definition of ssl_tls */ struct ssl_tls { @@ -173,13 +178,96 @@ ssl_finish(void) /* De-allocate any allocated globals * For OpenSSL 3, these can all safely be passed a NULL pointer */ EVP_MD_free(g_md_md5); + g_md_md5 = NULL; EVP_MD_free(g_md_sha1); + g_md_sha1 = NULL; EVP_CIPHER_free(g_cipher_des_ede3_cbc); + g_cipher_des_ede3_cbc = NULL; EVP_MAC_free(g_mac_hmac); + g_mac_hmac = NULL; +#endif + +#ifdef HAS_KEYLOG_CALLBACK + free(g_keylog_filename); + g_keylog_filename = NULL; #endif return 0; } +/*****************************************************************************/ +int +ssl_set_pre_master_secret_logfile(const char *filename) +{ + int rv = 0; +#ifdef HAS_KEYLOG_CALLBACK + int fd = -1; + + /* Remove any existing setting */ + free(g_keylog_filename); + g_keylog_filename = NULL; + + if (filename == NULL || filename[0] == '\0') + { + /* all done */ + } + else if (filename[0] != '/') + { + LOG(LOG_LEVEL_ERROR, "TLS pre-master log file must start with '/'"); + } + else if ((fd = g_file_open_rw(filename)) < 0) + { + /* Can't open file for writing. We'll log a single error here + * rather than lots of errors in the callback */ + LOG(LOG_LEVEL_ERROR, "Can't write TLS pre-master secrets to %s [%s]", + filename, g_get_strerror()); + } + else if (g_file_close(fd) != 0) + { + /* Ignore this one */ + } + else if ((g_keylog_filename = g_strdup(filename)) == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory setting TLS pre-master log"); + } + else + { + rv = 1; + } +#else + LOG(LOG_LEVEL_WARNING, + "This system is unable to log TLS pre-master secrets"); +#endif + return rv; +} + +/*****************************************************************************/ +/* Log the pre-master secret for debugging purposes */ +#ifdef HAS_KEYLOG_CALLBACK +static void +log_pre_master_secret(const SSL *ssl, const char *line) +{ + if (g_keylog_filename != NULL && g_keylog_filename[0] == '/') + { + int fd = g_file_open_rw(g_keylog_filename); + if (fd < 0) + { + LOG(LOG_LEVEL_ERROR, "Can't write pre-master secret to %s [ %s]", + g_keylog_filename, g_get_strerror()); + } + else + { + g_chmod_hex(g_keylog_filename, 0x640); // Must be group readable + g_file_seek_end(fd, 0); + g_file_write(fd, line, strlen(line)); + g_file_write(fd, "\n", 1); + (void)g_file_close(fd); + } + } +} +#endif // HAS_KEYLOG_CALLBACK + +/*****************************************************************************/ + /* rc4 stuff * * For OpenSSL 3.0, the rc4 encryption algorithm is only provided by the @@ -1167,6 +1255,10 @@ ssl_tls_accept(struct ssl_tls *self, long ssl_protocols, } #endif +#ifdef HAS_KEYLOG_CALLBACK + SSL_CTX_set_keylog_callback(self->ctx, log_pre_master_secret); +#endif // HAS_KEYLOG_CALLBACK + self->ssl = SSL_new(self->ctx); if (self->ssl == NULL) diff --git a/common/ssl_calls.h b/common/ssl_calls.h index 0c94cb08..5939288c 100644 --- a/common/ssl_calls.h +++ b/common/ssl_calls.h @@ -30,6 +30,14 @@ int ssl_init(void); int ssl_finish(void); +/** + * Sets a log file for recording TLS pre-master secrets + * + * @param filename Filename to log secrets in + * @return != 0 if the log was successfully set + */ +int +ssl_set_pre_master_secret_logfile(const char *filename); void * ssl_rc4_info_create(void); void diff --git a/docs/man/xrdp.ini.5.in b/docs/man/xrdp.ini.5.in index cd6f8c42..8c4dddf5 100644 --- a/docs/man/xrdp.ini.5.in +++ b/docs/man/xrdp.ini.5.in @@ -73,6 +73,19 @@ If not specified, defaults to \fB@sysconfdir@/@sysconfsubdir@/cert.pem\fP, \fB@s This parameter is effective only if \fBsecurity_layer\fP is set to \fBtls\fP or \fBnegotiate\fP. +.TP +\fBtls_pms_log_file\fR=\fI\fR +Logs TLS pre-master secrets to the specified file. This allows packet capture +tools (e.g. Wireshark) to decrypt captured PDUs. + +The file must be writeable by xrdp and readable by the packet capture tool. +A good way to achive this is to create a temporary directory with +permissions 2750 owned by the user running xrdp, and in the group used by +the packet sniffer. + +SETTING THIS OPTION IS A SECURITY RISK. ONLY SET THIS OPTION FOR DEBUGGING +COMMUNICATIONS BETWEEN XRDP AND A CLIENT. + .TP \fBchannel_code\fP=\fI[true|false]\fP If set to \fB0\fR, \fBfalse\fR or \fBno\fR this option disables all channels \fBxrdp\fR(8). diff --git a/libxrdp/xrdp_rdp.c b/libxrdp/xrdp_rdp.c index 56e98858..1b0fdbb9 100644 --- a/libxrdp/xrdp_rdp.c +++ b/libxrdp/xrdp_rdp.c @@ -277,6 +277,14 @@ xrdp_rdp_read_config(const char *xrdp_ini, struct xrdp_client_info *client_info) client_info->key_file, g_get_strerror()); } } + else if (g_strcasecmp(item, "tls_pms_log_file") == 0) + { + if (ssl_set_pre_master_secret_logfile(value)) + { + LOG(LOG_LEVEL_WARNING, "TLS pre-master secrets will be logged. " + "This is a security risk."); + } + } else if (g_strcasecmp(item, "domain_user_separator") == 0 && g_strlen(value) > 0) { diff --git a/xrdp/xrdp.ini.in b/xrdp/xrdp.ini.in index e7ea9823..9bdefde2 100644 --- a/xrdp/xrdp.ini.in +++ b/xrdp/xrdp.ini.in @@ -66,6 +66,9 @@ crypt_level=high certificate= key_file= +; [Debug] Log file for TLS pre-master secrets - see xrdp.ini(5) +#tls_pms_log_file=/tmp/xrdp-pms/premaster.log + ; set SSL protocols ; can be comma separated list of 'SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3' ssl_protocols=TLSv1.2, TLSv1.3