diff mbox series

[18/24] wolfssl: implement check_cert_subject

Message ID 20240404181630.2431991-18-juliusz@wolfssl.com
State New
Headers show
Series [01/24] wolfssl: simplify tls_get_cipher | expand

Commit Message

Juliusz Sosinowicz April 4, 2024, 6:16 p.m. UTC
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 | 253 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 220 insertions(+), 33 deletions(-)
diff mbox series

Patch

diff --git a/src/crypto/tls_wolfssl.c b/src/crypto/tls_wolfssl.c
index 0cdc4c809a..b88e259e40 100644
--- a/src/crypto/tls_wolfssl.c
+++ b/src/crypto/tls_wolfssl.c
@@ -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];
 
@@ -121,6 +124,21 @@  static struct tls_context * tls_context_new(const struct tls_config *conf)
 	return context;
 }
 
+static void tls_context_free(struct tls_context* context)
+{
+	if (context) {
+		if (context->check_cert_subject)
+			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)
@@ -373,9 +391,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;
@@ -413,18 +431,19 @@  void * tls_init(const struct tls_config *conf)
 
 void tls_deinit(void *ssl_ctx)
 {
-	struct tls_context *context = wolfSSL_CTX_get_ex_data(ssl_ctx,
-			TLS_SSL_CTX_CTX_EX_IDX);
+	struct tls_context *context =
+			/* Need to cast the const away */
+			(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;
 	}
 }
@@ -467,7 +486,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. */
@@ -493,6 +512,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);
@@ -542,7 +562,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;
@@ -576,6 +597,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;
 }
 
@@ -954,6 +983,138 @@  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_ERROR, "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)
+ * @processedNIDs: 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* processedNIDs)
+{
+	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; processedNIDs[i] != NID_undef && i < DN_FIELD_LEN; i++) {
+		if (processedNIDs[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;
+	}
+	processedNIDs[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 processedNIDs[DN_FIELD_LEN], i;
+
+	for (i = 0; i < DN_FIELD_LEN; i++)
+		processedNIDs[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, processedNIDs)) {
+			wpa_printf(MSG_INFO, "wolfSSL: No match for DN '%s'",
+				   field);
+			return 0;
+		}
+	}
+
+	return 1;
+}
 
 static struct wpabuf * get_x509_cert(WOLFSSL_X509 *cert)
 {
@@ -976,7 +1137,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;
@@ -1029,7 +1190,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;
@@ -1126,8 +1287,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);
@@ -1231,7 +1393,20 @@  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);
@@ -1412,8 +1587,9 @@  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) {
-		wpa_printf(MSG_INFO, "Error setting subject match");
+					     params->domain_match,
+					     params->check_cert_subject) < 0) {
+		wpa_printf(MSG_ERROR, "Error setting subject match");
 		return -1;
 	}
 
@@ -1421,14 +1597,14 @@  int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
 				   params->ca_cert_blob,
 				   params->ca_cert_blob_len,
 				   params->ca_path) < 0) {
-		wpa_printf(MSG_INFO, "Error setting CA cert");
+		wpa_printf(MSG_ERROR, "Error setting CA cert");
 		return -1;
 	}
 
 	if (tls_connection_client_cert(conn, params->client_cert,
 				       params->client_cert_blob,
 				       params->client_cert_blob_len) < 0) {
-		wpa_printf(MSG_INFO, "Error setting client cert");
+		wpa_printf(MSG_ERROR, "Error setting client cert");
 		return -1;
 	}
 
@@ -1436,13 +1612,13 @@  int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
 				       params->private_key_passwd,
 				       params->private_key_blob,
 				       params->private_key_blob_len) < 0) {
-		wpa_printf(MSG_INFO, "Error setting private key");
+		wpa_printf(MSG_ERROR, "Error setting private key");
 		return -1;
 	}
 
 	if (handle_ciphersuites(NULL, conn->ssl, params->openssl_ciphers,
 			params->flags) != 0) {
-		wpa_printf(MSG_INFO, "Error setting ciphersuites");
+		wpa_printf(MSG_ERROR, "Error setting ciphersuites");
 		return -1;
 	}
 
@@ -1475,7 +1651,7 @@  int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
 		wolfSSL_CTX_EnableOCSP(ctx, 0);
 #else /* HAVE_OCSP */
 	if (params->flags & TLS_CONN_REQUIRE_OCSP) {
-		wpa_printf(MSG_INFO,
+		wpa_printf(MSG_ERROR,
 			   "wolfSSL: No OCSP support included - reject configuration");
 		return -1;
 	}
@@ -1636,19 +1812,33 @@  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)
 {
+	struct tls_context *context =
+			/* Need to cast away const as this is one of the only places
+			 * where we should modify it */
+			(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'",
+		wpa_printf(MSG_ERROR, "SSL: Failed to load ca cert file '%s'",
 			   params->ca_cert);
 		return -1;
 	}
 
 	if (tls_global_client_cert(tls_ctx, params->client_cert) < 0) {
-		wpa_printf(MSG_INFO,
+		wpa_printf(MSG_ERROR,
 			   "SSL: Failed to load client cert file '%s'",
 			   params->client_cert);
 		return -1;
@@ -1656,26 +1846,26 @@  int tls_global_set_params(void *tls_ctx,
 
 	if (tls_global_private_key(tls_ctx, params->private_key,
 				   params->private_key_passwd) < 0) {
-		wpa_printf(MSG_INFO,
+		wpa_printf(MSG_ERROR,
 			   "SSL: Failed to load private key file '%s'",
 			   params->private_key);
 		return -1;
 	}
 
 	if (tls_global_dh(tls_ctx, params->dh_file) < 0) {
-		wpa_printf(MSG_INFO, "SSL: Failed to load DH file '%s'",
+		wpa_printf(MSG_ERROR, "SSL: Failed to load DH file '%s'",
 			   params->dh_file);
 		return -1;
 	}
 
 	if (handle_ciphersuites(tls_ctx, NULL, params->openssl_ciphers,
 			params->flags) != 0) {
-		wpa_printf(MSG_INFO, "Error setting ciphersuites");
+		wpa_printf(MSG_ERROR, "Error setting ciphersuites");
 		return -1;
 	}
 
 	if (params->openssl_ecdh_curves) {
-		wpa_printf(MSG_INFO,
+		wpa_printf(MSG_ERROR,
 			   "wolfSSL: openssl_ecdh_curves not supported");
 		return -1;
 	}
@@ -1717,7 +1907,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;
@@ -1736,8 +1926,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
@@ -1753,8 +1942,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;
 }