diff --git a/CMakeLists.txt b/CMakeLists.txt index 85f1d2f2..d2514bb9 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 ed17dfd2..e47b5674 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 f611b387..4a4355c1 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 7e1df5c6..960c37db 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 aa411c35..e885f941 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 7746b53a..d4f3e48b 100644 --- a/lib/tls/mbedtls/ssl.c +++ b/lib/tls/mbedtls/ssl.c @@ -20,6 +20,7 @@ */ #include "private-libwebsockets.h" +#include 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 bea50b75..fc57d1fd 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 63504919..8c89a698 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 729abfcd..08458501 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 7da354fe..d69919a1 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); +}