]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Allow for persistent TLS session keys
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 16 Dec 2021 18:04:02 +0000 (12:04 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 16 Dec 2021 18:04:02 +0000 (12:04 -0600)
raddb/mods-available/eap
src/lib/tls/cache.c
src/lib/tls/conf-h
src/lib/tls/conf.c

index b40f5f8538145c13b7d74bcd2f5fdfd61bb35366..fdbf88b88e70bb6953ee9c350119344917e494f5 100644 (file)
@@ -818,6 +818,28 @@ eap {
                        #
 #                      require_perfect_forward_secrecy = no
 
+                       #
+                       #  session_ticket_key::
+                       #
+                       #  Sets a persistent key used to encrypt stateless session
+                       #  tickets.  If this is not set, then a random key will be
+                       #  chosen when the server starts.
+                       *
+                       #  As the ticket key length depends on the version/flavour
+                       #  of OpenSSL being used, the value provided is fed into
+                       #  a HKDF function (digest SHA256,
+                       #  label "freeradius-session-ticket").  The number of
+                       #  bytes OpenSSL indicates it needs are then extracted from
+                       #  the HKDF.
+                       #
+                       #  It is important that a strong key is chosen here.  If the
+                       #  key were ever revealed, then an attacker could manipulate
+                       #  the contents of a session ticket.  This could in turn
+                       #  allow privilege escalation, or if OpenSSL's ticket parsing
+                       #  code is less than perfect, buffer overflow attacks.
+                       #
+#                      session_ticket_key = "super-secret-key"
+
                        #
                        #  [NOTE]
                        #  ====
index a63af0bcf6afe54a36bc89b4e791644f23e1c8cf..0ad715c7bcd86b77aec810fd920c8c532e8bb194 100644 (file)
@@ -30,6 +30,7 @@ USES_APPLE_DEPRECATED_API     /* OpenSSL API has been deprecated by Apple */
 #define LOG_PREFIX "tls"
 
 #include <openssl/ssl.h>
+#include <openssl/kdf.h>
 
 #include <freeradius-devel/internal/internal.h>
 #include <freeradius-devel/server/pair.h>
@@ -1190,29 +1191,96 @@ int fr_tls_cache_ctx_init(SSL_CTX *ctx, fr_tls_cache_conf_t const *cache_conf)
                FALL_THROUGH;
 
        case FR_TLS_CACHE_STATELESS:
+       {
+               size_t key_len;
+               uint8_t *key_buff;
+               EVP_PKEY_CTX *pkey_ctx = NULL;
+
                if (!(cache_conf->mode & FR_TLS_CACHE_STATEFUL)) tls_cache_disable_statefull_resumption(ctx);
 
+               /*
+                *      If keys is NULL, then OpenSSL returns the expected
+                *      key length, which may be different across diferent
+                *      flavours/versions of OpenSSL.
+                *
+                *      We could calculate this in conf.c, but, if in future
+                *      OpenSSL decides to use different key lengths based
+                *      on other parameters in the ctx, that'd break.
+                */
+               key_len = SSL_CTX_set_tlsext_ticket_keys(ctx, NULL, 0);
+
+               if (unlikely((pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL)) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed initialising KDF");
+               kdf_error:
+                       if (pkey_ctx) EVP_PKEY_CTX_free(pkey_ctx);
+                       return -1;
+               }
+               if (unlikely(EVP_PKEY_derive_init(pkey_ctx) != 1)) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed initialising KDF derivation ctx");
+                       goto kdf_error;
+               }
+               if (unlikely(EVP_PKEY_CTX_set_hkdf_md(pkey_ctx, UNCONST(struct evp_md_st *, EVP_sha256())) != 1)) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed setting KDF MD");
+                       goto kdf_error;
+               }
+               if (unlikely(EVP_PKEY_CTX_set1_hkdf_key(pkey_ctx,
+                                                       UNCONST(unsigned char *, cache_conf->session_ticket_key),
+                                                       talloc_array_length(cache_conf->session_ticket_key)) != 1)) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed setting KDF key");
+                       goto kdf_error;
+               }
+               if (unlikely(EVP_PKEY_CTX_add1_hkdf_info(pkey_ctx,
+                                                        (unsigned char *)"freeradius-session-ticket",
+                                                        sizeof("freeradius-session-ticket") - 1) != 1)) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed setting KDF label");
+                       goto kdf_error;
+               }
+
+               /*
+                *      SSL_CTX_set_tlsext_ticket_keys memcpys its
+                *      inputs so this is just a temporary buffer.
+                */
+               MEM(key_buff = talloc_array(NULL, uint8_t, key_len));
+               if (EVP_PKEY_derive(pkey_ctx, key_buff, &key_len) != 1) {
+                       fr_tls_log_strerror_printf(NULL);
+                       PERROR("Failed deriving session ticket key");
+
+                       talloc_free(key_buff);
+                       goto kdf_error;
+               }
+               EVP_PKEY_CTX_free(pkey_ctx);
+
+               fr_assert(talloc_array_length(key_buff) == key_len);
                /*
                 *      Ensure the same keys are used across all threads
                 */
                if (SSL_CTX_set_tlsext_ticket_keys(ctx,
-                                                  UNCONST(uint8_t *, cache_conf->session_ticket_key_rand),
-                                                  sizeof(cache_conf->session_ticket_key_rand)) != 1) {
+                                                  key_buff, key_len) != 1) {
                        fr_tls_log_strerror_printf(NULL);
                        PERROR("Failed setting session ticket keys");
                        return -1;
                }
 
+               DEBUG3("Derived session-ticket-key:");
+               HEXDUMP3(key_buff, key_len, NULL);
+               talloc_free(key_buff);
+
                /*
                 *      These callbacks embed and extract the
                 *      session-state list from the session-ticket.
                 */
-               if (SSL_CTX_set_session_ticket_cb(ctx,
-                                                 tls_cache_session_ticket_app_data_set,
-                                                 tls_cache_session_ticket_app_data_get,
-                                                 UNCONST(fr_tls_cache_conf_t *, cache_conf)) != 1) {
+               if (unlikely(SSL_CTX_set_session_ticket_cb(ctx,
+                                                          tls_cache_session_ticket_app_data_set,
+                                                          tls_cache_session_ticket_app_data_get,
+                                                          UNCONST(fr_tls_cache_conf_t *, cache_conf)) != 1)) {
                        fr_tls_log_strerror_printf(NULL);
                        PERROR("Failed setting session ticket callbacks");
+                       return -1;
                }
 
                /*
@@ -1224,6 +1292,7 @@ int fr_tls_cache_ctx_init(SSL_CTX *ctx, fr_tls_cache_conf_t const *cache_conf)
 #if OPENSSL_VERSION_NUMBER >= 0x10101000L
                SSL_CTX_set_num_tickets(ctx, 1);
 #endif
+       }
                break;
        }
 
index 43d1073bf5c9c6cd8c1c6bb25a75a8bc44cf50fe..dc36c2c4bdcd6318ffdd5413c777e0bef4d6c97c 100644 (file)
@@ -107,7 +107,8 @@ typedef struct {
        bool            require_pfs;                    //!< Only allow session resumption if a cipher suite that
                                                        //!< supports perfect forward secrecy.
 
-       uint8_t         session_ticket_key_rand[16 + 32 + 32];  //!< OpenSSL really needs to export this length.
+       uint8_t const   *session_ticket_key;            //!< Raw input data.  Is fed through HKDF to produce the
+                                                       ///< actual session key we use.
 } fr_tls_cache_conf_t;
 
 /** Certificate verification configuration
@@ -117,7 +118,7 @@ typedef struct {
        fr_tls_verify_mode_t mode;                      //!< What certificates we apply OpenSSL's pre-validation
                                                        ///< mode to.
 
-       fr_tls_verify_mode_t attribute_mode;                    //!< What set of certificates we're going to convert to
+       fr_tls_verify_mode_t attribute_mode;            //!< What set of certificates we're going to convert to
                                                        ///< pairs for verification.
 
        bool            check_crl;                      //!< Check certificate revocation lists.
index dcaf2148ee0be905f51d5098e8b1c3d90e6cedc6..4a60f5b0d6313f0e0932ef978105bcd430c60f32 100644 (file)
@@ -95,6 +95,8 @@ static CONF_PARSER tls_cache_config[] = {
        { FR_CONF_OFFSET("require_perfect_forward_secrecy", FR_TYPE_BOOL, fr_tls_cache_conf_t, require_pfs), .dflt = "no" },
 #endif
 
+       { FR_CONF_OFFSET("session_ticket_key", FR_TYPE_OCTETS, fr_tls_cache_conf_t, session_ticket_key) },
+
        /*
         *      Deprecated
         */
@@ -471,7 +473,15 @@ fr_tls_conf_t *fr_tls_conf_parse_server(CONF_SECTION *cs)
         *      Generate random, ephemeral, session-ticket keys.
         */
        if (conf->cache.mode & FR_TLS_CACHE_STATELESS) {
-               fr_rand_buffer(conf->cache.session_ticket_key_rand, sizeof(conf->cache.session_ticket_key_rand));
+               /*
+                *      Fill the key with randomness if one
+                *      wasn't specified by the user.
+                */
+               if (!conf->cache.session_ticket_key) {
+                       MEM(conf->cache.session_ticket_key = talloc_array(conf, uint8_t, 256));
+                       fr_rand_buffer(UNCONST(uint8_t *, conf->cache.session_ticket_key),
+                                      talloc_array_length(conf->cache.session_ticket_key));
+               }
        }
 
        /*