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);
+}