]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add sslkeylog config option and implementation
authorNeil Horman <nhorman@openssl.org>
Mon, 26 Aug 2024 21:59:46 +0000 (17:59 -0400)
committerMatt Caswell <matt@openssl.org>
Mon, 21 Oct 2024 10:34:35 +0000 (11:34 +0100)
Add a config option for sslkeylog (disabled by default)

When enabled, SSL_CTX_new[_ex] becomes sensitive to the SSLKEYLOGFILE
environment variable.  It records keylog callback messages to the file
specified in the environment variable according to the format specified
in https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html

Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Hugo Landau <hlandau@devever.net>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/25297)

Configure
ssl/ssl_lib.c
ssl/ssl_local.h

index 2b41c4f69f665da0bf8e71cc61ad7d7012fa5033..2dd6234d1cca7be28b10b7e5b24c77283d4d1d1b 100755 (executable)
--- a/Configure
+++ b/Configure
@@ -526,6 +526,7 @@ my @disablables = (
     "ssl-trace",
     "static-engine",
     "stdio",
+    "sslkeylog",
     "tests",
     "tfo",
     "thread-pool",
@@ -598,6 +599,7 @@ our %disabled = ( # "what"         => "comment"
                   "sctp"                => "default",
                   "ssl3"                => "default",
                   "ssl3-method"         => "default",
+                  "sslkeylog"           => "default",
                   "tfo"                 => "default",
                   "trace"               => "default",
                   "ubsan"               => "default",
index 54003b0f7909a0b18358bbf424e66ed4f27f5afb..7989f29a5d1b8266a91b4d9f69d9a7df2db45bc8 100644 (file)
@@ -27,6 +27,7 @@
 #include "internal/cryptlib.h"
 #include "internal/nelem.h"
 #include "internal/refcount.h"
+#include "internal/thread_once.h"
 #include "internal/ktls.h"
 #include "internal/to_hex.h"
 #include "quic/quic_local.h"
@@ -3849,6 +3850,55 @@ static int ssl_session_cmp(const SSL_SESSION *a, const SSL_SESSION *b)
     return memcmp(a->session_id, b->session_id, a->session_id_length);
 }
 
+#ifndef OPENSSL_NO_SSLKEYLOG
+/**
+ * @brief Static initialization for a one-time action to initialize the SSL key log.
+ */
+static CRYPTO_ONCE ssl_keylog_once = CRYPTO_ONCE_STATIC_INIT;
+
+/**
+ * @brief Pointer to a read-write lock used to protect access to the key log.
+ */
+static CRYPTO_RWLOCK *keylog_lock = NULL;
+
+/**
+ * @brief Pointer to a BIO structure used for writing the key log information.
+ */
+static BIO *keylog_bio = NULL;
+
+/**
+ * @brief Ref counter tracking the number of keylog_bio users.
+ */
+static unsigned int keylog_count = 0;
+
+/**
+ * @brief Initializes the SSLKEYLOGFILE lock.
+ *
+ * @return 1 on success, 0 on failure.
+ */
+DEFINE_RUN_ONCE_STATIC(ssl_keylog_init)
+{
+    keylog_lock = CRYPTO_THREAD_lock_new();
+    if (keylog_lock == NULL)
+        return 0;
+    return 1;
+}
+
+static void sslkeylogfile_cb(const SSL *ssl, const char *line)
+{
+    if (keylog_lock == NULL)
+        return;
+
+    if (!CRYPTO_THREAD_write_lock(keylog_lock))
+        return;
+    if (keylog_bio != NULL) {
+        BIO_printf(keylog_bio, "%s\n", line);
+        (void)BIO_flush(keylog_bio);
+    }
+    CRYPTO_THREAD_unlock(keylog_lock);
+}
+#endif
+
 /*
  * These wrapper functions should remain rather than redeclaring
  * SSL_SESSION_hash and SSL_SESSION_cmp for void* types and casting each
@@ -3860,6 +3910,9 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
                         const SSL_METHOD *meth)
 {
     SSL_CTX *ret = NULL;
+#ifndef OPENSSL_NO_SSLKEYLOG
+    const char *keylogfile = ossl_safe_getenv("SSLKEYLOGFILE");
+#endif
 #ifndef OPENSSL_NO_COMP_ALG
     int i;
 #endif
@@ -4120,6 +4173,43 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq,
         goto err;
     }
 
+#ifndef OPENSSL_NO_SSLKEYLOG
+    if (keylogfile != NULL && strlen(keylogfile) != 0) {
+        /* Make sure we have a global lock allocated */
+        if (!RUN_ONCE(&ssl_keylog_once, ssl_keylog_init)) {
+            /* log an error, but don't fail here */
+            ERR_raise(ERR_LIB_SSL, ERR_R_SSL_LIB);
+            goto err;
+        }
+
+        /* Grab out global lock */
+        if (!CRYPTO_THREAD_write_lock(keylog_lock)) {
+            ERR_raise(ERR_LIB_SSL, ERR_R_SSL_LIB);
+        } else {
+            /*
+             * If the bio for the requested keylog file hasn't been
+             * created yet, go ahead and create it, and set it to append
+             * if its already there.
+             */
+            if (keylog_bio == NULL) {
+                keylog_bio = BIO_new_file(keylogfile, "a");
+                if (keylog_bio == NULL)
+                    ERR_raise(ERR_LIB_SSL, ERR_R_SSL_LIB);
+                else
+                    /* up our ref count for the newly created case */
+                    keylog_count++;
+            } else {
+                /* up our refcount for the already-created case */
+                keylog_count++;
+            }
+            /* If we have a bio now, assign the callback handler */
+            if (keylog_bio != NULL)
+                ret->sslkeylog_callback = sslkeylogfile_cb;
+            /* unlock, and we're done */
+            CRYPTO_THREAD_unlock(keylog_lock);
+        }
+    }
+#endif
     return ret;
  err:
     SSL_CTX_free(ret);
@@ -4157,6 +4247,20 @@ void SSL_CTX_free(SSL_CTX *a)
         return;
     REF_ASSERT_ISNT(i < 0);
 
+#ifndef OPENSSL_NO_SSLKEYLOG
+    if (keylog_lock != NULL && CRYPTO_THREAD_write_lock(keylog_lock)) {
+        if (a->sslkeylog_callback != NULL)
+            keylog_count--;
+        a->sslkeylog_callback = NULL;
+        /* If we're the last user, close the bio */
+        if (keylog_count == 0) {
+            BIO_free(keylog_bio);
+            keylog_bio = NULL;
+        }
+        CRYPTO_THREAD_unlock(keylog_lock);
+    }
+#endif
+
     X509_VERIFY_PARAM_free(a->param);
     dane_ctx_final(&a->dane);
 
@@ -6750,8 +6854,13 @@ static int nss_keylog_int(const char *prefix,
     size_t out_len = 0, i, prefix_len;
     SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(sc);
 
+#ifndef OPENSSL_NO_SSLKEYLOG
+    if (sctx->keylog_callback == NULL && sctx->sslkeylog_callback == NULL)
+        return 1;
+#else
     if (sctx->keylog_callback == NULL)
         return 1;
+#endif
 
     /*
      * Our output buffer will contain the following strings, rendered with
@@ -6778,7 +6887,12 @@ static int nss_keylog_int(const char *prefix,
         cursor += ossl_to_lowerhex(cursor, parameter_2[i]);
     *cursor = '\0';
 
-    sctx->keylog_callback(SSL_CONNECTION_GET_SSL(sc), (const char *)out);
+#ifndef OPENSSL_NO_SSLKEYLOG
+    if (sctx->sslkeylog_callback != NULL)
+        sctx->sslkeylog_callback(SSL_CONNECTION_GET_SSL(sc), (const char *)out);
+#endif
+    if (sctx->keylog_callback != NULL)
+        sctx->keylog_callback(SSL_CONNECTION_GET_SSL(sc), (const char *)out);
     OPENSSL_clear_free(out, out_len);
     return 1;
 }
index af36f3832452209c867121d442a1a468cdaa124c..2d2e4674b05f65f036ae82d0e338316c4d8938ea 100644 (file)
@@ -1099,6 +1099,16 @@ struct ssl_ctx_st {
      */
     SSL_CTX_keylog_cb_func keylog_callback;
 
+    /*
+     * Private callback for internal key logging based on SSLKEYLOG env
+     * We don't want to create a chaining mechanism as we're never sure
+     * if the application wants to set an additional callback or override
+     * the one set via SSLKEYLOGFILE, so we just keep them separate
+     */
+# ifndef OPENSSL_NO_SSLKEYLOG
+    SSL_CTX_keylog_cb_func sslkeylog_callback;
+# endif
+
     /*
      * The maximum number of bytes advertised in session tickets that can be
      * sent as early data.