tls: add x509 query api
This adds a single api on lws that allows querying elements from the peer certificate on a connection. The api works the same regardless of the TLS backend.
This commit is contained in:
parent
41d1326da0
commit
00ffebfd24
10 changed files with 333 additions and 4 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue