check in v3.8.2 source
This commit is contained in:
0
tls/empty.c
Normal file
0
tls/empty.c
Normal file
931
tls/tls.c
Normal file
931
tls/tls.c
Normal file
@@ -0,0 +1,931 @@
|
||||
/* $OpenBSD: tls.c,v 1.98 2023/07/02 06:37:27 beck Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/safestack.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
static struct tls_config *tls_config_default;
|
||||
|
||||
static int tls_init_rv = -1;
|
||||
|
||||
static void
|
||||
tls_do_init(void)
|
||||
{
|
||||
OPENSSL_init_ssl(OPENSSL_INIT_NO_LOAD_CONFIG, NULL);
|
||||
|
||||
if (BIO_sock_init() != 1)
|
||||
return;
|
||||
|
||||
if ((tls_config_default = tls_config_new_internal()) == NULL)
|
||||
return;
|
||||
|
||||
tls_config_default->refcount++;
|
||||
|
||||
tls_init_rv = 0;
|
||||
}
|
||||
|
||||
int
|
||||
tls_init(void)
|
||||
{
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
|
||||
if (pthread_once(&once, tls_do_init) != 0)
|
||||
return -1;
|
||||
|
||||
return tls_init_rv;
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_error(struct tls *ctx)
|
||||
{
|
||||
return ctx->error.msg;
|
||||
}
|
||||
|
||||
void
|
||||
tls_error_clear(struct tls_error *error)
|
||||
{
|
||||
free(error->msg);
|
||||
error->msg = NULL;
|
||||
error->num = 0;
|
||||
error->tls = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_error_vset(struct tls_error *error, int errnum, const char *fmt, va_list ap)
|
||||
{
|
||||
char *errmsg = NULL;
|
||||
int rv = -1;
|
||||
|
||||
tls_error_clear(error);
|
||||
|
||||
error->num = errnum;
|
||||
error->tls = 1;
|
||||
|
||||
if (vasprintf(&errmsg, fmt, ap) == -1) {
|
||||
errmsg = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (errnum == -1) {
|
||||
error->msg = errmsg;
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (asprintf(&error->msg, "%s: %s", errmsg, strerror(errnum)) == -1) {
|
||||
error->msg = NULL;
|
||||
goto err;
|
||||
}
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
free(errmsg);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_error_set(struct tls_error *error, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int errnum, rv;
|
||||
|
||||
errnum = errno;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(error, errnum, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_error_setx(struct tls_error *error, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(error, -1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_error(struct tls_config *config, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int errnum, rv;
|
||||
|
||||
errnum = errno;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(&config->error, errnum, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_errorx(struct tls_config *config, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(&config->error, -1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_error(struct tls *ctx, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int errnum, rv;
|
||||
|
||||
errnum = errno;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(&ctx->error, errnum, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_errorx(struct tls *ctx, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(&ctx->error, -1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_ssl_errorx(struct tls *ctx, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int rv;
|
||||
|
||||
/* Only set an error if a more specific one does not already exist. */
|
||||
if (ctx->error.tls != 0)
|
||||
return (0);
|
||||
|
||||
va_start(ap, fmt);
|
||||
rv = tls_error_vset(&ctx->error, -1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
struct tls_sni_ctx *
|
||||
tls_sni_ctx_new(void)
|
||||
{
|
||||
return (calloc(1, sizeof(struct tls_sni_ctx)));
|
||||
}
|
||||
|
||||
void
|
||||
tls_sni_ctx_free(struct tls_sni_ctx *sni_ctx)
|
||||
{
|
||||
if (sni_ctx == NULL)
|
||||
return;
|
||||
|
||||
SSL_CTX_free(sni_ctx->ssl_ctx);
|
||||
X509_free(sni_ctx->ssl_cert);
|
||||
|
||||
free(sni_ctx);
|
||||
}
|
||||
|
||||
struct tls *
|
||||
tls_new(void)
|
||||
{
|
||||
struct tls *ctx;
|
||||
|
||||
if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
|
||||
return (NULL);
|
||||
|
||||
tls_reset(ctx);
|
||||
|
||||
if (tls_configure(ctx, tls_config_default) == -1) {
|
||||
free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (ctx);
|
||||
}
|
||||
|
||||
int
|
||||
tls_configure(struct tls *ctx, struct tls_config *config)
|
||||
{
|
||||
if (config == NULL)
|
||||
config = tls_config_default;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
config->refcount++;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
|
||||
tls_config_free(ctx->config);
|
||||
|
||||
ctx->config = config;
|
||||
ctx->keypair = config->keypair;
|
||||
|
||||
if ((ctx->flags & TLS_SERVER) != 0)
|
||||
return (tls_configure_server(ctx));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_cert_hash(X509 *cert, char **hash)
|
||||
{
|
||||
char d[EVP_MAX_MD_SIZE], *dhex = NULL;
|
||||
int dlen, rv = -1;
|
||||
|
||||
free(*hash);
|
||||
*hash = NULL;
|
||||
|
||||
if (X509_digest(cert, EVP_sha256(), d, &dlen) != 1)
|
||||
goto err;
|
||||
|
||||
if (tls_hex_string(d, dlen, &dhex, NULL) != 0)
|
||||
goto err;
|
||||
|
||||
if (asprintf(hash, "SHA256:%s", dhex) == -1) {
|
||||
*hash = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
err:
|
||||
free(dhex);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_cert_pubkey_hash(X509 *cert, char **hash)
|
||||
{
|
||||
char d[EVP_MAX_MD_SIZE], *dhex = NULL;
|
||||
int dlen, rv = -1;
|
||||
|
||||
free(*hash);
|
||||
*hash = NULL;
|
||||
|
||||
if (X509_pubkey_digest(cert, EVP_sha256(), d, &dlen) != 1)
|
||||
goto err;
|
||||
|
||||
if (tls_hex_string(d, dlen, &dhex, NULL) != 0)
|
||||
goto err;
|
||||
|
||||
if (asprintf(hash, "SHA256:%s", dhex) == -1) {
|
||||
*hash = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
free(dhex);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_keypair_to_pkey(struct tls *ctx, struct tls_keypair *keypair, EVP_PKEY **pkey)
|
||||
{
|
||||
BIO *bio = NULL;
|
||||
X509 *x509 = NULL;
|
||||
char *mem;
|
||||
size_t len;
|
||||
int ret = -1;
|
||||
|
||||
*pkey = NULL;
|
||||
|
||||
if (ctx->config->use_fake_private_key) {
|
||||
mem = keypair->cert_mem;
|
||||
len = keypair->cert_len;
|
||||
} else {
|
||||
mem = keypair->key_mem;
|
||||
len = keypair->key_len;
|
||||
}
|
||||
|
||||
if (mem == NULL)
|
||||
return (0);
|
||||
|
||||
if (len > INT_MAX) {
|
||||
tls_set_errorx(ctx, ctx->config->use_fake_private_key ?
|
||||
"cert too long" : "key too long");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((bio = BIO_new_mem_buf(mem, len)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to create buffer");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->config->use_fake_private_key) {
|
||||
if ((x509 = PEM_read_bio_X509(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to read X509 certificate");
|
||||
goto err;
|
||||
}
|
||||
if ((*pkey = X509_get_pubkey(x509)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to retrieve pubkey");
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
if ((*pkey = PEM_read_bio_PrivateKey(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to read private key");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
err:
|
||||
BIO_free(bio);
|
||||
X509_free(x509);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_keypair_setup_pkey(struct tls *ctx, struct tls_keypair *keypair, EVP_PKEY *pkey)
|
||||
{
|
||||
RSA_METHOD *rsa_method;
|
||||
EC_KEY_METHOD *ecdsa_method;
|
||||
RSA *rsa = NULL;
|
||||
EC_KEY *eckey = NULL;
|
||||
int ret = -1;
|
||||
|
||||
/* Only install the pubkey hash if fake private keys are used. */
|
||||
if (!ctx->config->skip_private_key_check)
|
||||
return (0);
|
||||
|
||||
if (keypair->pubkey_hash == NULL) {
|
||||
tls_set_errorx(ctx, "public key hash not set");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (EVP_PKEY_id(pkey)) {
|
||||
case EVP_PKEY_RSA:
|
||||
if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL ||
|
||||
RSA_set_ex_data(rsa, 0, keypair->pubkey_hash) == 0) {
|
||||
tls_set_errorx(ctx, "RSA key setup failure");
|
||||
goto err;
|
||||
}
|
||||
if (ctx->config->sign_cb != NULL) {
|
||||
rsa_method = tls_signer_rsa_method();
|
||||
if (rsa_method == NULL ||
|
||||
RSA_set_ex_data(rsa, 1, ctx->config) == 0 ||
|
||||
RSA_set_method(rsa, rsa_method) == 0) {
|
||||
tls_set_errorx(ctx, "failed to setup RSA key");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
/* Reset the key to work around caching in OpenSSL 3. */
|
||||
if (EVP_PKEY_set1_RSA(pkey, rsa) == 0) {
|
||||
tls_set_errorx(ctx, "failed to set RSA key");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case EVP_PKEY_EC:
|
||||
if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL ||
|
||||
EC_KEY_set_ex_data(eckey, 0, keypair->pubkey_hash) == 0) {
|
||||
tls_set_errorx(ctx, "EC key setup failure");
|
||||
goto err;
|
||||
}
|
||||
if (ctx->config->sign_cb != NULL) {
|
||||
ecdsa_method = tls_signer_ecdsa_method();
|
||||
if (ecdsa_method == NULL ||
|
||||
EC_KEY_set_ex_data(eckey, 1, ctx->config) == 0 ||
|
||||
EC_KEY_set_method(eckey, ecdsa_method) == 0) {
|
||||
tls_set_errorx(ctx, "failed to setup EC key");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
/* Reset the key to work around caching in OpenSSL 3. */
|
||||
if (EVP_PKEY_set1_EC_KEY(pkey, eckey) == 0) {
|
||||
tls_set_errorx(ctx, "failed to set EC key");
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
tls_set_errorx(ctx, "incorrect key type");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
RSA_free(rsa);
|
||||
EC_KEY_free(eckey);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
int
|
||||
tls_configure_ssl_keypair(struct tls *ctx, SSL_CTX *ssl_ctx,
|
||||
struct tls_keypair *keypair, int required)
|
||||
{
|
||||
EVP_PKEY *pkey = NULL;
|
||||
|
||||
if (!required &&
|
||||
keypair->cert_mem == NULL &&
|
||||
keypair->key_mem == NULL)
|
||||
return(0);
|
||||
|
||||
if (keypair->cert_mem != NULL) {
|
||||
if (keypair->cert_len > INT_MAX) {
|
||||
tls_set_errorx(ctx, "certificate too long");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_mem(ssl_ctx,
|
||||
keypair->cert_mem, keypair->cert_len) != 1) {
|
||||
tls_set_errorx(ctx, "failed to load certificate");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (tls_keypair_to_pkey(ctx, keypair, &pkey) == -1)
|
||||
goto err;
|
||||
if (pkey != NULL) {
|
||||
if (tls_keypair_setup_pkey(ctx, keypair, pkey) == -1)
|
||||
goto err;
|
||||
if (SSL_CTX_use_PrivateKey(ssl_ctx, pkey) != 1) {
|
||||
tls_set_errorx(ctx, "failed to load private key");
|
||||
goto err;
|
||||
}
|
||||
EVP_PKEY_free(pkey);
|
||||
pkey = NULL;
|
||||
}
|
||||
|
||||
if (!ctx->config->skip_private_key_check &&
|
||||
SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
||||
tls_set_errorx(ctx, "private/public key mismatch");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_configure_ssl(struct tls *ctx, SSL_CTX *ssl_ctx)
|
||||
{
|
||||
SSL_CTX_clear_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2);
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1);
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_1);
|
||||
|
||||
SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1_2);
|
||||
SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1_3);
|
||||
|
||||
if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_2) == 0)
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2);
|
||||
if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_3) == 0)
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3);
|
||||
|
||||
if (ctx->config->alpn != NULL) {
|
||||
if (SSL_CTX_set_alpn_protos(ssl_ctx, ctx->config->alpn,
|
||||
ctx->config->alpn_len) != 0) {
|
||||
tls_set_errorx(ctx, "failed to set alpn");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->config->ciphers != NULL) {
|
||||
if (SSL_CTX_set_cipher_list(ssl_ctx,
|
||||
ctx->config->ciphers) != 1) {
|
||||
tls_set_errorx(ctx, "failed to set ciphers");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->config->verify_time == 0) {
|
||||
X509_VERIFY_PARAM_set_flags(SSL_CTX_get0_param(ssl_ctx),
|
||||
X509_V_FLAG_NO_CHECK_TIME);
|
||||
}
|
||||
|
||||
/* Disable any form of session caching by default */
|
||||
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF);
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TICKET);
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_ssl_cert_verify_cb(X509_STORE_CTX *x509_ctx, void *arg)
|
||||
{
|
||||
struct tls *ctx = arg;
|
||||
int x509_err;
|
||||
|
||||
if (ctx->config->verify_cert == 0)
|
||||
return (1);
|
||||
|
||||
if ((X509_verify_cert(x509_ctx)) < 0) {
|
||||
tls_set_errorx(ctx, "X509 verify cert failed");
|
||||
return (0);
|
||||
}
|
||||
|
||||
x509_err = X509_STORE_CTX_get_error(x509_ctx);
|
||||
if (x509_err == X509_V_OK)
|
||||
return (1);
|
||||
|
||||
tls_set_errorx(ctx, "certificate verification failed: %s",
|
||||
X509_verify_cert_error_string(x509_err));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_configure_ssl_verify(struct tls *ctx, SSL_CTX *ssl_ctx, int verify)
|
||||
{
|
||||
size_t ca_len = ctx->config->ca_len;
|
||||
char *ca_mem = ctx->config->ca_mem;
|
||||
char *crl_mem = ctx->config->crl_mem;
|
||||
size_t crl_len = ctx->config->crl_len;
|
||||
char *ca_free = NULL;
|
||||
STACK_OF(X509_INFO) *xis = NULL;
|
||||
X509_STORE *store;
|
||||
X509_INFO *xi;
|
||||
BIO *bio = NULL;
|
||||
int rv = -1;
|
||||
int i;
|
||||
|
||||
SSL_CTX_set_verify(ssl_ctx, verify, NULL);
|
||||
SSL_CTX_set_cert_verify_callback(ssl_ctx, tls_ssl_cert_verify_cb, ctx);
|
||||
|
||||
if (ctx->config->verify_depth >= 0)
|
||||
SSL_CTX_set_verify_depth(ssl_ctx, ctx->config->verify_depth);
|
||||
|
||||
if (ctx->config->verify_cert == 0)
|
||||
goto done;
|
||||
|
||||
/* If no CA has been specified, attempt to load the default. */
|
||||
if (ctx->config->ca_mem == NULL && ctx->config->ca_path == NULL) {
|
||||
if (tls_config_load_file(&ctx->error, "CA", tls_default_ca_cert_file(),
|
||||
&ca_mem, &ca_len) != 0)
|
||||
goto err;
|
||||
ca_free = ca_mem;
|
||||
}
|
||||
|
||||
if (ca_mem != NULL) {
|
||||
if (ca_len > INT_MAX) {
|
||||
tls_set_errorx(ctx, "ca too long");
|
||||
goto err;
|
||||
}
|
||||
if (SSL_CTX_load_verify_mem(ssl_ctx, ca_mem, ca_len) != 1) {
|
||||
tls_set_errorx(ctx, "ssl verify memory setup failure");
|
||||
goto err;
|
||||
}
|
||||
} else if (SSL_CTX_load_verify_locations(ssl_ctx, NULL,
|
||||
ctx->config->ca_path) != 1) {
|
||||
tls_set_errorx(ctx, "ssl verify locations failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (crl_mem != NULL) {
|
||||
if (crl_len > INT_MAX) {
|
||||
tls_set_errorx(ctx, "crl too long");
|
||||
goto err;
|
||||
}
|
||||
if ((bio = BIO_new_mem_buf(crl_mem, crl_len)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to create buffer");
|
||||
goto err;
|
||||
}
|
||||
if ((xis = PEM_X509_INFO_read_bio(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to parse crl");
|
||||
goto err;
|
||||
}
|
||||
store = SSL_CTX_get_cert_store(ssl_ctx);
|
||||
for (i = 0; i < sk_X509_INFO_num(xis); i++) {
|
||||
xi = sk_X509_INFO_value(xis, i);
|
||||
if (xi->crl == NULL)
|
||||
continue;
|
||||
if (!X509_STORE_add_crl(store, xi->crl)) {
|
||||
tls_set_error(ctx, "failed to add crl");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
X509_STORE_set_flags(store,
|
||||
X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
||||
}
|
||||
|
||||
done:
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
sk_X509_INFO_pop_free(xis, X509_INFO_free);
|
||||
BIO_free(bio);
|
||||
free(ca_free);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
void
|
||||
tls_free(struct tls *ctx)
|
||||
{
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
|
||||
tls_reset(ctx);
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void
|
||||
tls_reset(struct tls *ctx)
|
||||
{
|
||||
struct tls_sni_ctx *sni, *nsni;
|
||||
|
||||
tls_config_free(ctx->config);
|
||||
ctx->config = NULL;
|
||||
|
||||
SSL_CTX_free(ctx->ssl_ctx);
|
||||
SSL_free(ctx->ssl_conn);
|
||||
X509_free(ctx->ssl_peer_cert);
|
||||
|
||||
ctx->ssl_conn = NULL;
|
||||
ctx->ssl_ctx = NULL;
|
||||
ctx->ssl_peer_cert = NULL;
|
||||
/* X509 objects in chain are freed with the SSL */
|
||||
ctx->ssl_peer_chain = NULL;
|
||||
|
||||
ctx->socket = -1;
|
||||
ctx->state = 0;
|
||||
|
||||
free(ctx->servername);
|
||||
ctx->servername = NULL;
|
||||
|
||||
free(ctx->error.msg);
|
||||
ctx->error.msg = NULL;
|
||||
ctx->error.num = -1;
|
||||
|
||||
tls_conninfo_free(ctx->conninfo);
|
||||
ctx->conninfo = NULL;
|
||||
|
||||
tls_ocsp_free(ctx->ocsp);
|
||||
ctx->ocsp = NULL;
|
||||
|
||||
for (sni = ctx->sni_ctx; sni != NULL; sni = nsni) {
|
||||
nsni = sni->next;
|
||||
tls_sni_ctx_free(sni);
|
||||
}
|
||||
ctx->sni_ctx = NULL;
|
||||
|
||||
ctx->read_cb = NULL;
|
||||
ctx->write_cb = NULL;
|
||||
ctx->cb_arg = NULL;
|
||||
}
|
||||
|
||||
int
|
||||
tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret, const char *prefix)
|
||||
{
|
||||
const char *errstr = "unknown error";
|
||||
unsigned long err;
|
||||
int ssl_err;
|
||||
|
||||
ssl_err = SSL_get_error(ssl_conn, ssl_ret);
|
||||
switch (ssl_err) {
|
||||
case SSL_ERROR_NONE:
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return (0);
|
||||
|
||||
case SSL_ERROR_WANT_READ:
|
||||
return (TLS_WANT_POLLIN);
|
||||
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
return (TLS_WANT_POLLOUT);
|
||||
|
||||
case SSL_ERROR_SYSCALL:
|
||||
if ((err = ERR_peek_error()) != 0) {
|
||||
errstr = ERR_error_string(err, NULL);
|
||||
} else if (ssl_ret == 0) {
|
||||
if ((ctx->state & TLS_HANDSHAKE_COMPLETE) != 0) {
|
||||
ctx->state |= TLS_EOF_NO_CLOSE_NOTIFY;
|
||||
return (0);
|
||||
}
|
||||
errstr = "unexpected EOF";
|
||||
} else if (ssl_ret == -1) {
|
||||
errstr = strerror(errno);
|
||||
}
|
||||
tls_set_ssl_errorx(ctx, "%s failed: %s", prefix, errstr);
|
||||
return (-1);
|
||||
|
||||
case SSL_ERROR_SSL:
|
||||
if ((err = ERR_peek_error()) != 0) {
|
||||
errstr = ERR_error_string(err, NULL);
|
||||
}
|
||||
tls_set_ssl_errorx(ctx, "%s failed: %s", prefix, errstr);
|
||||
return (-1);
|
||||
|
||||
case SSL_ERROR_WANT_CONNECT:
|
||||
case SSL_ERROR_WANT_ACCEPT:
|
||||
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||
default:
|
||||
tls_set_ssl_errorx(ctx, "%s failed (%d)", prefix, ssl_err);
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
tls_handshake(struct tls *ctx)
|
||||
{
|
||||
int rv = -1;
|
||||
|
||||
tls_error_clear(&ctx->error);
|
||||
|
||||
if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) {
|
||||
tls_set_errorx(ctx, "invalid operation for context");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((ctx->state & TLS_HANDSHAKE_COMPLETE) != 0) {
|
||||
tls_set_errorx(ctx, "handshake already completed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((ctx->flags & TLS_CLIENT) != 0)
|
||||
rv = tls_handshake_client(ctx);
|
||||
else if ((ctx->flags & TLS_SERVER_CONN) != 0)
|
||||
rv = tls_handshake_server(ctx);
|
||||
|
||||
if (rv == 0) {
|
||||
ctx->ssl_peer_cert = SSL_get_peer_certificate(ctx->ssl_conn);
|
||||
ctx->ssl_peer_chain = SSL_get_peer_cert_chain(ctx->ssl_conn);
|
||||
if (tls_conninfo_populate(ctx) == -1)
|
||||
rv = -1;
|
||||
if (ctx->ocsp == NULL)
|
||||
ctx->ocsp = tls_ocsp_setup_from_peer(ctx);
|
||||
}
|
||||
out:
|
||||
/* Prevent callers from performing incorrect error handling */
|
||||
errno = 0;
|
||||
return (rv);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
tls_read(struct tls *ctx, void *buf, size_t buflen)
|
||||
{
|
||||
ssize_t rv = -1;
|
||||
int ssl_ret;
|
||||
|
||||
tls_error_clear(&ctx->error);
|
||||
|
||||
if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0) {
|
||||
if ((rv = tls_handshake(ctx)) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (buflen > INT_MAX) {
|
||||
tls_set_errorx(ctx, "buflen too long");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if ((ssl_ret = SSL_read(ctx->ssl_conn, buf, buflen)) > 0) {
|
||||
rv = (ssize_t)ssl_ret;
|
||||
goto out;
|
||||
}
|
||||
rv = (ssize_t)tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "read");
|
||||
|
||||
out:
|
||||
/* Prevent callers from performing incorrect error handling */
|
||||
errno = 0;
|
||||
return (rv);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
tls_write(struct tls *ctx, const void *buf, size_t buflen)
|
||||
{
|
||||
ssize_t rv = -1;
|
||||
int ssl_ret;
|
||||
|
||||
tls_error_clear(&ctx->error);
|
||||
|
||||
if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0) {
|
||||
if ((rv = tls_handshake(ctx)) != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (buflen > INT_MAX) {
|
||||
tls_set_errorx(ctx, "buflen too long");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if ((ssl_ret = SSL_write(ctx->ssl_conn, buf, buflen)) > 0) {
|
||||
rv = (ssize_t)ssl_ret;
|
||||
goto out;
|
||||
}
|
||||
rv = (ssize_t)tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "write");
|
||||
|
||||
out:
|
||||
/* Prevent callers from performing incorrect error handling */
|
||||
errno = 0;
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_close(struct tls *ctx)
|
||||
{
|
||||
int ssl_ret;
|
||||
int rv = 0;
|
||||
|
||||
tls_error_clear(&ctx->error);
|
||||
|
||||
if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) {
|
||||
tls_set_errorx(ctx, "invalid operation for context");
|
||||
rv = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ctx->state & TLS_SSL_NEEDS_SHUTDOWN) {
|
||||
ERR_clear_error();
|
||||
ssl_ret = SSL_shutdown(ctx->ssl_conn);
|
||||
if (ssl_ret < 0) {
|
||||
rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret,
|
||||
"shutdown");
|
||||
if (rv == TLS_WANT_POLLIN || rv == TLS_WANT_POLLOUT)
|
||||
goto out;
|
||||
}
|
||||
ctx->state &= ~TLS_SSL_NEEDS_SHUTDOWN;
|
||||
}
|
||||
|
||||
if (ctx->socket != -1) {
|
||||
if (shutdown(ctx->socket, SHUT_RDWR) != 0) {
|
||||
if (rv == 0 &&
|
||||
errno != ENOTCONN && errno != ECONNRESET) {
|
||||
tls_set_error(ctx, "shutdown");
|
||||
rv = -1;
|
||||
}
|
||||
}
|
||||
if (close(ctx->socket) != 0) {
|
||||
if (rv == 0) {
|
||||
tls_set_error(ctx, "close");
|
||||
rv = -1;
|
||||
}
|
||||
}
|
||||
ctx->socket = -1;
|
||||
}
|
||||
|
||||
if ((ctx->state & TLS_EOF_NO_CLOSE_NOTIFY) != 0) {
|
||||
tls_set_errorx(ctx, "EOF without close notify");
|
||||
rv = -1;
|
||||
}
|
||||
|
||||
out:
|
||||
/* Prevent callers from performing incorrect error handling */
|
||||
errno = 0;
|
||||
return (rv);
|
||||
}
|
171
tls/tls_bio_cb.c
Normal file
171
tls/tls_bio_cb.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/* $OpenBSD: tls_bio_cb.c,v 1.21 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2016 Tobias Pape <tobias@netshed.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
static int bio_cb_write(BIO *bio, const char *buf, int num);
|
||||
static int bio_cb_read(BIO *bio, char *buf, int size);
|
||||
static int bio_cb_puts(BIO *bio, const char *str);
|
||||
static long bio_cb_ctrl(BIO *bio, int cmd, long num, void *ptr);
|
||||
|
||||
static BIO_METHOD *bio_cb_method;
|
||||
|
||||
static pthread_mutex_t bio_cb_method_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void
|
||||
bio_cb_method_init(void)
|
||||
{
|
||||
BIO_METHOD *bio_method;
|
||||
|
||||
if (bio_cb_method != NULL)
|
||||
return;
|
||||
|
||||
bio_method = BIO_meth_new(BIO_TYPE_MEM, "libtls_callbacks");
|
||||
if (bio_method == NULL)
|
||||
return;
|
||||
|
||||
BIO_meth_set_write(bio_method, bio_cb_write);
|
||||
BIO_meth_set_read(bio_method, bio_cb_read);
|
||||
BIO_meth_set_puts(bio_method, bio_cb_puts);
|
||||
BIO_meth_set_ctrl(bio_method, bio_cb_ctrl);
|
||||
|
||||
bio_cb_method = bio_method;
|
||||
}
|
||||
|
||||
static BIO_METHOD *
|
||||
bio_s_cb(void)
|
||||
{
|
||||
if (bio_cb_method != NULL)
|
||||
return (bio_cb_method);
|
||||
|
||||
pthread_mutex_lock(&bio_cb_method_lock);
|
||||
bio_cb_method_init();
|
||||
pthread_mutex_unlock(&bio_cb_method_lock);
|
||||
|
||||
return (bio_cb_method);
|
||||
}
|
||||
|
||||
static int
|
||||
bio_cb_puts(BIO *bio, const char *str)
|
||||
{
|
||||
return (bio_cb_write(bio, str, strlen(str)));
|
||||
}
|
||||
|
||||
static long
|
||||
bio_cb_ctrl(BIO *bio, int cmd, long num, void *ptr)
|
||||
{
|
||||
long ret = 1;
|
||||
|
||||
switch (cmd) {
|
||||
case BIO_CTRL_GET_CLOSE:
|
||||
ret = (long)BIO_get_shutdown(bio);
|
||||
break;
|
||||
case BIO_CTRL_SET_CLOSE:
|
||||
BIO_set_shutdown(bio, (int)num);
|
||||
break;
|
||||
case BIO_CTRL_DUP:
|
||||
case BIO_CTRL_FLUSH:
|
||||
break;
|
||||
case BIO_CTRL_INFO:
|
||||
case BIO_CTRL_GET:
|
||||
case BIO_CTRL_SET:
|
||||
default:
|
||||
ret = BIO_ctrl(BIO_next(bio), cmd, num, ptr);
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
static int
|
||||
bio_cb_write(BIO *bio, const char *buf, int num)
|
||||
{
|
||||
struct tls *ctx = BIO_get_data(bio);
|
||||
int rv;
|
||||
|
||||
BIO_clear_retry_flags(bio);
|
||||
rv = (ctx->write_cb)(ctx, buf, num, ctx->cb_arg);
|
||||
if (rv == TLS_WANT_POLLIN) {
|
||||
BIO_set_retry_read(bio);
|
||||
rv = -1;
|
||||
} else if (rv == TLS_WANT_POLLOUT) {
|
||||
BIO_set_retry_write(bio);
|
||||
rv = -1;
|
||||
}
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static int
|
||||
bio_cb_read(BIO *bio, char *buf, int size)
|
||||
{
|
||||
struct tls *ctx = BIO_get_data(bio);
|
||||
int rv;
|
||||
|
||||
BIO_clear_retry_flags(bio);
|
||||
rv = (ctx->read_cb)(ctx, buf, size, ctx->cb_arg);
|
||||
if (rv == TLS_WANT_POLLIN) {
|
||||
BIO_set_retry_read(bio);
|
||||
rv = -1;
|
||||
} else if (rv == TLS_WANT_POLLOUT) {
|
||||
BIO_set_retry_write(bio);
|
||||
rv = -1;
|
||||
}
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_cbs(struct tls *ctx, tls_read_cb read_cb, tls_write_cb write_cb,
|
||||
void *cb_arg)
|
||||
{
|
||||
const BIO_METHOD *bio_cb;
|
||||
BIO *bio;
|
||||
int rv = -1;
|
||||
|
||||
if (read_cb == NULL || write_cb == NULL) {
|
||||
tls_set_errorx(ctx, "no callbacks provided");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->read_cb = read_cb;
|
||||
ctx->write_cb = write_cb;
|
||||
ctx->cb_arg = cb_arg;
|
||||
|
||||
if ((bio_cb = bio_s_cb()) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to create callback method");
|
||||
goto err;
|
||||
}
|
||||
if ((bio = BIO_new(bio_cb)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to create callback i/o");
|
||||
goto err;
|
||||
}
|
||||
BIO_set_data(bio, ctx);
|
||||
BIO_set_init(bio, 1);
|
||||
|
||||
SSL_set_bio(ctx->ssl_conn, bio, bio);
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
return (rv);
|
||||
}
|
485
tls/tls_client.c
Normal file
485
tls/tls_client.c
Normal file
@@ -0,0 +1,485 @@
|
||||
/* $OpenBSD: tls_client.c,v 1.49 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
struct tls *
|
||||
tls_client(void)
|
||||
{
|
||||
struct tls *ctx;
|
||||
|
||||
if (tls_init() == -1)
|
||||
return (NULL);
|
||||
|
||||
if ((ctx = tls_new()) == NULL)
|
||||
return (NULL);
|
||||
|
||||
ctx->flags |= TLS_CLIENT;
|
||||
|
||||
return (ctx);
|
||||
}
|
||||
|
||||
int
|
||||
tls_connect(struct tls *ctx, const char *host, const char *port)
|
||||
{
|
||||
return tls_connect_servername(ctx, host, port, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
tls_connect_servername(struct tls *ctx, const char *host, const char *port,
|
||||
const char *servername)
|
||||
{
|
||||
struct addrinfo hints, *res, *res0;
|
||||
const char *h = NULL, *p = NULL;
|
||||
char *hs = NULL, *ps = NULL;
|
||||
int rv = -1, s = -1, ret;
|
||||
|
||||
if ((ctx->flags & TLS_CLIENT) == 0) {
|
||||
tls_set_errorx(ctx, "not a client context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (host == NULL) {
|
||||
tls_set_errorx(ctx, "host not specified");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* If port is NULL, try to extract a port from the specified host. */
|
||||
if (port == NULL) {
|
||||
ret = tls_host_port(host, &hs, &ps);
|
||||
if (ret == -1) {
|
||||
tls_set_errorx(ctx, "memory allocation failure");
|
||||
goto err;
|
||||
}
|
||||
if (ret != 0) {
|
||||
tls_set_errorx(ctx, "no port provided");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
h = (hs != NULL) ? hs : host;
|
||||
p = (ps != NULL) ? ps : port;
|
||||
|
||||
/*
|
||||
* First check if the host is specified as a numeric IP address,
|
||||
* either IPv4 or IPv6, before trying to resolve the host.
|
||||
* The AI_ADDRCONFIG resolver option will not return IPv4 or IPv6
|
||||
* records if it is not configured on an interface; not considering
|
||||
* loopback addresses. Checking the numeric addresses first makes
|
||||
* sure that connection attempts to numeric addresses and especially
|
||||
* 127.0.0.1 or ::1 loopback addresses are always possible.
|
||||
*/
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* try as an IPv4 literal */
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
if (getaddrinfo(h, p, &hints, &res0) != 0) {
|
||||
/* try again as an IPv6 literal */
|
||||
hints.ai_family = AF_INET6;
|
||||
if (getaddrinfo(h, p, &hints, &res0) != 0) {
|
||||
/* last try, with name resolution and save the error */
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
if ((s = getaddrinfo(h, p, &hints, &res0)) != 0) {
|
||||
tls_set_error(ctx, "%s", gai_strerror(s));
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* It was resolved somehow; now try connecting to what we got */
|
||||
s = -1;
|
||||
for (res = res0; res; res = res->ai_next) {
|
||||
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (s == -1) {
|
||||
tls_set_error(ctx, "socket");
|
||||
continue;
|
||||
}
|
||||
if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
|
||||
tls_set_error(ctx, "connect");
|
||||
close(s);
|
||||
s = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break; /* Connected. */
|
||||
}
|
||||
freeaddrinfo(res0);
|
||||
|
||||
if (s == -1)
|
||||
goto err;
|
||||
|
||||
if (servername == NULL)
|
||||
servername = h;
|
||||
|
||||
if (tls_connect_socket(ctx, s, servername) != 0) {
|
||||
close(s);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->socket = s;
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
free(hs);
|
||||
free(ps);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_client_read_session(struct tls *ctx)
|
||||
{
|
||||
int sfd = ctx->config->session_fd;
|
||||
uint8_t *session = NULL;
|
||||
size_t session_len = 0;
|
||||
SSL_SESSION *ss = NULL;
|
||||
BIO *bio = NULL;
|
||||
struct stat sb;
|
||||
ssize_t n;
|
||||
int rv = -1;
|
||||
|
||||
if (fstat(sfd, &sb) == -1) {
|
||||
tls_set_error(ctx, "failed to stat session file");
|
||||
goto err;
|
||||
}
|
||||
if (sb.st_size < 0 || sb.st_size > INT_MAX) {
|
||||
tls_set_errorx(ctx, "invalid session file size");
|
||||
goto err;
|
||||
}
|
||||
session_len = (size_t)sb.st_size;
|
||||
|
||||
/* A zero size file means that we do not yet have a valid session. */
|
||||
if (session_len == 0)
|
||||
goto done;
|
||||
|
||||
if ((session = malloc(session_len)) == NULL)
|
||||
goto err;
|
||||
|
||||
n = pread(sfd, session, session_len, 0);
|
||||
if (n < 0 || (size_t)n != session_len) {
|
||||
tls_set_error(ctx, "failed to read session file");
|
||||
goto err;
|
||||
}
|
||||
if ((bio = BIO_new_mem_buf(session, session_len)) == NULL)
|
||||
goto err;
|
||||
if ((ss = PEM_read_bio_SSL_SESSION(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
tls_set_errorx(ctx, "failed to parse session");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (SSL_set_session(ctx->ssl_conn, ss) != 1) {
|
||||
tls_set_errorx(ctx, "failed to set session");
|
||||
goto err;
|
||||
}
|
||||
|
||||
done:
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
freezero(session, session_len);
|
||||
SSL_SESSION_free(ss);
|
||||
BIO_free(bio);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_client_write_session(struct tls *ctx)
|
||||
{
|
||||
int sfd = ctx->config->session_fd;
|
||||
SSL_SESSION *ss = NULL;
|
||||
BIO *bio = NULL;
|
||||
long data_len;
|
||||
char *data;
|
||||
off_t offset;
|
||||
size_t len;
|
||||
ssize_t n;
|
||||
int rv = -1;
|
||||
|
||||
if ((ss = SSL_get1_session(ctx->ssl_conn)) == NULL) {
|
||||
if (ftruncate(sfd, 0) == -1) {
|
||||
tls_set_error(ctx, "failed to truncate session file");
|
||||
goto err;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
||||
goto err;
|
||||
if (PEM_write_bio_SSL_SESSION(bio, ss) == 0)
|
||||
goto err;
|
||||
if ((data_len = BIO_get_mem_data(bio, &data)) <= 0)
|
||||
goto err;
|
||||
|
||||
len = (size_t)data_len;
|
||||
offset = 0;
|
||||
|
||||
if (ftruncate(sfd, len) == -1) {
|
||||
tls_set_error(ctx, "failed to truncate session file");
|
||||
goto err;
|
||||
}
|
||||
while (len > 0) {
|
||||
if ((n = pwrite(sfd, data + offset, len, offset)) == -1) {
|
||||
tls_set_error(ctx, "failed to write session file");
|
||||
goto err;
|
||||
}
|
||||
offset += n;
|
||||
len -= n;
|
||||
}
|
||||
|
||||
done:
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
SSL_SESSION_free(ss);
|
||||
BIO_free_all(bio);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_connect_common(struct tls *ctx, const char *servername)
|
||||
{
|
||||
union tls_addr addrbuf;
|
||||
size_t servername_len;
|
||||
int rv = -1;
|
||||
|
||||
if ((ctx->flags & TLS_CLIENT) == 0) {
|
||||
tls_set_errorx(ctx, "not a client context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (servername != NULL) {
|
||||
if ((ctx->servername = strdup(servername)) == NULL) {
|
||||
tls_set_errorx(ctx, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there's a trailing dot, remove it. While an FQDN includes
|
||||
* the terminating dot representing the zero-length label of
|
||||
* the root (RFC 8499, section 2), the SNI explicitly does not
|
||||
* include it (RFC 6066, section 3).
|
||||
*/
|
||||
servername_len = strlen(ctx->servername);
|
||||
if (servername_len > 0 &&
|
||||
ctx->servername[servername_len - 1] == '.')
|
||||
ctx->servername[servername_len - 1] = '\0';
|
||||
}
|
||||
|
||||
if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
|
||||
tls_set_errorx(ctx, "ssl context failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_configure_ssl(ctx, ctx->ssl_ctx) != 0)
|
||||
goto err;
|
||||
|
||||
if (tls_configure_ssl_keypair(ctx, ctx->ssl_ctx,
|
||||
ctx->config->keypair, 0) != 0)
|
||||
goto err;
|
||||
|
||||
if (ctx->config->verify_name) {
|
||||
if (ctx->servername == NULL) {
|
||||
tls_set_errorx(ctx, "server name not specified");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (tls_configure_ssl_verify(ctx, ctx->ssl_ctx, SSL_VERIFY_PEER) == -1)
|
||||
goto err;
|
||||
|
||||
if (ctx->config->ecdhecurves != NULL) {
|
||||
if (SSL_CTX_set1_groups(ctx->ssl_ctx, ctx->config->ecdhecurves,
|
||||
ctx->config->ecdhecurves_len) != 1) {
|
||||
tls_set_errorx(ctx, "failed to set ecdhe curves");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_verify_cb) != 1) {
|
||||
tls_set_errorx(ctx, "ssl OCSP verification setup failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) {
|
||||
tls_set_errorx(ctx, "ssl connection failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (SSL_set_app_data(ctx->ssl_conn, ctx) != 1) {
|
||||
tls_set_errorx(ctx, "ssl application data failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->config->session_fd != -1) {
|
||||
SSL_clear_options(ctx->ssl_conn, SSL_OP_NO_TICKET);
|
||||
if (tls_client_read_session(ctx) == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) {
|
||||
tls_set_errorx(ctx, "ssl OCSP extension setup failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 6066 (SNI): Literal IPv4 and IPv6 addresses are not
|
||||
* permitted in "HostName".
|
||||
*/
|
||||
if (ctx->servername != NULL &&
|
||||
inet_pton(AF_INET, ctx->servername, &addrbuf) != 1 &&
|
||||
inet_pton(AF_INET6, ctx->servername, &addrbuf) != 1) {
|
||||
if (SSL_set_tlsext_host_name(ctx->ssl_conn,
|
||||
ctx->servername) == 0) {
|
||||
tls_set_errorx(ctx, "server name indication failure");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->state |= TLS_CONNECTED;
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_connect_socket(struct tls *ctx, int s, const char *servername)
|
||||
{
|
||||
return tls_connect_fds(ctx, s, s, servername);
|
||||
}
|
||||
|
||||
int
|
||||
tls_connect_fds(struct tls *ctx, int fd_read, int fd_write,
|
||||
const char *servername)
|
||||
{
|
||||
int rv = -1;
|
||||
|
||||
if (fd_read < 0 || fd_write < 0) {
|
||||
tls_set_errorx(ctx, "invalid file descriptors");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_connect_common(ctx, servername) != 0)
|
||||
goto err;
|
||||
|
||||
if (SSL_set_rfd(ctx->ssl_conn, fd_read) != 1 ||
|
||||
SSL_set_wfd(ctx->ssl_conn, fd_write) != 1) {
|
||||
tls_set_errorx(ctx, "ssl file descriptor failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
err:
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_connect_cbs(struct tls *ctx, tls_read_cb read_cb,
|
||||
tls_write_cb write_cb, void *cb_arg, const char *servername)
|
||||
{
|
||||
int rv = -1;
|
||||
|
||||
if (tls_connect_common(ctx, servername) != 0)
|
||||
goto err;
|
||||
|
||||
if (tls_set_cbs(ctx, read_cb, write_cb, cb_arg) != 0)
|
||||
goto err;
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_handshake_client(struct tls *ctx)
|
||||
{
|
||||
X509 *cert = NULL;
|
||||
int match, ssl_ret;
|
||||
int rv = -1;
|
||||
|
||||
if ((ctx->flags & TLS_CLIENT) == 0) {
|
||||
tls_set_errorx(ctx, "not a client context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((ctx->state & TLS_CONNECTED) == 0) {
|
||||
tls_set_errorx(ctx, "context not connected");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->state |= TLS_SSL_NEEDS_SHUTDOWN;
|
||||
|
||||
ERR_clear_error();
|
||||
if ((ssl_ret = SSL_connect(ctx->ssl_conn)) != 1) {
|
||||
rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "handshake");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->config->verify_name) {
|
||||
cert = SSL_get_peer_certificate(ctx->ssl_conn);
|
||||
if (cert == NULL) {
|
||||
tls_set_errorx(ctx, "no server certificate");
|
||||
goto err;
|
||||
}
|
||||
if (tls_check_name(ctx, cert, ctx->servername, &match) == -1)
|
||||
goto err;
|
||||
if (!match) {
|
||||
tls_set_errorx(ctx, "name `%s' not present in"
|
||||
" server certificate", ctx->servername);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->state |= TLS_HANDSHAKE_COMPLETE;
|
||||
|
||||
if (ctx->config->session_fd != -1) {
|
||||
if (tls_client_write_session(ctx) == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
X509_free(cert);
|
||||
|
||||
return (rv);
|
||||
}
|
930
tls/tls_config.c
Normal file
930
tls/tls_config.c
Normal file
@@ -0,0 +1,930 @@
|
||||
/* $OpenBSD: tls_config.c,v 1.67 2023/07/02 06:37:27 beck Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <tls.h>
|
||||
|
||||
#include "tls_internal.h"
|
||||
|
||||
static const char default_ca_file[] = TLS_DEFAULT_CA_FILE;
|
||||
|
||||
const char *
|
||||
tls_default_ca_cert_file(void)
|
||||
{
|
||||
return default_ca_file;
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_load_file(struct tls_error *error, const char *filetype,
|
||||
const char *filename, char **buf, size_t *len)
|
||||
{
|
||||
struct stat st;
|
||||
int fd = -1;
|
||||
ssize_t n;
|
||||
|
||||
free(*buf);
|
||||
*buf = NULL;
|
||||
*len = 0;
|
||||
|
||||
if ((fd = open(filename, O_RDONLY)) == -1) {
|
||||
tls_error_set(error, "failed to open %s file '%s'",
|
||||
filetype, filename);
|
||||
goto err;
|
||||
}
|
||||
if (fstat(fd, &st) != 0) {
|
||||
tls_error_set(error, "failed to stat %s file '%s'",
|
||||
filetype, filename);
|
||||
goto err;
|
||||
}
|
||||
if (st.st_size < 0)
|
||||
goto err;
|
||||
*len = (size_t)st.st_size;
|
||||
if ((*buf = malloc(*len)) == NULL) {
|
||||
tls_error_set(error, "failed to allocate buffer for "
|
||||
"%s file", filetype);
|
||||
goto err;
|
||||
}
|
||||
n = read(fd, *buf, *len);
|
||||
if (n < 0 || (size_t)n != *len) {
|
||||
tls_error_set(error, "failed to read %s file '%s'",
|
||||
filetype, filename);
|
||||
goto err;
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
freezero(*buf, *len);
|
||||
*buf = NULL;
|
||||
*len = 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct tls_config *
|
||||
tls_config_new_internal(void)
|
||||
{
|
||||
struct tls_config *config;
|
||||
unsigned char sid[TLS_MAX_SESSION_ID_LENGTH];
|
||||
|
||||
if ((config = calloc(1, sizeof(*config))) == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (pthread_mutex_init(&config->mutex, NULL) != 0)
|
||||
goto err;
|
||||
|
||||
config->refcount = 1;
|
||||
config->session_fd = -1;
|
||||
|
||||
if ((config->keypair = tls_keypair_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Default configuration.
|
||||
*/
|
||||
if (tls_config_set_dheparams(config, "none") != 0)
|
||||
goto err;
|
||||
if (tls_config_set_ecdhecurves(config, "default") != 0)
|
||||
goto err;
|
||||
if (tls_config_set_ciphers(config, "secure") != 0)
|
||||
goto err;
|
||||
|
||||
if (tls_config_set_protocols(config, TLS_PROTOCOLS_DEFAULT) != 0)
|
||||
goto err;
|
||||
if (tls_config_set_verify_depth(config, 6) != 0)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Set session ID context to a random value. For the simple case
|
||||
* of a single process server this is good enough. For multiprocess
|
||||
* servers the session ID needs to be set by the caller.
|
||||
*/
|
||||
arc4random_buf(sid, sizeof(sid));
|
||||
if (tls_config_set_session_id(config, sid, sizeof(sid)) != 0)
|
||||
goto err;
|
||||
config->ticket_keyrev = arc4random();
|
||||
config->ticket_autorekey = 1;
|
||||
|
||||
tls_config_prefer_ciphers_server(config);
|
||||
|
||||
tls_config_verify(config);
|
||||
|
||||
return (config);
|
||||
|
||||
err:
|
||||
tls_config_free(config);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
struct tls_config *
|
||||
tls_config_new(void)
|
||||
{
|
||||
if (tls_init() == -1)
|
||||
return (NULL);
|
||||
|
||||
return tls_config_new_internal();
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_free(struct tls_config *config)
|
||||
{
|
||||
struct tls_keypair *kp, *nkp;
|
||||
int refcount;
|
||||
|
||||
if (config == NULL)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&config->mutex);
|
||||
refcount = --config->refcount;
|
||||
pthread_mutex_unlock(&config->mutex);
|
||||
|
||||
if (refcount > 0)
|
||||
return;
|
||||
|
||||
for (kp = config->keypair; kp != NULL; kp = nkp) {
|
||||
nkp = kp->next;
|
||||
tls_keypair_free(kp);
|
||||
}
|
||||
|
||||
free(config->error.msg);
|
||||
|
||||
free(config->alpn);
|
||||
free((char *)config->ca_mem);
|
||||
free((char *)config->ca_path);
|
||||
free((char *)config->ciphers);
|
||||
free((char *)config->crl_mem);
|
||||
free(config->ecdhecurves);
|
||||
|
||||
pthread_mutex_destroy(&config->mutex);
|
||||
|
||||
free(config);
|
||||
}
|
||||
|
||||
static void
|
||||
tls_config_keypair_add(struct tls_config *config, struct tls_keypair *keypair)
|
||||
{
|
||||
struct tls_keypair *kp;
|
||||
|
||||
kp = config->keypair;
|
||||
while (kp->next != NULL)
|
||||
kp = kp->next;
|
||||
|
||||
kp->next = keypair;
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_config_error(struct tls_config *config)
|
||||
{
|
||||
return config->error.msg;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_clear_keys(struct tls_config *config)
|
||||
{
|
||||
struct tls_keypair *kp;
|
||||
|
||||
for (kp = config->keypair; kp != NULL; kp = kp->next)
|
||||
tls_keypair_clear_key(kp);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_parse_protocols(uint32_t *protocols, const char *protostr)
|
||||
{
|
||||
uint32_t proto, protos = 0;
|
||||
char *s, *p, *q;
|
||||
int negate;
|
||||
|
||||
if (protostr == NULL) {
|
||||
*protocols = TLS_PROTOCOLS_DEFAULT;
|
||||
return (0);
|
||||
}
|
||||
|
||||
if ((s = strdup(protostr)) == NULL)
|
||||
return (-1);
|
||||
|
||||
q = s;
|
||||
while ((p = strsep(&q, ",:")) != NULL) {
|
||||
while (*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
|
||||
negate = 0;
|
||||
if (*p == '!') {
|
||||
negate = 1;
|
||||
p++;
|
||||
}
|
||||
|
||||
if (negate && protos == 0)
|
||||
protos = TLS_PROTOCOLS_ALL;
|
||||
|
||||
proto = 0;
|
||||
if (strcasecmp(p, "all") == 0 ||
|
||||
strcasecmp(p, "legacy") == 0)
|
||||
proto = TLS_PROTOCOLS_ALL;
|
||||
else if (strcasecmp(p, "default") == 0 ||
|
||||
strcasecmp(p, "secure") == 0)
|
||||
proto = TLS_PROTOCOLS_DEFAULT;
|
||||
if (strcasecmp(p, "tlsv1") == 0)
|
||||
proto = TLS_PROTOCOL_TLSv1;
|
||||
else if (strcasecmp(p, "tlsv1.0") == 0)
|
||||
proto = TLS_PROTOCOL_TLSv1_2;
|
||||
else if (strcasecmp(p, "tlsv1.1") == 0)
|
||||
proto = TLS_PROTOCOL_TLSv1_2;
|
||||
else if (strcasecmp(p, "tlsv1.2") == 0)
|
||||
proto = TLS_PROTOCOL_TLSv1_2;
|
||||
else if (strcasecmp(p, "tlsv1.3") == 0)
|
||||
proto = TLS_PROTOCOL_TLSv1_3;
|
||||
|
||||
if (proto == 0) {
|
||||
free(s);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (negate)
|
||||
protos &= ~proto;
|
||||
else
|
||||
protos |= proto;
|
||||
}
|
||||
|
||||
*protocols = protos;
|
||||
|
||||
free(s);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_config_parse_alpn(struct tls_config *config, const char *alpn,
|
||||
char **alpn_data, size_t *alpn_len)
|
||||
{
|
||||
size_t buf_len, i, len;
|
||||
char *buf = NULL;
|
||||
char *s = NULL;
|
||||
char *p, *q;
|
||||
|
||||
free(*alpn_data);
|
||||
*alpn_data = NULL;
|
||||
*alpn_len = 0;
|
||||
|
||||
if ((buf_len = strlen(alpn) + 1) > 65535) {
|
||||
tls_config_set_errorx(config, "alpn too large");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((buf = malloc(buf_len)) == NULL) {
|
||||
tls_config_set_errorx(config, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((s = strdup(alpn)) == NULL) {
|
||||
tls_config_set_errorx(config, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
q = s;
|
||||
while ((p = strsep(&q, ",")) != NULL) {
|
||||
if ((len = strlen(p)) == 0) {
|
||||
tls_config_set_errorx(config,
|
||||
"alpn protocol with zero length");
|
||||
goto err;
|
||||
}
|
||||
if (len > 255) {
|
||||
tls_config_set_errorx(config,
|
||||
"alpn protocol too long");
|
||||
goto err;
|
||||
}
|
||||
buf[i++] = len & 0xff;
|
||||
memcpy(&buf[i], p, len);
|
||||
i += len;
|
||||
}
|
||||
|
||||
free(s);
|
||||
|
||||
*alpn_data = buf;
|
||||
*alpn_len = buf_len;
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
free(buf);
|
||||
free(s);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_alpn(struct tls_config *config, const char *alpn)
|
||||
{
|
||||
return tls_config_parse_alpn(config, alpn, &config->alpn,
|
||||
&config->alpn_len);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_config_add_keypair_file_internal(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file, const char *ocsp_file)
|
||||
{
|
||||
struct tls_keypair *keypair;
|
||||
|
||||
if ((keypair = tls_keypair_new()) == NULL)
|
||||
return (-1);
|
||||
if (tls_keypair_set_cert_file(keypair, &config->error, cert_file) != 0)
|
||||
goto err;
|
||||
if (key_file != NULL &&
|
||||
tls_keypair_set_key_file(keypair, &config->error, key_file) != 0)
|
||||
goto err;
|
||||
if (ocsp_file != NULL &&
|
||||
tls_keypair_set_ocsp_staple_file(keypair, &config->error,
|
||||
ocsp_file) != 0)
|
||||
goto err;
|
||||
|
||||
tls_config_keypair_add(config, keypair);
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
tls_keypair_free(keypair);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_config_add_keypair_mem_internal(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len,
|
||||
const uint8_t *staple, size_t staple_len)
|
||||
{
|
||||
struct tls_keypair *keypair;
|
||||
|
||||
if ((keypair = tls_keypair_new()) == NULL)
|
||||
return (-1);
|
||||
if (tls_keypair_set_cert_mem(keypair, &config->error, cert, cert_len) != 0)
|
||||
goto err;
|
||||
if (key != NULL &&
|
||||
tls_keypair_set_key_mem(keypair, &config->error, key, key_len) != 0)
|
||||
goto err;
|
||||
if (staple != NULL &&
|
||||
tls_keypair_set_ocsp_staple_mem(keypair, &config->error, staple,
|
||||
staple_len) != 0)
|
||||
goto err;
|
||||
|
||||
tls_config_keypair_add(config, keypair);
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
tls_keypair_free(keypair);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_add_keypair_mem(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len)
|
||||
{
|
||||
return tls_config_add_keypair_mem_internal(config, cert, cert_len, key,
|
||||
key_len, NULL, 0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_add_keypair_file(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file)
|
||||
{
|
||||
return tls_config_add_keypair_file_internal(config, cert_file,
|
||||
key_file, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_add_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len, const uint8_t *staple,
|
||||
size_t staple_len)
|
||||
{
|
||||
return tls_config_add_keypair_mem_internal(config, cert, cert_len, key,
|
||||
key_len, staple, staple_len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_add_keypair_ocsp_file(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file, const char *ocsp_file)
|
||||
{
|
||||
return tls_config_add_keypair_file_internal(config, cert_file,
|
||||
key_file, ocsp_file);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ca_file(struct tls_config *config, const char *ca_file)
|
||||
{
|
||||
return tls_config_load_file(&config->error, "CA", ca_file,
|
||||
&config->ca_mem, &config->ca_len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ca_path(struct tls_config *config, const char *ca_path)
|
||||
{
|
||||
return tls_set_string(&config->ca_path, ca_path);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ca_mem(struct tls_config *config, const uint8_t *ca, size_t len)
|
||||
{
|
||||
return tls_set_mem(&config->ca_mem, &config->ca_len, ca, len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_cert_file(struct tls_config *config, const char *cert_file)
|
||||
{
|
||||
return tls_keypair_set_cert_file(config->keypair, &config->error,
|
||||
cert_file);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_cert_mem(struct tls_config *config, const uint8_t *cert,
|
||||
size_t len)
|
||||
{
|
||||
return tls_keypair_set_cert_mem(config->keypair, &config->error,
|
||||
cert, len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ciphers(struct tls_config *config, const char *ciphers)
|
||||
{
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
|
||||
if (ciphers == NULL ||
|
||||
strcasecmp(ciphers, "default") == 0 ||
|
||||
strcasecmp(ciphers, "secure") == 0)
|
||||
ciphers = TLS_CIPHERS_DEFAULT;
|
||||
else if (strcasecmp(ciphers, "compat") == 0)
|
||||
ciphers = TLS_CIPHERS_COMPAT;
|
||||
else if (strcasecmp(ciphers, "legacy") == 0)
|
||||
ciphers = TLS_CIPHERS_LEGACY;
|
||||
else if (strcasecmp(ciphers, "all") == 0 ||
|
||||
strcasecmp(ciphers, "insecure") == 0)
|
||||
ciphers = TLS_CIPHERS_ALL;
|
||||
|
||||
if ((ssl_ctx = SSL_CTX_new(SSLv23_method())) == NULL) {
|
||||
tls_config_set_errorx(config, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers) != 1) {
|
||||
tls_config_set_errorx(config, "no ciphers for '%s'", ciphers);
|
||||
goto err;
|
||||
}
|
||||
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return tls_set_string(&config->ciphers, ciphers);
|
||||
|
||||
err:
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_crl_file(struct tls_config *config, const char *crl_file)
|
||||
{
|
||||
return tls_config_load_file(&config->error, "CRL", crl_file,
|
||||
&config->crl_mem, &config->crl_len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_crl_mem(struct tls_config *config, const uint8_t *crl,
|
||||
size_t len)
|
||||
{
|
||||
return tls_set_mem(&config->crl_mem, &config->crl_len, crl, len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_dheparams(struct tls_config *config, const char *params)
|
||||
{
|
||||
int keylen;
|
||||
|
||||
if (params == NULL || strcasecmp(params, "none") == 0)
|
||||
keylen = 0;
|
||||
else if (strcasecmp(params, "auto") == 0)
|
||||
keylen = -1;
|
||||
else if (strcasecmp(params, "legacy") == 0)
|
||||
keylen = 1024;
|
||||
else {
|
||||
tls_config_set_errorx(config, "invalid dhe param '%s'", params);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
config->dheparams = keylen;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ecdhecurve(struct tls_config *config, const char *curve)
|
||||
{
|
||||
if (curve == NULL ||
|
||||
strcasecmp(curve, "none") == 0 ||
|
||||
strcasecmp(curve, "auto") == 0) {
|
||||
curve = TLS_ECDHE_CURVES;
|
||||
} else if (strchr(curve, ',') != NULL || strchr(curve, ':') != NULL) {
|
||||
tls_config_set_errorx(config, "invalid ecdhe curve '%s'",
|
||||
curve);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return tls_config_set_ecdhecurves(config, curve);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ecdhecurves(struct tls_config *config, const char *curves)
|
||||
{
|
||||
int *curves_list = NULL, *curves_new;
|
||||
size_t curves_num = 0;
|
||||
char *cs = NULL;
|
||||
char *p, *q;
|
||||
int rv = -1;
|
||||
int nid;
|
||||
|
||||
free(config->ecdhecurves);
|
||||
config->ecdhecurves = NULL;
|
||||
config->ecdhecurves_len = 0;
|
||||
|
||||
if (curves == NULL || strcasecmp(curves, "default") == 0)
|
||||
curves = TLS_ECDHE_CURVES;
|
||||
|
||||
if ((cs = strdup(curves)) == NULL) {
|
||||
tls_config_set_errorx(config, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
q = cs;
|
||||
while ((p = strsep(&q, ",:")) != NULL) {
|
||||
while (*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
|
||||
nid = OBJ_sn2nid(p);
|
||||
if (nid == NID_undef)
|
||||
nid = OBJ_ln2nid(p);
|
||||
if (nid == NID_undef)
|
||||
nid = EC_curve_nist2nid(p);
|
||||
if (nid == NID_undef) {
|
||||
tls_config_set_errorx(config,
|
||||
"invalid ecdhe curve '%s'", p);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((curves_new = reallocarray(curves_list, curves_num + 1,
|
||||
sizeof(int))) == NULL) {
|
||||
tls_config_set_errorx(config, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
curves_list = curves_new;
|
||||
curves_list[curves_num] = nid;
|
||||
curves_num++;
|
||||
}
|
||||
|
||||
config->ecdhecurves = curves_list;
|
||||
config->ecdhecurves_len = curves_num;
|
||||
curves_list = NULL;
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
free(cs);
|
||||
free(curves_list);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_key_file(struct tls_config *config, const char *key_file)
|
||||
{
|
||||
return tls_keypair_set_key_file(config->keypair, &config->error,
|
||||
key_file);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_key_mem(struct tls_config *config, const uint8_t *key,
|
||||
size_t len)
|
||||
{
|
||||
return tls_keypair_set_key_mem(config->keypair, &config->error,
|
||||
key, len);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_config_set_keypair_file_internal(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file, const char *ocsp_file)
|
||||
{
|
||||
if (tls_config_set_cert_file(config, cert_file) != 0)
|
||||
return (-1);
|
||||
if (tls_config_set_key_file(config, key_file) != 0)
|
||||
return (-1);
|
||||
if (ocsp_file != NULL &&
|
||||
tls_config_set_ocsp_staple_file(config, ocsp_file) != 0)
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_config_set_keypair_mem_internal(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len,
|
||||
const uint8_t *staple, size_t staple_len)
|
||||
{
|
||||
if (tls_config_set_cert_mem(config, cert, cert_len) != 0)
|
||||
return (-1);
|
||||
if (tls_config_set_key_mem(config, key, key_len) != 0)
|
||||
return (-1);
|
||||
if ((staple != NULL) &&
|
||||
(tls_config_set_ocsp_staple_mem(config, staple, staple_len) != 0))
|
||||
return (-1);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_keypair_file(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file)
|
||||
{
|
||||
return tls_config_set_keypair_file_internal(config, cert_file, key_file,
|
||||
NULL);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_keypair_mem(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len)
|
||||
{
|
||||
return tls_config_set_keypair_mem_internal(config, cert, cert_len,
|
||||
key, key_len, NULL, 0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_keypair_ocsp_file(struct tls_config *config,
|
||||
const char *cert_file, const char *key_file, const char *ocsp_file)
|
||||
{
|
||||
return tls_config_set_keypair_file_internal(config, cert_file, key_file,
|
||||
ocsp_file);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len,
|
||||
const uint8_t *staple, size_t staple_len)
|
||||
{
|
||||
return tls_config_set_keypair_mem_internal(config, cert, cert_len,
|
||||
key, key_len, staple, staple_len);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
tls_config_set_protocols(struct tls_config *config, uint32_t protocols)
|
||||
{
|
||||
config->protocols = protocols;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_session_fd(struct tls_config *config, int session_fd)
|
||||
{
|
||||
struct stat sb;
|
||||
mode_t mugo;
|
||||
|
||||
if (session_fd == -1) {
|
||||
config->session_fd = session_fd;
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (fstat(session_fd, &sb) == -1) {
|
||||
tls_config_set_error(config, "failed to stat session file");
|
||||
return (-1);
|
||||
}
|
||||
if (!S_ISREG(sb.st_mode)) {
|
||||
tls_config_set_errorx(config,
|
||||
"session file is not a regular file");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (sb.st_uid != getuid()) {
|
||||
tls_config_set_errorx(config, "session file has incorrect "
|
||||
"owner (uid %u != %u)", sb.st_uid, getuid());
|
||||
return (-1);
|
||||
}
|
||||
mugo = sb.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO);
|
||||
if (mugo != (S_IRUSR|S_IWUSR)) {
|
||||
tls_config_set_errorx(config, "session file has incorrect "
|
||||
"permissions (%o != 600)", mugo);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
config->session_fd = session_fd;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_sign_cb(struct tls_config *config, tls_sign_cb cb, void *cb_arg)
|
||||
{
|
||||
config->use_fake_private_key = 1;
|
||||
config->skip_private_key_check = 1;
|
||||
config->sign_cb = cb;
|
||||
config->sign_cb_arg = cb_arg;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_verify_depth(struct tls_config *config, int verify_depth)
|
||||
{
|
||||
config->verify_depth = verify_depth;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_prefer_ciphers_client(struct tls_config *config)
|
||||
{
|
||||
config->ciphers_server = 0;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_prefer_ciphers_server(struct tls_config *config)
|
||||
{
|
||||
config->ciphers_server = 1;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_insecure_noverifycert(struct tls_config *config)
|
||||
{
|
||||
config->verify_cert = 0;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_insecure_noverifyname(struct tls_config *config)
|
||||
{
|
||||
config->verify_name = 0;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_insecure_noverifytime(struct tls_config *config)
|
||||
{
|
||||
config->verify_time = 0;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_verify(struct tls_config *config)
|
||||
{
|
||||
config->verify_cert = 1;
|
||||
config->verify_name = 1;
|
||||
config->verify_time = 1;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_ocsp_require_stapling(struct tls_config *config)
|
||||
{
|
||||
config->ocsp_require_stapling = 1;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_verify_client(struct tls_config *config)
|
||||
{
|
||||
config->verify_client = 1;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_verify_client_optional(struct tls_config *config)
|
||||
{
|
||||
config->verify_client = 2;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_skip_private_key_check(struct tls_config *config)
|
||||
{
|
||||
config->skip_private_key_check = 1;
|
||||
}
|
||||
|
||||
void
|
||||
tls_config_use_fake_private_key(struct tls_config *config)
|
||||
{
|
||||
config->use_fake_private_key = 1;
|
||||
config->skip_private_key_check = 1;
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ocsp_staple_file(struct tls_config *config, const char *staple_file)
|
||||
{
|
||||
return tls_keypair_set_ocsp_staple_file(config->keypair, &config->error,
|
||||
staple_file);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_ocsp_staple_mem(struct tls_config *config, const uint8_t *staple,
|
||||
size_t len)
|
||||
{
|
||||
return tls_keypair_set_ocsp_staple_mem(config->keypair, &config->error,
|
||||
staple, len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_session_id(struct tls_config *config,
|
||||
const unsigned char *session_id, size_t len)
|
||||
{
|
||||
if (len > TLS_MAX_SESSION_ID_LENGTH) {
|
||||
tls_config_set_errorx(config, "session ID too large");
|
||||
return (-1);
|
||||
}
|
||||
memset(config->session_id, 0, sizeof(config->session_id));
|
||||
memcpy(config->session_id, session_id, len);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_set_session_lifetime(struct tls_config *config, int lifetime)
|
||||
{
|
||||
if (lifetime > TLS_MAX_SESSION_TIMEOUT) {
|
||||
tls_config_set_errorx(config, "session lifetime too large");
|
||||
return (-1);
|
||||
}
|
||||
if (lifetime != 0 && lifetime < TLS_MIN_SESSION_TIMEOUT) {
|
||||
tls_config_set_errorx(config, "session lifetime too small");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
config->session_lifetime = lifetime;
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_add_ticket_key(struct tls_config *config, uint32_t keyrev,
|
||||
unsigned char *key, size_t keylen)
|
||||
{
|
||||
struct tls_ticket_key newkey;
|
||||
int i;
|
||||
|
||||
if (TLS_TICKET_KEY_SIZE != keylen ||
|
||||
sizeof(newkey.aes_key) + sizeof(newkey.hmac_key) > keylen) {
|
||||
tls_config_set_errorx(config,
|
||||
"wrong amount of ticket key data");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
keyrev = htonl(keyrev);
|
||||
memset(&newkey, 0, sizeof(newkey));
|
||||
memcpy(newkey.key_name, &keyrev, sizeof(keyrev));
|
||||
memcpy(newkey.aes_key, key, sizeof(newkey.aes_key));
|
||||
memcpy(newkey.hmac_key, key + sizeof(newkey.aes_key),
|
||||
sizeof(newkey.hmac_key));
|
||||
newkey.time = time(NULL);
|
||||
|
||||
for (i = 0; i < TLS_NUM_TICKETS; i++) {
|
||||
struct tls_ticket_key *tk = &config->ticket_keys[i];
|
||||
if (memcmp(newkey.key_name, tk->key_name,
|
||||
sizeof(tk->key_name)) != 0)
|
||||
continue;
|
||||
|
||||
/* allow re-entry of most recent key */
|
||||
if (i == 0 && memcmp(newkey.aes_key, tk->aes_key,
|
||||
sizeof(tk->aes_key)) == 0 && memcmp(newkey.hmac_key,
|
||||
tk->hmac_key, sizeof(tk->hmac_key)) == 0)
|
||||
return (0);
|
||||
tls_config_set_errorx(config, "ticket key already present");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
memmove(&config->ticket_keys[1], &config->ticket_keys[0],
|
||||
sizeof(config->ticket_keys) - sizeof(config->ticket_keys[0]));
|
||||
config->ticket_keys[0] = newkey;
|
||||
|
||||
config->ticket_autorekey = 0;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_config_ticket_autorekey(struct tls_config *config)
|
||||
{
|
||||
unsigned char key[TLS_TICKET_KEY_SIZE];
|
||||
int rv;
|
||||
|
||||
arc4random_buf(key, sizeof(key));
|
||||
rv = tls_config_add_ticket_key(config, config->ticket_keyrev++, key,
|
||||
sizeof(key));
|
||||
config->ticket_autorekey = 1;
|
||||
return (rv);
|
||||
}
|
344
tls/tls_conninfo.c
Normal file
344
tls/tls_conninfo.c
Normal file
@@ -0,0 +1,344 @@
|
||||
/* $OpenBSD: tls_conninfo.c,v 1.23 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2015 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
int ASN1_time_tm_clamp_notafter(struct tm *tm);
|
||||
|
||||
int
|
||||
tls_hex_string(const unsigned char *in, size_t inlen, char **out,
|
||||
size_t *outlen)
|
||||
{
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
size_t i, len;
|
||||
char *p;
|
||||
|
||||
if (outlen != NULL)
|
||||
*outlen = 0;
|
||||
|
||||
if (inlen >= SIZE_MAX)
|
||||
return (-1);
|
||||
if ((*out = reallocarray(NULL, inlen + 1, 2)) == NULL)
|
||||
return (-1);
|
||||
|
||||
p = *out;
|
||||
len = 0;
|
||||
for (i = 0; i < inlen; i++) {
|
||||
p[len++] = hex[(in[i] >> 4) & 0x0f];
|
||||
p[len++] = hex[in[i] & 0x0f];
|
||||
}
|
||||
p[len++] = 0;
|
||||
|
||||
if (outlen != NULL)
|
||||
*outlen = len;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_get_peer_cert_hash(struct tls *ctx, char **hash)
|
||||
{
|
||||
*hash = NULL;
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (0);
|
||||
|
||||
if (tls_cert_hash(ctx->ssl_peer_cert, hash) == -1) {
|
||||
tls_set_errorx(ctx, "unable to compute peer certificate hash - out of memory");
|
||||
*hash = NULL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_get_peer_cert_issuer(struct tls *ctx, char **issuer)
|
||||
{
|
||||
X509_NAME *name = NULL;
|
||||
|
||||
*issuer = NULL;
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (-1);
|
||||
if ((name = X509_get_issuer_name(ctx->ssl_peer_cert)) == NULL)
|
||||
return (-1);
|
||||
*issuer = X509_NAME_oneline(name, 0, 0);
|
||||
if (*issuer == NULL)
|
||||
return (-1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_get_peer_cert_subject(struct tls *ctx, char **subject)
|
||||
{
|
||||
X509_NAME *name = NULL;
|
||||
|
||||
*subject = NULL;
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (-1);
|
||||
if ((name = X509_get_subject_name(ctx->ssl_peer_cert)) == NULL)
|
||||
return (-1);
|
||||
*subject = X509_NAME_oneline(name, 0, 0);
|
||||
if (*subject == NULL)
|
||||
return (-1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_get_peer_cert_times(struct tls *ctx, time_t *notbefore,
|
||||
time_t *notafter)
|
||||
{
|
||||
struct tm before_tm, after_tm;
|
||||
ASN1_TIME *before, *after;
|
||||
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (-1);
|
||||
|
||||
if ((before = X509_get_notBefore(ctx->ssl_peer_cert)) == NULL)
|
||||
goto err;
|
||||
if ((after = X509_get_notAfter(ctx->ssl_peer_cert)) == NULL)
|
||||
goto err;
|
||||
if (ASN1_time_parse(before->data, before->length, &before_tm, 0) == -1)
|
||||
goto err;
|
||||
if (ASN1_time_parse(after->data, after->length, &after_tm, 0) == -1)
|
||||
goto err;
|
||||
if (!ASN1_time_tm_clamp_notafter(&after_tm))
|
||||
goto err;
|
||||
if ((*notbefore = timegm(&before_tm)) == -1)
|
||||
goto err;
|
||||
if ((*notafter = timegm(&after_tm)) == -1)
|
||||
goto err;
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_get_peer_cert_info(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (0);
|
||||
|
||||
if (tls_get_peer_cert_hash(ctx, &ctx->conninfo->hash) == -1)
|
||||
goto err;
|
||||
if (tls_get_peer_cert_subject(ctx, &ctx->conninfo->subject) == -1)
|
||||
goto err;
|
||||
if (tls_get_peer_cert_issuer(ctx, &ctx->conninfo->issuer) == -1)
|
||||
goto err;
|
||||
if (tls_get_peer_cert_times(ctx, &ctx->conninfo->notbefore,
|
||||
&ctx->conninfo->notafter) == -1)
|
||||
goto err;
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_conninfo_alpn_proto(struct tls *ctx)
|
||||
{
|
||||
const unsigned char *p;
|
||||
unsigned int len;
|
||||
|
||||
free(ctx->conninfo->alpn);
|
||||
ctx->conninfo->alpn = NULL;
|
||||
|
||||
SSL_get0_alpn_selected(ctx->ssl_conn, &p, &len);
|
||||
if (len > 0) {
|
||||
if ((ctx->conninfo->alpn = malloc(len + 1)) == NULL)
|
||||
return (-1);
|
||||
memcpy(ctx->conninfo->alpn, p, len);
|
||||
ctx->conninfo->alpn[len] = '\0';
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_conninfo_cert_pem(struct tls *ctx)
|
||||
{
|
||||
int i, rv = -1;
|
||||
BIO *membio = NULL;
|
||||
BUF_MEM *bptr = NULL;
|
||||
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return 0;
|
||||
if ((membio = BIO_new(BIO_s_mem()))== NULL)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* We have to write the peer cert out separately, because
|
||||
* the certificate chain may or may not contain it.
|
||||
*/
|
||||
if (!PEM_write_bio_X509(membio, ctx->ssl_peer_cert))
|
||||
goto err;
|
||||
for (i = 0; i < sk_X509_num(ctx->ssl_peer_chain); i++) {
|
||||
X509 *chaincert = sk_X509_value(ctx->ssl_peer_chain, i);
|
||||
if (chaincert != ctx->ssl_peer_cert &&
|
||||
!PEM_write_bio_X509(membio, chaincert))
|
||||
goto err;
|
||||
}
|
||||
|
||||
BIO_get_mem_ptr(membio, &bptr);
|
||||
free(ctx->conninfo->peer_cert);
|
||||
ctx->conninfo->peer_cert_len = 0;
|
||||
if ((ctx->conninfo->peer_cert = malloc(bptr->length)) == NULL)
|
||||
goto err;
|
||||
ctx->conninfo->peer_cert_len = bptr->length;
|
||||
memcpy(ctx->conninfo->peer_cert, bptr->data,
|
||||
ctx->conninfo->peer_cert_len);
|
||||
|
||||
/* BIO_free() will kill BUF_MEM - because we have not set BIO_NOCLOSE */
|
||||
rv = 0;
|
||||
err:
|
||||
BIO_free(membio);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_conninfo_session(struct tls *ctx)
|
||||
{
|
||||
ctx->conninfo->session_resumed = SSL_session_reused(ctx->ssl_conn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
tls_conninfo_populate(struct tls *ctx)
|
||||
{
|
||||
const char *tmp;
|
||||
|
||||
tls_conninfo_free(ctx->conninfo);
|
||||
|
||||
if ((ctx->conninfo = calloc(1, sizeof(struct tls_conninfo))) == NULL) {
|
||||
tls_set_errorx(ctx, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_conninfo_alpn_proto(ctx) == -1)
|
||||
goto err;
|
||||
|
||||
if ((tmp = SSL_get_cipher(ctx->ssl_conn)) == NULL)
|
||||
goto err;
|
||||
if ((ctx->conninfo->cipher = strdup(tmp)) == NULL)
|
||||
goto err;
|
||||
ctx->conninfo->cipher_strength = SSL_get_cipher_bits(ctx->ssl_conn, NULL);
|
||||
|
||||
if (ctx->servername != NULL) {
|
||||
if ((ctx->conninfo->servername =
|
||||
strdup(ctx->servername)) == NULL)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((tmp = SSL_get_version(ctx->ssl_conn)) == NULL)
|
||||
goto err;
|
||||
if ((ctx->conninfo->version = strdup(tmp)) == NULL)
|
||||
goto err;
|
||||
|
||||
if (tls_get_peer_cert_info(ctx) == -1)
|
||||
goto err;
|
||||
|
||||
if (tls_conninfo_cert_pem(ctx) == -1)
|
||||
goto err;
|
||||
|
||||
if (tls_conninfo_session(ctx) == -1)
|
||||
goto err;
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
tls_conninfo_free(ctx->conninfo);
|
||||
ctx->conninfo = NULL;
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
void
|
||||
tls_conninfo_free(struct tls_conninfo *conninfo)
|
||||
{
|
||||
if (conninfo == NULL)
|
||||
return;
|
||||
|
||||
free(conninfo->alpn);
|
||||
free(conninfo->cipher);
|
||||
free(conninfo->servername);
|
||||
free(conninfo->version);
|
||||
|
||||
free(conninfo->hash);
|
||||
free(conninfo->issuer);
|
||||
free(conninfo->subject);
|
||||
|
||||
free(conninfo->peer_cert);
|
||||
|
||||
free(conninfo);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_conn_alpn_selected(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->alpn);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_conn_cipher(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->cipher);
|
||||
}
|
||||
|
||||
int
|
||||
tls_conn_cipher_strength(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (0);
|
||||
return (ctx->conninfo->cipher_strength);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_conn_servername(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->servername);
|
||||
}
|
||||
|
||||
int
|
||||
tls_conn_session_resumed(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (0);
|
||||
return (ctx->conninfo->session_resumed);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_conn_version(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->version);
|
||||
}
|
326
tls/tls_internal.h
Normal file
326
tls/tls_internal.h
Normal file
@@ -0,0 +1,326 @@
|
||||
/* $OpenBSD: tls_internal.h,v 1.83 2023/06/27 18:19:59 tb Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef HEADER_TLS_INTERNAL_H
|
||||
#define HEADER_TLS_INTERNAL_H
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
__BEGIN_HIDDEN_DECLS
|
||||
|
||||
#ifndef TLS_DEFAULT_CA_FILE
|
||||
#define TLS_DEFAULT_CA_FILE "/etc/ssl/cert.pem"
|
||||
#endif
|
||||
|
||||
#define TLS_CIPHERS_DEFAULT "TLSv1.3:TLSv1.2+AEAD+ECDHE:TLSv1.2+AEAD+DHE"
|
||||
#define TLS_CIPHERS_COMPAT "HIGH:!aNULL"
|
||||
#define TLS_CIPHERS_LEGACY "HIGH:MEDIUM:!aNULL"
|
||||
#define TLS_CIPHERS_ALL "ALL:!aNULL:!eNULL"
|
||||
|
||||
#define TLS_ECDHE_CURVES "X25519,P-256,P-384"
|
||||
|
||||
union tls_addr {
|
||||
struct in_addr ip4;
|
||||
struct in6_addr ip6;
|
||||
};
|
||||
|
||||
struct tls_error {
|
||||
char *msg;
|
||||
int num;
|
||||
int tls;
|
||||
};
|
||||
|
||||
struct tls_keypair {
|
||||
struct tls_keypair *next;
|
||||
|
||||
char *cert_mem;
|
||||
size_t cert_len;
|
||||
char *key_mem;
|
||||
size_t key_len;
|
||||
char *ocsp_staple;
|
||||
size_t ocsp_staple_len;
|
||||
char *pubkey_hash;
|
||||
};
|
||||
|
||||
#define TLS_MIN_SESSION_TIMEOUT (4)
|
||||
#define TLS_MAX_SESSION_TIMEOUT (24 * 60 * 60)
|
||||
|
||||
#define TLS_NUM_TICKETS 4
|
||||
#define TLS_TICKET_NAME_SIZE 16
|
||||
#define TLS_TICKET_AES_SIZE 32
|
||||
#define TLS_TICKET_HMAC_SIZE 16
|
||||
|
||||
struct tls_ticket_key {
|
||||
/* The key_name must be 16 bytes according to -lssl */
|
||||
unsigned char key_name[TLS_TICKET_NAME_SIZE];
|
||||
unsigned char aes_key[TLS_TICKET_AES_SIZE];
|
||||
unsigned char hmac_key[TLS_TICKET_HMAC_SIZE];
|
||||
time_t time;
|
||||
};
|
||||
|
||||
typedef int (*tls_sign_cb)(void *_cb_arg, const char *_pubkey_hash,
|
||||
const uint8_t *_input, size_t _input_len, int _padding_type,
|
||||
uint8_t **_out_signature, size_t *_out_signature_len);
|
||||
|
||||
struct tls_config {
|
||||
struct tls_error error;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
int refcount;
|
||||
|
||||
char *alpn;
|
||||
size_t alpn_len;
|
||||
const char *ca_path;
|
||||
char *ca_mem;
|
||||
size_t ca_len;
|
||||
const char *ciphers;
|
||||
int ciphers_server;
|
||||
char *crl_mem;
|
||||
size_t crl_len;
|
||||
int dheparams;
|
||||
int *ecdhecurves;
|
||||
size_t ecdhecurves_len;
|
||||
struct tls_keypair *keypair;
|
||||
int ocsp_require_stapling;
|
||||
uint32_t protocols;
|
||||
unsigned char session_id[TLS_MAX_SESSION_ID_LENGTH];
|
||||
int session_fd;
|
||||
int session_lifetime;
|
||||
struct tls_ticket_key ticket_keys[TLS_NUM_TICKETS];
|
||||
uint32_t ticket_keyrev;
|
||||
int ticket_autorekey;
|
||||
int verify_cert;
|
||||
int verify_client;
|
||||
int verify_depth;
|
||||
int verify_name;
|
||||
int verify_time;
|
||||
int skip_private_key_check;
|
||||
int use_fake_private_key;
|
||||
tls_sign_cb sign_cb;
|
||||
void *sign_cb_arg;
|
||||
};
|
||||
|
||||
struct tls_conninfo {
|
||||
char *alpn;
|
||||
char *cipher;
|
||||
int cipher_strength;
|
||||
char *servername;
|
||||
int session_resumed;
|
||||
char *version;
|
||||
|
||||
char *hash;
|
||||
char *issuer;
|
||||
char *subject;
|
||||
|
||||
uint8_t *peer_cert;
|
||||
size_t peer_cert_len;
|
||||
|
||||
time_t notbefore;
|
||||
time_t notafter;
|
||||
};
|
||||
|
||||
#define TLS_CLIENT (1 << 0)
|
||||
#define TLS_SERVER (1 << 1)
|
||||
#define TLS_SERVER_CONN (1 << 2)
|
||||
|
||||
#define TLS_EOF_NO_CLOSE_NOTIFY (1 << 0)
|
||||
#define TLS_CONNECTED (1 << 1)
|
||||
#define TLS_HANDSHAKE_COMPLETE (1 << 2)
|
||||
#define TLS_SSL_NEEDS_SHUTDOWN (1 << 3)
|
||||
|
||||
struct tls_ocsp_result {
|
||||
const char *result_msg;
|
||||
int response_status;
|
||||
int cert_status;
|
||||
int crl_reason;
|
||||
time_t this_update;
|
||||
time_t next_update;
|
||||
time_t revocation_time;
|
||||
};
|
||||
|
||||
struct tls_ocsp {
|
||||
/* responder location */
|
||||
char *ocsp_url;
|
||||
|
||||
/* cert data, this struct does not own these */
|
||||
X509 *main_cert;
|
||||
STACK_OF(X509) *extra_certs;
|
||||
|
||||
struct tls_ocsp_result *ocsp_result;
|
||||
};
|
||||
|
||||
struct tls_sni_ctx {
|
||||
struct tls_sni_ctx *next;
|
||||
|
||||
struct tls_keypair *keypair;
|
||||
|
||||
SSL_CTX *ssl_ctx;
|
||||
X509 *ssl_cert;
|
||||
};
|
||||
|
||||
struct tls {
|
||||
struct tls_config *config;
|
||||
struct tls_keypair *keypair;
|
||||
|
||||
struct tls_error error;
|
||||
|
||||
uint32_t flags;
|
||||
uint32_t state;
|
||||
|
||||
char *servername;
|
||||
int socket;
|
||||
|
||||
SSL *ssl_conn;
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
struct tls_sni_ctx *sni_ctx;
|
||||
|
||||
X509 *ssl_peer_cert;
|
||||
STACK_OF(X509) *ssl_peer_chain;
|
||||
|
||||
struct tls_conninfo *conninfo;
|
||||
|
||||
struct tls_ocsp *ocsp;
|
||||
|
||||
tls_read_cb read_cb;
|
||||
tls_write_cb write_cb;
|
||||
void *cb_arg;
|
||||
};
|
||||
|
||||
int tls_set_mem(char **_dest, size_t *_destlen, const void *_src,
|
||||
size_t _srclen);
|
||||
int tls_set_string(const char **_dest, const char *_src);
|
||||
|
||||
struct tls_keypair *tls_keypair_new(void);
|
||||
void tls_keypair_clear_key(struct tls_keypair *_keypair);
|
||||
void tls_keypair_free(struct tls_keypair *_keypair);
|
||||
int tls_keypair_set_cert_file(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const char *_cert_file);
|
||||
int tls_keypair_set_cert_mem(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const uint8_t *_cert, size_t _len);
|
||||
int tls_keypair_set_key_file(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const char *_key_file);
|
||||
int tls_keypair_set_key_mem(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const uint8_t *_key, size_t _len);
|
||||
int tls_keypair_set_ocsp_staple_file(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const char *_ocsp_file);
|
||||
int tls_keypair_set_ocsp_staple_mem(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, const uint8_t *_staple, size_t _len);
|
||||
int tls_keypair_load_cert(struct tls_keypair *_keypair,
|
||||
struct tls_error *_error, X509 **_cert);
|
||||
|
||||
struct tls_sni_ctx *tls_sni_ctx_new(void);
|
||||
void tls_sni_ctx_free(struct tls_sni_ctx *sni_ctx);
|
||||
|
||||
struct tls_config *tls_config_new_internal(void);
|
||||
|
||||
struct tls *tls_new(void);
|
||||
struct tls *tls_server_conn(struct tls *ctx);
|
||||
|
||||
int tls_check_name(struct tls *ctx, X509 *cert, const char *servername,
|
||||
int *match);
|
||||
int tls_configure_server(struct tls *ctx);
|
||||
|
||||
int tls_configure_ssl(struct tls *ctx, SSL_CTX *ssl_ctx);
|
||||
int tls_configure_ssl_keypair(struct tls *ctx, SSL_CTX *ssl_ctx,
|
||||
struct tls_keypair *keypair, int required);
|
||||
int tls_configure_ssl_verify(struct tls *ctx, SSL_CTX *ssl_ctx, int verify);
|
||||
|
||||
int tls_handshake_client(struct tls *ctx);
|
||||
int tls_handshake_server(struct tls *ctx);
|
||||
|
||||
int tls_config_load_file(struct tls_error *error, const char *filetype,
|
||||
const char *filename, char **buf, size_t *len);
|
||||
int tls_config_ticket_autorekey(struct tls_config *config);
|
||||
int tls_host_port(const char *hostport, char **host, char **port);
|
||||
|
||||
int tls_set_cbs(struct tls *ctx,
|
||||
tls_read_cb read_cb, tls_write_cb write_cb, void *cb_arg);
|
||||
|
||||
void tls_error_clear(struct tls_error *error);
|
||||
int tls_error_set(struct tls_error *error, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_error_setx(struct tls_error *error, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_config_set_error(struct tls_config *cfg, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_config_set_errorx(struct tls_config *cfg, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_set_error(struct tls *ctx, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_set_errorx(struct tls *ctx, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
int tls_set_ssl_errorx(struct tls *ctx, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)))
|
||||
__attribute__((__nonnull__ (2)));
|
||||
|
||||
int tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret,
|
||||
const char *prefix);
|
||||
|
||||
int tls_conninfo_populate(struct tls *ctx);
|
||||
void tls_conninfo_free(struct tls_conninfo *conninfo);
|
||||
|
||||
int tls_ocsp_verify_cb(SSL *ssl, void *arg);
|
||||
int tls_ocsp_stapling_cb(SSL *ssl, void *arg);
|
||||
void tls_ocsp_free(struct tls_ocsp *ctx);
|
||||
struct tls_ocsp *tls_ocsp_setup_from_peer(struct tls *ctx);
|
||||
int tls_hex_string(const unsigned char *_in, size_t _inlen, char **_out,
|
||||
size_t *_outlen);
|
||||
int tls_cert_hash(X509 *_cert, char **_hash);
|
||||
int tls_cert_pubkey_hash(X509 *_cert, char **_hash);
|
||||
|
||||
int tls_password_cb(char *_buf, int _size, int _rwflag, void *_u);
|
||||
|
||||
RSA_METHOD *tls_signer_rsa_method(void);
|
||||
EC_KEY_METHOD *tls_signer_ecdsa_method(void);
|
||||
|
||||
#define TLS_PADDING_NONE 0
|
||||
#define TLS_PADDING_RSA_PKCS1 1
|
||||
|
||||
int tls_config_set_sign_cb(struct tls_config *_config, tls_sign_cb _cb,
|
||||
void *_cb_arg);
|
||||
|
||||
struct tls_signer* tls_signer_new(void);
|
||||
void tls_signer_free(struct tls_signer * _signer);
|
||||
const char *tls_signer_error(struct tls_signer * _signer);
|
||||
int tls_signer_add_keypair_file(struct tls_signer *_signer,
|
||||
const char *_cert_file, const char *_key_file);
|
||||
int tls_signer_add_keypair_mem(struct tls_signer *_signer, const uint8_t *_cert,
|
||||
size_t _cert_len, const uint8_t *_key, size_t _key_len);
|
||||
int tls_signer_sign(struct tls_signer *_signer, const char *_pubkey_hash,
|
||||
const uint8_t *_input, size_t _input_len, int _padding_type,
|
||||
uint8_t **_out_signature, size_t *_out_signature_len);
|
||||
|
||||
__END_HIDDEN_DECLS
|
||||
|
||||
/* XXX this function is not fully hidden so relayd can use it */
|
||||
void tls_config_skip_private_key_check(struct tls_config *config);
|
||||
void tls_config_use_fake_private_key(struct tls_config *config);
|
||||
|
||||
#endif /* HEADER_TLS_INTERNAL_H */
|
169
tls/tls_keypair.c
Normal file
169
tls/tls_keypair.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/* $OpenBSD: tls_keypair.c,v 1.8 2021/01/05 17:37:12 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <tls.h>
|
||||
|
||||
#include "tls_internal.h"
|
||||
|
||||
struct tls_keypair *
|
||||
tls_keypair_new(void)
|
||||
{
|
||||
return calloc(1, sizeof(struct tls_keypair));
|
||||
}
|
||||
|
||||
static int
|
||||
tls_keypair_pubkey_hash(struct tls_keypair *keypair, struct tls_error *error)
|
||||
{
|
||||
X509 *cert = NULL;
|
||||
int rv = -1;
|
||||
|
||||
free(keypair->pubkey_hash);
|
||||
keypair->pubkey_hash = NULL;
|
||||
|
||||
if (keypair->cert_mem == NULL) {
|
||||
rv = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (tls_keypair_load_cert(keypair, error, &cert) == -1)
|
||||
goto err;
|
||||
if (tls_cert_pubkey_hash(cert, &keypair->pubkey_hash) == -1)
|
||||
goto err;
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
X509_free(cert);
|
||||
done:
|
||||
return (rv);
|
||||
}
|
||||
|
||||
void
|
||||
tls_keypair_clear_key(struct tls_keypair *keypair)
|
||||
{
|
||||
freezero(keypair->key_mem, keypair->key_len);
|
||||
keypair->key_mem = NULL;
|
||||
keypair->key_len = 0;
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_cert_file(struct tls_keypair *keypair, struct tls_error *error,
|
||||
const char *cert_file)
|
||||
{
|
||||
if (tls_config_load_file(error, "certificate", cert_file,
|
||||
&keypair->cert_mem, &keypair->cert_len) == -1)
|
||||
return -1;
|
||||
return tls_keypair_pubkey_hash(keypair, error);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_cert_mem(struct tls_keypair *keypair, struct tls_error *error,
|
||||
const uint8_t *cert, size_t len)
|
||||
{
|
||||
if (tls_set_mem(&keypair->cert_mem, &keypair->cert_len, cert, len) == -1)
|
||||
return -1;
|
||||
return tls_keypair_pubkey_hash(keypair, error);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_key_file(struct tls_keypair *keypair, struct tls_error *error,
|
||||
const char *key_file)
|
||||
{
|
||||
tls_keypair_clear_key(keypair);
|
||||
return tls_config_load_file(error, "key", key_file,
|
||||
&keypair->key_mem, &keypair->key_len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_key_mem(struct tls_keypair *keypair, struct tls_error *error,
|
||||
const uint8_t *key, size_t len)
|
||||
{
|
||||
tls_keypair_clear_key(keypair);
|
||||
return tls_set_mem(&keypair->key_mem, &keypair->key_len, key, len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_ocsp_staple_file(struct tls_keypair *keypair,
|
||||
struct tls_error *error, const char *ocsp_file)
|
||||
{
|
||||
return tls_config_load_file(error, "ocsp", ocsp_file,
|
||||
&keypair->ocsp_staple, &keypair->ocsp_staple_len);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_set_ocsp_staple_mem(struct tls_keypair *keypair,
|
||||
struct tls_error *error, const uint8_t *staple, size_t len)
|
||||
{
|
||||
return tls_set_mem(&keypair->ocsp_staple, &keypair->ocsp_staple_len,
|
||||
staple, len);
|
||||
}
|
||||
|
||||
void
|
||||
tls_keypair_free(struct tls_keypair *keypair)
|
||||
{
|
||||
if (keypair == NULL)
|
||||
return;
|
||||
|
||||
tls_keypair_clear_key(keypair);
|
||||
|
||||
free(keypair->cert_mem);
|
||||
free(keypair->ocsp_staple);
|
||||
free(keypair->pubkey_hash);
|
||||
|
||||
free(keypair);
|
||||
}
|
||||
|
||||
int
|
||||
tls_keypair_load_cert(struct tls_keypair *keypair, struct tls_error *error,
|
||||
X509 **cert)
|
||||
{
|
||||
char *errstr = "unknown";
|
||||
BIO *cert_bio = NULL;
|
||||
unsigned long ssl_err;
|
||||
int rv = -1;
|
||||
|
||||
X509_free(*cert);
|
||||
*cert = NULL;
|
||||
|
||||
if (keypair->cert_mem == NULL) {
|
||||
tls_error_set(error, "keypair has no certificate");
|
||||
goto err;
|
||||
}
|
||||
if ((cert_bio = BIO_new_mem_buf(keypair->cert_mem,
|
||||
keypair->cert_len)) == NULL) {
|
||||
tls_error_set(error, "failed to create certificate bio");
|
||||
goto err;
|
||||
}
|
||||
if ((*cert = PEM_read_bio_X509(cert_bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
if ((ssl_err = ERR_peek_error()) != 0)
|
||||
errstr = ERR_error_string(ssl_err, NULL);
|
||||
tls_error_set(error, "failed to load certificate: %s", errstr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
BIO_free(cert_bio);
|
||||
|
||||
return (rv);
|
||||
}
|
463
tls/tls_ocsp.c
Normal file
463
tls/tls_ocsp.c
Normal file
@@ -0,0 +1,463 @@
|
||||
/* $OpenBSD: tls_ocsp.c,v 1.23 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2015 Marko Kreen <markokr@gmail.com>
|
||||
* Copyright (c) 2016 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ocsp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
#define MAXAGE_SEC (14*24*60*60)
|
||||
#define JITTER_SEC (60)
|
||||
|
||||
/*
|
||||
* State for request.
|
||||
*/
|
||||
|
||||
static struct tls_ocsp *
|
||||
tls_ocsp_new(void)
|
||||
{
|
||||
return (calloc(1, sizeof(struct tls_ocsp)));
|
||||
}
|
||||
|
||||
void
|
||||
tls_ocsp_free(struct tls_ocsp *ocsp)
|
||||
{
|
||||
if (ocsp == NULL)
|
||||
return;
|
||||
|
||||
X509_free(ocsp->main_cert);
|
||||
free(ocsp->ocsp_result);
|
||||
free(ocsp->ocsp_url);
|
||||
|
||||
free(ocsp);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_ocsp_asn1_parse_time(struct tls *ctx, ASN1_GENERALIZEDTIME *gt, time_t *gt_time)
|
||||
{
|
||||
struct tm tm;
|
||||
|
||||
if (gt == NULL)
|
||||
return -1;
|
||||
/* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */
|
||||
if (ASN1_time_parse(gt->data, gt->length, &tm,
|
||||
V_ASN1_GENERALIZEDTIME) == -1)
|
||||
return -1;
|
||||
if ((*gt_time = timegm(&tm)) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_ocsp_fill_info(struct tls *ctx, int response_status, int cert_status,
|
||||
int crl_reason, ASN1_GENERALIZEDTIME *revtime,
|
||||
ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd)
|
||||
{
|
||||
struct tls_ocsp_result *info = NULL;
|
||||
|
||||
free(ctx->ocsp->ocsp_result);
|
||||
ctx->ocsp->ocsp_result = NULL;
|
||||
|
||||
if ((info = calloc(1, sizeof (struct tls_ocsp_result))) == NULL) {
|
||||
tls_set_error(ctx, "calloc");
|
||||
return -1;
|
||||
}
|
||||
info->response_status = response_status;
|
||||
info->cert_status = cert_status;
|
||||
info->crl_reason = crl_reason;
|
||||
if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
info->result_msg =
|
||||
OCSP_response_status_str(info->response_status);
|
||||
} else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) {
|
||||
info->result_msg = OCSP_cert_status_str(info->cert_status);
|
||||
} else {
|
||||
info->result_msg = OCSP_crl_reason_str(info->crl_reason);
|
||||
}
|
||||
info->revocation_time = info->this_update = info->next_update = -1;
|
||||
if (revtime != NULL &&
|
||||
tls_ocsp_asn1_parse_time(ctx, revtime, &info->revocation_time) != 0) {
|
||||
tls_set_error(ctx,
|
||||
"unable to parse revocation time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
if (thisupd != NULL &&
|
||||
tls_ocsp_asn1_parse_time(ctx, thisupd, &info->this_update) != 0) {
|
||||
tls_set_error(ctx,
|
||||
"unable to parse this update time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
if (nextupd != NULL &&
|
||||
tls_ocsp_asn1_parse_time(ctx, nextupd, &info->next_update) != 0) {
|
||||
tls_set_error(ctx,
|
||||
"unable to parse next update time in OCSP reply");
|
||||
goto err;
|
||||
}
|
||||
ctx->ocsp->ocsp_result = info;
|
||||
return 0;
|
||||
|
||||
err:
|
||||
free(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static OCSP_CERTID *
|
||||
tls_ocsp_get_certid(X509 *main_cert, STACK_OF(X509) *extra_certs,
|
||||
SSL_CTX *ssl_ctx)
|
||||
{
|
||||
X509_NAME *issuer_name;
|
||||
X509 *issuer;
|
||||
X509_STORE_CTX *storectx = NULL;
|
||||
X509_OBJECT *obj = NULL;
|
||||
OCSP_CERTID *cid = NULL;
|
||||
X509_STORE *store;
|
||||
|
||||
if ((issuer_name = X509_get_issuer_name(main_cert)) == NULL)
|
||||
goto out;
|
||||
|
||||
if (extra_certs != NULL) {
|
||||
issuer = X509_find_by_subject(extra_certs, issuer_name);
|
||||
if (issuer != NULL) {
|
||||
cid = OCSP_cert_to_id(NULL, main_cert, issuer);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if ((store = SSL_CTX_get_cert_store(ssl_ctx)) == NULL)
|
||||
goto out;
|
||||
if ((storectx = X509_STORE_CTX_new()) == NULL)
|
||||
goto out;
|
||||
if (X509_STORE_CTX_init(storectx, store, main_cert, extra_certs) != 1)
|
||||
goto out;
|
||||
if ((obj = X509_STORE_CTX_get_obj_by_subject(storectx, X509_LU_X509,
|
||||
issuer_name)) == NULL)
|
||||
goto out;
|
||||
|
||||
cid = OCSP_cert_to_id(NULL, main_cert, X509_OBJECT_get0_X509(obj));
|
||||
|
||||
out:
|
||||
X509_STORE_CTX_free(storectx);
|
||||
X509_OBJECT_free(obj);
|
||||
|
||||
return cid;
|
||||
}
|
||||
|
||||
struct tls_ocsp *
|
||||
tls_ocsp_setup_from_peer(struct tls *ctx)
|
||||
{
|
||||
struct tls_ocsp *ocsp = NULL;
|
||||
STACK_OF(OPENSSL_STRING) *ocsp_urls = NULL;
|
||||
|
||||
if ((ocsp = tls_ocsp_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
/* steal state from ctx struct */
|
||||
ocsp->main_cert = SSL_get_peer_certificate(ctx->ssl_conn);
|
||||
ocsp->extra_certs = SSL_get_peer_cert_chain(ctx->ssl_conn);
|
||||
if (ocsp->main_cert == NULL) {
|
||||
tls_set_errorx(ctx, "no peer certificate for OCSP");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ocsp_urls = X509_get1_ocsp(ocsp->main_cert);
|
||||
if (ocsp_urls == NULL) {
|
||||
tls_set_errorx(ctx, "no OCSP URLs in peer certificate");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ocsp->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0));
|
||||
if (ocsp->ocsp_url == NULL) {
|
||||
tls_set_errorx(ctx, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
X509_email_free(ocsp_urls);
|
||||
return ocsp;
|
||||
|
||||
err:
|
||||
tls_ocsp_free(ocsp);
|
||||
X509_email_free(ocsp_urls);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_ocsp_verify_response(struct tls *ctx, OCSP_RESPONSE *resp)
|
||||
{
|
||||
OCSP_BASICRESP *br = NULL;
|
||||
ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL;
|
||||
OCSP_CERTID *cid = NULL;
|
||||
STACK_OF(X509) *combined = NULL;
|
||||
int response_status=0, cert_status=0, crl_reason=0;
|
||||
int ret = -1;
|
||||
unsigned long flags;
|
||||
|
||||
if ((br = OCSP_response_get1_basic(resp)) == NULL) {
|
||||
tls_set_errorx(ctx, "cannot load ocsp reply");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip validation of 'extra_certs' as this should be done
|
||||
* already as part of main handshake.
|
||||
*/
|
||||
flags = OCSP_TRUSTOTHER;
|
||||
|
||||
/* now verify */
|
||||
if (OCSP_basic_verify(br, ctx->ocsp->extra_certs,
|
||||
SSL_CTX_get_cert_store(ctx->ssl_ctx), flags) != 1) {
|
||||
tls_set_errorx(ctx, "ocsp verify failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* signature OK, look inside */
|
||||
response_status = OCSP_response_status(resp);
|
||||
if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
||||
tls_set_errorx(ctx, "ocsp verify failed: response - %s",
|
||||
OCSP_response_status_str(response_status));
|
||||
goto err;
|
||||
}
|
||||
|
||||
cid = tls_ocsp_get_certid(ctx->ocsp->main_cert,
|
||||
ctx->ocsp->extra_certs, ctx->ssl_ctx);
|
||||
if (cid == NULL) {
|
||||
tls_set_errorx(ctx, "ocsp verify failed: no issuer cert");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (OCSP_resp_find_status(br, cid, &cert_status, &crl_reason,
|
||||
&revtime, &thisupd, &nextupd) != 1) {
|
||||
tls_set_errorx(ctx, "ocsp verify failed: no result for cert");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (OCSP_check_validity(thisupd, nextupd, JITTER_SEC,
|
||||
MAXAGE_SEC) != 1) {
|
||||
tls_set_errorx(ctx,
|
||||
"ocsp verify failed: ocsp response not current");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_ocsp_fill_info(ctx, response_status, cert_status,
|
||||
crl_reason, revtime, thisupd, nextupd) != 0)
|
||||
goto err;
|
||||
|
||||
/* finally can look at status */
|
||||
if (cert_status != V_OCSP_CERTSTATUS_GOOD && cert_status !=
|
||||
V_OCSP_CERTSTATUS_UNKNOWN) {
|
||||
tls_set_errorx(ctx, "ocsp verify failed: revoked cert - %s",
|
||||
OCSP_crl_reason_str(crl_reason));
|
||||
goto err;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
sk_X509_free(combined);
|
||||
OCSP_CERTID_free(cid);
|
||||
OCSP_BASICRESP_free(br);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a raw OCSP response from an OCSP server request.
|
||||
* OCSP details can then be retrieved with tls_peer_ocsp_* functions.
|
||||
* returns 0 if certificate ok, -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
tls_ocsp_process_response_internal(struct tls *ctx, const unsigned char *response,
|
||||
size_t size)
|
||||
{
|
||||
int ret;
|
||||
OCSP_RESPONSE *resp;
|
||||
|
||||
resp = d2i_OCSP_RESPONSE(NULL, &response, size);
|
||||
if (resp == NULL) {
|
||||
tls_ocsp_free(ctx->ocsp);
|
||||
ctx->ocsp = NULL;
|
||||
tls_set_error(ctx, "unable to parse OCSP response");
|
||||
return -1;
|
||||
}
|
||||
ret = tls_ocsp_verify_response(ctx, resp);
|
||||
OCSP_RESPONSE_free(resp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TLS handshake verification callback for stapled requests */
|
||||
int
|
||||
tls_ocsp_verify_cb(SSL *ssl, void *arg)
|
||||
{
|
||||
const unsigned char *raw = NULL;
|
||||
int size, res = -1;
|
||||
struct tls *ctx;
|
||||
|
||||
if ((ctx = SSL_get_app_data(ssl)) == NULL)
|
||||
return -1;
|
||||
|
||||
size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw);
|
||||
if (size <= 0) {
|
||||
if (ctx->config->ocsp_require_stapling) {
|
||||
tls_set_errorx(ctx, "no stapled OCSP response provided");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
tls_ocsp_free(ctx->ocsp);
|
||||
if ((ctx->ocsp = tls_ocsp_setup_from_peer(ctx)) == NULL)
|
||||
return 0;
|
||||
|
||||
if (ctx->config->verify_cert == 0 || ctx->config->verify_time == 0)
|
||||
return 1;
|
||||
|
||||
res = tls_ocsp_process_response_internal(ctx, raw, size);
|
||||
|
||||
return (res == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/* Staple the OCSP information in ctx->ocsp to the server handshake. */
|
||||
int
|
||||
tls_ocsp_stapling_cb(SSL *ssl, void *arg)
|
||||
{
|
||||
int ret = SSL_TLSEXT_ERR_ALERT_FATAL;
|
||||
unsigned char *ocsp_staple = NULL;
|
||||
struct tls *ctx;
|
||||
|
||||
if ((ctx = SSL_get_app_data(ssl)) == NULL)
|
||||
goto err;
|
||||
|
||||
if (ctx->keypair == NULL || ctx->keypair->ocsp_staple == NULL ||
|
||||
ctx->keypair->ocsp_staple_len == 0)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
if ((ocsp_staple = malloc(ctx->keypair->ocsp_staple_len)) == NULL)
|
||||
goto err;
|
||||
|
||||
memcpy(ocsp_staple, ctx->keypair->ocsp_staple,
|
||||
ctx->keypair->ocsp_staple_len);
|
||||
|
||||
if (SSL_set_tlsext_status_ocsp_resp(ctx->ssl_conn, ocsp_staple,
|
||||
ctx->keypair->ocsp_staple_len) != 1)
|
||||
goto err;
|
||||
|
||||
ret = SSL_TLSEXT_ERR_OK;
|
||||
err:
|
||||
if (ret != SSL_TLSEXT_ERR_OK)
|
||||
free(ocsp_staple);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
/* Retrieve OCSP URL from peer certificate, if present. */
|
||||
const char *
|
||||
tls_peer_ocsp_url(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return NULL;
|
||||
return ctx->ocsp->ocsp_url;
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_peer_ocsp_result(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return NULL;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return NULL;
|
||||
return ctx->ocsp->ocsp_result->result_msg;
|
||||
}
|
||||
|
||||
int
|
||||
tls_peer_ocsp_response_status(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->response_status;
|
||||
}
|
||||
|
||||
int
|
||||
tls_peer_ocsp_cert_status(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->cert_status;
|
||||
}
|
||||
|
||||
int
|
||||
tls_peer_ocsp_crl_reason(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->crl_reason;
|
||||
}
|
||||
|
||||
time_t
|
||||
tls_peer_ocsp_this_update(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->this_update;
|
||||
}
|
||||
|
||||
time_t
|
||||
tls_peer_ocsp_next_update(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->next_update;
|
||||
}
|
||||
|
||||
time_t
|
||||
tls_peer_ocsp_revocation_time(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ocsp == NULL)
|
||||
return -1;
|
||||
if (ctx->ocsp->ocsp_result == NULL)
|
||||
return -1;
|
||||
return ctx->ocsp->ocsp_result->revocation_time;
|
||||
}
|
||||
|
||||
int
|
||||
tls_ocsp_process_response(struct tls *ctx, const unsigned char *response,
|
||||
size_t size)
|
||||
{
|
||||
if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0)
|
||||
return -1;
|
||||
return tls_ocsp_process_response_internal(ctx, response, size);
|
||||
}
|
99
tls/tls_peer.c
Normal file
99
tls/tls_peer.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/* $OpenBSD: tls_peer.c,v 1.8 2017/04/10 17:11:13 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2015 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
const char *
|
||||
tls_peer_cert_hash(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->hash);
|
||||
}
|
||||
const char *
|
||||
tls_peer_cert_issuer(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->issuer);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_peer_cert_subject(struct tls *ctx)
|
||||
{
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
return (ctx->conninfo->subject);
|
||||
}
|
||||
|
||||
int
|
||||
tls_peer_cert_provided(struct tls *ctx)
|
||||
{
|
||||
return (ctx->ssl_peer_cert != NULL);
|
||||
}
|
||||
|
||||
int
|
||||
tls_peer_cert_contains_name(struct tls *ctx, const char *name)
|
||||
{
|
||||
int match;
|
||||
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (0);
|
||||
|
||||
if (tls_check_name(ctx, ctx->ssl_peer_cert, name, &match) == -1)
|
||||
return (0);
|
||||
|
||||
return (match);
|
||||
}
|
||||
|
||||
time_t
|
||||
tls_peer_cert_notbefore(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (-1);
|
||||
if (ctx->conninfo == NULL)
|
||||
return (-1);
|
||||
return (ctx->conninfo->notbefore);
|
||||
}
|
||||
|
||||
time_t
|
||||
tls_peer_cert_notafter(struct tls *ctx)
|
||||
{
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (-1);
|
||||
if (ctx->conninfo == NULL)
|
||||
return (-1);
|
||||
return (ctx->conninfo->notafter);
|
||||
}
|
||||
|
||||
const uint8_t *
|
||||
tls_peer_cert_chain_pem(struct tls *ctx, size_t *size)
|
||||
{
|
||||
if (ctx->ssl_peer_cert == NULL)
|
||||
return (NULL);
|
||||
if (ctx->conninfo == NULL)
|
||||
return (NULL);
|
||||
*size = ctx->conninfo->peer_cert_len;
|
||||
return (ctx->conninfo->peer_cert);
|
||||
}
|
||||
|
468
tls/tls_server.c
Normal file
468
tls/tls_server.c
Normal file
@@ -0,0 +1,468 @@
|
||||
/* $OpenBSD: tls_server.c,v 1.49 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
struct tls *
|
||||
tls_server(void)
|
||||
{
|
||||
struct tls *ctx;
|
||||
|
||||
if (tls_init() == -1)
|
||||
return (NULL);
|
||||
|
||||
if ((ctx = tls_new()) == NULL)
|
||||
return (NULL);
|
||||
|
||||
ctx->flags |= TLS_SERVER;
|
||||
|
||||
return (ctx);
|
||||
}
|
||||
|
||||
struct tls *
|
||||
tls_server_conn(struct tls *ctx)
|
||||
{
|
||||
struct tls *conn_ctx;
|
||||
|
||||
if ((conn_ctx = tls_new()) == NULL)
|
||||
return (NULL);
|
||||
|
||||
conn_ctx->flags |= TLS_SERVER_CONN;
|
||||
|
||||
pthread_mutex_lock(&ctx->config->mutex);
|
||||
ctx->config->refcount++;
|
||||
pthread_mutex_unlock(&ctx->config->mutex);
|
||||
|
||||
conn_ctx->config = ctx->config;
|
||||
conn_ctx->keypair = ctx->config->keypair;
|
||||
|
||||
return (conn_ctx);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_server_alpn_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen, void *arg)
|
||||
{
|
||||
struct tls *ctx = arg;
|
||||
|
||||
if (SSL_select_next_proto((unsigned char**)out, outlen,
|
||||
ctx->config->alpn, ctx->config->alpn_len, in, inlen) ==
|
||||
OPENSSL_NPN_NEGOTIATED)
|
||||
return (SSL_TLSEXT_ERR_OK);
|
||||
|
||||
return (SSL_TLSEXT_ERR_NOACK);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_servername_cb(SSL *ssl, int *al, void *arg)
|
||||
{
|
||||
struct tls *ctx = (struct tls *)arg;
|
||||
struct tls_sni_ctx *sni_ctx;
|
||||
union tls_addr addrbuf;
|
||||
struct tls *conn_ctx;
|
||||
const char *name;
|
||||
int match;
|
||||
|
||||
if ((conn_ctx = SSL_get_app_data(ssl)) == NULL)
|
||||
goto err;
|
||||
|
||||
if ((name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) ==
|
||||
NULL) {
|
||||
/*
|
||||
* The servername callback gets called even when there is no
|
||||
* TLS servername extension provided by the client. Sigh!
|
||||
*/
|
||||
return (SSL_TLSEXT_ERR_NOACK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per RFC 6066 section 3: ensure that name is not an IP literal.
|
||||
*
|
||||
* While we should treat this as an error, a number of clients
|
||||
* (Python, Ruby and Safari) are not RFC compliant. To avoid handshake
|
||||
* failures, pretend that we did not receive the extension.
|
||||
*/
|
||||
if (inet_pton(AF_INET, name, &addrbuf) == 1 ||
|
||||
inet_pton(AF_INET6, name, &addrbuf) == 1)
|
||||
return (SSL_TLSEXT_ERR_NOACK);
|
||||
|
||||
free(conn_ctx->servername);
|
||||
if ((conn_ctx->servername = strdup(name)) == NULL)
|
||||
goto err;
|
||||
|
||||
/* Find appropriate SSL context for requested servername. */
|
||||
for (sni_ctx = ctx->sni_ctx; sni_ctx != NULL; sni_ctx = sni_ctx->next) {
|
||||
if (tls_check_name(ctx, sni_ctx->ssl_cert, name,
|
||||
&match) == -1)
|
||||
goto err;
|
||||
if (match) {
|
||||
conn_ctx->keypair = sni_ctx->keypair;
|
||||
SSL_set_SSL_CTX(conn_ctx->ssl_conn, sni_ctx->ssl_ctx);
|
||||
return (SSL_TLSEXT_ERR_OK);
|
||||
}
|
||||
}
|
||||
|
||||
/* No match, use the existing context/certificate. */
|
||||
return (SSL_TLSEXT_ERR_OK);
|
||||
|
||||
err:
|
||||
/*
|
||||
* There is no way to tell libssl that an internal failure occurred.
|
||||
* The only option we have is to return a fatal alert.
|
||||
*/
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
return (SSL_TLSEXT_ERR_ALERT_FATAL);
|
||||
}
|
||||
|
||||
static struct tls_ticket_key *
|
||||
tls_server_ticket_key(struct tls_config *config, unsigned char *keyname)
|
||||
{
|
||||
struct tls_ticket_key *key = NULL;
|
||||
time_t now;
|
||||
int i;
|
||||
|
||||
now = time(NULL);
|
||||
if (config->ticket_autorekey == 1) {
|
||||
if (now - 3 * (config->session_lifetime / 4) >
|
||||
config->ticket_keys[0].time) {
|
||||
if (tls_config_ticket_autorekey(config) == -1)
|
||||
return (NULL);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < TLS_NUM_TICKETS; i++) {
|
||||
struct tls_ticket_key *tk = &config->ticket_keys[i];
|
||||
if (now - config->session_lifetime > tk->time)
|
||||
continue;
|
||||
if (keyname == NULL || timingsafe_memcmp(keyname,
|
||||
tk->key_name, sizeof(tk->key_name)) == 0) {
|
||||
key = tk;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (key);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_server_ticket_cb(SSL *ssl, unsigned char *keyname, unsigned char *iv,
|
||||
EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int mode)
|
||||
{
|
||||
struct tls_ticket_key *key;
|
||||
struct tls *tls_ctx;
|
||||
|
||||
if ((tls_ctx = SSL_get_app_data(ssl)) == NULL)
|
||||
return (-1);
|
||||
|
||||
if (mode == 1) {
|
||||
/* create new session */
|
||||
key = tls_server_ticket_key(tls_ctx->config, NULL);
|
||||
if (key == NULL) {
|
||||
tls_set_errorx(tls_ctx, "no valid ticket key found");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
memcpy(keyname, key->key_name, sizeof(key->key_name));
|
||||
arc4random_buf(iv, EVP_MAX_IV_LENGTH);
|
||||
if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL,
|
||||
key->aes_key, iv)) {
|
||||
tls_set_errorx(tls_ctx, "failed to init encrypt");
|
||||
return (-1);
|
||||
}
|
||||
if (!HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
|
||||
EVP_sha256(), NULL)) {
|
||||
tls_set_errorx(tls_ctx, "failed to init hmac");
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
} else {
|
||||
/* get key by name */
|
||||
key = tls_server_ticket_key(tls_ctx->config, keyname);
|
||||
if (key == NULL)
|
||||
return (0);
|
||||
|
||||
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL,
|
||||
key->aes_key, iv)) {
|
||||
tls_set_errorx(tls_ctx, "failed to init decrypt");
|
||||
return (-1);
|
||||
}
|
||||
if (!HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
|
||||
EVP_sha256(), NULL)) {
|
||||
tls_set_errorx(tls_ctx, "failed to init hmac");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* time to renew the ticket? is it the primary key? */
|
||||
if (key != &tls_ctx->config->ticket_keys[0])
|
||||
return (2);
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
tls_configure_server_ssl(struct tls *ctx, SSL_CTX **ssl_ctx,
|
||||
struct tls_keypair *keypair)
|
||||
{
|
||||
SSL_CTX_free(*ssl_ctx);
|
||||
|
||||
if ((*ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
|
||||
tls_set_errorx(ctx, "ssl context failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(*ssl_ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
|
||||
|
||||
if (SSL_CTX_set_tlsext_servername_callback(*ssl_ctx,
|
||||
tls_servername_cb) != 1) {
|
||||
tls_set_error(ctx, "failed to set servername callback");
|
||||
goto err;
|
||||
}
|
||||
if (SSL_CTX_set_tlsext_servername_arg(*ssl_ctx, ctx) != 1) {
|
||||
tls_set_error(ctx, "failed to set servername callback arg");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_configure_ssl(ctx, *ssl_ctx) != 0)
|
||||
goto err;
|
||||
if (tls_configure_ssl_keypair(ctx, *ssl_ctx, keypair, 1) != 0)
|
||||
goto err;
|
||||
if (ctx->config->verify_client != 0) {
|
||||
int verify = SSL_VERIFY_PEER;
|
||||
if (ctx->config->verify_client == 1)
|
||||
verify |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||
if (tls_configure_ssl_verify(ctx, *ssl_ctx, verify) == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->config->alpn != NULL)
|
||||
SSL_CTX_set_alpn_select_cb(*ssl_ctx, tls_server_alpn_cb,
|
||||
ctx);
|
||||
|
||||
if (ctx->config->dheparams == -1)
|
||||
SSL_CTX_set_dh_auto(*ssl_ctx, 1);
|
||||
else if (ctx->config->dheparams == 1024)
|
||||
SSL_CTX_set_dh_auto(*ssl_ctx, 2);
|
||||
|
||||
if (ctx->config->ecdhecurves != NULL) {
|
||||
SSL_CTX_set_ecdh_auto(*ssl_ctx, 1);
|
||||
if (SSL_CTX_set1_groups(*ssl_ctx, ctx->config->ecdhecurves,
|
||||
ctx->config->ecdhecurves_len) != 1) {
|
||||
tls_set_errorx(ctx, "failed to set ecdhe curves");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->config->ciphers_server == 1)
|
||||
SSL_CTX_set_options(*ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
|
||||
if (SSL_CTX_set_tlsext_status_cb(*ssl_ctx, tls_ocsp_stapling_cb) != 1) {
|
||||
tls_set_errorx(ctx, "failed to add OCSP stapling callback");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->config->session_lifetime > 0) {
|
||||
/* set the session lifetime and enable tickets */
|
||||
SSL_CTX_set_timeout(*ssl_ctx, ctx->config->session_lifetime);
|
||||
SSL_CTX_clear_options(*ssl_ctx, SSL_OP_NO_TICKET);
|
||||
if (!SSL_CTX_set_tlsext_ticket_key_cb(*ssl_ctx,
|
||||
tls_server_ticket_cb)) {
|
||||
tls_set_error(ctx,
|
||||
"failed to set the TLS ticket callback");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_session_id_context(*ssl_ctx, ctx->config->session_id,
|
||||
sizeof(ctx->config->session_id)) != 1) {
|
||||
tls_set_error(ctx, "failed to set session id context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
SSL_CTX_free(*ssl_ctx);
|
||||
*ssl_ctx = NULL;
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_configure_server_sni(struct tls *ctx)
|
||||
{
|
||||
struct tls_sni_ctx **sni_ctx;
|
||||
struct tls_keypair *kp;
|
||||
|
||||
if (ctx->config->keypair->next == NULL)
|
||||
return (0);
|
||||
|
||||
/* Set up additional SSL contexts for SNI. */
|
||||
sni_ctx = &ctx->sni_ctx;
|
||||
for (kp = ctx->config->keypair->next; kp != NULL; kp = kp->next) {
|
||||
if ((*sni_ctx = tls_sni_ctx_new()) == NULL) {
|
||||
tls_set_errorx(ctx, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
(*sni_ctx)->keypair = kp;
|
||||
if (tls_configure_server_ssl(ctx, &(*sni_ctx)->ssl_ctx, kp) == -1)
|
||||
goto err;
|
||||
if (tls_keypair_load_cert(kp, &ctx->error,
|
||||
&(*sni_ctx)->ssl_cert) == -1)
|
||||
goto err;
|
||||
sni_ctx = &(*sni_ctx)->next;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_configure_server(struct tls *ctx)
|
||||
{
|
||||
if (tls_configure_server_ssl(ctx, &ctx->ssl_ctx,
|
||||
ctx->config->keypair) == -1)
|
||||
goto err;
|
||||
if (tls_configure_server_sni(ctx) == -1)
|
||||
goto err;
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static struct tls *
|
||||
tls_accept_common(struct tls *ctx)
|
||||
{
|
||||
struct tls *conn_ctx = NULL;
|
||||
|
||||
if ((ctx->flags & TLS_SERVER) == 0) {
|
||||
tls_set_errorx(ctx, "not a server context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((conn_ctx = tls_server_conn(ctx)) == NULL) {
|
||||
tls_set_errorx(ctx, "connection context failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((conn_ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) {
|
||||
tls_set_errorx(ctx, "ssl failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (SSL_set_app_data(conn_ctx->ssl_conn, conn_ctx) != 1) {
|
||||
tls_set_errorx(ctx, "ssl application data failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return conn_ctx;
|
||||
|
||||
err:
|
||||
tls_free(conn_ctx);
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
int
|
||||
tls_accept_socket(struct tls *ctx, struct tls **cctx, int s)
|
||||
{
|
||||
return (tls_accept_fds(ctx, cctx, s, s));
|
||||
}
|
||||
|
||||
int
|
||||
tls_accept_fds(struct tls *ctx, struct tls **cctx, int fd_read, int fd_write)
|
||||
{
|
||||
struct tls *conn_ctx;
|
||||
|
||||
if ((conn_ctx = tls_accept_common(ctx)) == NULL)
|
||||
goto err;
|
||||
|
||||
if (SSL_set_rfd(conn_ctx->ssl_conn, fd_read) != 1 ||
|
||||
SSL_set_wfd(conn_ctx->ssl_conn, fd_write) != 1) {
|
||||
tls_set_errorx(ctx, "ssl file descriptor failure");
|
||||
goto err;
|
||||
}
|
||||
|
||||
*cctx = conn_ctx;
|
||||
|
||||
return (0);
|
||||
err:
|
||||
tls_free(conn_ctx);
|
||||
*cctx = NULL;
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_accept_cbs(struct tls *ctx, struct tls **cctx,
|
||||
tls_read_cb read_cb, tls_write_cb write_cb, void *cb_arg)
|
||||
{
|
||||
struct tls *conn_ctx;
|
||||
|
||||
if ((conn_ctx = tls_accept_common(ctx)) == NULL)
|
||||
goto err;
|
||||
|
||||
if (tls_set_cbs(conn_ctx, read_cb, write_cb, cb_arg) != 0)
|
||||
goto err;
|
||||
|
||||
*cctx = conn_ctx;
|
||||
|
||||
return (0);
|
||||
err:
|
||||
tls_free(conn_ctx);
|
||||
*cctx = NULL;
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_handshake_server(struct tls *ctx)
|
||||
{
|
||||
int ssl_ret;
|
||||
int rv = -1;
|
||||
|
||||
if ((ctx->flags & TLS_SERVER_CONN) == 0) {
|
||||
tls_set_errorx(ctx, "not a server connection context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->state |= TLS_SSL_NEEDS_SHUTDOWN;
|
||||
|
||||
ERR_clear_error();
|
||||
if ((ssl_ret = SSL_accept(ctx->ssl_conn)) != 1) {
|
||||
rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "handshake");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctx->state |= TLS_HANDSHAKE_COMPLETE;
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
return (rv);
|
||||
}
|
451
tls/tls_signer.c
Normal file
451
tls/tls_signer.c
Normal file
@@ -0,0 +1,451 @@
|
||||
/* $OpenBSD: tls_signer.c,v 1.9 2023/06/18 19:12:58 tb Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2021 Eric Faurot <eric@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include "tls.h"
|
||||
#include "tls_internal.h"
|
||||
|
||||
struct tls_signer_key {
|
||||
char *hash;
|
||||
RSA *rsa;
|
||||
EC_KEY *ecdsa;
|
||||
struct tls_signer_key *next;
|
||||
};
|
||||
|
||||
struct tls_signer {
|
||||
struct tls_error error;
|
||||
struct tls_signer_key *keys;
|
||||
};
|
||||
|
||||
static pthread_mutex_t signer_method_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
struct tls_signer *
|
||||
tls_signer_new(void)
|
||||
{
|
||||
struct tls_signer *signer;
|
||||
|
||||
if ((signer = calloc(1, sizeof(*signer))) == NULL)
|
||||
return (NULL);
|
||||
|
||||
return (signer);
|
||||
}
|
||||
|
||||
void
|
||||
tls_signer_free(struct tls_signer *signer)
|
||||
{
|
||||
struct tls_signer_key *skey;
|
||||
|
||||
if (signer == NULL)
|
||||
return;
|
||||
|
||||
tls_error_clear(&signer->error);
|
||||
|
||||
while (signer->keys) {
|
||||
skey = signer->keys;
|
||||
signer->keys = skey->next;
|
||||
RSA_free(skey->rsa);
|
||||
EC_KEY_free(skey->ecdsa);
|
||||
free(skey->hash);
|
||||
free(skey);
|
||||
}
|
||||
|
||||
free(signer);
|
||||
}
|
||||
|
||||
const char *
|
||||
tls_signer_error(struct tls_signer *signer)
|
||||
{
|
||||
return (signer->error.msg);
|
||||
}
|
||||
|
||||
int
|
||||
tls_signer_add_keypair_mem(struct tls_signer *signer, const uint8_t *cert,
|
||||
size_t cert_len, const uint8_t *key, size_t key_len)
|
||||
{
|
||||
struct tls_signer_key *skey = NULL;
|
||||
char *errstr = "unknown";
|
||||
int ssl_err;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
X509 *x509 = NULL;
|
||||
BIO *bio = NULL;
|
||||
char *hash = NULL;
|
||||
|
||||
/* Compute certificate hash */
|
||||
if ((bio = BIO_new_mem_buf(cert, cert_len)) == NULL) {
|
||||
tls_error_setx(&signer->error,
|
||||
"failed to create certificate bio");
|
||||
goto err;
|
||||
}
|
||||
if ((x509 = PEM_read_bio_X509(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
if ((ssl_err = ERR_peek_error()) != 0)
|
||||
errstr = ERR_error_string(ssl_err, NULL);
|
||||
tls_error_setx(&signer->error, "failed to load certificate: %s",
|
||||
errstr);
|
||||
goto err;
|
||||
}
|
||||
if (tls_cert_pubkey_hash(x509, &hash) == -1) {
|
||||
tls_error_setx(&signer->error,
|
||||
"failed to get certificate hash");
|
||||
goto err;
|
||||
}
|
||||
|
||||
X509_free(x509);
|
||||
x509 = NULL;
|
||||
BIO_free(bio);
|
||||
bio = NULL;
|
||||
|
||||
/* Read private key */
|
||||
if ((bio = BIO_new_mem_buf(key, key_len)) == NULL) {
|
||||
tls_error_setx(&signer->error, "failed to create key bio");
|
||||
goto err;
|
||||
}
|
||||
if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, tls_password_cb,
|
||||
NULL)) == NULL) {
|
||||
tls_error_setx(&signer->error, "failed to read private key");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if ((skey = calloc(1, sizeof(*skey))) == NULL) {
|
||||
tls_error_set(&signer->error, "failed to create key entry");
|
||||
goto err;
|
||||
}
|
||||
skey->hash = hash;
|
||||
if ((skey->rsa = EVP_PKEY_get1_RSA(pkey)) == NULL &&
|
||||
(skey->ecdsa = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
|
||||
tls_error_setx(&signer->error, "unknown key type");
|
||||
goto err;
|
||||
}
|
||||
|
||||
skey->next = signer->keys;
|
||||
signer->keys = skey;
|
||||
EVP_PKEY_free(pkey);
|
||||
BIO_free(bio);
|
||||
|
||||
return (0);
|
||||
|
||||
err:
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(x509);
|
||||
BIO_free(bio);
|
||||
free(hash);
|
||||
free(skey);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
int
|
||||
tls_signer_add_keypair_file(struct tls_signer *signer, const char *cert_file,
|
||||
const char *key_file)
|
||||
{
|
||||
char *cert = NULL, *key = NULL;
|
||||
size_t cert_len, key_len;
|
||||
int rv = -1;
|
||||
|
||||
if (tls_config_load_file(&signer->error, "certificate", cert_file,
|
||||
&cert, &cert_len) == -1)
|
||||
goto err;
|
||||
|
||||
if (tls_config_load_file(&signer->error, "key", key_file, &key,
|
||||
&key_len) == -1)
|
||||
goto err;
|
||||
|
||||
rv = tls_signer_add_keypair_mem(signer, cert, cert_len, key, key_len);
|
||||
|
||||
err:
|
||||
free(cert);
|
||||
free(key);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_sign_rsa(struct tls_signer *signer, struct tls_signer_key *skey,
|
||||
const uint8_t *input, size_t input_len, int padding_type,
|
||||
uint8_t **out_signature, size_t *out_signature_len)
|
||||
{
|
||||
int rsa_padding, rsa_size, signature_len;
|
||||
char *signature = NULL;
|
||||
|
||||
*out_signature = NULL;
|
||||
*out_signature_len = 0;
|
||||
|
||||
if (padding_type == TLS_PADDING_NONE) {
|
||||
rsa_padding = RSA_NO_PADDING;
|
||||
} else if (padding_type == TLS_PADDING_RSA_PKCS1) {
|
||||
rsa_padding = RSA_PKCS1_PADDING;
|
||||
} else {
|
||||
tls_error_setx(&signer->error, "invalid RSA padding type (%d)",
|
||||
padding_type);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (input_len > INT_MAX) {
|
||||
tls_error_setx(&signer->error, "input too large");
|
||||
return (-1);
|
||||
}
|
||||
if ((rsa_size = RSA_size(skey->rsa)) <= 0) {
|
||||
tls_error_setx(&signer->error, "invalid RSA size: %d",
|
||||
rsa_size);
|
||||
return (-1);
|
||||
}
|
||||
if ((signature = calloc(1, rsa_size)) == NULL) {
|
||||
tls_error_set(&signer->error, "RSA signature");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if ((signature_len = RSA_private_encrypt((int)input_len, input,
|
||||
signature, skey->rsa, rsa_padding)) <= 0) {
|
||||
/* XXX - include further details from libcrypto. */
|
||||
tls_error_setx(&signer->error, "RSA signing failed");
|
||||
free(signature);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
*out_signature = signature;
|
||||
*out_signature_len = (size_t)signature_len;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_sign_ecdsa(struct tls_signer *signer, struct tls_signer_key *skey,
|
||||
const uint8_t *input, size_t input_len, int padding_type,
|
||||
uint8_t **out_signature, size_t *out_signature_len)
|
||||
{
|
||||
unsigned char *signature;
|
||||
int signature_len;
|
||||
|
||||
*out_signature = NULL;
|
||||
*out_signature_len = 0;
|
||||
|
||||
if (padding_type != TLS_PADDING_NONE) {
|
||||
tls_error_setx(&signer->error, "invalid ECDSA padding");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (input_len > INT_MAX) {
|
||||
tls_error_setx(&signer->error, "digest too large");
|
||||
return (-1);
|
||||
}
|
||||
if ((signature_len = ECDSA_size(skey->ecdsa)) <= 0) {
|
||||
tls_error_setx(&signer->error, "invalid ECDSA size: %d",
|
||||
signature_len);
|
||||
return (-1);
|
||||
}
|
||||
if ((signature = calloc(1, signature_len)) == NULL) {
|
||||
tls_error_set(&signer->error, "ECDSA signature");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (!ECDSA_sign(0, input, input_len, signature, &signature_len,
|
||||
skey->ecdsa)) {
|
||||
/* XXX - include further details from libcrypto. */
|
||||
tls_error_setx(&signer->error, "ECDSA signing failed");
|
||||
free(signature);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
*out_signature = signature;
|
||||
*out_signature_len = signature_len;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
tls_signer_sign(struct tls_signer *signer, const char *pubkey_hash,
|
||||
const uint8_t *input, size_t input_len, int padding_type,
|
||||
uint8_t **out_signature, size_t *out_signature_len)
|
||||
{
|
||||
struct tls_signer_key *skey;
|
||||
|
||||
*out_signature = NULL;
|
||||
*out_signature_len = 0;
|
||||
|
||||
for (skey = signer->keys; skey; skey = skey->next)
|
||||
if (!strcmp(pubkey_hash, skey->hash))
|
||||
break;
|
||||
|
||||
if (skey == NULL) {
|
||||
tls_error_setx(&signer->error, "key not found");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (skey->rsa != NULL)
|
||||
return tls_sign_rsa(signer, skey, input, input_len,
|
||||
padding_type, out_signature, out_signature_len);
|
||||
|
||||
if (skey->ecdsa != NULL)
|
||||
return tls_sign_ecdsa(signer, skey, input, input_len,
|
||||
padding_type, out_signature, out_signature_len);
|
||||
|
||||
tls_error_setx(&signer->error, "unknown key type");
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static int
|
||||
tls_rsa_priv_enc(int from_len, const unsigned char *from, unsigned char *to,
|
||||
RSA *rsa, int rsa_padding)
|
||||
{
|
||||
struct tls_config *config;
|
||||
uint8_t *signature = NULL;
|
||||
size_t signature_len = 0;
|
||||
const char *pubkey_hash;
|
||||
int padding_type;
|
||||
|
||||
/*
|
||||
* This function is called via RSA_private_encrypt() and has to conform
|
||||
* to its calling convention/signature. The caller is required to
|
||||
* provide a 'to' buffer of at least RSA_size() bytes.
|
||||
*/
|
||||
|
||||
pubkey_hash = RSA_get_ex_data(rsa, 0);
|
||||
config = RSA_get_ex_data(rsa, 1);
|
||||
|
||||
if (pubkey_hash == NULL || config == NULL)
|
||||
goto err;
|
||||
|
||||
if (rsa_padding == RSA_NO_PADDING) {
|
||||
padding_type = TLS_PADDING_NONE;
|
||||
} else if (rsa_padding == RSA_PKCS1_PADDING) {
|
||||
padding_type = TLS_PADDING_RSA_PKCS1;
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (from_len < 0)
|
||||
goto err;
|
||||
|
||||
if (config->sign_cb(config->sign_cb_arg, pubkey_hash, from, from_len,
|
||||
padding_type, &signature, &signature_len) == -1)
|
||||
goto err;
|
||||
|
||||
if (signature_len > INT_MAX || (int)signature_len > RSA_size(rsa))
|
||||
goto err;
|
||||
|
||||
memcpy(to, signature, signature_len);
|
||||
free(signature);
|
||||
|
||||
return ((int)signature_len);
|
||||
|
||||
err:
|
||||
free(signature);
|
||||
|
||||
return (-1);
|
||||
}
|
||||
|
||||
RSA_METHOD *
|
||||
tls_signer_rsa_method(void)
|
||||
{
|
||||
static RSA_METHOD *rsa_method = NULL;
|
||||
|
||||
pthread_mutex_lock(&signer_method_lock);
|
||||
|
||||
if (rsa_method != NULL)
|
||||
goto out;
|
||||
|
||||
rsa_method = RSA_meth_new("libtls RSA method", 0);
|
||||
if (rsa_method == NULL)
|
||||
goto out;
|
||||
|
||||
RSA_meth_set_priv_enc(rsa_method, tls_rsa_priv_enc);
|
||||
|
||||
out:
|
||||
pthread_mutex_unlock(&signer_method_lock);
|
||||
|
||||
return (rsa_method);
|
||||
}
|
||||
|
||||
static ECDSA_SIG *
|
||||
tls_ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv,
|
||||
const BIGNUM *rp, EC_KEY *eckey)
|
||||
{
|
||||
struct tls_config *config;
|
||||
ECDSA_SIG *ecdsa_sig = NULL;
|
||||
uint8_t *signature = NULL;
|
||||
size_t signature_len = 0;
|
||||
const unsigned char *p;
|
||||
const char *pubkey_hash;
|
||||
|
||||
/*
|
||||
* This function is called via ECDSA_do_sign_ex() and has to conform
|
||||
* to its calling convention/signature.
|
||||
*/
|
||||
|
||||
pubkey_hash = EC_KEY_get_ex_data(eckey, 0);
|
||||
config = EC_KEY_get_ex_data(eckey, 1);
|
||||
|
||||
if (pubkey_hash == NULL || config == NULL)
|
||||
goto err;
|
||||
|
||||
if (dgst_len < 0)
|
||||
goto err;
|
||||
|
||||
if (config->sign_cb(config->sign_cb_arg, pubkey_hash, dgst, dgst_len,
|
||||
TLS_PADDING_NONE, &signature, &signature_len) == -1)
|
||||
goto err;
|
||||
|
||||
p = signature;
|
||||
if ((ecdsa_sig = d2i_ECDSA_SIG(NULL, &p, signature_len)) == NULL)
|
||||
goto err;
|
||||
|
||||
free(signature);
|
||||
|
||||
return (ecdsa_sig);
|
||||
|
||||
err:
|
||||
free(signature);
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
EC_KEY_METHOD *
|
||||
tls_signer_ecdsa_method(void)
|
||||
{
|
||||
static EC_KEY_METHOD *ecdsa_method = NULL;
|
||||
const EC_KEY_METHOD *default_method;
|
||||
int (*sign)(int type, const unsigned char *dgst, int dlen,
|
||||
unsigned char *sig, unsigned int *siglen,
|
||||
const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey);
|
||||
int (*sign_setup)(EC_KEY *eckey, BN_CTX *ctx_in,
|
||||
BIGNUM **kinvp, BIGNUM **rp);
|
||||
|
||||
pthread_mutex_lock(&signer_method_lock);
|
||||
|
||||
if (ecdsa_method != NULL)
|
||||
goto out;
|
||||
|
||||
default_method = EC_KEY_get_default_method();
|
||||
ecdsa_method = EC_KEY_METHOD_new(default_method);
|
||||
if (ecdsa_method == NULL)
|
||||
goto out;
|
||||
|
||||
EC_KEY_METHOD_get_sign(default_method, &sign, &sign_setup, NULL);
|
||||
EC_KEY_METHOD_set_sign(ecdsa_method, sign, sign_setup,
|
||||
tls_ecdsa_do_sign);
|
||||
|
||||
out:
|
||||
pthread_mutex_unlock(&signer_method_lock);
|
||||
|
||||
return (ecdsa_method);
|
||||
}
|
226
tls/tls_util.c
Normal file
226
tls/tls_util.c
Normal file
@@ -0,0 +1,226 @@
|
||||
/* $OpenBSD: tls_util.c,v 1.16 2023/05/14 07:26:25 op Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2014 Ted Unangst <tedu@openbsd.org>
|
||||
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "tls.h"
|
||||
#include "tls_internal.h"
|
||||
|
||||
static void *
|
||||
memdup(const void *in, size_t len)
|
||||
{
|
||||
void *out;
|
||||
|
||||
if ((out = malloc(len)) == NULL)
|
||||
return NULL;
|
||||
memcpy(out, in, len);
|
||||
return out;
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_mem(char **dest, size_t *destlen, const void *src, size_t srclen)
|
||||
{
|
||||
free(*dest);
|
||||
*dest = NULL;
|
||||
*destlen = 0;
|
||||
if (src != NULL) {
|
||||
if ((*dest = memdup(src, srclen)) == NULL)
|
||||
return -1;
|
||||
*destlen = srclen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
tls_set_string(const char **dest, const char *src)
|
||||
{
|
||||
free((char *)*dest);
|
||||
*dest = NULL;
|
||||
if (src != NULL)
|
||||
if ((*dest = strdup(src)) == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the host and port from a colon separated value. For a literal IPv6
|
||||
* address the address must be contained with square braces. If a host and
|
||||
* port are successfully extracted, the function will return 0 and the
|
||||
* caller is responsible for freeing the host and port. If no port is found
|
||||
* then the function will return 1, with both host and port being NULL.
|
||||
* On memory allocation failure -1 will be returned.
|
||||
*/
|
||||
int
|
||||
tls_host_port(const char *hostport, char **host, char **port)
|
||||
{
|
||||
char *h, *p, *s;
|
||||
int rv = 1;
|
||||
|
||||
*host = NULL;
|
||||
*port = NULL;
|
||||
|
||||
if ((s = strdup(hostport)) == NULL)
|
||||
goto err;
|
||||
|
||||
h = p = s;
|
||||
|
||||
/* See if this is an IPv6 literal with square braces. */
|
||||
if (p[0] == '[') {
|
||||
h++;
|
||||
if ((p = strchr(s, ']')) == NULL)
|
||||
goto done;
|
||||
*p++ = '\0';
|
||||
}
|
||||
|
||||
/* Find the port separator. */
|
||||
if ((p = strchr(p, ':')) == NULL)
|
||||
goto done;
|
||||
|
||||
/* If there is another separator then we have issues. */
|
||||
if (strchr(p + 1, ':') != NULL)
|
||||
goto done;
|
||||
|
||||
*p++ = '\0';
|
||||
|
||||
if (asprintf(host, "%s", h) == -1) {
|
||||
*host = NULL;
|
||||
goto err;
|
||||
}
|
||||
if (asprintf(port, "%s", p) == -1) {
|
||||
*port = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rv = 0;
|
||||
goto done;
|
||||
|
||||
err:
|
||||
free(*host);
|
||||
*host = NULL;
|
||||
free(*port);
|
||||
*port = NULL;
|
||||
rv = -1;
|
||||
|
||||
done:
|
||||
free(s);
|
||||
|
||||
return (rv);
|
||||
}
|
||||
|
||||
int
|
||||
tls_password_cb(char *buf, int size, int rwflag, void *u)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
if (size < 0)
|
||||
return (0);
|
||||
|
||||
if (u == NULL) {
|
||||
memset(buf, 0, size);
|
||||
return (0);
|
||||
}
|
||||
|
||||
if ((len = strlcpy(buf, u, size)) >= (size_t)size)
|
||||
return (0);
|
||||
|
||||
return (len);
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
tls_load_file(const char *name, size_t *len, char *password)
|
||||
{
|
||||
FILE *fp;
|
||||
EVP_PKEY *key = NULL;
|
||||
BIO *bio = NULL;
|
||||
char *data;
|
||||
uint8_t *buf = NULL;
|
||||
struct stat st;
|
||||
size_t size = 0;
|
||||
int fd = -1;
|
||||
ssize_t n;
|
||||
|
||||
*len = 0;
|
||||
|
||||
if ((fd = open(name, O_RDONLY)) == -1)
|
||||
return (NULL);
|
||||
|
||||
/* Just load the file into memory without decryption */
|
||||
if (password == NULL) {
|
||||
if (fstat(fd, &st) != 0)
|
||||
goto err;
|
||||
if (st.st_size < 0)
|
||||
goto err;
|
||||
size = (size_t)st.st_size;
|
||||
if ((buf = malloc(size)) == NULL)
|
||||
goto err;
|
||||
n = read(fd, buf, size);
|
||||
if (n < 0 || (size_t)n != size)
|
||||
goto err;
|
||||
close(fd);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Or read the (possibly) encrypted key from file */
|
||||
if ((fp = fdopen(fd, "r")) == NULL)
|
||||
goto err;
|
||||
fd = -1;
|
||||
|
||||
key = PEM_read_PrivateKey(fp, NULL, tls_password_cb, password);
|
||||
fclose(fp);
|
||||
if (key == NULL)
|
||||
goto err;
|
||||
|
||||
/* Write unencrypted key to memory buffer */
|
||||
if ((bio = BIO_new(BIO_s_mem())) == NULL)
|
||||
goto err;
|
||||
if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL))
|
||||
goto err;
|
||||
if ((size = BIO_get_mem_data(bio, &data)) <= 0)
|
||||
goto err;
|
||||
if ((buf = malloc(size)) == NULL)
|
||||
goto err;
|
||||
memcpy(buf, data, size);
|
||||
|
||||
BIO_free_all(bio);
|
||||
EVP_PKEY_free(key);
|
||||
|
||||
done:
|
||||
*len = size;
|
||||
return (buf);
|
||||
|
||||
err:
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
freezero(buf, size);
|
||||
BIO_free_all(bio);
|
||||
EVP_PKEY_free(key);
|
||||
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void
|
||||
tls_unload_file(uint8_t *buf, size_t len)
|
||||
{
|
||||
freezero(buf, len);
|
||||
}
|
331
tls/tls_verify.c
Normal file
331
tls/tls_verify.c
Normal file
@@ -0,0 +1,331 @@
|
||||
/* $OpenBSD: tls_verify.c,v 1.28 2023/06/01 07:32:25 tb Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include <tls.h>
|
||||
#include "tls_internal.h"
|
||||
|
||||
static int
|
||||
tls_match_name(const char *cert_name, const char *name)
|
||||
{
|
||||
const char *cert_domain, *domain, *next_dot;
|
||||
|
||||
if (strcasecmp(cert_name, name) == 0)
|
||||
return 0;
|
||||
|
||||
/* Wildcard match? */
|
||||
if (cert_name[0] == '*') {
|
||||
/*
|
||||
* Valid wildcards:
|
||||
* - "*.domain.tld"
|
||||
* - "*.sub.domain.tld"
|
||||
* - etc.
|
||||
* Reject "*.tld".
|
||||
* No attempt to prevent the use of eg. "*.co.uk".
|
||||
*/
|
||||
cert_domain = &cert_name[1];
|
||||
/* Disallow "*" */
|
||||
if (cert_domain[0] == '\0')
|
||||
return -1;
|
||||
/* Disallow "*foo" */
|
||||
if (cert_domain[0] != '.')
|
||||
return -1;
|
||||
/* Disallow "*.." */
|
||||
if (cert_domain[1] == '.')
|
||||
return -1;
|
||||
next_dot = strchr(&cert_domain[1], '.');
|
||||
/* Disallow "*.bar" */
|
||||
if (next_dot == NULL)
|
||||
return -1;
|
||||
/* Disallow "*.bar.." */
|
||||
if (next_dot[1] == '.')
|
||||
return -1;
|
||||
|
||||
domain = strchr(name, '.');
|
||||
|
||||
/* No wildcard match against a name with no host part. */
|
||||
if (name[0] == '.')
|
||||
return -1;
|
||||
/* No wildcard match against a name with no domain part. */
|
||||
if (domain == NULL || strlen(domain) == 1)
|
||||
return -1;
|
||||
|
||||
if (strcasecmp(cert_domain, domain) == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* See RFC 5280 section 4.2.1.6 for SubjectAltName details.
|
||||
* alt_match is set to 1 if a matching alternate name is found.
|
||||
* alt_exists is set to 1 if any known alternate name exists in the certificate.
|
||||
*/
|
||||
static int
|
||||
tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name,
|
||||
int *alt_match, int *alt_exists)
|
||||
{
|
||||
STACK_OF(GENERAL_NAME) *altname_stack = NULL;
|
||||
union tls_addr addrbuf;
|
||||
int addrlen, type;
|
||||
int count, i;
|
||||
int critical = 0;
|
||||
int rv = -1;
|
||||
|
||||
*alt_match = 0;
|
||||
*alt_exists = 0;
|
||||
|
||||
altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, &critical,
|
||||
NULL);
|
||||
if (altname_stack == NULL) {
|
||||
if (critical != -1) {
|
||||
tls_set_errorx(ctx, "error decoding subjectAltName");
|
||||
goto err;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (inet_pton(AF_INET, name, &addrbuf) == 1) {
|
||||
type = GEN_IPADD;
|
||||
addrlen = 4;
|
||||
} else if (inet_pton(AF_INET6, name, &addrbuf) == 1) {
|
||||
type = GEN_IPADD;
|
||||
addrlen = 16;
|
||||
} else {
|
||||
type = GEN_DNS;
|
||||
addrlen = 0;
|
||||
}
|
||||
|
||||
count = sk_GENERAL_NAME_num(altname_stack);
|
||||
for (i = 0; i < count; i++) {
|
||||
GENERAL_NAME *altname;
|
||||
|
||||
altname = sk_GENERAL_NAME_value(altname_stack, i);
|
||||
|
||||
if (altname->type == GEN_DNS || altname->type == GEN_IPADD)
|
||||
*alt_exists = 1;
|
||||
|
||||
if (altname->type != type)
|
||||
continue;
|
||||
|
||||
if (type == GEN_DNS) {
|
||||
const unsigned char *data;
|
||||
int format, len;
|
||||
|
||||
format = ASN1_STRING_type(altname->d.dNSName);
|
||||
if (format == V_ASN1_IA5STRING) {
|
||||
data = ASN1_STRING_get0_data(altname->d.dNSName);
|
||||
len = ASN1_STRING_length(altname->d.dNSName);
|
||||
|
||||
if (len < 0 || (size_t)len != strlen(data)) {
|
||||
tls_set_errorx(ctx,
|
||||
"error verifying name '%s': "
|
||||
"NUL byte in subjectAltName, "
|
||||
"probably a malicious certificate",
|
||||
name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per RFC 5280 section 4.2.1.6:
|
||||
* " " is a legal domain name, but that
|
||||
* dNSName must be rejected.
|
||||
*/
|
||||
if (strcmp(data, " ") == 0) {
|
||||
tls_set_errorx(ctx,
|
||||
"error verifying name '%s': "
|
||||
"a dNSName of \" \" must not be "
|
||||
"used", name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (tls_match_name(data, name) == 0) {
|
||||
*alt_match = 1;
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
fprintf(stdout, "%s: unhandled subjectAltName "
|
||||
"dNSName encoding (%d)\n", getprogname(),
|
||||
format);
|
||||
#endif
|
||||
}
|
||||
|
||||
} else if (type == GEN_IPADD) {
|
||||
const unsigned char *data;
|
||||
int datalen;
|
||||
|
||||
datalen = ASN1_STRING_length(altname->d.iPAddress);
|
||||
data = ASN1_STRING_get0_data(altname->d.iPAddress);
|
||||
|
||||
if (datalen < 0) {
|
||||
tls_set_errorx(ctx,
|
||||
"Unexpected negative length for an "
|
||||
"IP address: %d", datalen);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per RFC 5280 section 4.2.1.6:
|
||||
* IPv4 must use 4 octets and IPv6 must use 16 octets.
|
||||
*/
|
||||
if (datalen == addrlen &&
|
||||
memcmp(data, &addrbuf, addrlen) == 0) {
|
||||
*alt_match = 1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_check_common_name(struct tls *ctx, X509 *cert, const char *name,
|
||||
int *cn_match)
|
||||
{
|
||||
unsigned char *utf8_bytes = NULL;
|
||||
X509_NAME *subject_name;
|
||||
char *common_name = NULL;
|
||||
union tls_addr addrbuf;
|
||||
int common_name_len;
|
||||
ASN1_STRING *data;
|
||||
int lastpos = -1;
|
||||
int rv = -1;
|
||||
|
||||
*cn_match = 0;
|
||||
|
||||
subject_name = X509_get_subject_name(cert);
|
||||
if (subject_name == NULL)
|
||||
goto done;
|
||||
|
||||
lastpos = X509_NAME_get_index_by_NID(subject_name,
|
||||
NID_commonName, lastpos);
|
||||
if (lastpos == -1)
|
||||
goto done;
|
||||
if (lastpos < 0)
|
||||
goto err;
|
||||
if (X509_NAME_get_index_by_NID(subject_name, NID_commonName, lastpos)
|
||||
!= -1) {
|
||||
/*
|
||||
* Having multiple CN's is possible, and even happened back in
|
||||
* the glory days of mullets and Hammer pants. In anything like
|
||||
* a modern TLS cert, CN is as close to deprecated as it gets,
|
||||
* and having more than one is bad. We therefore fail if we have
|
||||
* more than one CN fed to us in the subject, treating the
|
||||
* certificate as hostile.
|
||||
*/
|
||||
tls_set_errorx(ctx, "error verifying name '%s': "
|
||||
"Certificate subject contains mutiple Common Name fields, "
|
||||
"probably a malicious or malformed certificate", name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name,
|
||||
lastpos));
|
||||
/*
|
||||
* Fail if we cannot encode the CN bytes as UTF-8.
|
||||
*/
|
||||
if ((common_name_len = ASN1_STRING_to_UTF8(&utf8_bytes, data)) < 0) {
|
||||
tls_set_errorx(ctx, "error verifying name '%s': "
|
||||
"Common Name field cannot be encoded as a UTF-8 string, "
|
||||
"probably a malicious certificate", name);
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Fail if the CN is of invalid length. RFC 5280 specifies that a CN
|
||||
* must be between 1 and 64 bytes long.
|
||||
*/
|
||||
if (common_name_len < 1 || common_name_len > 64) {
|
||||
tls_set_errorx(ctx, "error verifying name '%s': "
|
||||
"Common Name field has invalid length, "
|
||||
"probably a malicious certificate", name);
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
* Fail if the resulting text contains a NUL byte.
|
||||
*/
|
||||
if (memchr(utf8_bytes, 0, common_name_len) != NULL) {
|
||||
tls_set_errorx(ctx, "error verifying name '%s': "
|
||||
"NUL byte in Common Name field, "
|
||||
"probably a malicious certificate", name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
common_name = strndup(utf8_bytes, common_name_len);
|
||||
if (common_name == NULL) {
|
||||
tls_set_error(ctx, "out of memory");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't want to attempt wildcard matching against IP addresses,
|
||||
* so perform a simple comparison here.
|
||||
*/
|
||||
if (inet_pton(AF_INET, name, &addrbuf) == 1 ||
|
||||
inet_pton(AF_INET6, name, &addrbuf) == 1) {
|
||||
if (strcmp(common_name, name) == 0)
|
||||
*cn_match = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (tls_match_name(common_name, name) == 0)
|
||||
*cn_match = 1;
|
||||
|
||||
done:
|
||||
rv = 0;
|
||||
|
||||
err:
|
||||
free(utf8_bytes);
|
||||
free(common_name);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
tls_check_name(struct tls *ctx, X509 *cert, const char *name, int *match)
|
||||
{
|
||||
int alt_exists;
|
||||
|
||||
*match = 0;
|
||||
|
||||
if (tls_check_subject_altname(ctx, cert, name, match,
|
||||
&alt_exists) == -1)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* As per RFC 6125 section 6.4.4, if any known alternate name existed
|
||||
* in the certificate, we do not attempt to match on the CN.
|
||||
*/
|
||||
if (*match || alt_exists)
|
||||
return 0;
|
||||
|
||||
return tls_check_common_name(ctx, cert, name, match);
|
||||
}
|
Reference in New Issue
Block a user