]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Automatically re-validate certificates when resuming from a session-ticket
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 17 Jun 2021 17:57:08 +0000 (12:57 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 17 Jun 2021 19:25:25 +0000 (14:25 -0500)
21 files changed:
share/dictionary/tls/dictionary.freeradius
src/lib/eap/attrs.h
src/lib/eap/base.c
src/lib/eap/tls.c
src/lib/tls/all.mk
src/lib/tls/attrs.h
src/lib/tls/base-h
src/lib/tls/base.c
src/lib/tls/cache.c
src/lib/tls/cache.h
src/lib/tls/conf-h
src/lib/tls/conf.c
src/lib/tls/pairs.c [new file with mode: 0644]
src/lib/tls/session.c
src/lib/tls/session.h
src/lib/tls/validate.c
src/lib/tls/validate.h [new file with mode: 0644]
src/process/tls/all.mk
src/process/tls/base.c
src/tests/eapol_test/config/tls/methods-enabled/tls
src/tests/eapol_test/config/tls/sites-enabled/tls

index 600a47e720202951ade281211aa62e5bc3a39b59..79f853868519d1c21c8429e61defea6db351877f 100644 (file)
 
 ATTRIBUTE      Packet-Type                             1       uint32
 
-VALUE  Packet-Type                     Cache-Load              1
-VALUE  Packet-Type                     Cache-Store             2
-VALUE  Packet-Type                     Cache-Clear             3
-VALUE  Packet-Type                     Certificate             4
+VALUE  Packet-Type                     Session-Load            1
+VALUE  Packet-Type                     Session-Store           2
+VALUE  Packet-Type                     Session-Clear           3
+VALUE  Packet-Type                     Certificate-Validate    4
+
+#
+#  We use response packet types instead of rcodes so that
+#  the virtual server can be tested in isolation.
+#
+VALUE  Packet-Type                     success                 10
+VALUE  Packet-Type                     failure                 11
+VALUE  Packet-Type                     notfound                12
 
 ATTRIBUTE      Session-Id                              10      octets
 ATTRIBUTE      Session-Data                            11      octets
index efe0495f4f507c2e215f52b0a79f6dfe335891d4..1eafcf6088eaf0aec16cce3c1907eb22a035a946 100644 (file)
@@ -30,6 +30,7 @@ extern fr_dict_t const *dict_freeradius;
 extern fr_dict_t const *dict_radius;
 
 extern fr_dict_attr_t const *attr_chbind_response_code;
+extern fr_dict_attr_t const *attr_eap_identity;
 extern fr_dict_attr_t const *attr_eap_session_id;
 extern fr_dict_attr_t const *attr_eap_type;
 extern fr_dict_attr_t const *attr_virtual_server;
index 637cf8eb8d12e38e5483172b02da2b4f6a889311..24d776b2bae73280f4a4d358420833b177237b3c 100644 (file)
@@ -81,6 +81,7 @@ fr_dict_autoload_t eap_base_dict[] = {
 
 fr_dict_attr_t const *attr_chbind_response_code;
 fr_dict_attr_t const *attr_eap_session_id;
+fr_dict_attr_t const *attr_eap_identity;
 fr_dict_attr_t const *attr_eap_type;
 fr_dict_attr_t const *attr_virtual_server;
 
@@ -98,6 +99,7 @@ fr_dict_attr_t const *attr_user_name;
 extern fr_dict_attr_autoload_t eap_base_dict_attr[];
 fr_dict_attr_autoload_t eap_base_dict_attr[] = {
        { .out = &attr_chbind_response_code, .name = "Chbind-Response-Code", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
+       { .out = &attr_eap_identity, .name = "EAP-Identity", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
        { .out = &attr_eap_session_id, .name = "EAP-Session-Id", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_eap_type, .name = "EAP-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
        { .out = &attr_state, .name = "State", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
index 6f4d97f045887f9d18b7d5b963fe91e81963efa3..33e1d7f231474f9fdcbd8cfcb4770f5f7747cd10 100644 (file)
@@ -1176,7 +1176,30 @@ eap_tls_session_t *eap_tls_session_init(request_t *request, eap_session_t *eap_s
         */
        eap_tls_session->tls_session = tls_session = fr_tls_session_alloc_server(eap_tls_session, ssl_ctx,
                                                                                 request, client_cert);
-       if (!tls_session) return NULL;
+       if (unlikely(!tls_session)) return NULL;
+
+       /*
+        *      Add the EAP-Identity value to the TLS session so
+        *      it's available in all the TLS callbacks.
+        */
+       {
+               fr_pair_t       *identity;
+
+               MEM(identity = fr_pair_afrom_da(tls_session, attr_eap_identity));
+               fr_pair_value_bstrdup_buffer(identity, eap_session->identity, true);
+               fr_tls_session_extra_pair_add_shallow(tls_session, identity);
+       }
+
+       /*
+        *      Add the EAP-Type we're running to the subrequest.
+        */
+       {
+               fr_pair_t       *type_vp;
+
+               MEM(type_vp = fr_pair_afrom_da(tls_session, attr_eap_type));
+               type_vp->vp_uint32 = eap_session->type;
+               fr_tls_session_extra_pair_add_shallow(tls_session, type_vp);
+       }
 
        /*
         *      Associate various bits of opaque data with the session.
index fdd5bc5dc867b57155d796d05e7519fd84dbf144..2910cabb6214f49173384f25697fd3c9eec0f6de 100644 (file)
@@ -12,6 +12,7 @@ SOURCES       := \
        engine.c \
        log.c \
        ocsp.c \
+       pairs.c \
        session.c \
        utils.c \
        validate.c \
index 1b1b41f986e1d4763bd4ddfa4bff5f0af84f439f..2a49c87370aa6f87139b6db25c3f32ba115abfa2 100644 (file)
@@ -68,7 +68,11 @@ extern fr_dict_attr_t const *attr_tls_session_id;
 
 extern fr_dict_attr_t const *attr_framed_mtu;
 
-extern fr_value_box_t const *enum_tls_packet_type_cache_load;
-extern fr_value_box_t const *enum_tls_packet_type_cache_store;
-extern fr_value_box_t const *enum_tls_packet_type_cache_clear;
-extern fr_value_box_t const *enum_tls_packet_type_certificate;
+extern fr_value_box_t const *enum_tls_packet_type_session_load;
+extern fr_value_box_t const *enum_tls_packet_type_session_store;
+extern fr_value_box_t const *enum_tls_packet_type_session_clear;
+extern fr_value_box_t const *enum_tls_packet_type_certificate_validate;
+
+extern fr_value_box_t const *enum_tls_packet_type_success;
+extern fr_value_box_t const *enum_tls_packet_type_failure;
+extern fr_value_box_t const *enum_tls_packet_type_notfound;
index c584230ec108f2dc9f1653dc7efe10969066d38d..46d82c63831c58bcda20f9e65c62302a0b28e681 100644 (file)
@@ -193,13 +193,6 @@ int                fr_tls_ocsp_state_cache_compile(fr_tls_cache_t *sections, CONF_SECTION *ser
 
 int            fr_tls_ocsp_staple_cache_compile(fr_tls_cache_t *sections, CONF_SECTION *server_cs);
 
-/*
- *     tls/validate.c
- */
-int            fr_tls_validate_cert_cb(int ok, X509_STORE_CTX *ctx);
-
-int            fr_tls_validate_client_cert_chain(SSL *ssl);
-
 /*
  *     tls/utils.c
  */
@@ -212,8 +205,8 @@ int         fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1);
 /*
  *     tls/virtual_server.c
  */
-unlang_action_t fr_tls_call_push(rlm_rcode_t *p_rcode, request_t *child, unlang_function_t resume,
-                                          request_t *request, fr_tls_conf_t *conf, fr_tls_session_t *tls_session);
+unlang_action_t fr_tls_call_push(request_t *child, unlang_function_t resume,
+                                fr_tls_conf_t *conf, fr_tls_session_t *tls_session);
 
 #ifdef __cplusplus
 }
index bbaf42661aaafb64891e0daf38df0c358c14472d..b790bf89dbefa3574694da38adbc033e1e57d2d3 100644 (file)
@@ -143,17 +143,31 @@ fr_dict_attr_autoload_t tls_dict_attr[] = {
        { NULL }
 };
 
-fr_value_box_t const   *enum_tls_packet_type_cache_load;
-fr_value_box_t const   *enum_tls_packet_type_cache_store;
-fr_value_box_t const   *enum_tls_packet_type_cache_clear;
-fr_value_box_t const   *enum_tls_packet_type_certificate;
+/*
+ *     request types
+ */
+fr_value_box_t const   *enum_tls_packet_type_session_load;
+fr_value_box_t const   *enum_tls_packet_type_session_store;
+fr_value_box_t const   *enum_tls_packet_type_session_clear;
+fr_value_box_t const   *enum_tls_packet_type_certificate_validate;
+
+/*
+ *     response types
+ */
+fr_value_box_t const   *enum_tls_packet_type_success;
+fr_value_box_t const   *enum_tls_packet_type_failure;
+fr_value_box_t const   *enum_tls_packet_type_notfound;
 
 extern fr_dict_enum_autoload_t tls_dict_enum[];
 fr_dict_enum_autoload_t tls_dict_enum[] = {
-       { .out = &enum_tls_packet_type_cache_load, .name = "Cache-Load", .attr = &attr_tls_packet_type },
-       { .out = &enum_tls_packet_type_cache_store, .name = "Cache-Store", .attr = &attr_tls_packet_type },
-       { .out = &enum_tls_packet_type_cache_clear, .name = "Cache-Clear", .attr = &attr_tls_packet_type },
-       { .out = &enum_tls_packet_type_certificate, .name = "Certificate", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_session_load, .name = "Session-Load", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_session_store, .name = "Session-Store", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_session_clear, .name = "Session-Clear", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_certificate_validate, .name = "Certificate-Validate", .attr = &attr_tls_packet_type },
+
+       { .out = &enum_tls_packet_type_success, .name = "Success", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_failure, .name = "Failure", .attr = &attr_tls_packet_type },
+       { .out = &enum_tls_packet_type_notfound, .name = "Notfound", .attr = &attr_tls_packet_type },
        { NULL }
 };
 
index bca743a422b43843c0d6bf2d7b6bd2657df46285..3a50ec0516ad95c988b0ff1dea5de55a1c4ec7aa 100644 (file)
@@ -21,6 +21,7 @@
  * @brief Functions to support TLS session resumption
  *
  * @copyright 2015-2016 The FreeRADIUS server project
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  */
 RCSID("$Id$")
 USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
@@ -39,6 +40,7 @@ USES_APPLE_DEPRECATED_API     /* OpenSSL API has been deprecated by Apple */
 #include "cache.h"
 #include "log.h"
 #include "missing.h"
+#include "validate.h"
 
 /** Retrieve session ID (in binary form) from the session
  *
@@ -104,32 +106,148 @@ void tls_cache_clear_state_reset(fr_tls_cache_t *cache)
        cache->clear.state = FR_TLS_CACHE_CLEAR_INIT;
 }
 
-/** Process the result of `cache store { ... }`
+/** Process the result of `session load { ... }`
+ */
+static unlang_action_t tls_cache_load_result(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
+                                            request_t *request, void *uctx)
+{
+       fr_tls_session_t        *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
+       fr_tls_cache_t          *tls_cache = tls_session->cache;
+       fr_pair_t               *vp;
+       uint8_t const           *q, **p;
+       SSL_SESSION             *sess;
+
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_tls_packet_type, 0);
+       if (!vp || (vp->vp_uint32 != enum_tls_packet_type_success->vb_uint32)) {
+               RWDEBUG("Failed acquiring session data");
+       error:
+               tls_cache->load.state = FR_TLS_CACHE_LOAD_FAILED;
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
+
+       vp = fr_pair_find_by_da(&request->session_state_pairs, attr_tls_session_data, 0);
+       if (!vp) {
+               RWDEBUG("No cached session found");
+               goto error;
+       }
+
+       q = vp->vp_octets;      /* openssl will mutate q, so we can't use vp_octets directly */
+       p = (unsigned char const **)&q;
+
+       sess = d2i_SSL_SESSION(NULL, p, vp->vp_length);
+       if (!sess) {
+               fr_tls_log_error(request, "Failed loading persisted session");
+               goto error;
+       }
+       RDEBUG3("Read %zu bytes of session data.  Session deserialized successfully", vp->vp_length);
+
+       /*
+        *      OpenSSL's API is very inconsistent.
+        *
+        *      We need to set external data here, so it can be
+        *      retrieved in fr_tls_cache_delete.
+        *
+        *      ex_data is not serialised in i2d_SSL_SESSION
+        *      so we don't have to bother unsetting it.
+        */
+       SSL_SESSION_set_ex_data(sess, FR_TLS_EX_INDEX_TLS_SESSION,
+                               SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_TLS_SESSION));
+
+       tls_cache->load.state = FR_TLS_CACHE_LOAD_RETRIEVED;
+       tls_cache->load.sess = sess;    /* This is consumed in tls_cache_load_cb */
+
+#if 0
+       /*
+        *      We set the session here temporarily so that
+        *      the certificate validation can run.
+        *
+        *      FIXME - Do ASYNC session cert validation.
+        */
+       SSL_set_session(ssl, sess);             /* Increases ref count */
+
+       if (fr_tls_validate_client_cert_chain(ssl) != 1) {
+               RWDEBUG("Validation failed, forcefully expiring resumed session");
+               SSL_SESSION_set_timeout(sess, 0);
+       }
+
+       SSL_remove_session(ssl, sess);          /* Decreases ref count */
+#endif
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Push a `session load { ... }` call into the current request, using a subrequest
+ *
+ * @param[in] request          The current request.
+ * @Param[in] tls_session      The current TLS session.
+ * @return
+ *      - UNLANG_ACTION_CALCULATE_RESULT on noop.
+ *     - UNLANG_ACTION_PUSHED_CHILD on success.
+ *      - UNLANG_ACTION_FAIL on failure.
+ */
+static unlang_action_t tls_cache_load_push(request_t *request, fr_tls_session_t *tls_session)
+{
+       fr_tls_cache_t          *tls_cache = tls_session->cache;
+       fr_tls_conf_t           *conf = fr_tls_session_conf(tls_session->ssl);
+       request_t               *child;
+       fr_pair_t               *vp;
+
+       if (tls_cache->load.state != FR_TLS_CACHE_LOAD_REQUESTED) return UNLANG_ACTION_CALCULATE_RESULT;
+
+       fr_assert(tls_cache->load.id);
+
+       MEM(child = unlang_subrequest_alloc(request, dict_tls));
+       request = child;
+
+       /*
+        *      Setup the child request for loading
+        *      session resumption data.
+        */
+       MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
+       vp->vp_uint32 = enum_tls_packet_type_session_load->vb_uint32;
+
+       /*
+        *      Add the session identifier we're
+        *      trying to load.
+        */
+       tls_cache_session_id_to_vp(child, tls_cache->load.id);
+
+       /*
+        *      Allocate a child, and set it up to call
+        *      the TLS virtual server.
+        */
+       if (fr_tls_call_push(child, tls_cache_load_result, conf, tls_session) < 0) {
+               talloc_free(child);
+               tls_cache_load_state_reset(tls_cache);
+               return UNLANG_ACTION_FAIL;
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Process the result of `session store { ... }`
  */
 static unlang_action_t tls_cache_store_result(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
                                              request_t *request, void *uctx)
 {
        fr_tls_session_t        *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
        fr_tls_cache_t          *tls_cache = tls_session->cache;
+       fr_pair_t               *vp;
 
        tls_cache_store_state_reset(tls_cache);
 
-       switch (tls_cache->store.rcode) {
-       case RLM_MODULE_OK:
-       case RLM_MODULE_UPDATED:
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_tls_packet_type, 0);
+       if (vp && (vp->vp_uint32 == enum_tls_packet_type_success->vb_uint32)) {
                tls_cache->store.state = FR_TLS_CACHE_STORE_PERSISTED;  /* Avoid spurious clear calls */
-               break;
-
-       default:        /* Every other rcode indicates the cache store ailed */
+       } else {
                RWDEBUG("Failed storing session data");
                tls_cache->store.state = FR_TLS_CACHE_STORE_INIT;
-               break;
        }
 
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-/** Push a `cache store { ... }` call into the current request, using a subrequest
+/** Push a `session store { ... }` call into the current request, using a subrequest
  *
  * @param[in] request          The current request.
  * @param[in] conf             TLS configuration.
@@ -162,7 +280,7 @@ unlang_action_t tls_cache_store_push(request_t *request, fr_tls_conf_t *conf, fr
         *      session resumption data.
         */
        MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
-       vp->vp_uint32 = enum_tls_packet_type_cache_store->vb_uint32;
+       vp->vp_uint32 = enum_tls_packet_type_session_store->vb_uint32;
 
        /*
         *      Add the session identifier we're trying
@@ -202,36 +320,34 @@ unlang_action_t tls_cache_store_push(request_t *request, fr_tls_conf_t *conf, fr
         *      Allocate a child, and set it up to call
         *      the TLS virtual server.
         */
-       if (fr_tls_call_push(&tls_cache->store.rcode, child, tls_cache_store_result,
-                            request, conf, tls_session) < 0) goto error;
+       if (fr_tls_call_push(child, tls_cache_store_result, conf, tls_session) < 0) goto error;
 
        return UNLANG_ACTION_PUSHED_CHILD;
 }
 
-/** Process the result of `cache clear { ... }`
+/** Process the result of `session clear { ... }`
  */
 static unlang_action_t tls_cache_clear_result(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
                                              request_t *request, void *uctx)
 {
        fr_tls_session_t        *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
        fr_tls_cache_t          *tls_cache = tls_session->cache;
+       fr_pair_t               *vp;
 
        tls_cache_clear_state_reset(tls_cache);
 
-       switch (tls_cache->clear.rcode) {
-       case RLM_MODULE_OK:
-       case RLM_MODULE_UPDATED:
-       case RLM_MODULE_NOTFOUND:
-       case RLM_MODULE_NOOP:
-               return UNLANG_ACTION_CALCULATE_RESULT;
-
-       default:
-               RWDEBUG("Failed deleting session data - security may be compromised");
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_tls_packet_type, 0);
+       if (vp &&
+           ((vp->vp_uint32 == enum_tls_packet_type_success->vb_uint32) ||
+            (vp->vp_uint32 == enum_tls_packet_type_notfound->vb_uint32))) {
                return UNLANG_ACTION_CALCULATE_RESULT;
        }
+
+       RWDEBUG("Failed deleting session data - security may be compromised");
+       return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-/** Push a `cache clear { ... }` call into the current request, using a subrequest
+/** Push a `session clear { ... }` call into the current request, using a subrequest
  *
  * @param[in] request          The current request.
  * @param[in] conf             TLS configuration.
@@ -268,7 +384,7 @@ unlang_action_t tls_cache_clear_push(request_t *request, fr_tls_conf_t *conf, fr
         *      session resumption data.
         */
        MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
-       vp->vp_uint32 = enum_tls_packet_type_cache_clear->vb_uint32;
+       vp->vp_uint32 = enum_tls_packet_type_session_clear->vb_uint32;
 
        /*
         *      Add the session identifier we're
@@ -280,8 +396,7 @@ unlang_action_t tls_cache_clear_push(request_t *request, fr_tls_conf_t *conf, fr
         *      Allocate a child, and set it up to call
         *      the TLS virtual server.
         */
-       if (fr_tls_call_push(&tls_cache->clear.rcode, child, tls_cache_clear_result,
-                            request, conf, tls_session) < 0) {
+       if (fr_tls_call_push(child, tls_cache_clear_result, conf, tls_session) < 0) {
                talloc_free(child);
                tls_cache_clear_state_reset(tls_cache);
                return UNLANG_ACTION_FAIL;
@@ -290,21 +405,27 @@ unlang_action_t tls_cache_clear_push(request_t *request, fr_tls_conf_t *conf, fr
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-/** Push a `cache clear { ... }` or `cache load { ... }` depending on what operations are pending
+/** Push a `session store { ... }` or session clear { ... }` or `session load { ... }` depending on what operations are pending
  *
  * @param[in] request          The current request.
  * @Param[in] tls_session      The current TLS session.
  * @return
- *     - 1 on noop.
- *     - 0 on success.
- *      - -1 on failure.
+ *     - UNLANG_ACTION_CALCULATE_RESULT        - No pending actions
+ *     - UNLANG_ACTION_PUSHED_CHILD            - Pending operations to evaluate.
  */
-int fr_tls_cache_pending_push(request_t *request, fr_tls_session_t *tls_session)
+unlang_action_t fr_tls_cache_pending_push(request_t *request, fr_tls_session_t *tls_session)
 {
        fr_tls_cache_t *tls_cache = tls_session->cache;
        fr_tls_conf_t *conf = fr_tls_session_conf(tls_session->ssl);
 
-       if (!tls_cache) return 1;       /* No caching allowed */
+       if (!tls_cache) return UNLANG_ACTION_CALCULATE_RESULT;  /* No caching allowed */
+
+       /*
+        *      Load stateful session data
+        */
+       if (tls_cache->load.state == FR_TLS_CACHE_LOAD_REQUESTED) {
+               return tls_cache_load_push(request, tls_session);
+       }
 
        /*
         *      We only support a single session
@@ -322,130 +443,6 @@ int fr_tls_cache_pending_push(request_t *request, fr_tls_session_t *tls_session)
                return tls_cache_store_push(request, conf, tls_session);
        }
 
-       return 1;
-}
-
-/** Process the result of `cache load { ... }`
- */
-static unlang_action_t tls_cache_load_result(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
-                                            request_t *request, void *uctx)
-{
-       fr_tls_session_t        *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
-       fr_tls_cache_t          *tls_cache = tls_session->cache;
-       fr_pair_t               *vp;
-       uint8_t const           *q, **p;
-       SSL_SESSION             *sess;
-
-       switch (tls_cache->load.rcode) {
-       case RLM_MODULE_OK:
-       case RLM_MODULE_UPDATED:
-               break;
-
-       default:        /* Every other rcode indicates the cache load failed */
-               RWDEBUG("Failed acquiring session data");
-       error:
-               tls_cache->load.state = FR_TLS_CACHE_LOAD_FAILED;
-               return UNLANG_ACTION_CALCULATE_RESULT;
-       }
-
-       vp = fr_pair_find_by_da(&request->session_state_pairs, attr_tls_session_data, 0);
-       if (!vp) {
-               RWDEBUG("No cached session found");
-               goto error;
-       }
-
-       q = vp->vp_octets;      /* openssl will mutate q, so we can't use vp_octets directly */
-       p = (unsigned char const **)&q;
-
-       sess = d2i_SSL_SESSION(NULL, p, vp->vp_length);
-       if (!sess) {
-               fr_tls_log_error(request, "Failed loading persisted session");
-               goto error;
-       }
-       RDEBUG3("Read %zu bytes of session data.  Session deserialized successfully", vp->vp_length);
-
-       /*
-        *      OpenSSL's API is very inconsistent.
-        *
-        *      We need to set external data here, so it can be
-        *      retrieved in fr_tls_cache_delete.
-        *
-        *      ex_data is not serialised in i2d_SSL_SESSION
-        *      so we don't have to bother unsetting it.
-        */
-       SSL_SESSION_set_ex_data(sess, FR_TLS_EX_INDEX_TLS_SESSION,
-                               SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_TLS_SESSION));
-
-       tls_cache->load.state = FR_TLS_CACHE_LOAD_RETRIEVED;
-       tls_cache->load.sess = sess;    /* This is consumed in tls_cache_load_cb */
-
-#if 0
-       /*
-        *      We set the session here temporarily so that
-        *      the certificate validation can run.
-        *
-        *      FIXME - Do ASYNC session cert validation.
-        */
-       SSL_set_session(ssl, sess);             /* Increases ref count */
-
-       if (fr_tls_validate_client_cert_chain(ssl) != 1) {
-               RWDEBUG("Validation failed, forcefully expiring resumed session");
-               SSL_SESSION_set_timeout(sess, 0);
-       }
-
-       SSL_remove_session(ssl, sess);          /* Decreases ref count */
-#endif
-
-       return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Push a `cache load { ... }` call into the current request, using a subrequest
- *
- * @param[in] request          The current request.
- * @Param[in] tls_session      The current TLS session.
- * @return
- *      - UNLANG_ACTION_CALCULATE_RESULT on noop.
- *     - UNLANG_ACTION_PUSHED_CHILD on success.
- *      - UNLANG_ACTION_FAIL on failure.
- */
-unlang_action_t fr_tls_cache_load_push(request_t *request, fr_tls_session_t *tls_session)
-{
-       fr_tls_cache_t          *tls_cache = tls_session->cache;
-       fr_tls_conf_t           *conf = fr_tls_session_conf(tls_session->ssl);
-       request_t               *child;
-       fr_pair_t               *vp;
-
-       if (tls_cache->load.state != FR_TLS_CACHE_LOAD_REQUESTED) return UNLANG_ACTION_CALCULATE_RESULT;
-
-       fr_assert(tls_cache->load.id);
-
-       MEM(child = unlang_subrequest_alloc(request, dict_tls));
-       request = child;
-
-       /*
-        *      Setup the child request for loading
-        *      session resumption data.
-        */
-       MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
-       vp->vp_uint32 = enum_tls_packet_type_cache_load->vb_uint32;
-
-       /*
-        *      Add the session identifier we're
-        *      trying to load.
-        */
-       tls_cache_session_id_to_vp(child, tls_cache->load.id);
-
-       /*
-        *      Allocate a child, and set it up to call
-        *      the TLS virtual server.
-        */
-       if (fr_tls_call_push(&tls_cache->load.rcode, child, tls_cache_load_result,
-                            request, conf, tls_session) < 0) {
-               talloc_free(child);
-               tls_cache_load_state_reset(tls_cache);
-               return UNLANG_ACTION_FAIL;
-       }
-
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
@@ -534,7 +531,7 @@ static SSL_SESSION *tls_cache_load_cb(SSL *ssl,
         *         load session information from a datastore and associated
         *         it with the SSL session.
         *      3. We asynchronously validate the certificate information
-        *         retrieved during the session cache load.
+        *         retrieved during the session session load.
         *      3. We call SSL_read() again, which in turn calls this callback
         *         again.
         */
@@ -677,7 +674,7 @@ int fr_tls_cache_disable_cb(SSL *ssl,
                return 1;
        }
 
-       RDEBUG2("Session-resumption enabled");
+       RDEBUG2("Allowing future session-resumption");
 
        return 0;
 }
@@ -882,6 +879,7 @@ static SSL_TICKET_RETURN tls_cache_session_ticket_app_data_get(SSL *ssl, SSL_SES
                                                               void *arg)
 {
        fr_tls_session_t        *tls_session = fr_tls_session(ssl);
+       fr_tls_conf_t           *conf = fr_tls_session_conf(tls_session->ssl);
        fr_tls_cache_conf_t     *tls_cache_conf = arg;  /* Not talloced */
        request_t               *request = NULL;
        uint8_t                 *data;
@@ -895,7 +893,7 @@ static SSL_TICKET_RETURN tls_cache_session_ticket_app_data_get(SSL *ssl, SSL_SES
        if (!tls_session->allow_session_resumption ||
            (!(tls_cache_conf->mode & FR_TLS_CACHE_STATELESS))) {
                ROPTIONAL(RDEBUG2, DEBUG2, "Session resumption not enabled for this TLS session, "
-                         "denying session resumption");
+                         "denying session resumption via session-ticket");
                return SSL_TICKET_RETURN_IGNORE;
        }
 
@@ -921,7 +919,7 @@ static SSL_TICKET_RETURN tls_cache_session_ticket_app_data_get(SSL *ssl, SSL_SES
         */
        if (SSL_SESSION_get0_ticket_appdata(sess, (void **)&data, &data_len) != 1) {
                fr_tls_log_error(request, "Failed retrieving application data from session-ticket, "
-                                "denying session resumption");
+                                "denying session resumption via session-ticket");
                return SSL_TICKET_RETURN_IGNORE_RENEW;
        }
 
@@ -941,7 +939,7 @@ static SSL_TICKET_RETURN tls_cache_session_ticket_app_data_get(SSL *ssl, SSL_SES
                if (fr_internal_decode_pair_dbuff(request->session_state_ctx, &cursor,
                                                  request->dict, &dbuff, NULL) < 0) {
                        fr_pair_list_free(&tmp);
-                       RPEDEBUG("Failed decoding session-state, denying session resumption");
+                       RPEDEBUG("Failed decoding session-state, denying session resumption via session-ticket");
                        return SSL_TICKET_RETURN_IGNORE_RENEW;
                }
        }
@@ -953,9 +951,30 @@ static SSL_TICKET_RETURN tls_cache_session_ticket_app_data_get(SSL *ssl, SSL_SES
 
        fr_pair_list_append(&request->session_state_pairs, &tmp);
 
-       /*
-        *      TODO - Run TLS certificate recv { ... } section
-        */
+       if (conf->virtual_server && tls_session->verify_client_cert) {
+               RDEBUG2("Requesting certificate re-validation for session-ticket");
+               /*
+                *      This sets the validation state of the tls_session
+                *      so that when we call ASYNC_pause_job(), and execution
+                *      jumps back to tls_session_async_handshake_cont
+                *      (just under SSL_read())
+                *      the code there knows what job it needs to push onto
+                *      the unlang stack.
+                */
+               fr_tls_validate_client_cert_request(tls_session);
+
+               ASYNC_pause_job();
+
+               /*
+                *      If we couldn't validate the client certificate
+                *      give the client the opportunity to send a new
+                *      one, but _don't_ allow session resumption.
+                */
+               if (!fr_tls_validate_client_cert_success(tls_session)) {
+                       RDEBUG2("Certificate re-validation failed, denying session resumption via session-ticket");
+                       return SSL_TICKET_RETURN_IGNORE_RENEW;
+               }
+       }
 
        return (status == SSL_TICKET_SUCCESS_RENEW) ? SSL_TICKET_RETURN_USE_RENEW : SSL_TICKET_RETURN_USE;
 }
index 04623b15a2418020a08f94e2317821c63c772af1..648ab0e648e37ba56dd6f110060e38979bbca8d4 100644 (file)
@@ -75,20 +75,17 @@ typedef struct {
        struct {
                fr_tls_cache_store_state_t      state;          //!< Tracks store state.
                SSL_SESSION                     *sess;          //!< Session to store.
-               rlm_rcode_t                     rcode;          //!< Result of calling `cache store { ... }`.
        } store;
 
        struct {
                fr_tls_cache_load_state_t       state;          //!< Tracks load requests from OpenSSL.
                uint8_t                         *id;            //!< Session ID to load.
                SSL_SESSION                     *sess;          //!< Deserialized session.
-               rlm_rcode_t                     rcode;          //!< Result of calling `cache load { ... }`.
        } load;
 
        struct {
                fr_tls_cache_clear_state_t      state;          //!< Tracks delete requests from OpenSSL.
                uint8_t                         *id;            //!< Session ID to be deleted.
-               rlm_rcode_t                     rcode;          //!< Result of calling `cache clear { ... }`.
        } clear;
 } fr_tls_cache_t;
 
@@ -106,8 +103,6 @@ uint8_t             *fr_tls_cache_id(TALLOC_CTX *ctx, SSL_SESSION *sess);
 
 unlang_action_t        fr_tls_cache_pending_push(request_t *request, fr_tls_session_t *tls_session);
 
-unlang_action_t        fr_tls_cache_load_push(request_t *request, fr_tls_session_t *tls_session);
-
 void           fr_tls_cache_deny(fr_tls_session_t *tls_session);
 
 int            fr_tls_cache_disable_cb(SSL *ssl, int is_forward_secure);
index e0e69bfe2c1963bc5651d46d5c99c2b7e6ed8cd2..2fa2afb064e00f9bc04b3e4e5bfae8de69dc0f8a 100644 (file)
@@ -109,8 +109,6 @@ typedef struct {
 
        uint32_t        lifetime;                       //!< The maximum period a session can be resumed after.
 
-       bool            verify;                         //!< Revalidate any sessions read in from the cache.
-
        bool            require_extms;                  //!< Only allow session resumption if the client/server
                                                        //!< supports the extended master session key.  This protects
                                                        //!< against the triple handshake attack.
index e942659e26730a20cc82fe43e6b6d00db42355d8..bfd87c28919ae8dbcbb6ffbddd0120f3d6465701 100644 (file)
@@ -80,7 +80,6 @@ static CONF_PARSER cache_config[] = {
        { FR_CONF_OFFSET("name", FR_TYPE_TMPL, fr_tls_cache_conf_t, id_name),
                         .dflt = "%{EAP-Type}%{Virtual-Server}", .quote = T_DOUBLE_QUOTED_STRING },
        { FR_CONF_OFFSET("lifetime", FR_TYPE_UINT32, fr_tls_cache_conf_t, lifetime), .dflt = "86400" },
-       { FR_CONF_OFFSET("verify", FR_TYPE_BOOL, fr_tls_cache_conf_t, verify), .dflt = "no" },
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
        { FR_CONF_OFFSET("require_extended_master_secret", FR_TYPE_BOOL, fr_tls_cache_conf_t, require_extms), .dflt = "no" },
diff --git a/src/lib/tls/pairs.c b/src/lib/tls/pairs.c
new file mode 100644 (file)
index 0000000..7b2468e
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file tls/pairs.c
+ * @brief Functions to convert certificate OIDs to attribute pairs
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSID("$Id$")
+USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
+
+#ifdef WITH_TLS
+#define LOG_PREFIX "tls - "
+
+#include <openssl/x509v3.h>
+#include <openssl/ssl.h>
+
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/server/request.h>
+
+#include "attrs.h"
+#include "base.h"
+#include "session.h"
+
+/*
+ *     For creating certificate attributes.
+ */
+static fr_dict_attr_t const **cert_attr_names[][2] = {
+       { &attr_tls_client_cert_common_name,                    &attr_tls_cert_common_name },
+       { &attr_tls_client_cert_expiration,                     &attr_tls_cert_expiration },
+       { &attr_tls_client_cert_issuer,                         &attr_tls_cert_issuer },
+       { &attr_tls_client_cert_serial,                         &attr_tls_cert_serial },
+       { &attr_tls_client_cert_subject,                        &attr_tls_cert_subject },
+       { &attr_tls_client_cert_subject_alt_name_dns,           &attr_tls_cert_subject_alt_name_dns },
+       { &attr_tls_client_cert_subject_alt_name_email,         &attr_tls_cert_subject_alt_name_email },
+       { &attr_tls_client_cert_subject_alt_name_upn,           &attr_tls_cert_subject_alt_name_upn }
+};
+
+#define IDX_COMMON_NAME                        (0)
+#define IDX_EXPIRATION                 (1)
+#define IDX_ISSUER                     (2)
+#define IDX_SERIAL                     (3)
+#define IDX_SUBJECT                    (4)
+#define IDX_SUBJECT_ALT_NAME_DNS       (5)
+#define IDX_SUBJECT_ALT_NAME_EMAIL     (6)
+#define IDX_SUBJECT_ALT_NAME_UPN       (7)
+
+static inline CC_HINT(always_inline)
+fr_pair_t *fr_tls_session_cert_attr_add(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *pair_list,
+                                       int attr, int attr_index, char const *value)
+{
+       fr_pair_t *vp;
+       fr_dict_attr_t const *da = *(cert_attr_names[attr][attr_index]);
+
+       MEM(vp = fr_pair_afrom_da(ctx, da));
+       if (value) {
+               if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) {
+                       RPWDEBUG("Failed creating attribute %s", da->name);
+                       talloc_free(vp);
+                       return NULL;
+               }
+       }
+       RINDENT();
+       RDEBUG3("%pP", vp);
+       REXDENT();
+       fr_pair_append(pair_list, vp);
+
+       return vp;
+}
+
+DIAG_OFF(DIAG_UNKNOWN_PRAGMAS)
+DIAG_OFF(used-but-marked-unused)       /* fix spurious warnings for sk macros */
+/** Extract attributes from an X509 certificate
+ *
+ * @param[out] pair_list       to copy attributes to.
+ * @param[in] ctx              to allocate attributes in.
+ * @param[in] tls_session      current TLS session.
+ * @param[in] cert             to validate.
+ * @param[in] depth            the certificate is in the certificate chain (0 == leaf).
+ * @return
+ *     - 0 on success.
+ *     - < 0 on failure.
+ */
+int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx,
+                                       fr_tls_session_t *tls_session, X509 *cert, int depth)
+{
+       char            buffer[2048];
+       char            attribute[256];
+       char            **identity;
+       int             attr_index, loc;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+       STACK_OF(X509_EXTENSION) const *ext_list = NULL;
+#else
+       STACK_OF(X509_EXTENSION) *ext_list = NULL;
+#endif
+
+       ASN1_INTEGER    *sn = NULL;
+       ASN1_TIME       *asn_time = NULL;
+
+       fr_pair_t       *vp = NULL;
+
+       request_t       *request;
+
+#define CERT_ATTR_ADD(_attr, _attr_index, _value) fr_tls_session_cert_attr_add(ctx, request, pair_list, _attr, _attr_index, _value)
+
+       attr_index = depth;
+       if (attr_index > 1) attr_index = 1;
+
+       request = fr_tls_session_request(tls_session->ssl);
+       identity = (char **)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_IDENTITY);
+
+       if (RDEBUG_ENABLED3) {
+               buffer[0] = '\0';
+               X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer));
+               buffer[sizeof(buffer) - 1] = '\0';
+               RDEBUG3("Creating attributes for \"%s\":", buffer[0] ? buffer : "Cert missing subject OID");
+       }
+
+       /*
+        *      Get the Serial Number
+        */
+       sn = X509_get_serialNumber(cert);
+       if (sn && ((size_t) sn->length < (sizeof(buffer) / 2))) {
+               char *p = buffer;
+               int i;
+
+               for (i = 0; i < sn->length; i++) {
+                       sprintf(p, "%02x", (unsigned int)sn->data[i]);
+                       p += 2;
+               }
+
+               CERT_ATTR_ADD(IDX_SERIAL, attr_index, buffer);
+       }
+
+       /*
+        *      Get the Expiration Date
+        */
+       buffer[0] = '\0';
+       asn_time = X509_get_notAfter(cert);
+       if (identity && asn_time && (asn_time->length < (int)sizeof(buffer))) {
+               time_t expires;
+
+               /*
+                *      Add expiration as a time since the epoch
+                */
+               if (fr_tls_utils_asn1time_to_epoch(&expires, asn_time) < 0) {
+                       RPWDEBUG("Failed parsing certificate expiry time");
+               } else {
+                       vp = CERT_ATTR_ADD(IDX_EXPIRATION, attr_index, NULL);
+                       vp->vp_date = fr_unix_time_from_sec(expires);
+               }
+       }
+
+       /*
+        *      Get the Subject & Issuer
+        */
+       buffer[0] = '\0';
+       X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer));
+       buffer[sizeof(buffer) - 1] = '\0';
+       if (identity && buffer[0]) {
+               CERT_ATTR_ADD(IDX_SUBJECT, attr_index, buffer);
+
+               /*
+                *      Get the Common Name, if there is a subject.
+                */
+               X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
+                                         NID_commonName, buffer, sizeof(buffer));
+               buffer[sizeof(buffer) - 1] = '\0';
+
+               if (buffer[0]) {
+                       CERT_ATTR_ADD(IDX_COMMON_NAME, attr_index, buffer);
+               }
+       }
+
+       X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer));
+       buffer[sizeof(buffer) - 1] = '\0';
+       if (identity && buffer[0]) {
+               CERT_ATTR_ADD(IDX_ISSUER, attr_index, buffer);
+       }
+
+       /*
+        *      Get the RFC822 Subject Alternative Name
+        */
+       loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0);
+       if (loc >= 0) {
+               X509_EXTENSION  *ext = NULL;
+               GENERAL_NAMES   *names = NULL;
+               int             i;
+
+               ext = X509_get_ext(cert, loc);
+               if (ext && (names = X509V3_EXT_d2i(ext))) {
+                       for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+                               GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
+
+                               switch (name->type) {
+#ifdef GEN_EMAIL
+                               case GEN_EMAIL: {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+                                       char const *rfc822Name = (char const *)ASN1_STRING_get0_data(name->d.rfc822Name);
+#else
+                                       char *rfc822Name = (char *)ASN1_STRING_data(name->d.rfc822Name);
+#endif
+
+                                       CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_EMAIL, attr_index, rfc822Name);
+                                       break;
+                               }
+#endif /* GEN_EMAIL */
+#ifdef GEN_DNS
+                               case GEN_DNS: {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+                                       char const *dNSName = (char const *)ASN1_STRING_get0_data(name->d.dNSName);
+#else
+                                       char *dNSName = (char *)ASN1_STRING_data(name->d.dNSName);
+#endif
+                                       CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_DNS, attr_index, dNSName);
+                                       break;
+                               }
+#endif /* GEN_DNS */
+#ifdef GEN_OTHERNAME
+                               case GEN_OTHERNAME:
+                                       /* look for a MS UPN */
+                                       if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break;
+
+                                       /* we've got a UPN - Must be ASN1-encoded UTF8 string */
+                                       if (name->d.otherName->value->type == V_ASN1_UTF8STRING) {
+                                               CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_UPN, attr_index,
+                                                                 (char *)name->d.otherName->value->value.utf8string);
+                                               break;
+                                       }
+
+                                       RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)");
+                                       break;
+#endif /* GEN_OTHERNAME */
+                               default:
+                                       /* XXX TODO handle other SAN types */
+                                       break;
+                               }
+                       }
+               }
+               if (names != NULL) GENERAL_NAMES_free(names);
+       }
+
+       /*
+        *      Only add extensions for the actual client certificate
+        */
+       if (attr_index == 0) {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+               ext_list = X509_get0_extensions(cert);
+#else
+               ext_list = cert->cert_info->extensions;
+#endif
+
+               /*
+                *      Grab the X509 extensions, and create attributes out of them.
+                *      For laziness, we re-use the OpenSSL names
+                */
+               if (sk_X509_EXTENSION_num(ext_list) > 0) {
+                       int i, len;
+                       char *p;
+                       BIO *out;
+
+                       MEM(out = BIO_new(BIO_s_mem()));
+                       strlcpy(attribute, "TLS-Client-Cert-", sizeof(attribute));
+
+                       for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
+                               char                    value[1024];
+                               ASN1_OBJECT             *obj;
+                               X509_EXTENSION          *ext;
+                               fr_dict_attr_t const    *da;
+
+                               ext = sk_X509_EXTENSION_value(ext_list, i);
+
+                               obj = X509_EXTENSION_get_object(ext);
+                               if (i2a_ASN1_OBJECT(out, obj) <= 0) {
+                                       RPWDEBUG("Skipping X509 Extension (%i) conversion to attribute. "
+                                                "Conversion from ASN1 failed...", i);
+                                       continue;
+                               }
+
+                               len = BIO_read(out, attribute + 16 , sizeof(attribute) - 16 - 1);
+                               if (len <= 0) continue;
+
+                               attribute[16 + len] = '\0';
+
+                               for (p = attribute + 16; *p != '\0'; p++) if (*p == ' ') *p = '-';
+
+                               X509V3_EXT_print(out, ext, 0, 0);
+                               len = BIO_read(out, value , sizeof(value) - 1);
+                               if (len <= 0) continue;
+
+                               value[len] = '\0';
+
+                               da = fr_dict_attr_by_name(NULL, fr_dict_root(dict_freeradius), attribute);
+                               if (!da) {
+                                       RWDEBUG3("Skipping attribute %s: "
+                                                "Add dictionary definition if you want to access it", attribute);
+                                       continue;
+                               }
+
+                               MEM(vp = fr_pair_afrom_da(ctx, da));
+                               if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) {
+                                       RPWDEBUG3("Skipping: %s += '%s'", attribute, value);
+                                       talloc_free(vp);
+                                       continue;
+                               }
+
+                               fr_pair_append(pair_list, vp);
+                       }
+                       BIO_free_all(out);
+               }
+       }
+
+       return 0;
+}
+DIAG_ON(used-but-marked-unused)
+DIAG_ON(DIAG_UNKNOWN_PRAGMAS)
+#endif
index 6e50dd76947b124cd5f4cfb73ceccb30735e783c..94256762b96c3af1db7cc7c03b5e3b934411b954 100644 (file)
 #include "log.h"
 #include "missing.h"
 
-/*
- *     For creating certificate attributes.
- */
-static fr_dict_attr_t const **cert_attr_names[][2] = {
-       { &attr_tls_client_cert_common_name,                    &attr_tls_cert_common_name },
-       { &attr_tls_client_cert_expiration,                     &attr_tls_cert_expiration },
-       { &attr_tls_client_cert_issuer,                         &attr_tls_cert_issuer },
-       { &attr_tls_client_cert_serial,                         &attr_tls_cert_serial },
-       { &attr_tls_client_cert_subject,                        &attr_tls_cert_subject },
-       { &attr_tls_client_cert_subject_alt_name_dns,           &attr_tls_cert_subject_alt_name_dns },
-       { &attr_tls_client_cert_subject_alt_name_email,         &attr_tls_cert_subject_alt_name_email },
-       { &attr_tls_client_cert_subject_alt_name_upn,           &attr_tls_cert_subject_alt_name_upn }
-};
-
-#define IDX_COMMON_NAME                        (0)
-#define IDX_EXPIRATION                 (1)
-#define IDX_ISSUER                     (2)
-#define IDX_SERIAL                     (3)
-#define IDX_SUBJECT                    (4)
-#define IDX_SUBJECT_ALT_NAME_DNS       (5)
-#define IDX_SUBJECT_ALT_NAME_EMAIL     (6)
-#define IDX_SUBJECT_ALT_NAME_UPN       (7)
-
 static char const *tls_version_str[] = {
        [SSL2_VERSION]                          = "SSL 2.0",
        [SSL3_VERSION]                          = "SSL 3.0",
@@ -370,7 +347,7 @@ unsigned int fr_tls_session_psk_server_cb(SSL *ssl, const char *identity,
 {
        size_t          psk_len = 0;
        fr_tls_conf_t   *conf;
-       request_t               *request;
+       request_t       *request;
 
        conf = (fr_tls_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF);
        if (!conf) return 0;
@@ -843,277 +820,6 @@ void fr_tls_session_msg_cb(int write_p, int msg_version, int content_type,
 #endif
 }
 
-static inline CC_HINT(always_inline)
-fr_pair_t *fr_tls_session_cert_attr_add(TALLOC_CTX *ctx, request_t *request, fr_pair_list_t *pair_list,
-                                       int attr, int attr_index, char const *value)
-{
-       fr_pair_t *vp;
-       fr_dict_attr_t const *da = *(cert_attr_names[attr][attr_index]);
-
-       MEM(vp = fr_pair_afrom_da(ctx, da));
-       if (value) {
-               if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) {
-                       RPWDEBUG("Failed creating attribute %s", da->name);
-                       talloc_free(vp);
-                       return NULL;
-               }
-       }
-       RINDENT();
-       RDEBUG3("%pP", vp);
-       REXDENT();
-       fr_pair_append(pair_list, vp);
-
-       return vp;
-}
-
-DIAG_OFF(DIAG_UNKNOWN_PRAGMAS)
-DIAG_OFF(used-but-marked-unused)       /* fix spurious warnings for sk macros */
-/** Extract attributes from an X509 certificate
- *
- * @param[out] pair_list       to copy attributes to.
- * @param[in] ctx              to allocate attributes in.
- * @param[in] tls_session      current TLS session.
- * @param[in] cert             to validate.
- * @param[in] depth            the certificate is in the certificate chain (0 == leaf).
- * @return
- *     - 0 on success.
- *     - < 0 on failure.
- */
-int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx,
-                                       fr_tls_session_t *tls_session, X509 *cert, int depth)
-{
-       char            buffer[1024];
-       char            attribute[256];
-       char            **identity;
-       int             attr_index, loc;
-
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-       STACK_OF(X509_EXTENSION) const *ext_list = NULL;
-#else
-       STACK_OF(X509_EXTENSION) *ext_list = NULL;
-#endif
-
-       ASN1_INTEGER    *sn = NULL;
-       ASN1_TIME       *asn_time = NULL;
-
-       fr_pair_t       *vp = NULL;
-
-       request_t               *request;
-
-#define CERT_ATTR_ADD(_attr, _attr_index, _value) fr_tls_session_cert_attr_add(ctx, request, pair_list, _attr, _attr_index, _value)
-
-       attr_index = depth;
-       if (attr_index > 1) attr_index = 1;
-
-       request = fr_tls_session_request(tls_session->ssl);
-       identity = (char **)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_IDENTITY);
-
-       if (RDEBUG_ENABLED3) {
-               buffer[0] = '\0';
-               X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer));
-               buffer[sizeof(buffer) - 1] = '\0';
-               RDEBUG3("Creating attributes for \"%s\":", buffer[0] ? buffer : "Cert missing subject OID");
-       }
-
-       /*
-        *      Get the Serial Number
-        */
-       sn = X509_get_serialNumber(cert);
-       if (sn && ((size_t) sn->length < (sizeof(buffer) / 2))) {
-               char *p = buffer;
-               int i;
-
-               for (i = 0; i < sn->length; i++) {
-                       sprintf(p, "%02x", (unsigned int)sn->data[i]);
-                       p += 2;
-               }
-
-               CERT_ATTR_ADD(IDX_SERIAL, attr_index, buffer);
-       }
-
-       /*
-        *      Get the Expiration Date
-        */
-       buffer[0] = '\0';
-       asn_time = X509_get_notAfter(cert);
-       if (identity && asn_time && (asn_time->length < (int)sizeof(buffer))) {
-               time_t expires;
-
-               /*
-                *      Add expiration as a time since the epoch
-                */
-               if (fr_tls_utils_asn1time_to_epoch(&expires, asn_time) < 0) {
-                       RPWDEBUG("Failed parsing certificate expiry time");
-               } else {
-                       vp = CERT_ATTR_ADD(IDX_EXPIRATION, attr_index, NULL);
-                       vp->vp_date = fr_unix_time_from_sec(expires);
-               }
-       }
-
-       /*
-        *      Get the Subject & Issuer
-        */
-       buffer[0] = '\0';
-       X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer));
-       buffer[sizeof(buffer) - 1] = '\0';
-       if (identity && buffer[0]) {
-               CERT_ATTR_ADD(IDX_SUBJECT, attr_index, buffer);
-
-               /*
-                *      Get the Common Name, if there is a subject.
-                */
-               X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
-                                         NID_commonName, buffer, sizeof(buffer));
-               buffer[sizeof(buffer) - 1] = '\0';
-
-               if (buffer[0]) {
-                       CERT_ATTR_ADD(IDX_COMMON_NAME, attr_index, buffer);
-               }
-       }
-
-       X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer));
-       buffer[sizeof(buffer) - 1] = '\0';
-       if (identity && buffer[0]) {
-               CERT_ATTR_ADD(IDX_ISSUER, attr_index, buffer);
-       }
-
-       /*
-        *      Get the RFC822 Subject Alternative Name
-        */
-       loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0);
-       if (loc >= 0) {
-               X509_EXTENSION  *ext = NULL;
-               GENERAL_NAMES   *names = NULL;
-               int             i;
-
-               ext = X509_get_ext(cert, loc);
-               if (ext && (names = X509V3_EXT_d2i(ext))) {
-                       for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
-                               GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
-
-                               switch (name->type) {
-#ifdef GEN_EMAIL
-                               case GEN_EMAIL: {
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-                                       char const *rfc822Name = (char const *)ASN1_STRING_get0_data(name->d.rfc822Name);
-#else
-                                       char *rfc822Name = (char *)ASN1_STRING_data(name->d.rfc822Name);
-#endif
-
-                                       CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_EMAIL, attr_index, rfc822Name);
-                                       break;
-                               }
-#endif /* GEN_EMAIL */
-#ifdef GEN_DNS
-                               case GEN_DNS: {
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-                                       char const *dNSName = (char const *)ASN1_STRING_get0_data(name->d.dNSName);
-#else
-                                       char *dNSName = (char *)ASN1_STRING_data(name->d.dNSName);
-#endif
-                                       CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_DNS, attr_index, dNSName);
-                                       break;
-                               }
-#endif /* GEN_DNS */
-#ifdef GEN_OTHERNAME
-                               case GEN_OTHERNAME:
-                                       /* look for a MS UPN */
-                                       if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break;
-
-                                       /* we've got a UPN - Must be ASN1-encoded UTF8 string */
-                                       if (name->d.otherName->value->type == V_ASN1_UTF8STRING) {
-                                               CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_UPN, attr_index,
-                                                                 (char *)name->d.otherName->value->value.utf8string);
-                                               break;
-                                       }
-
-                                       RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)");
-                                       break;
-#endif /* GEN_OTHERNAME */
-                               default:
-                                       /* XXX TODO handle other SAN types */
-                                       break;
-                               }
-                       }
-               }
-               if (names != NULL) GENERAL_NAMES_free(names);
-       }
-
-       /*
-        *      Only add extensions for the actual client certificate
-        */
-       if (attr_index == 0) {
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-               ext_list = X509_get0_extensions(cert);
-#else
-               ext_list = cert->cert_info->extensions;
-#endif
-
-               /*
-                *      Grab the X509 extensions, and create attributes out of them.
-                *      For laziness, we re-use the OpenSSL names
-                */
-               if (sk_X509_EXTENSION_num(ext_list) > 0) {
-                       int i, len;
-                       char *p;
-                       BIO *out;
-
-                       MEM(out = BIO_new(BIO_s_mem()));
-                       strlcpy(attribute, "TLS-Client-Cert-", sizeof(attribute));
-
-                       for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
-                               char                    value[1024];
-                               ASN1_OBJECT             *obj;
-                               X509_EXTENSION          *ext;
-                               fr_dict_attr_t const    *da;
-
-                               ext = sk_X509_EXTENSION_value(ext_list, i);
-
-                               obj = X509_EXTENSION_get_object(ext);
-                               if (i2a_ASN1_OBJECT(out, obj) <= 0) {
-                                       RPWDEBUG("Skipping X509 Extension (%i) conversion to attribute. "
-                                                "Conversion from ASN1 failed...", i);
-                                       continue;
-                               }
-
-                               len = BIO_read(out, attribute + 16 , sizeof(attribute) - 16 - 1);
-                               if (len <= 0) continue;
-
-                               attribute[16 + len] = '\0';
-
-                               for (p = attribute + 16; *p != '\0'; p++) if (*p == ' ') *p = '-';
-
-                               X509V3_EXT_print(out, ext, 0, 0);
-                               len = BIO_read(out, value , sizeof(value) - 1);
-                               if (len <= 0) continue;
-
-                               value[len] = '\0';
-
-                               da = fr_dict_attr_by_name(NULL, fr_dict_root(dict_freeradius), attribute);
-                               if (!da) {
-                                       RWDEBUG3("Skipping attribute %s: "
-                                                "Add dictionary definition if you want to access it", attribute);
-                                       continue;
-                               }
-
-                               MEM(vp = fr_pair_afrom_da(ctx, da));
-                               if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) {
-                                       RPWDEBUG3("Skipping: %s += '%s'", attribute, value);
-                                       talloc_free(vp);
-                                       continue;
-                               }
-
-                               fr_pair_append(pair_list, vp);
-                       }
-                       BIO_free_all(out);
-               }
-       }
-
-       return 0;
-}
-DIAG_ON(used-but-marked-unused)
-DIAG_ON(DIAG_UNKNOWN_PRAGMAS)
-
 /** Decrypt application data
  *
  * @note Handshake must have completed before this function may be called.
@@ -1529,7 +1235,9 @@ static unlang_action_t tls_session_async_handshake_cont(rlm_rcode_t *p_result, i
         *      asynchronously.
         */
        switch (SSL_get_error(tls_session->ssl, ret)) {
-       case SSL_ERROR_WANT_ASYNC:      /* Certification validation */
+       case SSL_ERROR_WANT_ASYNC:      /* Certification validation or cache loads */
+       {
+               unlang_action_t ua;
                /*
                 *      Call this function again once we're done
                 *      asynchronously satisfying the load request.
@@ -1540,11 +1248,36 @@ static unlang_action_t tls_session_async_handshake_cont(rlm_rcode_t *p_result, i
                        goto finish;
                }
 
-               if (fr_tls_cache_load_push(request, tls_session) < 0) {
+               /*
+                *      First service any pending cache actions
+                */
+               ua = fr_tls_cache_pending_push(request, tls_session);
+               switch (ua) {
+               case UNLANG_ACTION_FAIL:
                        if (unlang_function_clear(request) < 0) goto error;
                        goto error;
+
+               case UNLANG_ACTION_PUSHED_CHILD:
+                       return ua;
+
+               default:
+                       break;
                }
-               return UNLANG_ACTION_PUSHED_CHILD;
+
+               /*
+                *      Next service any pending certificate
+                *      validation actions.
+                */
+               ua = fr_tls_validate_client_cert_pending_push(request, tls_session);
+               switch (ua) {
+               case UNLANG_ACTION_FAIL:
+                       if (unlang_function_clear(request) < 0) goto error;
+                       goto error;
+
+               default:
+                       return ua;
+               }
+       }
 
        default:
                /*
@@ -1706,10 +1439,9 @@ fr_tls_session_t *fr_tls_session_alloc_client(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx)
        request_t               *request;
        fr_tls_conf_t           *conf = fr_tls_ctx_conf(ssl_ctx);
 
-       tls_session = talloc_zero(ctx, fr_tls_session_t);
-       if (!tls_session) return NULL;
-
+       MEM(tls_session = talloc_zero(ctx, fr_tls_session_t));
        talloc_set_destructor(tls_session, _fr_tls_session_free);
+       fr_pair_list_init(&tls_session->extra_pairs);
 
        tls_session->ssl = SSL_new(ssl_ctx);
        if (!tls_session->ssl) {
@@ -1773,28 +1505,25 @@ fr_tls_session_t *fr_tls_session_alloc_client(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx)
 fr_tls_session_t *fr_tls_session_alloc_server(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx, request_t *request, bool client_cert)
 {
        fr_tls_session_t        *tls_session = NULL;
-       SSL                     *new_tls = NULL;
+       SSL                     *ssl = NULL;
        int                     verify_mode = 0;
        fr_pair_t               *vp;
        fr_tls_conf_t           *conf = fr_tls_ctx_conf(ssl_ctx);
 
        RDEBUG2("Initiating new TLS session");
 
-       new_tls = SSL_new(ssl_ctx);
-       if (new_tls == NULL) {
+       MEM(tls_session = talloc_zero(ctx, fr_tls_session_t));
+
+       ssl = SSL_new(ssl_ctx);
+       if (ssl == NULL) {
                fr_tls_log_error(request, "Error creating new TLS session");
                return NULL;
        }
+       fr_pair_list_init(&tls_session->extra_pairs);
 
-       tls_session = talloc_zero(ctx, fr_tls_session_t);
-       if (tls_session == NULL) {
-               RERROR("Error allocating memory for TLS session");
-               SSL_free(new_tls);
-               return NULL;
-       }
        session_init(tls_session);
        tls_session->ctx = ssl_ctx;
-       tls_session->ssl = new_tls;
+       tls_session->ssl = ssl;
        talloc_set_destructor(tls_session, _fr_tls_session_free);
 
        fr_tls_session_request_bind(tls_session->ssl, request);
@@ -1825,9 +1554,9 @@ fr_tls_session_t *fr_tls_session_alloc_server(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx,
         *      Add the message callback to identify what type of
         *      message/handshake is passed
         */
-       SSL_set_msg_callback(new_tls, fr_tls_session_msg_cb);
-       SSL_set_msg_callback_arg(new_tls, tls_session);
-       SSL_set_info_callback(new_tls, fr_tls_session_info_cb);
+       SSL_set_msg_callback(ssl, fr_tls_session_msg_cb);
+       SSL_set_msg_callback_arg(ssl, tls_session);
+       SSL_set_info_callback(ssl, fr_tls_session_info_cb);
 
        /*
         *      This sets the context sessions can be resumed in.
@@ -1931,6 +1660,8 @@ fr_tls_session_t *fr_tls_session_alloc_server(TALLOC_CTX *ctx, SSL_CTX *ssl_ctx,
                verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
                verify_mode |= SSL_VERIFY_CLIENT_ONCE;
        }
+       tls_session->verify_client_cert = client_cert;
+
        SSL_set_verify(tls_session->ssl, verify_mode, fr_tls_validate_cert_cb);
        SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf);
        SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_TLS_SESSION, (void *)tls_session);
index 985589a2ee5034db3403a7a90a28539999584302..c5b743942c56ae2251396a89551b3eba72b59257 100644 (file)
@@ -28,8 +28,12 @@ RCSIDH(session_h, "$Id$")
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 
+typedef struct fr_tls_session_s fr_tls_session_t;
+
+#include "cache.h"
 #include "conf.h"
 #include "index.h"
+#include "validate.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -95,7 +99,7 @@ typedef enum {
  * In the case of EAP-TLS + dependents a #eap_tls_session_t struct is used to track
  * the transfer of TLS records.
  */
-typedef struct {
+struct fr_tls_session_s {
        SSL_CTX                 *ctx;                           //!< TLS configuration context.
        SSL                     *ssl;                           //!< This SSL session.
        SSL_SESSION             *session;                       //!< Session resumption data.
@@ -124,6 +128,9 @@ typedef struct {
 
        fr_tls_cache_t          *cache;                         //!< Current session resumption state.
        bool                    allow_session_resumption;       //!< Whether session resumption is allowed.
+       bool                    verify_client_cert;             //!< Whether client cert verification has been requested.
+
+       fr_tls_validate_t       validate;                       //!< Current session certificate validation state.
 
        bool                    invalid;                        //!< Whether heartbleed attack was detected.
 
@@ -133,7 +140,10 @@ typedef struct {
        bool                    pending_alert;
        uint8_t                 pending_alert_level;
        uint8_t                 pending_alert_description;
-} fr_tls_session_t;
+
+       fr_pair_list_t          extra_pairs;                    //!< Pairs to add to cache and certificate validation
+                                                               ///< calls.  These will be duplicated for every call.
+};
 
 /** Return the tls config associated with a tls_session
  *
@@ -223,6 +233,46 @@ static inline request_t *fr_tls_session_request(SSL const *ssl)
        return talloc_get_type_abort(SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST), request_t);
 }
 
+/** Add extra pairs to the temporary subrequests
+ *
+ * @param[in] child            to add extra pairs to.
+ * @param[in] tls_session      to add extra pairs from.
+ */
+static inline CC_HINT(nonnull)
+void fr_tls_session_extra_pairs_copy_to_child(request_t *child, fr_tls_session_t *tls_session)
+{
+       if (!fr_pair_list_empty(&tls_session->extra_pairs)) {
+               MEM(fr_pair_list_copy(child->request_ctx, &child->request_pairs, &tls_session->extra_pairs) >= 0);
+       }
+}
+
+/** Add an additional pair (copying it) to the list of extra pairs
+ *
+ * @param[in] tls_session      to add extra pairs to.
+ * @param[in] vp               to add to tls_session.
+ */
+static inline CC_HINT(nonnull)
+void fr_tls_session_extra_pair_add(fr_tls_session_t *tls_session, fr_pair_t *vp)
+{
+       fr_pair_t       *copy;
+
+       MEM(copy = fr_pair_copy(tls_session, vp));
+       fr_pair_append(&tls_session->extra_pairs, copy);
+}
+
+/** Add an additional pair to the list of extra pairs
+ *
+ * @param[in] tls_session      to add extra pairs to.
+ * @param[in] vp               to add to tls_session.
+ */
+static inline CC_HINT(nonnull)
+void fr_tls_session_extra_pair_add_shallow(fr_tls_session_t *tls_session, fr_pair_t *vp)
+{
+       fr_assert(talloc_parent(vp) == tls_session);
+       fr_pair_append(&tls_session->extra_pairs, vp);
+}
+
+
 int            fr_tls_session_password_cb(char *buf, int num, int rwflag, void *userdata);
 
 unsigned int   fr_tls_session_psk_client_cb(SSL *ssl, UNUSED char const *hint,
index ac6588a22d4ab31b2c1ffa08e9ff7c82a9173539..a53a80ff606ec7864517cdb7c359c57847f18e06 100644 (file)
@@ -30,8 +30,9 @@
 
 #include <freeradius-devel/server/exec.h>
 #include <freeradius-devel/server/pair.h>
+#include <freeradius-devel/unlang/function.h>
+#include <freeradius-devel/unlang/subrequest.h>
 #include <freeradius-devel/util/debug.h>
-
 #include <freeradius-devel/util/strerror.h>
 #include <freeradius-devel/util/syserror.h>
 
@@ -411,4 +412,109 @@ int fr_tls_validate_client_cert_chain(SSL *ssl)
 
        return ret;
 }
+
+/** Process the result of `validate certificate { ... }`
+ *
+ */
+static unlang_action_t tls_validate_client_cert_result(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
+                                                      request_t *request, void *uctx)
+{
+       fr_tls_session_t        *tls_session = talloc_get_type_abort(uctx, fr_tls_session_t);
+       fr_pair_t               *vp;
+
+       fr_assert(tls_session->validate.state == FR_TLS_VALIDATION_REQUESTED);
+
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_tls_packet_type, 0);
+       if (!vp || (vp->vp_uint32 != enum_tls_packet_type_success->vb_uint32)) {
+               REDEBUG("Failed (re-)validating certificates");
+               tls_session->validate.state = FR_TLS_VALIDATION_FAILED;
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
+
+       tls_session->validate.state = FR_TLS_VALIDATION_SUCCESS;
+
+       RDEBUG2("Certificates (re-)validated");
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Push a `validate certificate { ... }` call into the current request, using a subrequest
+ *
+ * @param[in] request          The current request.
+ * @Param[in] tls_session      The current TLS session.
+ * @return
+ *      - UNLANG_ACTION_CALCULATE_RESULT on noop.
+ *     - UNLANG_ACTION_PUSHED_CHILD on success.
+ *      - UNLANG_ACTION_FAIL on failure.
+ */
+static unlang_action_t tls_validate_client_cert_push(request_t *request, fr_tls_session_t *tls_session)
+{
+       fr_tls_conf_t           *conf = fr_tls_session_conf(tls_session->ssl);
+       request_t               *child;
+       fr_pair_t               *vp;
+       unlang_action_t         ua;
+
+       MEM(child = unlang_subrequest_alloc(request, dict_tls));
+       request = child;
+
+       /*
+        *      Add extra pairs to the subrequest
+        */
+       fr_tls_session_extra_pairs_copy_to_child(child, tls_session);
+
+       /*
+        *      Setup the child request for loading
+        *      session resumption data.
+        */
+       MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
+       vp->vp_uint32 = enum_tls_packet_type_certificate_validate->vb_uint32;
+
+       /*
+        *      Allocate a child, and set it up to call
+        *      the TLS virtual server.
+        */
+       ua = fr_tls_call_push(child, tls_validate_client_cert_result, conf, tls_session);
+       if (ua < 0) {
+               PERROR("Failed calling TLS virtual server");
+               talloc_free(child);
+               return UNLANG_ACTION_FAIL;
+       }
+
+       return ua;
+}
+
+/** Check we validated the client cert successfully
+ *
+ */
+bool fr_tls_validate_client_cert_success(fr_tls_session_t *tls_session)
+{
+       return tls_session->validate.state == FR_TLS_VALIDATION_SUCCESS;
+}
+
+/** Setup a validation request
+ *
+ */
+void fr_tls_validate_client_cert_request(fr_tls_session_t *tls_session)
+{
+       fr_assert(tls_session->validate.state == FR_TLS_VALIDATION_INIT);
+
+       tls_session->validate.state = FR_TLS_VALIDATION_REQUESTED;
+}
+
+/** Push a `validate certificate { ... }` section
+ *
+ * @param[in] request          The current request.
+ * @Param[in] tls_session      The current TLS session.
+ * @return
+ *     - UNLANG_ACTION_CALCULATE_RESULT        - No pending actions
+ *     - UNLANG_ACTION_PUSHED_CHILD            - Pending operations to evaluate.
+ */
+unlang_action_t fr_tls_validate_client_cert_pending_push(request_t *request, fr_tls_session_t *tls_session)
+{
+       if (tls_session->validate.state == FR_TLS_VALIDATION_REQUESTED) {
+               return tls_validate_client_cert_push(request, tls_session);
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
 #endif /* WITH_TLS */
diff --git a/src/lib/tls/validate.h b/src/lib/tls/validate.h
new file mode 100644 (file)
index 0000000..03a4464
--- /dev/null
@@ -0,0 +1,67 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+#ifdef WITH_TLS
+/**
+ * $Id$
+ *
+ * @file lib/tls/validate.h
+ * @brief Structures for session-resumption management.
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSIDH(validate_h, "$Id$")
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Certificate validation states
+ *
+ */
+typedef enum {
+       FR_TLS_VALIDATION_INIT = 0,             //!< OpenSSL hasn't requested certificate validation.
+       FR_TLS_VALIDATION_REQUESTED,            //!< OpenSSL requested validation.
+       FR_TLS_VALIDATION_SUCCESS,              //!< Certificate chain was validate.
+       FR_TLS_VALIDATION_FAILED                //!< Certificate validation failed.
+} fr_tls_validation_state_t;
+
+/** Certificate validation state
+ *
+ */
+typedef struct {
+       rlm_rcode_t                     rcode;
+       fr_tls_validation_state_t       state;  //!< Whether OpenSSL has requested
+                                               ///< certificate validation.
+} fr_tls_validate_t;
+
+int            fr_tls_validate_cert_cb(int ok, X509_STORE_CTX *ctx);
+
+int            fr_tls_validate_client_cert_chain(SSL *ssl);
+
+bool           fr_tls_validate_client_cert_success(fr_tls_session_t *tls_session);
+
+void           fr_tls_validate_client_cert_request(fr_tls_session_t *tls_session);
+
+unlang_action_t fr_tls_validate_client_cert_pending_push(request_t *request, fr_tls_session_t *tls_session);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* WITH_TLS */
index 77a614e52a9d0a7a39981aa15cc74d6a2cf9452d..d6652bde5f5baeac6ca70f049aa3583dc6ba558a 100644 (file)
@@ -1,5 +1,8 @@
-TARGETNAME := process_tls
+TARGETNAME     := process_tls
+
+ifneq "$(OPENSSL_LIBS)" ""
+TARGET         := $(TARGETNAME).a
+endif
 
 SOURCES                := base.c
 TGT_PREREQS    := libfreeradius-tls.a
-TGT_LDLIBS     := $(PCAP_LIBS)
index 9a042699275707a5403af3b916ff5e352d5f010a..98658e08c5fcb3300648705f3335b6fb58ffbd49 100644 (file)
 /**
  * $Id$
  * @file src/process/tls/base.c
- * @brief ARP processing.
+ * @brief TLS processing.
  *
  * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  */
 #include <freeradius-devel/server/protocol.h>
 #include <freeradius-devel/unlang/base.h>
 #include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/protocol/tls/freeradius.h>
 
 static fr_dict_t const *dict_tls;
 
@@ -42,140 +43,95 @@ fr_dict_attr_autoload_t process_tls_dict_attr[] = {
 };
 
 typedef struct {
-       uint64_t        nothing;                // so that the next field isn't at offset 0
-
-       CONF_SECTION    *request;
-       CONF_SECTION    *reply;
-       CONF_SECTION    *recv_reply;
-       CONF_SECTION    *reverse_request;
-       CONF_SECTION    *reverse_reply;
-       CONF_SECTION    *do_not_respond;
+       CONF_SECTION    *session_load;
+       CONF_SECTION    *session_store;
+       CONF_SECTION    *session_clear;
+       CONF_SECTION    *certificate_validate;
 } process_tls_sections_t;
 
 typedef struct {
-       bool            test;
-
        process_tls_sections_t  sections;
 } process_tls_t;
 
-#define PROCESS_PACKET_TYPE            fr_tls_packet_code_t
-#define PROCESS_CODE_MAX               FR_ARP_CODE_MAX
-#define PROCESS_CODE_DO_NOT_RESPOND    FR_ARP_DO_NOT_RESPOND
-#define PROCESS_PACKET_CODE_VALID      FR_ARP_PACKET_CODE_VALID
+#define FR_TLS_PACKET_CODE_VALID(_code) (((_code) > 0) && ((_code) <= FR_PACKET_TYPE_VALUE_NOTFOUND))
+
 #define PROCESS_INST                   process_tls_t
+#define PROCESS_PACKET_TYPE            uint32_t
+#define PROCESS_PACKET_CODE_VALID      FR_TLS_PACKET_CODE_VALID
+
 #include <freeradius-devel/server/process.h>
 
 static fr_process_state_t const process_state[] = {
-       [ FR_ARP_REQUEST ] = {
+       [FR_PACKET_TYPE_VALUE_SESSION_LOAD] = {
                .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_ARP_REPLY,
-                       [RLM_MODULE_OK] =       FR_ARP_REPLY,
-                       [RLM_MODULE_UPDATED] =  FR_ARP_REPLY,
-
-                       [RLM_MODULE_REJECT] =   FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_ARP_DO_NOT_RESPOND,
+                       [RLM_MODULE_OK] =       FR_PACKET_TYPE_VALUE_SUCCESS,
+                       [RLM_MODULE_UPDATED] =  FR_PACKET_TYPE_VALUE_SUCCESS,
+
+                       [RLM_MODULE_NOOP] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_REJECT] =   FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_FAIL] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_INVALID] =  FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_DISALLOW] = FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_NOTFOUND] = FR_PACKET_TYPE_VALUE_NOTFOUND,
                },
                .rcode = RLM_MODULE_NOOP,
                .recv = recv_generic,
-               .resume = resume_recv_generic,
-               .section_offset = PROCESS_CONF_OFFSET(request),
+               .resume = resume_recv_no_send,
+               .section_offset = PROCESS_CONF_OFFSET(session_load),
        },
-       [ FR_ARP_REPLY ] = {
+       [FR_PACKET_TYPE_VALUE_SESSION_STORE] = {
                .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_ARP_REPLY,
-                       [RLM_MODULE_OK] =       FR_ARP_REPLY,
-                       [RLM_MODULE_UPDATED] =  FR_ARP_REPLY,
-
-                       [RLM_MODULE_REJECT] =   FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_ARP_DO_NOT_RESPOND,
-               },
-               .rcode = RLM_MODULE_NOOP,
-               .send = send_generic,
-               .resume = resume_send_generic,
-               .section_offset = PROCESS_CONF_OFFSET(reply),
-       },
-
-       [ FR_ARP_REVERSE_REQUEST ] = {
-               .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_ARP_REVERSE_REPLY,
-                       [RLM_MODULE_OK] =       FR_ARP_REVERSE_REPLY,
-                       [RLM_MODULE_UPDATED] =  FR_ARP_REVERSE_REPLY,
-
-                       [RLM_MODULE_REJECT] =   FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_ARP_DO_NOT_RESPOND,
+                       [RLM_MODULE_OK] =       FR_PACKET_TYPE_VALUE_SUCCESS,
+                       [RLM_MODULE_UPDATED] =  FR_PACKET_TYPE_VALUE_SUCCESS,
+
+                       [RLM_MODULE_NOOP] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_REJECT] =   FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_FAIL] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_INVALID] =  FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_DISALLOW] = FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_NOTFOUND] = FR_PACKET_TYPE_VALUE_NOTFOUND,
                },
                .rcode = RLM_MODULE_NOOP,
                .recv = recv_generic,
-               .resume = resume_recv_generic,
-               .section_offset = PROCESS_CONF_OFFSET(reverse_request),
+               .resume = resume_recv_no_send,
+               .section_offset = PROCESS_CONF_OFFSET(session_store),
        },
-       [ FR_ARP_REVERSE_REPLY ] = {
+       [FR_PACKET_TYPE_VALUE_SESSION_CLEAR] = {
                .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_ARP_REVERSE_REPLY,
-                       [RLM_MODULE_OK] =       FR_ARP_REVERSE_REPLY,
-                       [RLM_MODULE_UPDATED] =  FR_ARP_REVERSE_REPLY,
-
-                       [RLM_MODULE_REJECT] =   FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_ARP_DO_NOT_RESPOND,
+                       [RLM_MODULE_OK] =       FR_PACKET_TYPE_VALUE_SUCCESS,
+                       [RLM_MODULE_UPDATED] =  FR_PACKET_TYPE_VALUE_SUCCESS,
+
+                       [RLM_MODULE_NOOP] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_REJECT] =   FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_FAIL] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_INVALID] =  FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_DISALLOW] = FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_NOTFOUND] = FR_PACKET_TYPE_VALUE_NOTFOUND,
                },
                .rcode = RLM_MODULE_NOOP,
-               .send = send_generic,
-               .resume = resume_send_generic,
-               .section_offset = PROCESS_CONF_OFFSET(reverse_reply),
+               .recv = recv_generic,
+               .resume = resume_recv_no_send,
+               .section_offset = PROCESS_CONF_OFFSET(session_clear),
        },
-
-       // @todo - recv reply, to look at other replies.
-
-       [ FR_ARP_DO_NOT_RESPOND ] = {
+       [FR_PACKET_TYPE_VALUE_CERTIFICATE_VALIDATE] = {
                .packet_type = {
-                       [RLM_MODULE_NOOP] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_OK] =       FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_UPDATED] =  FR_ARP_DO_NOT_RESPOND,
-
-                       [RLM_MODULE_REJECT] =   FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_FAIL] =     FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_INVALID] =  FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_DISALLOW] = FR_ARP_DO_NOT_RESPOND,
-                       [RLM_MODULE_NOTFOUND] = FR_ARP_DO_NOT_RESPOND,
+                       [RLM_MODULE_OK] =       FR_PACKET_TYPE_VALUE_SUCCESS,
+
+                       [RLM_MODULE_UPDATED] =  FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_NOOP] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_REJECT] =   FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_FAIL] =     FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_INVALID] =  FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_DISALLOW] = FR_PACKET_TYPE_VALUE_FAILURE,
+                       [RLM_MODULE_NOTFOUND] = FR_PACKET_TYPE_VALUE_NOTFOUND,
                },
                .rcode = RLM_MODULE_NOOP,
-               .send = send_generic,
-               .resume = resume_send_generic,
-               .section_offset = PROCESS_CONF_OFFSET(do_not_respond),
+               .recv = recv_generic,
+               .resume = resume_recv_no_send,
+               .section_offset = PROCESS_CONF_OFFSET(certificate_validate),
        },
 };
 
-/*
- *     Debug the packet if requested.
- */
-static void tls_packet_debug(request_t *request, fr_radius_packet_t const *packet, fr_pair_list_t const *list, bool received)
-{
-       if (!packet) return;
-       if (!RDEBUG_ENABLED) return;
-
-       log_request(L_DBG, L_DBG_LVL_1, request, __FILE__, __LINE__, "%s %s",
-                   received ? "Received" : "Sending",
-                   fr_tls_packet_codes[packet->code]);
-
-       if (received || request->parent) {
-               log_request_pair_list(L_DBG_LVL_1, request, NULL, list, NULL);
-       } else {
-               log_request_proto_pair_list(L_DBG_LVL_1, request, NULL, list, NULL);
-       }
-}
-
 static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
        fr_process_state_t const *state;
@@ -183,7 +139,6 @@ static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mc
        PROCESS_TRACE;
 
        (void)talloc_get_type_abort_const(mctx->instance, process_tls_t);
-       fr_assert(PROCESS_PACKET_CODE_VALID(request->packet->code));
 
        request->component = "tls";
        request->module = NULL;
@@ -191,49 +146,36 @@ static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mc
 
        UPDATE_STATE(packet);
 
-       tls_packet_debug(request, request->packet, &request->request_pairs, true);
+       log_request_pair_list(L_DBG_LVL_1, request, NULL, &request->request_pairs, NULL);
 
        return state->recv(p_result, mctx, request);
 }
 
-
 static const virtual_server_compile_t compile_list[] = {
        {
                .name = "store",
                .name2 = "session",
                .component = MOD_AUTHORIZE,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session)
+               .offset = PROCESS_CONF_OFFSET(session_store)
        },
        {
                .name = "load",
                .name2 = "session",
                .component = MOD_AUTHORIZE,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session)
+               .offset = PROCESS_CONF_OFFSET(session_load)
        },
        {
                .name = "clear",
                .name2 = "session",
                .component = MOD_AUTHORIZE,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session)
+               .offset = PROCESS_CONF_OFFSET(session_clear)
        },
        {
-               .name = "recv",
+               .name = "validate",
                .name2 = "certificate",
                .component = MOD_AUTHORIZE,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_certificate)
+               .offset = PROCESS_CONF_OFFSET(certificate_validate)
        },
-       {
-               .name = "send",
-               .name2 = "success",
-               .component = MOD_POST_AUTH,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_success)
-       },
-       {
-               .name = "send",
-               .name2 = "failure",
-               .component = MOD_POST_AUTH,
-               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_failure)
-       }
        COMPILE_TERMINATOR
 };
 
index f7bc40db117805aa597d49d9581d932e0c422264..32447884cce7e6febe2d40c7f899f4be6e02c130 100644 (file)
@@ -1,13 +1,36 @@
+#
+#  Should use the same set of snakeoil certs as
+#  eapol_test.
+#
+tls-config eap-tls-test  {
+       virtual_server = eap-tls-test
+       chain {
+               certificate_file = ${certdir}/rsa/server.pem
+
+               private_key_password = whatever
+               private_key_file = ${certdir}/rsa/server.pem
+               ca_file = ${cadir}/rsa/ca.pem
+       }
+
+       ca_file = ${cadir}/rsa/ca.pem
+       ca_path = ${cadir}
+       dh_file = ${certdir}/dh
+
+       fragment_size = 1024
+       include_length = no
+
+       cipher_list = "DEFAULT"
+       ecdh_curve = "prime256v1"
+
+       verify {
+       }
+
+       ocsp {
+       }
+}
+
 type = tls
 tls {
        # Point to the common TLS configuration
-       tls = tls-common
-
-       #
-       # As part of checking a client certificate, the EAP-TLS
-       # sets some attributes such as TLS-Client-Cert-CN. This
-       # virtual server has access to these attributes, and can
-       # be used to accept or reject the request.
-       #
-#      virtual_server = check-eap-tls
+       tls = eap-tls-test
 }
index 973978278aa621e3797ceab32173782446d1c09c..b4ff6ac347e0b74d962d0200e85d3e922aad93d6 100644 (file)
@@ -1,42 +1,7 @@
-server inner-tunnel {
-       namespace = radius
-       listen {
-              type = Access-Request
-       }
-
-       recv Access-Request {
-               update request {
-                       &User-Name := &outer.User-Name
-               }
-               files
-               pap
-               chap
-               mschap
-               eap
-       }
-
-       authenticate pap {
-               pap
-       }
-
-       authenticate chap {
-               chap
-       }
-
-       authenticate mschap {
-               mschap
-       }
-
-       authenticate eap {
-               eap
-       }
-
-       send Access-Accept {
-       }
-
-       send Access-Reject {
-       }
+server eap-tls-test {
+       namespace = tls
 
-       send Access-Challenge {
+       validate certificate {
+               ok
        }
 }