From b06665b85122ca55ebab166e7e48c2f367fc40d0 Mon Sep 17 00:00:00 2001
From: Andy Green <andy@warmcat.com>
Date: Mon, 6 Nov 2017 06:28:55 +0800
Subject: [PATCH] mbedtls: improve SNI for client certs

---
 lib/private-libwebsockets.h                   |  3 +-
 lib/server/ssl-server.c                       |  2 +-
 lib/tls/mbedtls/server.c                      | 48 ++++++++++++++++---
 lib/tls/mbedtls/wrapper/include/openssl/ssl.h |  3 ++
 lib/tls/mbedtls/wrapper/library/ssl_x509.c    | 22 +++++++++
 lib/tls/mbedtls/wrapper/platform/ssl_pm.c     | 21 ++++++--
 lib/tls/openssl/server.c                      |  7 ++-
 7 files changed, 89 insertions(+), 17 deletions(-)

diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 9ff8b111..cd93b804 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -2469,8 +2469,7 @@ lws_ssl_get_error_string(int status, int ret, char *buf, size_t len);
  */
 
 LWS_EXTERN int
-lws_tls_server_client_cert_verify_config(struct lws_context_creation_info *info,
-					 struct lws_vhost *vh);
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh);
 LWS_EXTERN int
 lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 				  struct lws_vhost *vhost, struct lws *wsi);
diff --git a/lib/server/ssl-server.c b/lib/server/ssl-server.c
index c480d336..ef8ba3d9 100644
--- a/lib/server/ssl-server.c
+++ b/lib/server/ssl-server.c
@@ -86,7 +86,7 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info,
 		if (lws_tls_server_vhost_backend_init(info, vhost, &wsi))
 			return -1;
 
-		lws_tls_server_client_cert_verify_config(info, vhost);
+		lws_tls_server_client_cert_verify_config(vhost);
 
 		vhost->protocols[0].callback(&wsi,
 			LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c
index 33efc87c..d351b0c3 100644
--- a/lib/tls/mbedtls/server.c
+++ b/lib/tls/mbedtls/server.c
@@ -23,20 +23,31 @@
 #include <mbedtls/x509_csr.h>
 
 int
-lws_tls_server_client_cert_verify_config(struct lws_context_creation_info *info,
-					 struct lws_vhost *vh)
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh)
 {
 	int verify_options = SSL_VERIFY_PEER;
 
 	/* as a server, are we requiring clients to identify themselves? */
-
-	if (!lws_check_opt(info->options,
-			  LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT))
+	if (!lws_check_opt(vh->options,
+			  LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) {
+		lwsl_notice("no client cert required\n");
 		return 0;
+	}
 
-	if (lws_check_opt(info->options, LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
+	/*
+	 * The wrapper has this messed-up mapping:
+	 *
+	 * 	   else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+	 *     mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+	 *
+	 * ie the meaning is inverted.  So where we should test for ! we don't
+	 */
+	if (lws_check_opt(vh->options, LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
 		verify_options = SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 
+	lwsl_notice("%s: vh %s requires client cert %d\n", __func__, vh->name,
+		    verify_options);
+
 	SSL_CTX_set_verify(vh->ssl_ctx, verify_options, NULL);
 
 	return 0;
@@ -78,7 +89,8 @@ lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx,
 		return 0;
 	}
 
-	lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port);
+	lwsl_info("SNI: Found: %s:%d at vhost '%s'\n", servername,
+					vh->listen_port, vhost->name);
 
 	/* select the ssl ctx from the selected vhost for this conn */
 	SSL_set_SSL_CTX(ssl, vhost->ssl_ctx);
@@ -192,6 +204,8 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 				  struct lws_vhost *vhost, struct lws *wsi)
 {
 	const SSL_METHOD *method = TLS_server_method();
+	uint8_t *p;
+	lws_filepos_t flen;
 
 	vhost->ssl_ctx = SSL_CTX_new(method);	/* create context */
 	if (!vhost->ssl_ctx) {
@@ -202,6 +216,26 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 	if (!vhost->use_ssl || !info->ssl_cert_filepath)
 		return 0;
 
+	if (info->ssl_ca_filepath) {
+		lwsl_notice("%s: vh %s: loading CA filepath %s\n", __func__,
+			    vhost->name, info->ssl_ca_filepath);
+		if (lws_tls_alloc_pem_to_der_file(vhost->context,
+				info->ssl_ca_filepath, NULL, 0, &p, &flen)) {
+			lwsl_err("couldn't find client CA file %s\n",
+					info->ssl_ca_filepath);
+
+			return 1;
+		}
+
+		if (SSL_CTX_add_client_CA_ASN1(vhost->ssl_ctx, (int)flen, p) != 1) {
+			lwsl_err("%s: SSL_CTX_add_client_CA_ASN1 unhappy\n",
+				 __func__);
+			free(p);
+			return 1;
+		}
+		free(p);
+	}
+
 	return lws_tls_server_certs_load(vhost, wsi,
 					 info->ssl_cert_filepath,
 					 info->ssl_private_key_filepath,
diff --git a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
index 232a6ee6..a6f950fb 100755
--- a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
+++ b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
@@ -46,6 +46,9 @@
 
  void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx);
 
+ int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ssl, int len,
+                 const unsigned char *d);
+
  SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc);
 
 /**
diff --git a/lib/tls/mbedtls/wrapper/library/ssl_x509.c b/lib/tls/mbedtls/wrapper/library/ssl_x509.c
index 4441490a..f3995a11 100644
--- a/lib/tls/mbedtls/wrapper/library/ssl_x509.c
+++ b/lib/tls/mbedtls/wrapper/library/ssl_x509.c
@@ -166,6 +166,28 @@ int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x)
     return 1;
 }
 
+/**
+ * @brief add CA client certification into the SSL
+ */
+int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ctx, int len,
+                const unsigned char *d)
+{
+	X509 *x;
+
+	x = d2i_X509(NULL, d, len);
+	if (!x) {
+		SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_X509() return NULL");
+		return 0;
+	}
+    SSL_ASSERT1(ctx);
+
+    X509_free(ctx->client_CA);
+
+    ctx->client_CA = x;
+
+    return 1;
+}
+
 /**
  * @brief add CA client certification into the SSL
  */
diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
index e14173b1..8445bfe8 100755
--- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
+++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
@@ -823,17 +823,32 @@ void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx)
 {
 	struct ssl_pm *ssl_pm = ssl->ssl_pm;
 	struct x509_pm *x509_pm = (struct x509_pm *)ctx->cert->x509->x509_pm;
+	struct x509_pm *x509_pm_ca = (struct x509_pm *)ctx->client_CA->x509_pm;
+
 	struct pkey_pm *pkey_pm = (struct pkey_pm *)ctx->cert->pkey->pkey_pm;
+	int mode;
 
 	if (ssl->cert)
 		ssl_cert_free(ssl->cert);
 	ssl->ctx = ctx;
 	ssl->cert = __ssl_cert_new(ctx->cert);
 
+	    if (ctx->verify_mode == SSL_VERIFY_PEER)
+	        mode = MBEDTLS_SSL_VERIFY_REQUIRED;
+	    else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+	        mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+	    else if (ctx->verify_mode == SSL_VERIFY_CLIENT_ONCE)
+	        mode = MBEDTLS_SSL_VERIFY_UNSET;
+	    else
+	        mode = MBEDTLS_SSL_VERIFY_NONE;
+
+	    // printf("ssl: %p, client ca x509_crt %p, mbedtls mode %d\n", ssl, x509_pm_ca->x509_crt, mode);
+
 	/* apply new ctx cert to ssl */
 
-	mbedtls_ssl_set_hs_own_cert(&ssl_pm->ssl, x509_pm->x509_crt, pkey_pm->pkey);
-	mbedtls_ssl_set_hs_authmode(&ssl_pm->ssl, MBEDTLS_SSL_VERIFY_NONE);
-	mbedtls_ssl_set_hs_ca_chain(&ssl_pm->ssl, x509_pm->x509_crt, NULL);
+	ssl->verify_mode = ctx->verify_mode;
 
+	mbedtls_ssl_set_hs_ca_chain(&ssl_pm->ssl, x509_pm_ca->x509_crt, NULL);
+	mbedtls_ssl_set_hs_own_cert(&ssl_pm->ssl, x509_pm->x509_crt, pkey_pm->pkey);
+	mbedtls_ssl_set_hs_authmode(&ssl_pm->ssl, mode);
 }
diff --git a/lib/tls/openssl/server.c b/lib/tls/openssl/server.c
index d04073e9..f4a62b47 100644
--- a/lib/tls/openssl/server.c
+++ b/lib/tls/openssl/server.c
@@ -58,18 +58,17 @@ OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
 }
 
 int
-lws_tls_server_client_cert_verify_config(struct lws_context_creation_info *info,
-					 struct lws_vhost *vh)
+lws_tls_server_client_cert_verify_config(struct lws_vhost *vh)
 {
 	int verify_options = SSL_VERIFY_PEER;
 
 	/* as a server, are we requiring clients to identify themselves? */
 
-	if (!lws_check_opt(info->options,
+	if (!lws_check_opt(vh->options,
 			  LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT))
 		return 0;
 
-	if (!lws_check_opt(info->options,
+	if (!lws_check_opt(vh->options,
 			   LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED))
 		verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 
-- 
GitLab