diff --git a/CMakeLists.txt b/CMakeLists.txt
index 85f1d2f2b94ecacd6bda2ba3fbaf485bb1b8fbde..d2514bb9391028fc1fd7ecec43dd990d389a66eb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1242,6 +1242,8 @@ CHECK_FUNCTION_EXISTS(SSL_CTX_set1_param LWS_HAVE_SSL_CTX_set1_param)
 CHECK_FUNCTION_EXISTS(SSL_set_info_callback LWS_HAVE_SSL_SET_INFO_CALLBACK)
 CHECK_FUNCTION_EXISTS(X509_VERIFY_PARAM_set1_host LWS_HAVE_X509_VERIFY_PARAM_set1_host)
 CHECK_FUNCTION_EXISTS(RSA_set0_key LWS_HAVE_RSA_SET0_KEY)
+CHECK_FUNCTION_EXISTS(X509_get_key_usage LWS_HAVE_X509_get_key_usage)
+CHECK_FUNCTION_EXISTS(SSL_CTX_get0_certificate LWS_HAVE_SSL_CTX_get0_certificate)
 if (LWS_WITH_SSL AND NOT LWS_WITH_MBEDTLS)
 CHECK_SYMBOL_EXISTS(SSL_CTX_get_extra_chain_certs_only openssl/ssl.h LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
 endif()
diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in
index ed17dfd27f709d25dfabd33bf0cd21cb151b6104..e47b567491d8633e36e87722e5243c97980b00f3 100644
--- a/cmake/lws_config.h.in
+++ b/cmake/lws_config.h.in
@@ -93,6 +93,8 @@
 #cmakedefine LWS_HAVE_SSL_CTX_set1_param
 #cmakedefine LWS_HAVE_X509_VERIFY_PARAM_set1_host
 #cmakedefine LWS_HAVE_RSA_SET0_KEY
+#cmakedefine LWS_HAVE_X509_get_key_usage
+#cmakedefine LWS_HAVE_SSL_CTX_get0_certificate
 
 #cmakedefine LWS_HAVE_UV_VERSION_H
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index f611b3871c4a2ed500ffa7ea1ae109505b5cf17f..4a4355c1cd2b0026ea1562e0add0d2705d566c89 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -5427,6 +5427,60 @@ lws_is_cgi(struct lws *wsi);
 LWS_VISIBLE LWS_EXTERN SSL*
 lws_get_ssl(struct lws *wsi);
 #endif
+
+enum lws_tls_cert_info {
+	LWS_TLS_CERT_INFO_VALIDITY_FROM,
+	LWS_TLS_CERT_INFO_VALIDITY_TO,
+	LWS_TLS_CERT_INFO_COMMON_NAME,
+	LWS_TLS_CERT_INFO_ISSUER_NAME,
+	LWS_TLS_CERT_INFO_USAGE,
+};
+
+union lws_tls_cert_info_results {
+	time_t time;
+	unsigned int usage;
+	struct {
+		int len;
+		char name[64]; /* KEEP LAST... name[] not allowed in union */
+	} ns;
+};
+
+/**
+ * lws_tls_peer_cert_info() - get information from the peer's TLS cert
+ *
+ * \param wsi: the connection to query
+ * \param type: one of LWS_TLS_CERT_INFO_
+ * \param buf: pointer to union to take result
+ * \param len: when result is a string, the true length of buf->ns.name[]
+ *
+ * lws_tls_peer_cert_info() lets you get hold of information from the peer
+ * certificate.
+ *
+ * This function works the same no matter if the TLS backend is OpenSSL or
+ * mbedTLS.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+		       union lws_tls_cert_info_results *buf, size_t len);
+
+/**
+ * lws_tls_vhost_cert_info() - get information from the vhost's own TLS cert
+ *
+ * \param vhost: the vhost to query
+ * \param type: one of LWS_TLS_CERT_INFO_
+ * \param buf: pointer to union to take result
+ * \param len: when result is a string, the true length of buf->ns.name[]
+ *
+ * lws_tls_vhost_cert_info() lets you get hold of information from the vhost
+ * certificate.
+ *
+ * This function works the same no matter if the TLS backend is OpenSSL or
+ * mbedTLS.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+		        union lws_tls_cert_info_results *buf, size_t len);
+
 ///@}
 
 /** \defgroup lws_ring LWS Ringbuffer APIs
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 7e1df5c6fa977b85c3fe2b35c2fd41c3d0f5c4e3..960c37dbc5c4ad6f5991e987b162bfe96e6d83ce 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -2405,7 +2405,9 @@ LWS_EXTERN void
 lws_ssl_bind_passphrase(lws_tls_ctx *ssl_ctx, struct lws_context_creation_info *info);
 LWS_EXTERN void
 lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret);
-
+LWS_EXTERN int
+lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
+			  union lws_tls_cert_info_results *buf, size_t len);
 #ifndef LWS_NO_SERVER
 LWS_EXTERN int
 lws_context_init_server_ssl(struct lws_context_creation_info *info,
diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c
index aa411c3538e8cc83effc39f013f30c79d44eeed0..e885f941401ccf4430064b9fc5da81a0d3313cc1 100644
--- a/lib/tls/mbedtls/server.c
+++ b/lib/tls/mbedtls/server.c
@@ -278,10 +278,19 @@ lws_tls_server_abort_connection(struct lws *wsi)
 enum lws_ssl_capable_status
 lws_tls_server_accept(struct lws *wsi)
 {
+	union lws_tls_cert_info_results ir;
 	int m, n = SSL_accept(wsi->ssl);
 
-	if (n == 1)
+	if (n == 1) {
+		n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
+					   sizeof(ir.ns.name));
+		if (!n)
+			lwsl_notice("%s: client cert CN '%s'\n",
+				    __func__, ir.ns.name);
+		else
+			lwsl_info("%s: couldn't get client cert CN\n", __func__);
 		return LWS_SSL_CAPABLE_DONE;
+	}
 
 	m = SSL_get_error(wsi->ssl, n);
 
diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c
index 7746b53ab62f69991a3478d30bf6a936e3408424..d4f3e48bb4ebe11367c52e83dcd49d8f002591d3 100644
--- a/lib/tls/mbedtls/ssl.c
+++ b/lib/tls/mbedtls/ssl.c
@@ -20,6 +20,7 @@
  */
 
 #include "private-libwebsockets.h"
+#include <mbedtls/oid.h>
 
 void
 lws_ssl_elaborate_error(void)
@@ -323,3 +324,99 @@ lws_tls_shutdown(struct lws *wsi)
 		return LWS_SSL_CAPABLE_ERROR;
 	}
 }
+
+static time_t
+lws_tls_mbedtls_time_to_unix(mbedtls_x509_time *xtime)
+{
+	struct tm t;
+
+	if (!xtime || !xtime->year || xtime->year < 0)
+		return (time_t)(long long)-1;
+
+	memset(&t, 0, sizeof(t));
+
+	t.tm_year = xtime->year - 1900;
+	t.tm_mon = xtime->mon - 1; /* mbedtls months are 1+, tm are 0+ */
+	t.tm_mday = xtime->day - 1; /* mbedtls days are 1+, tm are 0+ */
+	t.tm_hour = xtime->hour;
+	t.tm_min = xtime->min;
+	t.tm_sec = xtime->sec;
+	t.tm_isdst = -1;
+
+	return mktime(&t);
+}
+
+static int
+lws_tls_mbedtls_get_x509_name(mbedtls_x509_name *name,
+			      union lws_tls_cert_info_results *buf, size_t len)
+{
+	while (name) {
+		if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) {
+			name = name->next;
+			continue;
+		}
+
+		if (len - 1 < name->val.len)
+			return -1;
+
+		memcpy(&buf->ns.name[0], name->val.p, name->val.len);
+		buf->ns.name[name->val.len] = '\0';
+		buf->ns.len = name->val.len;
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static int
+lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type,
+			  union lws_tls_cert_info_results *buf, size_t len)
+{
+	if (!x509)
+		return -1;
+
+	switch (type) {
+	case LWS_TLS_CERT_INFO_VALIDITY_FROM:
+		buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from);
+		if (buf->time == (time_t)(long long)-1)
+			return -1;
+		break;
+
+	case LWS_TLS_CERT_INFO_VALIDITY_TO:
+		buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to);
+		if (buf->time == (time_t)(long long)-1)
+			return -1;
+		break;
+
+	case LWS_TLS_CERT_INFO_COMMON_NAME:
+		return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len);
+
+	case LWS_TLS_CERT_INFO_ISSUER_NAME:
+		return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len);
+
+	case LWS_TLS_CERT_INFO_USAGE:
+		buf->usage = x509->key_usage;
+		break;
+	}
+
+	return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+		        union lws_tls_cert_info_results *buf, size_t len)
+{
+	mbedtls_x509_crt *x509 = ssl_ctx_get_mbedtls_x509_crt(vhost->ssl_ctx);
+
+	return lws_tls_mbedtls_cert_info(x509, type, buf, len);
+}
+
+LWS_VISIBLE int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+		       union lws_tls_cert_info_results *buf, size_t len)
+{
+	mbedtls_x509_crt *x509 = ssl_get_peer_mbedtls_x509_crt(wsi->ssl);
+
+	return lws_tls_mbedtls_cert_info(x509, type, buf, len);
+}
diff --git a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
index bea50b75fd8277773b0bb0536c7bed37b87620fa..fc57d1fdfa3b405c86b52fc938155fe36f15fb8d 100755
--- a/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
+++ b/lib/tls/mbedtls/wrapper/include/openssl/ssl.h
@@ -35,6 +35,12 @@
 #define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS		(1 << 3)
 #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS		(1 << 4)
 
+ mbedtls_x509_crt *
+ ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx);
+
+ mbedtls_x509_crt *
+ ssl_get_peer_mbedtls_x509_crt(SSL *ssl);
+
 /**
  * @brief create a SSL context
  *
diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
index 63504919ce4942992314af0efc57252ef6974d2c..8c89a698a775794abce35a1075ac2593c6f27965 100755
--- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
+++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
@@ -316,6 +316,28 @@ int ssl_pm_handshake(SSL *ssl)
     return -1; /* openssl death */
 }
 
+mbedtls_x509_crt *
+ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx)
+{
+	struct x509_pm *x509_pm = (struct x509_pm *)ssl_ctx->cert->x509->x509_pm;
+
+	if (!x509_pm)
+		return NULL;
+
+	return x509_pm->x509_crt;
+}
+
+mbedtls_x509_crt *
+ssl_get_peer_mbedtls_x509_crt(SSL *ssl)
+{
+	struct x509_pm *x509_pm = (struct x509_pm *)ssl->session->peer->x509_pm;
+
+	if (!x509_pm)
+		return NULL;
+
+	return x509_pm->ex_crt;
+}
+
 int ssl_pm_shutdown(SSL *ssl)
 {
     int ret;
diff --git a/lib/tls/openssl/server.c b/lib/tls/openssl/server.c
index 729abfcddd88e1e670db84c7de94bfa349fafc56..084585011f6144e9fd3908a4446bb1876488bc17 100644
--- a/lib/tls/openssl/server.c
+++ b/lib/tls/openssl/server.c
@@ -30,6 +30,8 @@ OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
 	SSL *ssl;
 	int n;
 	struct lws *wsi;
+	union lws_tls_cert_info_results ir;
+	X509 *topcert = X509_STORE_CTX_get_current_cert(x509_ctx);
 
 	ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
 		SSL_get_ex_data_X509_STORE_CTX_idx());
@@ -40,6 +42,13 @@ OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
 	 */
 	wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index);
 
+	n = lws_tls_openssl_cert_info(topcert, LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
+				   sizeof(ir.ns.name));
+	if (!n)
+		lwsl_info("%s: client cert CN '%s'\n", __func__, ir.ns.name);
+	else
+		lwsl_info("%s: couldn't get client cert CN\n", __func__);
+
 	n = wsi->vhost->protocols[0].callback(wsi,
 			LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION,
 					   x509_ctx, ssl, preverify_ok);
@@ -318,7 +327,6 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 	lwsl_notice(" OpenSSL doesn't support ECDH\n");
 #endif
 
-
 	return 0;
 }
 
@@ -383,10 +391,19 @@ lws_tls_server_abort_connection(struct lws *wsi)
 enum lws_ssl_capable_status
 lws_tls_server_accept(struct lws *wsi)
 {
+	union lws_tls_cert_info_results ir;
 	int m, n = SSL_accept(wsi->ssl);
 
-	if (n == 1)
+	if (n == 1) {
+		n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
+					   sizeof(ir.ns.name));
+		if (!n)
+			lwsl_notice("%s: client cert CN '%s'\n",
+				    __func__, ir.ns.name);
+		else
+			lwsl_info("%s: couldn't get client cert CN\n", __func__);
 		return LWS_SSL_CAPABLE_DONE;
+	}
 
 	m = lws_ssl_get_error(wsi, n);
 
diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c
index 7da354fe2b716c233a2a13cfbd22b4c32f9c1b85..d69919a1e7b0acd196db7b974092ac8227134f0e 100644
--- a/lib/tls/openssl/ssl.c
+++ b/lib/tls/openssl/ssl.c
@@ -484,3 +484,121 @@ lws_tls_shutdown(struct lws *wsi)
 		return LWS_SSL_CAPABLE_ERROR;
 	}
 }
+
+static int
+dec(char c)
+{
+	return c - '0';
+}
+
+static time_t
+lws_tls_openssl_asn1time_to_unix(ASN1_TIME *as)
+{
+	const char *p = (const char *)as->data;
+	struct tm t;
+
+	/* [YY]YYMMDDHHMMSSZ */
+
+	memset(&t, 0, sizeof(t));
+
+	if (strlen(p) == 13) {
+		t.tm_year = (dec(p[0]) * 10) + dec(p[1]) + 100;
+		p += 2;
+	} else {
+		t.tm_year = (dec(p[0]) * 1000) + (dec(p[1]) * 100) +
+			    (dec(p[2]) * 10) + dec(p[3]);
+		p += 4;
+	}
+	t.tm_mon = (dec(p[0]) * 10) + dec(p[1]) - 1;
+	p += 2;
+	t.tm_mday = (dec(p[0]) * 10) + dec(p[1]) - 1;
+	p += 2;
+	t.tm_hour = (dec(p[0]) * 10) + dec(p[1]);
+	p += 2;
+	t.tm_min = (dec(p[0]) * 10) + dec(p[1]);
+	p += 2;
+	t.tm_sec = (dec(p[0]) * 10) + dec(p[1]);
+	t.tm_isdst = 0;
+
+	return mktime(&t);
+}
+
+int
+lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type,
+			  union lws_tls_cert_info_results *buf, size_t len)
+{
+	X509_NAME *xn;
+	char *p;
+
+	if (!x509)
+		return -1;
+
+	switch (type) {
+	case LWS_TLS_CERT_INFO_VALIDITY_FROM:
+		buf->time = lws_tls_openssl_asn1time_to_unix(
+					X509_get_notBefore(x509));
+		if (buf->time == (time_t)-1)
+			return -1;
+		break;
+
+	case LWS_TLS_CERT_INFO_VALIDITY_TO:
+		buf->time = lws_tls_openssl_asn1time_to_unix(
+					X509_get_notAfter(x509));
+		if (buf->time == (time_t)-1)
+			return -1;
+		break;
+
+	case LWS_TLS_CERT_INFO_COMMON_NAME:
+		xn = X509_get_subject_name(x509);
+		if (!xn)
+			return -1;
+		X509_NAME_oneline(xn, buf->ns.name, (int)len - 1);
+		p = strstr(buf->ns.name, "/CN=");
+		if (p)
+			strcpy(buf->ns.name, p + 4);
+		buf->ns.len = (int)strlen(buf->ns.name);
+		return 0;
+
+	case LWS_TLS_CERT_INFO_ISSUER_NAME:
+		xn = X509_get_issuer_name(x509);
+		if (!xn)
+			return -1;
+		X509_NAME_oneline(xn, buf->ns.name, (int)len - 1);
+		buf->ns.len = (int)strlen(buf->ns.name);
+		return 0;
+
+	case LWS_TLS_CERT_INFO_USAGE:
+#if defined(LWS_HAVE_X509_get_key_usage)
+		buf->usage = X509_get_key_usage(x509);
+		break;
+#else
+		return -1;
+#endif
+	}
+
+	return 0;
+}
+
+LWS_VISIBLE LWS_EXTERN int
+lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type,
+		        union lws_tls_cert_info_results *buf, size_t len)
+{
+#if defined(LWS_HAVE_SSL_CTX_get0_certificate)
+	X509 *x509 = SSL_CTX_get0_certificate(vhost->ssl_ctx);
+
+	return lws_tls_openssl_cert_info(x509, type, buf, len);
+#else
+	lwsl_notice("openssl is too old to support %s\n", __func__);
+
+	return -1;
+#endif
+}
+
+LWS_VISIBLE int
+lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type,
+		       union lws_tls_cert_info_results *buf, size_t len)
+{
+	X509 *x509 = SSL_get_peer_certificate(wsi->ssl);
+
+	return lws_tls_openssl_cert_info(x509, type, buf, len);
+}