mirror of https://git.kore.io/kore.git
1224 lines
28 KiB
C
1224 lines
28 KiB
C
/*
|
|
* Copyright (c) 2022 Joris Vink <joris@coders.se>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* This TLS backend is the original TLS code used in Kore.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/dh.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/ecdsa.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/x509v3.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include "kore.h"
|
|
#include "http.h"
|
|
|
|
/*
|
|
* Disable deprecated declaration warnings if we're building against
|
|
* OpenSSL 3 as they marked all low-level APIs as deprecated.
|
|
*
|
|
* Work is being done to replace these, but for now let things build.
|
|
*/
|
|
#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
#endif
|
|
|
|
#define TLS_SESSION_ID "kore_tls_sessionid"
|
|
|
|
/* Helper for weird API designs (looking at you OpenSSL). */
|
|
union deconst {
|
|
void *p;
|
|
const void *cp;
|
|
};
|
|
|
|
static int tls_domain_x509_verify(int, X509_STORE_CTX *);
|
|
static X509 *tls_domain_load_certificate_chain(SSL_CTX *,
|
|
const void *, size_t);
|
|
static EVP_PKEY *tls_privsep_private_key(EVP_PKEY *, struct kore_domain *);
|
|
|
|
static int tls_sni_cb(SSL *, int *, void *);
|
|
static void tls_info_callback(const SSL *, int, int);
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
static void tls_acme_challenge_set_cert(SSL *, struct kore_domain *);
|
|
static int tls_acme_alpn(SSL *, const unsigned char **, unsigned char *,
|
|
const unsigned char *, unsigned int, void *);
|
|
#endif
|
|
|
|
static void tls_keymgr_await_data(void);
|
|
static void tls_keymgr_msg_response(struct kore_msg *, const void *);
|
|
|
|
static int tls_keymgr_rsa_init(RSA *);
|
|
static int tls_keymgr_rsa_finish(RSA *);
|
|
static int tls_keymgr_rsa_privenc(int, const unsigned char *,
|
|
unsigned char *, RSA *, int);
|
|
|
|
static DH *dh_params = NULL;
|
|
static RSA_METHOD *keymgr_rsa_meth = NULL;
|
|
static int tls_version = KORE_TLS_VERSION_BOTH;
|
|
static char *tls_cipher_list = KORE_DEFAULT_CIPHER_LIST;
|
|
|
|
static u_int8_t keymgr_buf[2048];
|
|
static size_t keymgr_buflen = 0;
|
|
static int keymgr_response = 0;
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
static u_int8_t acme_alpn_name[] =
|
|
{ 0xa, 'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1' };
|
|
#endif
|
|
|
|
struct kore_privsep keymgr_privsep;
|
|
int kore_keymgr_active = 0;
|
|
|
|
int
|
|
kore_tls_supported(void)
|
|
{
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
void
|
|
kore_tls_init(void)
|
|
{
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
ERR_load_crypto_strings();
|
|
|
|
}
|
|
|
|
void
|
|
kore_tls_log_version(void)
|
|
{
|
|
kore_log(LOG_NOTICE, "TLS backend %s", OPENSSL_VERSION_TEXT);
|
|
|
|
#if !defined(TLS1_3_VERSION)
|
|
if (!kore_quiet) {
|
|
kore_log(LOG_NOTICE,
|
|
"%s has no TLS 1.3 - will only use TLS 1.2",
|
|
OPENSSL_VERSION_TEXT);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
kore_tls_cleanup(void)
|
|
{
|
|
RSA_meth_free(keymgr_rsa_meth);
|
|
}
|
|
|
|
void
|
|
kore_tls_version_set(int version)
|
|
{
|
|
tls_version = version;
|
|
}
|
|
|
|
void
|
|
kore_tls_dh_check(void)
|
|
{
|
|
if (dh_params != NULL)
|
|
return;
|
|
|
|
if (!kore_tls_dh_load(KORE_DHPARAM_PATH))
|
|
fatal("failed to load default DH parameters");
|
|
}
|
|
|
|
int
|
|
kore_tls_dh_load(const char *path)
|
|
{
|
|
BIO *bio;
|
|
|
|
if (dh_params != NULL) {
|
|
kore_log(LOG_ERR, "tls_dhparam already specified");
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
if ((bio = BIO_new_file(path, "r")) == NULL) {
|
|
kore_log(LOG_ERR, "tls_dhparam file '%s' not accessible", path);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
dh_params = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
|
|
BIO_free(bio);
|
|
|
|
if (dh_params == NULL) {
|
|
kore_log(LOG_ERR, "PEM_read_bio_DHparams(): %s", ssl_errno_s);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
int
|
|
kore_tls_ciphersuite_set(const char *list)
|
|
{
|
|
if (strcmp(tls_cipher_list, KORE_DEFAULT_CIPHER_LIST)) {
|
|
kore_log(LOG_ERR, "tls_cipher specified twice");
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
tls_cipher_list = kore_strdup(list);
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
void
|
|
kore_tls_keymgr_init(void)
|
|
{
|
|
const RSA_METHOD *meth;
|
|
|
|
if ((meth = RSA_get_default_method()) == NULL)
|
|
fatal("failed to obtain RSA method");
|
|
|
|
if ((keymgr_rsa_meth = RSA_meth_new("kore RSA keymgr method",
|
|
RSA_METHOD_FLAG_NO_CHECK)) == NULL)
|
|
fatal("failed to setup RSA method");
|
|
|
|
RSA_meth_set_init(keymgr_rsa_meth, tls_keymgr_rsa_init);
|
|
RSA_meth_set_finish(keymgr_rsa_meth, tls_keymgr_rsa_finish);
|
|
RSA_meth_set_priv_enc(keymgr_rsa_meth, tls_keymgr_rsa_privenc);
|
|
|
|
RSA_meth_set_pub_enc(keymgr_rsa_meth, RSA_meth_get_pub_enc(meth));
|
|
RSA_meth_set_pub_dec(keymgr_rsa_meth, RSA_meth_get_pub_dec(meth));
|
|
RSA_meth_set_bn_mod_exp(keymgr_rsa_meth, RSA_meth_get_bn_mod_exp(meth));
|
|
|
|
kore_msg_register(KORE_MSG_KEYMGR_RESP, tls_keymgr_msg_response);
|
|
}
|
|
|
|
void
|
|
kore_tls_domain_setup(struct kore_domain *dom, int type,
|
|
const void *data, size_t datalen)
|
|
{
|
|
const u_int8_t *ptr;
|
|
EVP_PKEY *pkey;
|
|
X509 *x509;
|
|
STACK_OF(X509_NAME) *certs;
|
|
const SSL_METHOD *method;
|
|
|
|
if (dom->tls_ctx != NULL)
|
|
SSL_CTX_free(dom->tls_ctx);
|
|
|
|
if ((method = TLS_method()) == NULL)
|
|
fatalx("TLS_method(): %s", ssl_errno_s);
|
|
|
|
if ((dom->tls_ctx = SSL_CTX_new(method)) == NULL)
|
|
fatalx("SSL_ctx_new(): %s", ssl_errno_s);
|
|
|
|
if (!SSL_CTX_set_min_proto_version(dom->tls_ctx, TLS1_2_VERSION))
|
|
fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s);
|
|
|
|
#if defined(TLS1_3_VERSION)
|
|
if (!SSL_CTX_set_max_proto_version(dom->tls_ctx, TLS1_3_VERSION))
|
|
fatalx("SSL_CTX_set_max_proto_version: %s", ssl_errno_s);
|
|
#else
|
|
if (!SSL_CTX_set_max_proto_version(dom->tls_ctx, TLS1_2_VERSION))
|
|
fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s);
|
|
#endif
|
|
|
|
switch (tls_version) {
|
|
case KORE_TLS_VERSION_1_3:
|
|
#if defined(TLS1_3_VERSION)
|
|
if (!SSL_CTX_set_min_proto_version(dom->tls_ctx,
|
|
TLS1_3_VERSION)) {
|
|
fatalx("SSL_CTX_set_min_proto_version: %s",
|
|
ssl_errno_s);
|
|
}
|
|
break;
|
|
#endif
|
|
case KORE_TLS_VERSION_1_2:
|
|
if (!SSL_CTX_set_max_proto_version(dom->tls_ctx,
|
|
TLS1_2_VERSION)) {
|
|
fatalx("SSL_CTX_set_min_proto_version: %s",
|
|
ssl_errno_s);
|
|
}
|
|
break;
|
|
case KORE_TLS_VERSION_BOTH:
|
|
break;
|
|
default:
|
|
fatalx("unknown tls_version: %d", tls_version);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case KORE_PEM_CERT_CHAIN:
|
|
x509 = tls_domain_load_certificate_chain(dom->tls_ctx,
|
|
data, datalen);
|
|
break;
|
|
case KORE_DER_CERT_DATA:
|
|
ptr = data;
|
|
if ((x509 = d2i_X509(NULL, &ptr, datalen)) == NULL)
|
|
fatalx("d2i_X509: %s", ssl_errno_s);
|
|
if (SSL_CTX_use_certificate(dom->tls_ctx, x509) == 0)
|
|
fatalx("SSL_CTX_use_certificate: %s", ssl_errno_s);
|
|
break;
|
|
default:
|
|
fatalx("%s: unknown type %d", __func__, type);
|
|
}
|
|
|
|
if (x509 == NULL) {
|
|
kore_log(LOG_NOTICE, "failed to load certificate for '%s': %s",
|
|
dom->domain, ssl_errno_s);
|
|
SSL_CTX_free(dom->tls_ctx);
|
|
dom->tls_ctx = NULL;
|
|
return;
|
|
}
|
|
|
|
if ((pkey = X509_get_pubkey(x509)) == NULL)
|
|
fatalx("certificate has no public key");
|
|
|
|
switch (EVP_PKEY_id(pkey)) {
|
|
case EVP_PKEY_RSA:
|
|
pkey = tls_privsep_private_key(pkey, dom);
|
|
break;
|
|
default:
|
|
fatalx("unknown public key in certificate");
|
|
}
|
|
|
|
if (!SSL_CTX_use_PrivateKey(dom->tls_ctx, pkey))
|
|
fatalx("SSL_CTX_use_PrivateKey(): %s", ssl_errno_s);
|
|
|
|
if (!SSL_CTX_check_private_key(dom->tls_ctx)) {
|
|
fatalx("Public/Private key for %s do not match (%s)",
|
|
dom->domain, ssl_errno_s);
|
|
}
|
|
|
|
if (dh_params == NULL)
|
|
fatal("no DH parameters specified");
|
|
|
|
SSL_CTX_set_tmp_dh(dom->tls_ctx, dh_params);
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_SINGLE_DH_USE);
|
|
|
|
if (!SSL_CTX_set_ecdh_auto(dom->tls_ctx, 1))
|
|
fatalx("SSL_CTX_set_ecdh_auto: %s", ssl_errno_s);
|
|
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_SINGLE_ECDH_USE);
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_COMPRESSION);
|
|
|
|
if (dom->cafile != NULL) {
|
|
if ((certs = SSL_load_client_CA_file(dom->cafile)) == NULL) {
|
|
fatalx("SSL_load_client_CA_file(%s): %s",
|
|
dom->cafile, ssl_errno_s);
|
|
}
|
|
|
|
SSL_CTX_load_verify_locations(dom->tls_ctx, dom->cafile, NULL);
|
|
SSL_CTX_set_verify_depth(dom->tls_ctx, dom->x509_verify_depth);
|
|
SSL_CTX_set_client_CA_list(dom->tls_ctx, certs);
|
|
SSL_CTX_set_verify(dom->tls_ctx, SSL_VERIFY_PEER |
|
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, tls_domain_x509_verify);
|
|
}
|
|
|
|
SSL_CTX_set_session_id_context(dom->tls_ctx,
|
|
(unsigned char *)TLS_SESSION_ID, strlen(TLS_SESSION_ID));
|
|
SSL_CTX_set_mode(dom->tls_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
|
|
|
if (tls_version == KORE_TLS_VERSION_BOTH) {
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_SSLv2);
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_SSLv3);
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_TLSv1);
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_TLSv1_1);
|
|
}
|
|
|
|
SSL_CTX_set_options(dom->tls_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
|
SSL_CTX_set_cipher_list(dom->tls_ctx, tls_cipher_list);
|
|
|
|
SSL_CTX_set_info_callback(dom->tls_ctx, tls_info_callback);
|
|
SSL_CTX_set_tlsext_servername_callback(dom->tls_ctx, tls_sni_cb);
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
SSL_CTX_set_alpn_select_cb(dom->tls_ctx, tls_acme_alpn, dom);
|
|
#endif
|
|
|
|
X509_free(x509);
|
|
}
|
|
|
|
void
|
|
kore_tls_domain_crl(struct kore_domain *dom, const void *pem, size_t pemlen)
|
|
{
|
|
int err;
|
|
BIO *in;
|
|
X509_CRL *crl;
|
|
X509_REVOKED *rev;
|
|
X509_STORE *store;
|
|
struct connection *c, *next;
|
|
|
|
ERR_clear_error();
|
|
in = BIO_new_mem_buf(pem, pemlen);
|
|
|
|
if ((store = SSL_CTX_get_cert_store(dom->tls_ctx)) == NULL) {
|
|
BIO_free(in);
|
|
kore_log(LOG_ERR, "SSL_CTX_get_cert_store(): %s", ssl_errno_s);
|
|
return;
|
|
}
|
|
|
|
X509_STORE_set_flags(store,
|
|
X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
|
|
|
for (;;) {
|
|
crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL);
|
|
if (crl == NULL) {
|
|
err = ERR_GET_REASON(ERR_peek_last_error());
|
|
if (err == PEM_R_NO_START_LINE) {
|
|
ERR_clear_error();
|
|
break;
|
|
}
|
|
|
|
kore_log(LOG_WARNING, "failed to read CRL %s: %s",
|
|
dom->crlfile, ssl_errno_s);
|
|
continue;
|
|
}
|
|
|
|
if (!X509_STORE_add_crl(store, crl)) {
|
|
err = ERR_GET_REASON(ERR_peek_last_error());
|
|
if (err == X509_R_CERT_ALREADY_IN_HASH_TABLE) {
|
|
X509_CRL_free(crl);
|
|
continue;
|
|
}
|
|
|
|
kore_log(LOG_WARNING, "failed to add CRL %s: %s",
|
|
dom->crlfile, ssl_errno_s);
|
|
X509_CRL_free(crl);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Check if any accepted connection authenticated themselves
|
|
* with a now revoked certificate.
|
|
*/
|
|
for (c = TAILQ_FIRST(&connections); c != NULL; c = next) {
|
|
next = TAILQ_NEXT(c, list);
|
|
if (c->proto != CONN_PROTO_HTTP)
|
|
continue;
|
|
|
|
/*
|
|
* Prune any connection that is currently not yet done
|
|
* with the TLS handshake. This is to prevent a race
|
|
* where a handshake could not yet be complete but
|
|
* did pass the x509 verification step and their cert
|
|
* was revoked in this CRL update.
|
|
*/
|
|
if (c->state == CONN_STATE_TLS_SHAKE) {
|
|
kore_connection_disconnect(c);
|
|
continue;
|
|
}
|
|
|
|
if (c->tls_cert == NULL)
|
|
continue;
|
|
|
|
if (X509_CRL_get0_by_cert(crl, &rev, c->tls_cert) != 1)
|
|
continue;
|
|
|
|
kore_connection_log(c,
|
|
"connection removed, its certificate is revoked");
|
|
|
|
kore_connection_disconnect(c);
|
|
}
|
|
}
|
|
|
|
BIO_free(in);
|
|
}
|
|
|
|
void
|
|
kore_tls_domain_cleanup(struct kore_domain *dom)
|
|
{
|
|
if (dom->tls_ctx != NULL)
|
|
SSL_CTX_free(dom->tls_ctx);
|
|
}
|
|
|
|
int
|
|
kore_tls_connection_accept(struct connection *c)
|
|
{
|
|
int r;
|
|
|
|
if (primary_dom == NULL) {
|
|
kore_connection_log(c,
|
|
"TLS handshake but no TLS configured on server");
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
if (primary_dom->tls_ctx == NULL) {
|
|
kore_connection_log(c,
|
|
"TLS configuration for %s not yet complete",
|
|
primary_dom->domain);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
if (c->tls == NULL) {
|
|
c->tls = SSL_new(primary_dom->tls_ctx);
|
|
if (c->tls == NULL)
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
SSL_set_fd(c->tls, c->fd);
|
|
SSL_set_accept_state(c->tls);
|
|
|
|
if (!SSL_set_ex_data(c->tls, 0, c)) {
|
|
kore_connection_log(c,
|
|
"SSL_set_ex_data: %s", ssl_errno_s);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
if (primary_dom->cafile != NULL)
|
|
c->flags |= CONN_LOG_TLS_FAILURE;
|
|
}
|
|
|
|
ERR_clear_error();
|
|
r = SSL_accept(c->tls);
|
|
if (r <= 0) {
|
|
r = SSL_get_error(c->tls, r);
|
|
switch (r) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
kore_connection_start_idletimer(c);
|
|
return (KORE_RESULT_RETRY);
|
|
default:
|
|
if (c->flags & CONN_LOG_TLS_FAILURE) {
|
|
kore_connection_log(c,
|
|
"SSL_accept: %s", ssl_errno_s);
|
|
}
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
}
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
if (c->proto == CONN_PROTO_ACME_ALPN) {
|
|
kore_connection_log(c, "disconnecting ACME client");
|
|
kore_connection_disconnect(c);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
#endif
|
|
|
|
if (SSL_get_verify_mode(c->tls) & SSL_VERIFY_PEER) {
|
|
c->tls_cert = SSL_get_peer_certificate(c->tls);
|
|
if (c->tls_cert == NULL) {
|
|
kore_connection_log(c, "no peer certificate returned");
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
} else {
|
|
c->tls_cert = NULL;
|
|
}
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
int
|
|
kore_tls_read(struct connection *c, size_t *bytes)
|
|
{
|
|
int r;
|
|
|
|
ERR_clear_error();
|
|
r = SSL_read(c->tls, (c->rnb->buf + c->rnb->s_off),
|
|
(c->rnb->b_len - c->rnb->s_off));
|
|
|
|
if (c->tls_reneg > 1)
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
if (r <= 0) {
|
|
r = SSL_get_error(c->tls, r);
|
|
switch (r) {
|
|
case SSL_ERROR_WANT_READ:
|
|
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:
|
|
*bytes = 0;
|
|
return (KORE_RESULT_OK);
|
|
case EAGAIN:
|
|
c->evt.flags &= ~KORE_EVENT_READ;
|
|
c->snb->flags |= NETBUF_MUST_RESEND;
|
|
return (KORE_RESULT_OK);
|
|
default:
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
default:
|
|
if (c->flags & CONN_LOG_TLS_FAILURE) {
|
|
kore_connection_log(c, "SSL_read: %s",
|
|
ssl_errno_s);
|
|
}
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
}
|
|
|
|
*bytes = (size_t)r;
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
int
|
|
kore_tls_write(struct connection *c, size_t len, size_t *written)
|
|
{
|
|
int r;
|
|
|
|
if (len > INT_MAX)
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
ERR_clear_error();
|
|
r = SSL_write(c->tls, (c->snb->buf + c->snb->s_off), len);
|
|
if (c->tls_reneg > 1)
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
if (r <= 0) {
|
|
r = SSL_get_error(c->tls, r);
|
|
switch (r) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
c->evt.flags &= ~KORE_EVENT_WRITE;
|
|
c->snb->flags |= NETBUF_MUST_RESEND;
|
|
return (KORE_RESULT_OK);
|
|
case SSL_ERROR_SYSCALL:
|
|
switch (errno) {
|
|
case EINTR:
|
|
*written = 0;
|
|
return (KORE_RESULT_OK);
|
|
case EAGAIN:
|
|
c->evt.flags &= ~KORE_EVENT_WRITE;
|
|
c->snb->flags |= NETBUF_MUST_RESEND;
|
|
return (KORE_RESULT_OK);
|
|
default:
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
default:
|
|
if (c->flags & CONN_LOG_TLS_FAILURE) {
|
|
kore_connection_log(c,
|
|
"SSL_write: %s", ssl_errno_s);
|
|
}
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
}
|
|
|
|
*written = (size_t)r;
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
void
|
|
kore_tls_connection_cleanup(struct connection *c)
|
|
{
|
|
if (c->tls != NULL) {
|
|
SSL_shutdown(c->tls);
|
|
SSL_free(c->tls);
|
|
}
|
|
|
|
if (c->tls_cert != NULL)
|
|
X509_free(c->tls_cert);
|
|
|
|
if (c->tls_sni != NULL)
|
|
kore_free(c->tls_sni);
|
|
}
|
|
|
|
|
|
KORE_PRIVATE_KEY *
|
|
kore_tls_rsakey_load(const char *path)
|
|
{
|
|
FILE *fp;
|
|
KORE_PRIVATE_KEY *pkey;
|
|
|
|
if (access(path, R_OK) == -1)
|
|
return (NULL);
|
|
|
|
if ((fp = fopen(path, "r")) == NULL)
|
|
fatalx("%s(%s): %s", __func__, path, errno_s);
|
|
|
|
if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL)
|
|
fatalx("PEM_read_PrivateKey: %s", ssl_errno_s);
|
|
|
|
fclose(fp);
|
|
|
|
return (pkey);
|
|
}
|
|
|
|
KORE_PRIVATE_KEY *
|
|
kore_tls_rsakey_generate(const char *path)
|
|
{
|
|
FILE *fp;
|
|
EVP_PKEY_CTX *ctx;
|
|
KORE_PRIVATE_KEY *pkey;
|
|
|
|
if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL)
|
|
fatalx("EVP_PKEY_CTX_new_id: %s", ssl_errno_s);
|
|
|
|
if (EVP_PKEY_keygen_init(ctx) <= 0)
|
|
fatalx("EVP_PKEY_keygen_init: %s", ssl_errno_s);
|
|
|
|
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KORE_RSAKEY_BITS) <= 0)
|
|
fatalx("EVP_PKEY_CTX_set_rsa_keygen_bits: %s", ssl_errno_s);
|
|
|
|
pkey = NULL;
|
|
if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
|
|
fatalx("EVP_PKEY_keygen: %s", ssl_errno_s);
|
|
|
|
if (path != NULL) {
|
|
if ((fp = fopen(path, "w")) == NULL)
|
|
fatalx("fopen(%s): %s", path, errno_s);
|
|
|
|
if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL))
|
|
fatalx("PEM_write_PrivateKey: %s", ssl_errno_s);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
return (pkey);
|
|
}
|
|
|
|
KORE_X509_NAMES *
|
|
kore_tls_x509_subject_name(struct connection *c)
|
|
{
|
|
X509_NAME *name;
|
|
|
|
if ((name = X509_get_subject_name(c->tls_cert)) == NULL) {
|
|
kore_connection_log(c,
|
|
"X509_get_subject_name: %s", ssl_errno_s);
|
|
}
|
|
|
|
return (name);
|
|
}
|
|
|
|
KORE_X509_NAMES *
|
|
kore_tls_x509_issuer_name(struct connection *c)
|
|
{
|
|
X509_NAME *name;
|
|
|
|
if ((name = X509_get_issuer_name(c->tls_cert)) == NULL)
|
|
kore_connection_log(c, "X509_get_issuer_name: %s", ssl_errno_s);
|
|
|
|
return (name);
|
|
}
|
|
|
|
int
|
|
kore_tls_x509name_foreach(KORE_X509_NAMES *name, int flags, void *udata,
|
|
int (*cb)(void *, int, int, const char *, const void *, size_t, int))
|
|
{
|
|
u_int8_t *data;
|
|
ASN1_STRING *astr;
|
|
X509_NAME_ENTRY *entry;
|
|
const char *field;
|
|
int islast, ret, idx, namelen, nid, len;
|
|
|
|
data = NULL;
|
|
ret = KORE_RESULT_ERROR;
|
|
|
|
if ((namelen = X509_NAME_entry_count(name)) == 0)
|
|
goto cleanup;
|
|
|
|
for (idx = 0; idx < namelen; idx++) {
|
|
if ((entry = X509_NAME_get_entry(name, idx)) == NULL)
|
|
goto cleanup;
|
|
|
|
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry));
|
|
if ((field = OBJ_nid2sn(nid)) == NULL)
|
|
goto cleanup;
|
|
|
|
switch (nid) {
|
|
case NID_commonName:
|
|
nid = KORE_X509_NAME_COMMON_NAME;
|
|
break;
|
|
default:
|
|
nid = -1;
|
|
break;
|
|
}
|
|
|
|
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 (idx != (namelen - 1))
|
|
islast = 0;
|
|
else
|
|
islast = 1;
|
|
|
|
if (!cb(udata, islast, nid, field, data, len, flags))
|
|
goto cleanup;
|
|
|
|
OPENSSL_free(data);
|
|
data = NULL;
|
|
}
|
|
|
|
ret = KORE_RESULT_OK;
|
|
|
|
cleanup:
|
|
if (data != NULL)
|
|
OPENSSL_free(data);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
kore_tls_x509_data(struct connection *c, u_int8_t **ptr, size_t *olen)
|
|
{
|
|
int len;
|
|
u_int8_t *der, *pp;
|
|
|
|
if ((len = i2d_X509(c->tls_cert, NULL)) <= 0) {
|
|
kore_connection_log(c, "i2d_X509: %s", ssl_errno_s);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
der = kore_calloc(1, len);
|
|
pp = der;
|
|
|
|
if (i2d_X509(c->tls_cert, &pp) <= 0) {
|
|
kore_free(der);
|
|
kore_connection_log(c, "i2d_X509: %s", ssl_errno_s);
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
|
|
*ptr = der;
|
|
*olen = len;
|
|
|
|
return (KORE_RESULT_OK);
|
|
}
|
|
|
|
void
|
|
kore_tls_seed(const void *data, size_t len)
|
|
{
|
|
RAND_poll();
|
|
RAND_seed(data, len);
|
|
}
|
|
|
|
static void
|
|
tls_info_callback(const SSL *ssl, int flags, int ret)
|
|
{
|
|
struct connection *c;
|
|
|
|
if (flags & SSL_CB_HANDSHAKE_START) {
|
|
if ((c = SSL_get_app_data(ssl)) == NULL)
|
|
fatal("no SSL_get_app_data");
|
|
|
|
#if defined(TLS1_3_VERSION)
|
|
if (SSL_version(ssl) != TLS1_3_VERSION)
|
|
#endif
|
|
c->tls_reneg++;
|
|
}
|
|
}
|
|
|
|
static int
|
|
tls_sni_cb(SSL *ssl, int *ad, void *arg)
|
|
{
|
|
struct connection *c;
|
|
struct kore_domain *dom;
|
|
const char *sname;
|
|
|
|
if ((c = SSL_get_ex_data(ssl, 0)) == NULL)
|
|
fatal("no connection data in %s", __func__);
|
|
|
|
sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
|
|
|
if (sname != NULL)
|
|
c->tls_sni = kore_strdup(sname);
|
|
|
|
if (sname != NULL &&
|
|
(dom = kore_domain_lookup(c->owner->server, sname)) != NULL) {
|
|
if (dom->tls_ctx == NULL) {
|
|
kore_log(LOG_NOTICE,
|
|
"TLS configuration for %s not complete",
|
|
dom->domain);
|
|
return (SSL_TLSEXT_ERR_NOACK);
|
|
}
|
|
|
|
SSL_set_SSL_CTX(ssl, dom->tls_ctx);
|
|
|
|
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);
|
|
}
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
/*
|
|
* If ALPN callback was called before SNI was parsed we
|
|
* must make sure we swap to the correct certificate now.
|
|
*/
|
|
if (c->flags & CONN_TLS_ALPN_ACME_SEEN)
|
|
tls_acme_challenge_set_cert(ssl, dom);
|
|
|
|
c->flags |= CONN_TLS_SNI_SEEN;
|
|
#endif
|
|
return (SSL_TLSEXT_ERR_OK);
|
|
}
|
|
|
|
return (SSL_TLSEXT_ERR_NOACK);
|
|
}
|
|
|
|
static int
|
|
tls_keymgr_rsa_init(RSA *rsa)
|
|
{
|
|
if (rsa != NULL) {
|
|
RSA_set_flags(rsa, RSA_flags(rsa) |
|
|
RSA_FLAG_EXT_PKEY | RSA_METHOD_FLAG_NO_CHECK);
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
tls_keymgr_rsa_privenc(int flen, const unsigned char *from, unsigned char *to,
|
|
RSA *rsa, int padding)
|
|
{
|
|
int ret;
|
|
size_t len;
|
|
struct kore_keyreq *req;
|
|
struct kore_domain *dom;
|
|
|
|
len = sizeof(*req) + flen;
|
|
if (len > sizeof(keymgr_buf))
|
|
fatal("keymgr_buf too small");
|
|
|
|
if ((dom = RSA_get_app_data(rsa)) == NULL)
|
|
fatal("RSA key has no domain attached");
|
|
|
|
memset(keymgr_buf, 0, sizeof(keymgr_buf));
|
|
|
|
req = (struct kore_keyreq *)keymgr_buf;
|
|
|
|
if (kore_strlcpy(req->domain, dom->domain, sizeof(req->domain)) >=
|
|
sizeof(req->domain))
|
|
fatal("%s: domain truncated", __func__);
|
|
|
|
req->data_len = flen;
|
|
req->padding = padding;
|
|
memcpy(&req->data[0], from, req->data_len);
|
|
|
|
kore_msg_send(KORE_WORKER_KEYMGR, KORE_MSG_KEYMGR_REQ, keymgr_buf, len);
|
|
tls_keymgr_await_data();
|
|
|
|
ret = -1;
|
|
if (keymgr_response) {
|
|
if (keymgr_buflen < INT_MAX &&
|
|
(int)keymgr_buflen == RSA_size(rsa)) {
|
|
ret = RSA_size(rsa);
|
|
memcpy(to, keymgr_buf, RSA_size(rsa));
|
|
}
|
|
}
|
|
|
|
keymgr_buflen = 0;
|
|
keymgr_response = 0;
|
|
kore_platform_event_all(worker->msg[1]->fd, worker->msg[1]);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static int
|
|
tls_keymgr_rsa_finish(RSA *rsa)
|
|
{
|
|
return (1);
|
|
}
|
|
|
|
static void
|
|
tls_keymgr_await_data(void)
|
|
{
|
|
int ret;
|
|
struct pollfd pfd[1];
|
|
u_int64_t start, cur;
|
|
#if !defined(KORE_NO_HTTP)
|
|
int process_requests;
|
|
#endif
|
|
|
|
/*
|
|
* We need to wait until the keymgr responds to us, so keep doing
|
|
* net_recv_flush() until our callback for KORE_MSG_KEYMGR_RESP
|
|
* tells us that we have obtained the response.
|
|
*
|
|
* This means other internal messages can still be delivered by
|
|
* this worker process to the appropriate callbacks but we do not
|
|
* drop out until we've either received an answer from the keymgr
|
|
* or until the timeout has been reached (1 second currently).
|
|
*
|
|
* If we end up waiting for the keymgr process we will call
|
|
* http_process (if not built with NOHTTP=1) to further existing
|
|
* requests so those do not block too much.
|
|
*
|
|
* This means that all incoming data will stop being processed
|
|
* while existing requests will get processed until we return
|
|
* from this call.
|
|
*/
|
|
start = kore_time_ms();
|
|
kore_platform_disable_read(worker->msg[1]->fd);
|
|
|
|
keymgr_response = 0;
|
|
memset(keymgr_buf, 0, sizeof(keymgr_buf));
|
|
|
|
#if !defined(KORE_NO_HTTP)
|
|
process_requests = 0;
|
|
#endif
|
|
|
|
for (;;) {
|
|
#if !defined(KORE_NO_HTTP)
|
|
if (process_requests) {
|
|
http_process();
|
|
process_requests = 0;
|
|
}
|
|
#endif
|
|
pfd[0].fd = worker->msg[1]->fd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[0].revents = 0;
|
|
|
|
ret = poll(pfd, 1, 100);
|
|
if (ret == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
fatal("poll: %s", errno_s);
|
|
}
|
|
|
|
cur = kore_time_ms();
|
|
if ((cur - start) > 1000)
|
|
break;
|
|
if (ret == 0) {
|
|
#if !defined(KORE_NO_HTTP)
|
|
/* No activity on channel, process HTTP requests. */
|
|
process_requests = 1;
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if (pfd[0].revents & (POLLERR | POLLHUP))
|
|
break;
|
|
if (!(pfd[0].revents & POLLIN))
|
|
break;
|
|
|
|
worker->msg[1]->evt.flags |= KORE_EVENT_READ;
|
|
if (!net_recv_flush(worker->msg[1]))
|
|
break;
|
|
|
|
if (keymgr_response)
|
|
break;
|
|
|
|
#if !defined(KORE_NO_HTTP)
|
|
/* If we've spent 100ms already, process HTTP requests. */
|
|
if ((cur - start) > 100) {
|
|
process_requests = 1;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void
|
|
tls_keymgr_msg_response(struct kore_msg *msg, const void *data)
|
|
{
|
|
keymgr_response = 1;
|
|
keymgr_buflen = msg->length;
|
|
|
|
if (keymgr_buflen > sizeof(keymgr_buf))
|
|
return;
|
|
|
|
memcpy(keymgr_buf, data, keymgr_buflen);
|
|
}
|
|
|
|
static int
|
|
tls_domain_x509_verify(int ok, X509_STORE_CTX *ctx)
|
|
{
|
|
struct connection *c;
|
|
SSL *tls;
|
|
X509 *cert;
|
|
const char *text;
|
|
int error, depth;
|
|
|
|
if ((tls = X509_STORE_CTX_get_ex_data(ctx,
|
|
SSL_get_ex_data_X509_STORE_CTX_idx())) == NULL)
|
|
fatal("X509_STORE_CTX_get_ex_data: no data");
|
|
|
|
if ((c = SSL_get_ex_data(tls, 0)) == NULL)
|
|
fatal("no connection data in %s", __func__);
|
|
|
|
error = X509_STORE_CTX_get_error(ctx);
|
|
cert = X509_STORE_CTX_get_current_cert(ctx);
|
|
|
|
if (ok == 0 && cert != NULL) {
|
|
text = X509_verify_cert_error_string(error);
|
|
depth = X509_STORE_CTX_get_error_depth(ctx);
|
|
|
|
kore_connection_log(c,
|
|
"X509 verification error depth:%d - %s", depth, text);
|
|
|
|
/* Continue on CRL validity errors. */
|
|
switch (error) {
|
|
case X509_V_ERR_CRL_HAS_EXPIRED:
|
|
case X509_V_ERR_CRL_NOT_YET_VALID:
|
|
case X509_V_ERR_UNABLE_TO_GET_CRL:
|
|
ok = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (ok);
|
|
}
|
|
|
|
/*
|
|
* What follows is basically a reimplementation of
|
|
* SSL_CTX_use_certificate_chain_file() from OpenSSL but with our
|
|
* BIO set to the pem data that we received.
|
|
*/
|
|
static X509 *
|
|
tls_domain_load_certificate_chain(SSL_CTX *ctx, const void *data, size_t len)
|
|
{
|
|
unsigned long err;
|
|
BIO *in;
|
|
X509 *x, *ca;
|
|
|
|
ERR_clear_error();
|
|
in = BIO_new_mem_buf(data, len);
|
|
|
|
if ((x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL)) == NULL)
|
|
return (NULL);
|
|
|
|
/* refcount for x509 will go up one. */
|
|
if (SSL_CTX_use_certificate(ctx, x) == 0)
|
|
return (NULL);
|
|
|
|
SSL_CTX_clear_chain_certs(ctx);
|
|
|
|
ERR_clear_error();
|
|
while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL) {
|
|
/* ca its reference count won't be increased. */
|
|
if (SSL_CTX_add0_chain_cert(ctx, ca) == 0)
|
|
return (NULL);
|
|
}
|
|
|
|
err = ERR_peek_last_error();
|
|
|
|
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
|
|
ERR_GET_REASON(err) != PEM_R_NO_START_LINE)
|
|
return (NULL);
|
|
|
|
BIO_free(in);
|
|
|
|
return (x);
|
|
}
|
|
|
|
static EVP_PKEY *
|
|
tls_privsep_private_key(EVP_PKEY *pub, struct kore_domain *dom)
|
|
{
|
|
EVP_PKEY *pkey;
|
|
RSA *rsa_new;
|
|
const RSA *rsa_cur;
|
|
const BIGNUM *e_cur, *n_cur;
|
|
BIGNUM *e_new, *n_new;
|
|
|
|
if ((pkey = EVP_PKEY_new()) == NULL)
|
|
fatalx("failed to create new EVP_PKEY data structure");
|
|
|
|
if ((rsa_new = RSA_new()) == NULL)
|
|
fatalx("failed to create new RSA data structure");
|
|
|
|
if ((rsa_cur = EVP_PKEY_get0_RSA(pub)) == NULL)
|
|
fatalx("no RSA public key present");
|
|
|
|
RSA_get0_key(rsa_cur, &n_cur, &e_cur, NULL);
|
|
|
|
if ((n_new = BN_dup(n_cur)) == NULL)
|
|
fatalx("BN_dup failed");
|
|
|
|
if ((e_new = BN_dup(e_cur)) == NULL)
|
|
fatalx("BN_dup failed");
|
|
|
|
RSA_set_app_data(rsa_new, dom);
|
|
RSA_set_method(rsa_new, keymgr_rsa_meth);
|
|
RSA_set0_key(rsa_new, n_new, e_new, NULL);
|
|
|
|
EVP_PKEY_set1_RSA(pkey, rsa_new);
|
|
|
|
return (pkey);
|
|
}
|
|
|
|
#if defined(KORE_USE_ACME)
|
|
static int
|
|
tls_acme_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen,
|
|
const unsigned char *in, unsigned int inlen, void *udata)
|
|
{
|
|
struct connection *c;
|
|
|
|
if ((c = SSL_get_ex_data(ssl, 0)) == NULL)
|
|
fatal("%s: no connection data present", __func__);
|
|
|
|
if (inlen != sizeof(acme_alpn_name))
|
|
return (SSL_TLSEXT_ERR_NOACK);
|
|
|
|
if (memcmp(acme_alpn_name, in, sizeof(acme_alpn_name)))
|
|
return (SSL_TLSEXT_ERR_NOACK);
|
|
|
|
*out = in + 1;
|
|
*outlen = inlen - 1;
|
|
|
|
c->flags |= CONN_TLS_ALPN_ACME_SEEN;
|
|
|
|
/*
|
|
* If SNI was already done, we can continue, otherwise we mark
|
|
* that we saw the right ALPN negotiation on this connection
|
|
* and wait for the SNI extension to be parsed.
|
|
*/
|
|
if (c->flags & CONN_TLS_SNI_SEEN) {
|
|
/* SNI was seen, we are on the right domain. */
|
|
tls_acme_challenge_set_cert(ssl, udata);
|
|
}
|
|
|
|
return (SSL_TLSEXT_ERR_OK);
|
|
}
|
|
|
|
static void
|
|
tls_acme_challenge_set_cert(SSL *ssl, struct kore_domain *dom)
|
|
{
|
|
struct connection *c;
|
|
const unsigned char *ptr;
|
|
X509 *x509;
|
|
|
|
if (dom->acme == 0) {
|
|
kore_log(LOG_NOTICE, "[%s] ACME not active", dom->domain);
|
|
return;
|
|
}
|
|
|
|
if (dom->acme_challenge == 0) {
|
|
kore_log(LOG_NOTICE,
|
|
"[%s] ACME auth challenge not active", dom->domain);
|
|
return;
|
|
}
|
|
|
|
kore_log(LOG_INFO, "[%s] acme-tls/1 challenge requested",
|
|
dom->domain);
|
|
|
|
if ((c = SSL_get_ex_data(ssl, 0)) == NULL)
|
|
fatal("%s: no connection data present", __func__);
|
|
|
|
ptr = dom->acme_cert;
|
|
if ((x509 = d2i_X509(NULL, &ptr, dom->acme_cert_len)) == NULL)
|
|
fatal("d2i_X509: %s", ssl_errno_s);
|
|
|
|
if (SSL_use_certificate(ssl, x509) == 0)
|
|
fatal("SSL_use_certificate: %s", ssl_errno_s);
|
|
|
|
SSL_clear_chain_certs(ssl);
|
|
SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
|
|
|
|
c->proto = CONN_PROTO_ACME_ALPN;
|
|
}
|
|
#endif /* KORE_USE_ACME */
|