TLS improvements.

These changes improve the constraint kore had with client authentication and
multiple domains.

- Add kore_x509_subject_name() which will return a C string containing
  the x509 subject name in full (in utf8).

- Log TLS errors if client authentication was turned on, will help debug
  issues with client authentication in the future.

- If SNI was present in the TLS handshake, check it against the host specified
  in the HTTP request and send a 421 in case they mismatch.

- Throw a 403 if client authentication was enabled but no client certificate
  was specified.
This commit is contained in:
Joris Vink 2019-11-19 11:09:24 +01:00
parent 283cd6da54
commit ca70f9d726
8 changed files with 134 additions and 20 deletions

View File

@ -425,6 +425,7 @@ enum http_status_code {
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_STATUS_REQUEST_RANGE_INVALID = 416,
HTTP_STATUS_EXPECTATION_FAILED = 417,
HTTP_STATUS_MISDIRECTED_REQUEST = 421,
HTTP_STATUS_INTERNAL_ERROR = 500,
HTTP_STATUS_NOT_IMPLEMENTED = 501,
HTTP_STATUS_BAD_GATEWAY = 502,

View File

@ -126,11 +126,7 @@ extern int daemon(int, int);
#define NETBUF_IS_STREAM 0x10
#define NETBUF_IS_FILEREF 0x20
#define X509_GET_CN(c, o, l) \
X509_NAME_get_text_by_NID(X509_get_subject_name(c), \
NID_commonName, o, l)
#define X509_CN_LENGTH (ub_common_name + 1)
#define KORE_X509_COMMON_NAME_ONLY 0x0001
#define KORE_PEM_CERT_CHAIN 1
#define KORE_DER_CERT_DATA 2
@ -203,6 +199,7 @@ TAILQ_HEAD(netbuf_head, netbuf);
#define CONN_WS_CLOSE_SENT 0x04
#define CONN_IS_BUSY 0x08
#define CONN_ACME_CHALLENGE 0x10
#define CONN_LOG_TLS_FAILURE 0x20
#define KORE_IDLE_TIMER_MAX 5000
@ -236,8 +233,9 @@ struct connection {
struct listener *owner;
X509 *cert;
SSL *ssl;
char *tls_sni;
int tls_reneg;
u_int8_t flags;
u_int16_t flags;
void *hdlr_extra;
int (*handle)(struct connection *);
@ -417,6 +415,7 @@ struct kore_module {
};
#if !defined(KORE_NO_HTTP)
struct kore_module_handle {
char *path;
char *func;
@ -866,6 +865,7 @@ char *kore_read_line(FILE *, char *, size_t);
EVP_PKEY *kore_rsakey_load(const char *);
EVP_PKEY *kore_rsakey_generate(const char *);
int kore_x509_subject_name(struct connection *, char **, int);
#if !defined(KORE_NO_HTTP)
void kore_websocket_handshake(struct http_request *,

View File

@ -47,7 +47,6 @@ static void accesslog_flush(struct kore_domain *, u_int64_t, int);
static u_int64_t time_cache = 0;
static char tbuf[128] = { '\0' };
char cnbuf[1024] = { '\0' };
static struct kore_buf *logbuf = NULL;
@ -67,8 +66,8 @@ kore_accesslog(struct http_request *req)
size_t avail;
time_t curtime;
int len, attempts;
char addr[INET6_ADDRSTRLEN];
const char *ptr, *method, *cn, *referer;
char addr[INET6_ADDRSTRLEN], *cn_value;
switch (req->method) {
case HTTP_METHOD_GET:
@ -103,9 +102,12 @@ kore_accesslog(struct http_request *req)
req->agent = "-";
cn = "-";
cn_value = NULL;
if (req->owner->cert != NULL) {
if (X509_GET_CN(req->owner->cert, cnbuf, sizeof(cnbuf)) != -1)
cn = cnbuf;
if (kore_x509_subject_name(req->owner, &cn_value,
KORE_X509_COMMON_NAME_ONLY))
cn = cn_value;
}
switch (req->owner->family) {
@ -193,6 +195,7 @@ kore_accesslog(struct http_request *req)
break;
}
kore_free(cn_value);
accesslog_unlock(worker);
}

View File

@ -63,12 +63,13 @@ kore_connection_new(void *owner)
c->ssl = NULL;
c->cert = NULL;
c->tls_reneg = 0;
c->flags = 0;
c->rnb = NULL;
c->snb = NULL;
c->owner = owner;
c->handle = NULL;
c->tls_reneg = 0;
c->tls_sni = NULL;
c->disconnect = NULL;
c->hdlr_extra = NULL;
c->proto = CONN_PROTO_UNKNOWN;
@ -251,7 +252,6 @@ kore_connection_handle(struct connection *c)
{
int r;
struct listener *listener;
char cn[X509_CN_LENGTH];
kore_debug("kore_connection_handle(%p) -> %d", c, c->state);
kore_connection_stop_idletimer(c);
@ -280,6 +280,9 @@ kore_connection_handle(struct connection *c)
ssl_errno_s);
return (KORE_RESULT_ERROR);
}
if (primary_dom->cafile != NULL)
c->flags |= CONN_LOG_TLS_FAILURE;
}
ERR_clear_error();
@ -293,6 +296,10 @@ kore_connection_handle(struct connection *c)
return (KORE_RESULT_OK);
default:
kore_debug("SSL_accept(): %s", ssl_errno_s);
if (c->flags & CONN_LOG_TLS_FAILURE) {
kore_log(LOG_NOTICE,
"SSL_accept: %s", ssl_errno_s);
}
return (KORE_RESULT_ERROR);
}
}
@ -308,14 +315,7 @@ kore_connection_handle(struct connection *c)
if (SSL_get_verify_mode(c->ssl) & SSL_VERIFY_PEER) {
c->cert = SSL_get_peer_certificate(c->ssl);
if (c->cert == NULL) {
kore_log(LOG_NOTICE,
"no client certificate presented?");
return (KORE_RESULT_ERROR);
}
if (X509_GET_CN(c->cert, cn, sizeof(cn)) == -1) {
kore_log(LOG_NOTICE,
"no CN found in client certificate");
kore_log(LOG_NOTICE, "no peer certificate");
return (KORE_RESULT_ERROR);
}
} else {
@ -385,6 +385,9 @@ kore_connection_remove(struct connection *c)
if (c->cert != NULL)
X509_free(c->cert);
if (c->tls_sni != NULL)
kore_free(c->tls_sni);
close(c->fd);
if (c->hdlr_extra != NULL)

View File

@ -1486,6 +1486,7 @@ static struct http_request *
http_request_new(struct connection *c, const char *host,
const char *method, char *path, const char *version)
{
struct kore_domain *dom;
struct http_request *req;
struct kore_module_handle *hdlr;
char *p, *hp;
@ -1550,6 +1551,23 @@ http_request_new(struct connection *c, const char *host,
break;
}
if (c->owner->server->tls && c->tls_sni != NULL) {
if (strcasecmp(c->tls_sni, host)) {
http_error_response(c, HTTP_STATUS_MISDIRECTED_REQUEST);
return (NULL);
}
}
if ((dom = kore_domain_lookup(c->owner->server, host)) == NULL) {
http_error_response(c, HTTP_STATUS_NOT_FOUND);
return (NULL);
}
if (dom->cafile != NULL && c->cert == NULL) {
http_error_response(c, HTTP_STATUS_FORBIDDEN);
return (NULL);
}
req = kore_pool_get(&http_request_pool);
req->owner = c;
@ -2209,6 +2227,9 @@ http_status_text(int status)
case HTTP_STATUS_EXPECTATION_FAILED:
r = "Expectation Failed";
break;
case HTTP_STATUS_MISDIRECTED_REQUEST:
r = "Misdirected Request";
break;
case HTTP_STATUS_INTERNAL_ERROR:
r = "Internal Server Error";
break;

View File

@ -360,6 +360,9 @@ kore_tls_sni_cb(SSL *ssl, int *ad, void *arg)
sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
kore_debug("kore_tls_sni_cb(): received host %s", sname);
if (sname != NULL)
c->tls_sni = kore_strdup(sname);
if (sname != NULL &&
(dom = kore_domain_lookup(c->owner->server, sname)) != NULL) {
if (dom->ssl_ctx == NULL) {
@ -375,6 +378,7 @@ kore_tls_sni_cb(SSL *ssl, int *ad, void *arg)
if (dom->cafile != NULL) {
SSL_set_verify(ssl, SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
c->flags |= CONN_LOG_TLS_FAILURE;
} else {
SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
}

View File

@ -373,6 +373,10 @@ net_write_tls(struct connection *c, size_t len, size_t *written)
/* FALLTHROUGH */
default:
kore_debug("SSL_write(): %s", ssl_errno_s);
if (c->flags & CONN_LOG_TLS_FAILURE) {
kore_log(LOG_NOTICE,
"SSL_write(): %s", ssl_errno_s);
}
return (KORE_RESULT_ERROR);
}
}
@ -401,6 +405,8 @@ net_read_tls(struct connection *c, size_t *bytes)
case SSL_ERROR_WANT_WRITE:
c->evt.flags &= ~KORE_EVENT_READ;
return (KORE_RESULT_OK);
case SSL_ERROR_ZERO_RETURN:
return (KORE_RESULT_ERROR);
case SSL_ERROR_SYSCALL:
switch (errno) {
case EINTR:
@ -416,6 +422,10 @@ net_read_tls(struct connection *c, size_t *bytes)
/* FALLTHROUGH */
default:
kore_debug("SSL_read(): %s", ssl_errno_s);
if (c->flags & CONN_LOG_TLS_FAILURE) {
kore_log(LOG_NOTICE,
"SSL_read(): %s", ssl_errno_s);
}
return (KORE_RESULT_ERROR);
}
}

View File

@ -585,6 +585,78 @@ kore_worker_name(int id)
return (buf);
}
int
kore_x509_subject_name(struct connection *c, char **out, int flags)
{
struct kore_buf buf;
u_int8_t *data;
ASN1_STRING *astr;
X509_NAME *name;
X509_NAME_ENTRY *entry;
const char *field;
int ret, idx, namelen, nid, len;
data = NULL;
ret = KORE_RESULT_ERROR;
kore_buf_init(&buf, 1024);
if (c->cert == NULL)
goto cleanup;
if ((name = X509_get_subject_name(c->cert)) == NULL)
goto cleanup;
namelen = sk_X509_NAME_ENTRY_num(name->entries);
if (namelen == 0)
goto cleanup;
for (idx = 0; idx < namelen; idx++) {
entry = sk_X509_NAME_ENTRY_value(name->entries, idx);
if (entry == NULL)
goto cleanup;
nid = OBJ_obj2nid(entry->object);
if ((field = OBJ_nid2sn(nid)) == NULL)
goto cleanup;
if ((astr = X509_NAME_ENTRY_get_data(entry)) == NULL)
goto cleanup;
data = NULL;
if ((len = ASN1_STRING_to_UTF8(&data, astr)) < 0)
goto cleanup;
if (flags & KORE_X509_COMMON_NAME_ONLY) {
if (nid == NID_commonName) {
kore_buf_append(&buf, data, len);
break;
}
} else {
kore_buf_appendf(&buf, "%s=", field);
kore_buf_append(&buf, data, len);
if (idx != (namelen - 1))
kore_buf_appendf(&buf, " ");
}
OPENSSL_free(data);
data = NULL;
}
ret = KORE_RESULT_OK;
*out = kore_buf_stringify(&buf, NULL);
buf.offset = 0;
buf.data = NULL;
cleanup:
if (data != NULL)
OPENSSL_free(data);
kore_buf_cleanup(&buf);
return (ret);
}
void
fatal(const char *fmt, ...)
{