]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
wolfssl: Implement check_cert_subject
authorJuliusz Sosinowicz <juliusz@wolfssl.com>
Thu, 4 Apr 2024 18:16:24 +0000 (20:16 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 2 Feb 2025 17:17:42 +0000 (19:17 +0200)
Overall design was copied from tls_openssl.c. Multiple same
distinguished names in one subject name are not supported.

Signed-off-by: Juliusz Sosinowicz <juliusz@wolfssl.com>
src/crypto/tls_wolfssl.c

index 13b036ec3412d22f756fd0d160c77ccfb8d14bcc..25e88c259616e7c24aec449d5930a732827246c2 100644 (file)
@@ -65,13 +65,15 @@ struct tls_context {
        int cert_in_cb;
        char *ocsp_stapling_response;
        unsigned int tls_session_lifetime;
+       /* This is alloc'ed and needs to be free'd */
+       char *check_cert_subject;
 };
 
 static struct tls_context *tls_global = NULL;
 
 /* wolfssl tls_connection */
 struct tls_connection {
-       struct tls_context *context;
+       const struct tls_context *context;
        WOLFSSL *ssl;
        int read_alerts;
        int write_alerts;
@@ -82,6 +84,7 @@ struct tls_connection {
        char *alt_subject_match;
        char *suffix_match;
        char *domain_match;
+       char *check_cert_subject;
 
        u8 srv_cert_hash[32];
 
@@ -122,6 +125,22 @@ static struct tls_context * tls_context_new(const struct tls_config *conf)
 }
 
 
+static void tls_context_free(struct tls_context *context)
+{
+       if (context) {
+               os_free(context->check_cert_subject);
+               os_free(context);
+       }
+}
+
+
+/* Helper to make sure the context stays const */
+static const struct tls_context * ssl_ctx_get_tls_context(void *ssl_ctx)
+{
+       return wolfSSL_CTX_get_ex_data(ssl_ctx, TLS_SSL_CTX_CTX_EX_IDX);
+}
+
+
 static void wolfssl_reset_in_data(struct tls_in_data *in,
                                  const struct wpabuf *buf)
 {
@@ -398,9 +417,9 @@ void * tls_init(const struct tls_config *conf)
        if (!ssl_ctx) {
                tls_ref_count--;
                if (context != tls_global)
-                       os_free(context);
+                       tls_context_free(context);
                if (tls_ref_count == 0) {
-                       os_free(tls_global);
+                       tls_context_free(tls_global);
                        tls_global = NULL;
                }
                return NULL;
@@ -440,16 +459,17 @@ void tls_deinit(void *ssl_ctx)
 {
        struct tls_context *context;
 
-       context = wolfSSL_CTX_get_ex_data(ssl_ctx, TLS_SSL_CTX_CTX_EX_IDX);
+       /* Need to cast the const away to be able to free this */
+       context = (struct tls_context *) ssl_ctx_get_tls_context(ssl_ctx);
        if (context != tls_global)
-               os_free(context);
+               tls_context_free(context);
 
        wolfSSL_CTX_free((WOLFSSL_CTX *) ssl_ctx);
 
        tls_ref_count--;
        if (tls_ref_count == 0) {
                wolfSSL_Cleanup();
-               os_free(tls_global);
+               tls_context_free(tls_global);
                tls_global = NULL;
        }
 }
@@ -492,8 +512,7 @@ struct tls_connection * tls_connection_init(void *tls_ctx)
        wolfSSL_SetIOReadCtx(conn->ssl,  &conn->input);
        wolfSSL_SetIOWriteCtx(conn->ssl, &conn->output);
        wolfSSL_set_ex_data(conn->ssl, TLS_SSL_CON_EX_IDX, conn);
-       conn->context = wolfSSL_CTX_get_ex_data(ssl_ctx,
-                                               TLS_SSL_CTX_CTX_EX_IDX);
+       conn->context = ssl_ctx_get_tls_context(ssl_ctx);
 
        /* Need randoms post-hanshake for EAP-FAST, export key and deriving
         * session ID in EAP methods. */
@@ -519,6 +538,7 @@ void tls_connection_deinit(void *tls_ctx, struct tls_connection *conn)
        os_free(conn->suffix_match);
        os_free(conn->domain_match);
        os_free(conn->peer_subject);
+       os_free(conn->check_cert_subject);
 
        /* self */
        os_free(conn);
@@ -568,7 +588,8 @@ static int tls_connection_set_subject_match(struct tls_connection *conn,
                                            const char *subject_match,
                                            const char *alt_subject_match,
                                            const char *suffix_match,
-                                           const char *domain_match)
+                                           const char *domain_match,
+                                           const char *check_cert_subject)
 {
        os_free(conn->subject_match);
        conn->subject_match = NULL;
@@ -602,6 +623,14 @@ static int tls_connection_set_subject_match(struct tls_connection *conn,
                        return -1;
        }
 
+       os_free(conn->check_cert_subject);
+       conn->check_cert_subject = NULL;
+       if (check_cert_subject) {
+               conn->check_cert_subject = os_strdup(check_cert_subject);
+               if (!conn->check_cert_subject)
+                       return -1;
+       }
+
        return 0;
 }
 
@@ -981,6 +1010,148 @@ static const char * wolfssl_tls_err_string(int err, const char *err_str)
 }
 
 
+/**
+ * match_dn_field - Match configuration DN field against Certificate DN field
+ * @cert: Certificate
+ * @nid: NID of DN field
+ * @field: Field name
+ * @value DN field value which is passed from configuration
+ *     e.g., if configuration have C=US and this argument will point to US.
+ * Returns: 1 on success and 0 on failure
+ */
+static int match_dn_field(WOLFSSL_X509 *cert, int nid, const char *field,
+                         const char *value)
+{
+       int ret = 0;
+       int len = os_strlen(value);
+       char buf[256];
+       /* Fetch value based on NID */
+       int buf_len = wolfSSL_X509_NAME_get_text_by_NID(
+               wolfSSL_X509_get_subject_name((WOLFSSL_X509 *) cert), nid,
+               buf, sizeof(buf));
+
+       if (buf_len >= 0) {
+               wpa_printf(MSG_DEBUG,
+                          "wolfSSL: Matching fields: '%s' '%s' '%s'", field,
+                          value, buf);
+
+               /* Check wildcard at the right end side */
+               /* E.g., if OU=develop* mentioned in configuration, allow 'OU'
+                * of the subject in the client certificate to start with
+                * 'develop' */
+               if (len > 0 && value[len - 1] == '*') {
+                       ret = buf_len >= len &&
+                               os_memcmp(buf, value, len - 1) == 0;
+               } else {
+                       ret = os_strcmp(buf, value) == 0;
+               }
+       } else {
+               wpa_printf(MSG_INFO,
+                          "wolfSSL: cert does not contain entry for '%s'",
+                          field);
+       }
+
+       return ret;
+}
+
+
+#define DN_FIELD_LEN 20
+
+/**
+ * get_value_from_field - Get value from DN field
+ * @cert: Certificate
+ * @field_str: DN field string which is passed from configuration file (e.g.,
+ *      C=US)
+ * @processed_nids: List of NIDs already processed
+ * Returns: 1 on success and 0 on failure
+ */
+static int get_value_from_field(WOLFSSL_X509 *cert, char *field_str,
+                               int *processed_nids)
+{
+       int nid, i;
+       char *context = NULL, *name, *value;
+
+       if (os_strcmp(field_str, "*") == 0)
+               return 1; /* wildcard matches everything */
+
+       name = str_token(field_str, "=", &context);
+       if (!name)
+               return 0;
+
+       nid = wolfSSL_OBJ_txt2nid(name);
+       if (nid == NID_undef) {
+               wpa_printf(MSG_ERROR,
+                          "wolfSSL: Unknown field '%s' in check_cert_subject",
+                          name);
+               return 0;
+       }
+
+       /* Check for duplicates */
+       for (i = 0; processed_nids[i] != NID_undef && i < DN_FIELD_LEN; i++) {
+               if (processed_nids[i] == nid) {
+                       wpa_printf(MSG_ERROR,
+                                  "wolfSSL: No support for multiple DN's in check_cert_subject");
+                       return 0;
+               }
+       }
+       if (i == DN_FIELD_LEN) {
+               wpa_printf(MSG_ERROR,
+                          "wolfSSL: Only %d DN's are supported in check_cert_subject",
+                          DN_FIELD_LEN);
+               return 0;
+       }
+       processed_nids[i] = nid;
+
+       value = str_token(field_str, "=", &context);
+       if (!value) {
+               wpa_printf(MSG_ERROR,
+                          "wolfSSL: Distinguished Name field '%s' value is not defined in check_cert_subject",
+                          name);
+               return 0;
+       }
+
+       return match_dn_field(cert, nid, name, value);
+}
+
+
+/**
+ * tls_match_dn_field - Match subject DN field with check_cert_subject
+ * @cert: Certificate
+ * @match: check_cert_subject string
+ * Returns: Return 1 on success and 0 on failure
+*/
+static int tls_match_dn_field(WOLFSSL_X509 *cert, const char *match)
+{
+       const char *token, *last = NULL;
+       /* Maximum length of each DN field is 255 characters */
+       char field[256];
+       int processed_nids[DN_FIELD_LEN], i;
+
+       for (i = 0; i < DN_FIELD_LEN; i++)
+               processed_nids[i] = NID_undef;
+
+       /* Process each '/' delimited field */
+       while ((token = cstr_token(match, "/", &last))) {
+               if (last - token >= (int) sizeof(field)) {
+                       wpa_printf(MSG_ERROR,
+                                  "wolfSSL: Too long DN matching field value in '%s'",
+                                  match);
+                       return 0;
+               }
+               os_memcpy(field, token, last - token);
+               field[last - token] = '\0';
+
+               if (!get_value_from_field(cert, field, processed_nids)) {
+                       wpa_printf(MSG_INFO, "wolfSSL: No match for DN '%s'",
+                                  field);
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+
 static struct wpabuf * get_x509_cert(WOLFSSL_X509 *cert)
 {
        struct wpabuf *buf = NULL;
@@ -1002,7 +1173,7 @@ static void wolfssl_tls_fail_event(struct tls_connection *conn,
 {
        union tls_event_data ev;
        struct wpabuf *cert = NULL;
-       struct tls_context *context = conn->context;
+       const struct tls_context *context = conn->context;
 
        if (!context->event_cb)
                return;
@@ -1057,7 +1228,7 @@ static void wolfssl_tls_cert_event(struct tls_connection *conn,
 {
        struct wpabuf *cert = NULL;
        union tls_event_data ev;
-       struct tls_context *context = conn->context;
+       const struct tls_context *context = conn->context;
        char *alt_subject[TLS_MAX_ALT_SUBJECT];
        int alt, num_alt_subject = 0;
        WOLFSSL_GENERAL_NAME *gen;
@@ -1154,8 +1325,9 @@ static int tls_verify_cb(int preverify_ok, WOLFSSL_X509_STORE_CTX *x509_ctx)
        int err, depth;
        WOLFSSL *ssl;
        struct tls_connection *conn;
-       struct tls_context *context;
+       const struct tls_context *context;
        char *match, *altmatch, *suffix_match, *domain_match;
+       const char *check_cert_subject;
        const char *err_str;
 
        err_cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
@@ -1259,7 +1431,19 @@ static int tls_verify_cb(int preverify_ok, WOLFSSL_X509_STORE_CTX *x509_ctx)
                   "TLS: %s - preverify_ok=%d err=%d (%s) ca_cert_verify=%d depth=%d buf='%s'",
                   __func__, preverify_ok, err, err_str,
                   conn->ca_cert_verify, depth, buf);
-       if (depth == 0 && match && os_strstr(buf, match) == NULL) {
+       check_cert_subject = conn->check_cert_subject;
+       if (!check_cert_subject)
+               check_cert_subject = conn->context->check_cert_subject;
+       if (check_cert_subject && depth == 0 &&
+           !tls_match_dn_field(err_cert, check_cert_subject)) {
+               wpa_printf(MSG_WARNING,
+                          "TLS: Subject '%s' did not match with '%s'",
+                          buf, check_cert_subject);
+               preverify_ok = 0;
+               wolfssl_tls_fail_event(conn, err_cert, err, depth, buf,
+                                      "Distinguished Name",
+                                      TLS_FAIL_DN_MISMATCH);
+       } else if (depth == 0 && match && os_strstr(buf, match) == NULL) {
                wpa_printf(MSG_WARNING,
                           "TLS: Subject '%s' did not match with '%s'",
                           buf, match);
@@ -1441,7 +1625,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
        if (tls_connection_set_subject_match(conn, params->subject_match,
                                             params->altsubject_match,
                                             params->suffix_match,
-                                            params->domain_match) < 0) {
+                                            params->domain_match,
+                                            params->check_cert_subject) < 0) {
                wpa_printf(MSG_INFO, "Error setting subject match");
                return -1;
        }
@@ -1665,10 +1850,25 @@ void ocsp_resp_free_cb(void *ocsp_stapling_response, unsigned char *response)
 int tls_global_set_params(void *tls_ctx,
                          const struct tls_connection_params *params)
 {
+       /* Need to cast away const as this is one of the only places
+        * where we should modify it */
+       struct tls_context *context =
+               (struct tls_context *) ssl_ctx_get_tls_context(tls_ctx);
+
        wpa_printf(MSG_DEBUG, "SSL: global set params");
 
-       if (params->check_cert_subject)
-               return -1; /* not yet supported */
+       os_free(context->check_cert_subject);
+       context->check_cert_subject = NULL;
+       if (params->check_cert_subject) {
+               context->check_cert_subject =
+                       os_strdup(params->check_cert_subject);
+               if (!context->check_cert_subject) {
+                       wpa_printf(MSG_ERROR,
+                                  "SSL: Failed to copy check_cert_subject '%s'",
+                                  params->check_cert_subject);
+                       return -1;
+               }
+       }
 
        if (tls_global_ca_cert(tls_ctx, params->ca_cert) < 0) {
                wpa_printf(MSG_INFO, "SSL: Failed to load ca cert file '%s'",
@@ -1746,7 +1946,7 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
                              const u8 *session_ctx, size_t session_ctx_len)
 {
        static int counter = 0;
-       struct tls_context *context;
+       const struct tls_context *context;
 
        if (!conn)
                return -1;
@@ -1765,8 +1965,7 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
 
        wolfSSL_set_accept_state(conn->ssl);
 
-       context = wolfSSL_CTX_get_ex_data((WOLFSSL_CTX *) ssl_ctx,
-                                         TLS_SSL_CTX_CTX_EX_IDX);
+       context = ssl_ctx_get_tls_context(ssl_ctx);
        if (context && context->tls_session_lifetime == 0) {
                /*
                 * Set session id context to a unique value to make sure
@@ -1782,8 +1981,6 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
                                               session_ctx_len);
        }
 
-       /* TODO: do we need to fake a session like OpenSSL does here? */
-
        return 0;
 }