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:
Andy Green 2017-10-26 09:54:25 +08:00
parent 41d1326da0
commit 00ffebfd24
10 changed files with 333 additions and 4 deletions

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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);

View file

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

View file

@ -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
*

View file

@ -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;

View file

@ -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);

View file

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