From b0b8ae0ce88a42b94b50a02ea56837446a0ec5ba Mon Sep 17 00:00:00 2001 From: Dmitry Simonenko Date: Thu, 23 Mar 2017 16:02:45 +0300 Subject: [PATCH] machinarium: integrate with OpenBSD tls library --- CMakeLists.txt | 9 +- lib/libtls/CMakeLists.txt | 14 + lib/libtls/tls.c | 640 +++++++++++++++++++++++++ lib/libtls/tls.h | 168 +++++++ lib/libtls/tls_cert.c | 674 ++++++++++++++++++++++++++ lib/libtls/tls_cert.h | 105 +++++ lib/libtls/tls_client.c | 287 +++++++++++ lib/libtls/tls_compat.c | 440 +++++++++++++++++ lib/libtls/tls_compat.h | 47 ++ lib/libtls/tls_config.c | 365 ++++++++++++++ lib/libtls/tls_conninfo.c | 216 +++++++++ lib/libtls/tls_internal.h | 199 ++++++++ lib/libtls/tls_ocsp.c | 969 ++++++++++++++++++++++++++++++++++++++ lib/libtls/tls_peer.c | 83 ++++ lib/libtls/tls_server.c | 193 ++++++++ lib/libtls/tls_util.c | 226 +++++++++ lib/libtls/tls_verify.c | 256 ++++++++++ src/CMakeLists.txt | 1 + 18 files changed, 4890 insertions(+), 2 deletions(-) create mode 100644 lib/libtls/CMakeLists.txt create mode 100644 lib/libtls/tls.c create mode 100644 lib/libtls/tls.h create mode 100644 lib/libtls/tls_cert.c create mode 100644 lib/libtls/tls_cert.h create mode 100644 lib/libtls/tls_client.c create mode 100644 lib/libtls/tls_compat.c create mode 100644 lib/libtls/tls_compat.h create mode 100644 lib/libtls/tls_config.c create mode 100644 lib/libtls/tls_conninfo.c create mode 100644 lib/libtls/tls_internal.h create mode 100644 lib/libtls/tls_ocsp.c create mode 100644 lib/libtls/tls_peer.c create mode 100644 lib/libtls/tls_server.c create mode 100644 lib/libtls/tls_util.c create mode 100644 lib/libtls/tls_verify.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dbd2ce9..8767b7c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,9 @@ if ("${CMAKE_BUILD_TYPE}" STREQUAL "") endif() if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") - set(CMAKE_C_FLAGS "-std=gnu99 -Wall -Wextra -fPIC -g -O2") + set(CMAKE_C_FLAGS "-std=gnu99 -fPIC -g -O2 -D_GNU_SOURCE") elseif("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - set(CMAKE_C_FLAGS "-std=gnu99 -Wall -Wextra -fPIC -g -O0") + set(CMAKE_C_FLAGS "-std=gnu99 -fPIC -g -O0 -D_GNU_SOURCE") endif() include_directories("${PROJECT_SOURCE_DIR}/src") @@ -33,6 +33,11 @@ include_directories(${LIBUV_INCLUDE_DIRS}) include_directories("${PROJECT_SOURCE_DIR}/lib/libcoro") include_directories("${PROJECT_BINARY_DIR}/lib/libcoro") +# libtls +include_directories("${PROJECT_SOURCE_DIR}/lib/libtls") +include_directories("${PROJECT_BINARY_DIR}/lib/libtls") +add_subdirectory(lib/libtls) + add_subdirectory(src) message (STATUS "") diff --git a/lib/libtls/CMakeLists.txt b/lib/libtls/CMakeLists.txt new file mode 100644 index 00000000..c1df3516 --- /dev/null +++ b/lib/libtls/CMakeLists.txt @@ -0,0 +1,14 @@ +set(tls_library tls) +set(tls_src + tls.c + tls_client.c + tls_config.c + tls_conninfo.c + tls_server.c + tls_ocsp.c + tls_peer.c + tls_util.c + tls_verify.c + tls_compat.c) +add_library(tls_library_static STATIC ${tls_src}) +set_target_properties(tls_library_static PROPERTIES OUTPUT_NAME ${tls_library}) diff --git a/lib/libtls/tls.c b/lib/libtls/tls.c new file mode 100644 index 00000000..f263a6c7 --- /dev/null +++ b/lib/libtls/tls.c @@ -0,0 +1,640 @@ +/* $OpenBSD: tls.c,v 1.11 2015/04/15 16:08:43 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +static struct tls_config *tls_config_default; + +static int tls_initialised = 0; + +int +tls_init(void) +{ + if (tls_initialised) + return (0); + + SSL_load_error_strings(); + SSL_library_init(); + + if (BIO_sock_init() != 1) + return (-1); + + if ((tls_config_default = tls_config_new()) == NULL) + return (-1); + + tls_initialised = 1; + + return (0); +} + +void +tls_deinit(void) +{ + if (tls_initialised) { + tls_compat_cleanup(); + + tls_config_free(tls_config_default); + tls_config_default = NULL; + + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + BIO_sock_cleanup(); + ERR_clear_error(); + ERR_remove_thread_state(NULL); + ERR_free_strings(); + + tls_initialised = 0; + } +} + +const char * +tls_error(struct tls *ctx) +{ + return ctx->errmsg; +} + +static int +tls_set_verror(struct tls *ctx, int errnum, const char *fmt, va_list ap) +{ + char *errmsg = NULL; + int rv = -1; + + free(ctx->errmsg); + ctx->errmsg = NULL; + + if (vasprintf(&errmsg, fmt, ap) == -1) { + errmsg = NULL; + goto err; + } + + if (errnum == -1) { + ctx->errmsg = errmsg; + return (0); + } + + if (asprintf(&ctx->errmsg, "%s: %s", errmsg, strerror(errnum)) == -1) { + ctx->errmsg = NULL; + goto err; + } + rv = 0; + + err: + free(errmsg); + + return (rv); +} + +int +tls_set_error(struct tls *ctx, const char *fmt, ...) +{ + va_list ap; + int rv; + + ctx->errnum = errno; + + va_start(ap, fmt); + rv = tls_set_verror(ctx, ctx->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_set_verror(ctx, -1, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_set_error_libssl(struct tls *ctx, const char *fmt, ...) +{ + va_list ap; + int rv; + const char *msg = NULL; + char *old; + int err; + + err = ERR_peek_error(); + if (err != 0) + msg = ERR_reason_error_string(err); + + va_start(ap, fmt); + rv = tls_set_verror(ctx, -1, fmt, ap); + va_end(ap); + if (rv != 0 || msg == NULL) + return rv; + + old = ctx->errmsg; + ctx->errmsg = NULL; + if (asprintf(&ctx->errmsg, "%s: %s", old, msg) == -1) { + ctx->errmsg = old; + return 0; + } + free(old); + + return 0; +} + +struct tls * +tls_new(void) +{ + struct tls *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + ctx->config = tls_config_default; + + tls_reset(ctx); + + return (ctx); +} + +int +tls_configure(struct tls *ctx, struct tls_config *config) +{ + if (config == NULL) + config = tls_config_default; + + ctx->config = config; + + if ((ctx->flags & TLS_SERVER) != 0) + return (tls_configure_server(ctx)); + + return (0); +} + +int +tls_configure_keypair(struct tls *ctx, int required) +{ + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + BIO *bio = NULL; + + if (!required && + ctx->config->cert_mem == NULL && + ctx->config->key_mem == NULL && + ctx->config->cert_file == NULL && + ctx->config->key_file == NULL) + return(0); + + if (ctx->config->cert_mem != NULL) { + if (ctx->config->cert_len > INT_MAX) { + tls_set_errorx(ctx, "certificate too long"); + goto err; + } + + if (SSL_CTX_use_certificate_chain_mem(ctx->ssl_ctx, + ctx->config->cert_mem, ctx->config->cert_len) != 1) { + tls_set_errorx(ctx, "failed to load certificate"); + goto err; + } + cert = NULL; + } + if (ctx->config->key_mem != NULL) { + if (ctx->config->key_len > INT_MAX) { + tls_set_errorx(ctx, "key too long"); + goto err; + } + + if ((bio = BIO_new_mem_buf(ctx->config->key_mem, + ctx->config->key_len)) == NULL) { + tls_set_errorx(ctx, "failed to create buffer"); + goto err; + } + if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, + NULL)) == NULL) { + tls_set_errorx(ctx, "failed to read private key"); + goto err; + } + if (SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey) != 1) { + tls_set_errorx(ctx, "failed to load private key"); + goto err; + } + BIO_free(bio); + bio = NULL; + EVP_PKEY_free(pkey); + pkey = NULL; + } + + if (ctx->config->cert_file != NULL) { + if (SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, + ctx->config->cert_file) != 1) { + tls_set_errorx(ctx, "failed to load certificate file"); + goto err; + } + } + if (ctx->config->key_file != NULL) { + if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, + ctx->config->key_file, SSL_FILETYPE_PEM) != 1) { + tls_set_errorx(ctx, "failed to load private key file"); + goto err; + } + } + + if (SSL_CTX_check_private_key(ctx->ssl_ctx) != 1) { + tls_set_errorx(ctx, "private/public key mismatch"); + goto err; + } + + return (0); + + err: + EVP_PKEY_free(pkey); + X509_free(cert); + BIO_free(bio); + + return (1); +} + +static void +tls_info_callback(const SSL *ssl, int where, int rc) +{ + struct tls *ctx = SSL_get_app_data(ssl); + (void)rc; + +#ifdef USE_LIBSSL_INTERNALS + if (!(ctx->state & TLS_HANDSHAKE_COMPLETE) && ssl->s3) { + /* steal info about used DH key */ + if (ssl->s3->tmp.dh && !ctx->used_dh_bits) { + ctx->used_dh_bits = DH_size(ssl->s3->tmp.dh) * 8; + } else if (ssl->s3->tmp.ecdh && !ctx->used_ecdh_nid) { + ctx->used_ecdh_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ssl->s3->tmp.ecdh)); + } + } +#endif + + /* detect renegotation on established connection */ + if (where & SSL_CB_HANDSHAKE_START) { + if (ctx->state & TLS_HANDSHAKE_COMPLETE) + ctx->state |= TLS_DO_ABORT; + } +} + +static int +tls_do_abort(struct tls *ctx) +{ + int ssl_ret, rv; + + 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) + return (rv); + } + + tls_set_errorx(ctx, "unexpected handshake, closing connection"); + return -1; +} + +int +tls_configure_ssl(struct tls *ctx) +{ + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv3); + + SSL_CTX_clear_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1); + SSL_CTX_clear_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1_1); + SSL_CTX_clear_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1_2); + + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_0) == 0) + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1); + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_1) == 0) + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1_1); + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_2) == 0) + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1_2); + + if (ctx->config->ciphers != NULL) { + if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, + ctx->config->ciphers) != 1) { + tls_set_errorx(ctx, "failed to set ciphers"); + goto err; + } + } + + SSL_CTX_set_info_callback(ctx->ssl_ctx, tls_info_callback); + +#ifdef X509_V_FLAG_NO_CHECK_TIME + if (ctx->config->verify_time == 0) { + X509_VERIFY_PARAM *vfp = SSL_CTX_get0_param(ctx->ssl_ctx); + X509_VERIFY_PARAM_set_flags(vfp, X509_V_FLAG_NO_CHECK_TIME); + } +#endif + + return (0); + + err: + return (-1); +} + +int +tls_configure_ssl_verify(struct tls *ctx, int verify) +{ + SSL_CTX_set_verify(ctx->ssl_ctx, verify, NULL); + + if (ctx->config->ca_mem != NULL) { + /* XXX do this in set. */ + if (ctx->config->ca_len > INT_MAX) { + tls_set_errorx(ctx, "ca too long"); + goto err; + } + if (SSL_CTX_load_verify_mem(ctx->ssl_ctx, + ctx->config->ca_mem, ctx->config->ca_len) != 1) { + tls_set_errorx(ctx, "ssl verify memory setup failure"); + goto err; + } + } else if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, + ctx->config->ca_file, ctx->config->ca_path) != 1) { + tls_set_errorx(ctx, "ssl verify setup failure"); + goto err; + } + if (ctx->config->verify_depth >= 0) + SSL_CTX_set_verify_depth(ctx->ssl_ctx, + ctx->config->verify_depth); + + return (0); + + err: + return (-1); +} + +void +tls_free(struct tls *ctx) +{ + if (ctx == NULL) + return; + tls_reset(ctx); + free(ctx); +} + +void +tls_reset(struct tls *ctx) +{ + 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; + + ctx->socket = -1; + ctx->state = 0; + + free(ctx->servername); + ctx->servername = NULL; + + free(ctx->errmsg); + ctx->errmsg = NULL; + ctx->errnum = 0; + + tls_free_conninfo(ctx->conninfo); + free(ctx->conninfo); + ctx->conninfo = NULL; + + ctx->used_dh_bits = 0; + ctx->used_ecdh_nid = 0; + + tls_ocsp_info_free(ctx->ocsp_info); + ctx->ocsp_info = NULL; + ctx->ocsp_result = NULL; + + if (ctx->flags & TLS_OCSP_CLIENT) + tls_ocsp_client_free(ctx); +} + +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) { + errstr = "Unexpected EOF"; + } else { + ctx->state |= TLS_EOF_NO_CLOSE_NOTIFY; + return (0); + } + } else if (ssl_ret == -1) { + errstr = strerror(errno); + } + tls_set_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_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_errorx(ctx, "%s failed (%i)", prefix, ssl_err); + return (-1); + } +} + +int +tls_handshake(struct tls *ctx) +{ + int rv = -1; + + if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) { + tls_set_errorx(ctx, "invalid operation for context"); + goto out; + } + + if (ctx->conninfo == NULL && + (ctx->conninfo = calloc(1, sizeof(*ctx->conninfo))) == NULL) + 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); + if (tls_get_conninfo(ctx) == -1) + rv = -1; + } + 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; + + if (ctx->state & TLS_DO_ABORT) { + rv = tls_do_abort(ctx); + goto out; + } + + 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; + + if (ctx->state & TLS_DO_ABORT) { + rv = tls_do_abort(ctx); + goto out; + } + + 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; + + if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) { + tls_set_errorx(ctx, "invalid operation for context"); + rv = -1; + goto out; + } + + if (ctx->ssl_conn != NULL) { + 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; + } + } + + 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); +} diff --git a/lib/libtls/tls.h b/lib/libtls/tls.h new file mode 100644 index 00000000..9fd0fb31 --- /dev/null +++ b/lib/libtls/tls.h @@ -0,0 +1,168 @@ +/* $OpenBSD: tls.h,v 1.12 2015/03/31 14:03:38 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * + * 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_H_ +#define _HEADER_TLS_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TLS_API 20141031 + +#define TLS_PROTOCOL_TLSv1_0 (1 << 1) +#define TLS_PROTOCOL_TLSv1_1 (1 << 2) +#define TLS_PROTOCOL_TLSv1_2 (1 << 3) +#define TLS_PROTOCOL_TLSv1 \ + (TLS_PROTOCOL_TLSv1_0|TLS_PROTOCOL_TLSv1_1|TLS_PROTOCOL_TLSv1_2) + +#define TLS_PROTOCOLS_ALL TLS_PROTOCOL_TLSv1 +#define TLS_PROTOCOLS_DEFAULT TLS_PROTOCOL_TLSv1_2 + +#define TLS_WANT_POLLIN -2 +#define TLS_WANT_POLLOUT -3 +#define TLS_NO_OCSP -4 +#define TLS_NO_CERT -5 + +#define TLS_OCSP_RESPONSE_SUCCESSFUL 0 +#define TLS_OCSP_RESPONSE_MALFORMED 1 +#define TLS_OCSP_RESPONSE_INTERNALERR 2 +#define TLS_OCSP_RESPONSE_TRYLATER 3 +#define TLS_OCSP_RESPONSE_SIGREQUIRED 5 +#define TLS_OCSP_RESPONSE_UNAUTHORIZED 6 + +#define TLS_OCSP_CERT_GOOD 0 +#define TLS_OCSP_CERT_REVOKED 1 +#define TLS_OCSP_CERT_UNKNOWN 2 + +#define TLS_CRL_REASON_UNPSECIFIED 0 +#define TLS_CRL_REASON_KEY_COMPROMISE 1 +#define TLS_CRL_REASON_CA_COMPROMISE 2 +#define TLS_CRL_REASON_AFFILIATION_CHANGED 3 +#define TLS_CRL_REASON_SUPERSEDED 4 +#define TLS_CRL_REASON_CESSATION_OF_OPERATION 5 +#define TLS_CRL_REASON_CERTIFICATE_HOLD 6 +#define TLS_CRL_REASON_REMOVE_FROM_CRL 8 +#define TLS_CRL_REASON_PRIVILEGE_WITH_DRAWN 9 +#define TLS_CRL_REASON_AA_COMPROMISE 10 + +struct tls; +struct tls_config; + +int tls_init(void); +void tls_deinit(void); + +const char *tls_backend_version(void); + +const char *tls_error(struct tls *_ctx); + +struct tls_config *tls_config_new(void); +void tls_config_free(struct tls_config *_config); + +int tls_config_set_ca_file(struct tls_config *_config, const char *_ca_file); +int tls_config_set_ca_path(struct tls_config *_config, const char *_ca_path); +int tls_config_set_ca_mem(struct tls_config *_config, const uint8_t *_ca, + size_t _len); +int tls_config_set_cert_file(struct tls_config *_config, + const char *_cert_file); +int tls_config_set_cert_mem(struct tls_config *_config, const uint8_t *_cert, + size_t _len); +int tls_config_set_ciphers(struct tls_config *_config, const char *_ciphers); +int tls_config_set_dheparams(struct tls_config *_config, const char *_params); +int tls_config_set_ecdhecurve(struct tls_config *_config, const char *_name); +int tls_config_set_key_file(struct tls_config *_config, const char *_key_file); +int tls_config_set_key_mem(struct tls_config *_config, const uint8_t *_key, + size_t _len); +int tls_config_set_ocsp_stapling_file(struct tls_config *_config, const char *_blob_file); +int tls_config_set_ocsp_stapling_mem(struct tls_config *_config, const uint8_t *_blob, size_t _len); +void tls_config_set_protocols(struct tls_config *_config, uint32_t _protocols); +void tls_config_set_verify_depth(struct tls_config *_config, int _verify_depth); + +void tls_config_prefer_ciphers_client(struct tls_config *_config); +void tls_config_prefer_ciphers_server(struct tls_config *_config); + +void tls_config_insecure_noverifycert(struct tls_config *_config); +void tls_config_insecure_noverifyname(struct tls_config *_config); +void tls_config_insecure_noverifytime(struct tls_config *_config); +void tls_config_verify(struct tls_config *_config); + +void tls_config_verify_client(struct tls_config *_config); +void tls_config_verify_client_optional(struct tls_config *_config); + +void tls_config_clear_keys(struct tls_config *_config); +int tls_config_parse_protocols(uint32_t *_protocols, const char *_protostr); + +struct tls *tls_client(void); +struct tls *tls_server(void); +int tls_configure(struct tls *_ctx, struct tls_config *_config); +void tls_reset(struct tls *_ctx); +void tls_free(struct tls *_ctx); + +int tls_accept_fds(struct tls *_ctx, struct tls **_cctx, int _fd_read, + int _fd_write); +int tls_accept_socket(struct tls *_ctx, struct tls **_cctx, int _socket); +int tls_connect(struct tls *_ctx, const char *_host, const char *_port); +int tls_connect_fds(struct tls *_ctx, int _fd_read, int _fd_write, + const char *_servername); +int tls_connect_servername(struct tls *_ctx, const char *_host, + const char *_port, const char *_servername); +int tls_connect_socket(struct tls *_ctx, int _s, const char *_servername); +int tls_handshake(struct tls *_ctx); +ssize_t tls_read(struct tls *_ctx, void *_buf, size_t _buflen); +ssize_t tls_write(struct tls *_ctx, const void *_buf, size_t _buflen); +int tls_close(struct tls *_ctx); + +int tls_peer_cert_provided(struct tls *ctx); +int tls_peer_cert_contains_name(struct tls *ctx, const char *name); + +const char * tls_peer_cert_hash(struct tls *_ctx); +const char * tls_peer_cert_issuer(struct tls *ctx); +const char * tls_peer_cert_subject(struct tls *ctx); +time_t tls_peer_cert_notbefore(struct tls *ctx); +time_t tls_peer_cert_notafter(struct tls *ctx); + +const char * tls_conn_version(struct tls *ctx); +const char * tls_conn_cipher(struct tls *ctx); + +uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password); + +ssize_t tls_get_connection_info(struct tls *ctx, char *buf, size_t buflen); + +int tls_ocsp_refresh_stapling(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls_config *config); +int tls_ocsp_check_peer(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls *client); +int tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, int *crl_reason, + time_t *this_update, time_t *next_update, time_t *revoction_time, + const char **result_text); + +int tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_TLS_H */ diff --git a/lib/libtls/tls_cert.c b/lib/libtls/tls_cert.c new file mode 100644 index 00000000..9d842f0f --- /dev/null +++ b/lib/libtls/tls_cert.c @@ -0,0 +1,674 @@ +/* $OpenBSD: tls_verify.c,v 1.7 2015/02/11 06:46:33 jsing Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas + * + * 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 +#include +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +#define TLS_CERT_INTERNAL_FUNCS +#include "tls_cert.h" + +/* + * Load cert data from X509 cert. + */ + +/* Upper bounds */ +#define UB_COMMON_NAME 255 +#define UB_COUNTRY_NAME 255 +#define UB_STATE_NAME 255 +#define UB_LOCALITY_NAME 255 +#define UB_STREET_ADDRESS 255 +#define UB_ORGANIZATION_NAME 255 +#define UB_ORGANIZATIONAL_UNIT_NAME 255 + +#define UB_GNAME_DNS 255 +#define UB_GNAME_EMAIL 255 +#define UB_GNAME_URI 255 + +/* Convert ASN1_INTEGER to decimal string string */ +static int +tls_parse_bigint(struct tls *ctx, const ASN1_INTEGER *asn1int, const char **dst_p) +{ + long small; + BIGNUM *big; + char *tmp, buf[64]; + + *dst_p = NULL; + small = ASN1_INTEGER_get(asn1int); + if (small < 0) { + big = ASN1_INTEGER_to_BN(asn1int, NULL); + if (big) { + tmp = BN_bn2dec(big); + if (tmp) + *dst_p = strdup(tmp); + OPENSSL_free(tmp); + } + BN_free(big); + } else { + snprintf(buf, sizeof buf, "%lu", small); + *dst_p = strdup(buf); + } + if (*dst_p) + return 0; + + tls_set_errorx(ctx, "cannot parse serial"); + return -1; +} + +/* + * Decode all string types used in RFC5280. + * + * OpenSSL used (before Jun 1 2014 commit) to pick between PrintableString, + * T61String, BMPString and UTF8String, depending on data. This code + * tries to match that. + * + * Disallow any ancient ASN.1 escape sequences. + */ + +static int +check_invalid_bytes(struct tls *ctx, unsigned char *data, unsigned int len, + int ascii_only, const char *desc) +{ + unsigned int i, c; + + /* data is utf8 string, check for crap */ + for (i = 0; i < len; i++) { + c = data[i]; + + if (ascii_only && (c & 0x80) != 0) { + tls_set_errorx(ctx, "invalid %s: contains non-ascii in ascii string", desc); + goto failed; + } else if (c < 0x20) { + /* ascii control chars, including NUL */ + if (c != '\t' && c != '\n' && c != '\r') { + tls_set_errorx(ctx, "invalid %s: contains C0 control char", desc); + goto failed; + } + } else if (c == 0xC2 && (i + 1) < len) { + /* C1 control chars in UTF-8: \xc2\x80 - \xc2\x9f */ + c = data[i + 1]; + if (c >= 0x80 && c <= 0x9F) { + tls_set_errorx(ctx, "invalid %s: contains C1 control char", desc); + goto failed; + } + } else if (c == 0x7F) { + tls_set_errorx(ctx, "invalid %s: contains DEL char", desc); + goto failed; + } + } + return 0; + failed: + return -1; +} + +static int +tls_parse_asn1string(struct tls *ctx, ASN1_STRING *a1str, const char **dst_p, int minchars, int maxchars, const char *desc) +{ + int format, len, ret = -1; + unsigned char *data; + ASN1_STRING *a1utf = NULL; + int ascii_only = 0; + char *cstr = NULL; + int mbres, mbconvert = -1; + + *dst_p = NULL; + + format = ASN1_STRING_type(a1str); + data = ASN1_STRING_data(a1str); + len = ASN1_STRING_length(a1str); + if (len < minchars) { + tls_set_errorx(ctx, "invalid %s: string too short", desc); + goto failed; + } + + switch (format) { + case V_ASN1_NUMERICSTRING: + case V_ASN1_VISIBLESTRING: + case V_ASN1_PRINTABLESTRING: + case V_ASN1_IA5STRING: + /* Ascii */ + if (len > maxchars) { + tls_set_errorx(ctx, "invalid %s: string too long", desc); + goto failed; + } + ascii_only = 1; + break; + case V_ASN1_T61STRING: + /* Latin1 */ + mbconvert = MBSTRING_ASC; + break; + case V_ASN1_BMPSTRING: + /* UCS-2 big-endian */ + mbconvert = MBSTRING_BMP; + break; + case V_ASN1_UNIVERSALSTRING: + /* UCS-4 big-endian */ + mbconvert = MBSTRING_UNIV; + break; + case V_ASN1_UTF8STRING: + /* + * UTF-8 - could be used directly if OpenSSL has already + * validated the data. ATM be safe and validate here. + */ + mbconvert = MBSTRING_UTF8; + break; + default: + tls_set_errorx(ctx, "invalid %s: unexpected string type", desc); + goto failed; + } + + /* Convert to UTF-8 */ + if (mbconvert != -1) { + mbres = ASN1_mbstring_ncopy(&a1utf, data, len, mbconvert, B_ASN1_UTF8STRING, minchars, maxchars); + if (mbres < 0) { + tls_set_error_libssl(ctx, "invalid %s", desc); + goto failed; + } + if (mbres != V_ASN1_UTF8STRING) { + tls_set_errorx(ctx, "multibyte conversion failed: expected UTF8 result"); + goto failed; + } + data = ASN1_STRING_data(a1utf); + len = ASN1_STRING_length(a1utf); + } + + /* must not allow \0 */ + if (memchr(data, 0, len) != NULL) { + tls_set_errorx(ctx, "invalid %s: contains NUL", desc); + goto failed; + } + + /* no escape codes please */ + if (check_invalid_bytes(ctx, data, len, ascii_only, desc) < 0) + goto failed; + + /* copy to new string */ + cstr = malloc(len + 1); + if (!cstr) { + tls_set_error(ctx, "malloc"); + goto failed; + } + memcpy(cstr, data, len); + cstr[len] = 0; + *dst_p = cstr; + ret = len; + failed: + ASN1_STRING_free(a1utf); + return ret; +} + +static int +tls_cert_get_dname_string(struct tls *ctx, X509_NAME *name, int nid, const char **str_p, int minchars, int maxchars, const char *desc) +{ + int loc, len; + X509_NAME_ENTRY *ne; + ASN1_STRING *a1str; + + *str_p = NULL; + + loc = X509_NAME_get_index_by_NID(name, nid, -1); + if (loc < 0) + return 0; + ne = X509_NAME_get_entry(name, loc); + if (!ne) + return 0; + a1str = X509_NAME_ENTRY_get_data(ne); + if (!a1str) + return 0; + len = tls_parse_asn1string(ctx, a1str, str_p, minchars, maxchars, desc); + if (len < 0) + return -1; + return 0; +} + +static int +tls_load_alt_ia5string(struct tls *ctx, ASN1_IA5STRING *ia5str, struct tls_cert *cert, int slot_type, int minchars, int maxchars, const char *desc) +{ + struct tls_cert_general_name *slot; + const char *data; + int len; + + slot = &cert->subject_alt_names[cert->subject_alt_name_count]; + + len = tls_parse_asn1string(ctx, ia5str, &data, minchars, maxchars, desc); + if (len < 0) + return 0; + + /* + * Per RFC 5280 section 4.2.1.6: + * " " is a legal domain name, but that + * dNSName must be rejected. + */ + if (len == 1 && data[0] == ' ') { + tls_set_errorx(ctx, "invalid %s: single space", desc); + return -1; + } + + slot->name_value = data; + slot->name_type = slot_type; + + cert->subject_alt_name_count++; + return 0; +} + +static int +tls_load_alt_ipaddr(struct tls *ctx, ASN1_OCTET_STRING *bin, struct tls_cert *cert) +{ + struct tls_cert_general_name *slot; + void *data; + int len; + + slot = &cert->subject_alt_names[cert->subject_alt_name_count]; + len = ASN1_STRING_length(bin); + data = ASN1_STRING_data(bin); + if (len < 0) { + tls_set_errorx(ctx, "negative length for ipaddress"); + return -1; + } + + /* + * Per RFC 5280 section 4.2.1.6: + * IPv4 must use 4 octets and IPv6 must use 16 octets. + */ + if (len == 4) { + slot->name_type = TLS_CERT_GNAME_IPv4; + } else if (len == 16) { + slot->name_type = TLS_CERT_GNAME_IPv6; + } else { + tls_set_errorx(ctx, "invalid length for ipaddress"); + return -1; + } + + slot->name_value = malloc(len); + if (slot->name_value == NULL) { + tls_set_error(ctx, "malloc"); + return -1; + } + + memcpy((void *)slot->name_value, data, len); + cert->subject_alt_name_count++; + return 0; +} + +/* See RFC 5280 section 4.2.1.6 for SubjectAltName details. */ +static int +tls_cert_get_altnames(struct tls *ctx, struct tls_cert *cert, X509 *x509_cert) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + GENERAL_NAME *altname; + int count, i; + int rv = -1; + + altname_stack = X509_get_ext_d2i(x509_cert, NID_subject_alt_name, NULL, NULL); + if (altname_stack == NULL) + return 0; + + count = sk_GENERAL_NAME_num(altname_stack); + if (count == 0) { + rv = 0; + goto out; + } + + cert->subject_alt_names = calloc(sizeof (struct tls_cert_general_name), count); + if (cert->subject_alt_names == NULL) { + tls_set_error(ctx, "calloc"); + goto out; + } + + for (i = 0; i < count; i++) { + altname = sk_GENERAL_NAME_value(altname_stack, i); + + if (altname->type == GEN_DNS) { + rv = tls_load_alt_ia5string(ctx, altname->d.dNSName, cert, TLS_CERT_GNAME_DNS, 1, UB_GNAME_DNS, "dns"); + } else if (altname->type == GEN_EMAIL) { + rv = tls_load_alt_ia5string(ctx, altname->d.rfc822Name, cert, TLS_CERT_GNAME_EMAIL, 1, UB_GNAME_EMAIL, "email"); + } else if (altname->type == GEN_URI) { + rv = tls_load_alt_ia5string(ctx, altname->d.uniformResourceIdentifier, cert, TLS_CERT_GNAME_URI, 1, UB_GNAME_URI, "uri"); + } else if (altname->type == GEN_IPADD) { + rv = tls_load_alt_ipaddr(ctx, altname->d.iPAddress, cert); + } else { + /* ignore unknown types */ + rv = 0; + } + if (rv < 0) + goto out; + } + rv = 0; + out: + sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free); + return rv; +} + +static int +tls_get_dname(struct tls *ctx, X509_NAME *name, struct tls_cert_dname *dname) +{ + int ret; + ret = tls_cert_get_dname_string(ctx, name, NID_commonName, &dname->common_name, + 0, UB_COMMON_NAME, "commonName"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_countryName, &dname->country_name, + 0, UB_COUNTRY_NAME, "countryName"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_stateOrProvinceName, &dname->state_or_province_name, + 0, UB_STATE_NAME, "stateName"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_localityName, &dname->locality_name, + 0, UB_LOCALITY_NAME, "localityName"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_streetAddress, &dname->street_address, + 0, UB_STREET_ADDRESS, "streetAddress"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_organizationName, &dname->organization_name, + 0, UB_ORGANIZATION_NAME, "organizationName"); + if (ret == 0) + ret = tls_cert_get_dname_string(ctx, name, NID_organizationalUnitName, &dname->organizational_unit_name, + 0, UB_ORGANIZATIONAL_UNIT_NAME, "organizationalUnitName"); + return ret; +} + +static int +tls_get_basic_constraints(struct tls *ctx, struct tls_cert *cert, X509 *x509) +{ + BASIC_CONSTRAINTS *bc; + int crit; + int ret = -1; + + bc = X509_get_ext_d2i(x509, NID_basic_constraints, &crit, NULL); + if (!bc) + return 0; + + cert->ext_set |= TLS_EXT_BASIC; + if (crit) + cert->ext_crit |= TLS_EXT_BASIC; + + cert->basic_constraints_ca = bc->ca ? 1 : 0; + if (bc->pathlen) { + cert->basic_constraints_pathlen = ASN1_INTEGER_get(bc->pathlen); + if (cert->basic_constraints_pathlen < 0) { + tls_set_error(ctx, "BasicConstraints has invalid pathlen"); + goto failed; + } + } else { + cert->basic_constraints_pathlen = -1; + } + ret = 0; +failed: + BASIC_CONSTRAINTS_free(bc); + return ret; +} + +static uint32_t map_bits(const uint32_t map[][2], uint32_t input) +{ + uint32_t i, out = 0; + for (i = 0; map[i][0]; i++) { + if (map[i][0] & input) + out |= map[i][1]; + } + return out; +} + +static int +tls_get_key_usage(struct tls *ctx, struct tls_cert *cert, X509 *x509) +{ + static const uint32_t ku_map[][2] = { + {KU_DIGITAL_SIGNATURE, KU_DIGITAL_SIGNATURE}, + {KU_NON_REPUDIATION, KU_NON_REPUDIATION}, + {KU_KEY_ENCIPHERMENT, KU_KEY_ENCIPHERMENT}, + {KU_DATA_ENCIPHERMENT, KU_DATA_ENCIPHERMENT}, + {KU_KEY_AGREEMENT, KU_KEY_AGREEMENT}, + {KU_KEY_CERT_SIGN, KU_KEY_CERT_SIGN}, + {KU_CRL_SIGN, KU_CRL_SIGN}, + {KU_ENCIPHER_ONLY, KU_ENCIPHER_ONLY}, + {KU_DECIPHER_ONLY, KU_DECIPHER_ONLY}, + {0, 0}, + }; + ASN1_BIT_STRING *ku; + int crit; + + ku = X509_get_ext_d2i(x509, NID_key_usage, &crit, NULL); + if (!ku) + return 0; + + cert->ext_set |= TLS_EXT_KEY_USAGE; + if (crit) + cert->ext_crit |= TLS_EXT_KEY_USAGE; + ASN1_BIT_STRING_free(ku); + + cert->key_usage_flags = map_bits(ku_map, X509_get_key_usage(x509)); + return 0; +} + +static int +tls_get_ext_key_usage(struct tls *ctx, struct tls_cert *cert, X509 *x509) +{ + static const uint32_t xku_map[][2] = { + {XKU_SSL_SERVER, TLS_XKU_SSL_SERVER}, + {XKU_SSL_CLIENT, TLS_XKU_SSL_CLIENT}, + {XKU_SMIME, TLS_XKU_SMIME}, + {XKU_CODE_SIGN, TLS_XKU_CODE_SIGN}, + {XKU_SGC, TLS_XKU_SGC}, + {XKU_OCSP_SIGN, TLS_XKU_OCSP_SIGN}, + {XKU_TIMESTAMP, TLS_XKU_TIMESTAMP}, + {XKU_DVCS, TLS_XKU_DVCS}, + {0, 0}, + }; + EXTENDED_KEY_USAGE *xku; + int crit; + + xku = X509_get_ext_d2i(x509, NID_ext_key_usage, &crit, NULL); + if (!xku) + return 0; + sk_ASN1_OBJECT_pop_free(xku, ASN1_OBJECT_free); + + cert->ext_set |= TLS_EXT_EXTENDED_KEY_USAGE; + if (crit) + cert->ext_crit |= TLS_EXT_EXTENDED_KEY_USAGE; + + cert->extended_key_usage_flags = map_bits(xku_map, X509_get_extended_key_usage(x509)); + return 0; +} + +static int +tls_load_extensions(struct tls *ctx, struct tls_cert *cert, X509 *x509) +{ + int ret; + + /* + * Force libssl to fill extension fields under X509 struct. + * Then libtls does not need to parse raw data. + */ + X509_check_ca(x509); + + ret = tls_get_basic_constraints(ctx, cert, x509); + if (ret == 0) + ret = tls_get_key_usage(ctx, cert, x509); + if (ret == 0) + ret = tls_get_ext_key_usage(ctx, cert, x509); + if (ret == 0) + ret = tls_cert_get_altnames(ctx, cert, x509); + return ret; +} + +static void * +tls_calc_fingerprint(struct tls *ctx, X509 *x509, const char *algo, size_t *outlen) +{ + const EVP_MD *md; + void *res; + int ret; + unsigned int tmplen, mdlen; + + if (outlen) + *outlen = 0; + + if (strcasecmp(algo, "sha1") == 0) { + md = EVP_sha1(); + } else if (strcasecmp(algo, "sha256") == 0) { + md = EVP_sha256(); + } else { + tls_set_errorx(ctx, "invalid fingerprint algorithm"); + return NULL; + } + + mdlen = EVP_MD_size(md); + res = malloc(mdlen); + if (!res) { + tls_set_error(ctx, "malloc"); + return NULL; + } + + ret = X509_digest(x509, md, res, &tmplen); + if (ret != 1 || tmplen != mdlen) { + free(res); + tls_set_errorx(ctx, "X509_digest failed"); + return NULL; + } + + if (outlen) + *outlen = mdlen; + + return res; +} + +static void +check_verify_error(struct tls *ctx, struct tls_cert *cert) +{ + long vres = SSL_get_verify_result(ctx->ssl_conn); + if (vres == X509_V_OK) { + cert->successful_verify = 1; + } else { + cert->successful_verify = 0; + } +} + +int +tls_parse_cert(struct tls *ctx, struct tls_cert **cert_p, const char *fingerprint_algo, X509 *x509) +{ + struct tls_cert *cert = NULL; + X509_NAME *subject, *issuer; + int ret = -1; + long version; + + *cert_p = NULL; + + version = X509_get_version(x509); + if (version < 0) { + tls_set_errorx(ctx, "invalid version"); + return -1; + } + + subject = X509_get_subject_name(x509); + if (!subject) { + tls_set_errorx(ctx, "cert does not have subject"); + return -1; + } + + issuer = X509_get_issuer_name(x509); + if (!issuer) { + tls_set_errorx(ctx, "cert does not have issuer"); + return -1; + } + + cert = calloc(sizeof *cert, 1); + if (!cert) { + tls_set_error(ctx, "calloc"); + goto failed; + } + cert->version = version; + + if (fingerprint_algo) { + cert->fingerprint = tls_calc_fingerprint(ctx, x509, fingerprint_algo, &cert->fingerprint_size); + if (!cert->fingerprint) + goto failed; + } + + ret = tls_get_dname(ctx, subject, &cert->subject); + if (ret == 0) + ret = tls_get_dname(ctx, issuer, &cert->issuer); + if (ret == 0) + ret = tls_asn1_parse_time(ctx, X509_get_notBefore(x509), &cert->not_before); + if (ret == 0) + ret = tls_asn1_parse_time(ctx, X509_get_notAfter(x509), &cert->not_after); + if (ret == 0) + ret = tls_parse_bigint(ctx, X509_get_serialNumber(x509), &cert->serial); + if (ret == 0) + ret = tls_load_extensions(ctx, cert, x509); + if (ret == 0) { + *cert_p = cert; + return 0; + } + failed: + tls_cert_free(cert); + return ret; +} + +int +tls_get_peer_cert(struct tls *ctx, struct tls_cert **cert_p, const char *fingerprint_algo) +{ + X509 *peer = ctx->ssl_peer_cert; + int res; + + *cert_p = NULL; + + if (!peer) { + tls_set_errorx(ctx, "peer does not have cert"); + return TLS_NO_CERT; + } + + ERR_clear_error(); + res = tls_parse_cert(ctx, cert_p, fingerprint_algo, peer); + if (res == 0) + check_verify_error(ctx, *cert_p); + ERR_clear_error(); + return res; +} + +static void +tls_cert_free_dname(struct tls_cert_dname *dname) +{ + free((void*)dname->common_name); + free((void*)dname->country_name); + free((void*)dname->state_or_province_name); + free((void*)dname->locality_name); + free((void*)dname->street_address); + free((void*)dname->organization_name); + free((void*)dname->organizational_unit_name); +} + +void +tls_cert_free(struct tls_cert *cert) +{ + int i; + if (!cert) + return; + + tls_cert_free_dname(&cert->issuer); + tls_cert_free_dname(&cert->subject); + + if (cert->subject_alt_name_count) { + for (i = 0; i < cert->subject_alt_name_count; i++) + free((void*)cert->subject_alt_names[i].name_value); + } + free(cert->subject_alt_names); + + free((void*)cert->serial); + free((void*)cert->fingerprint); + free(cert); +} diff --git a/lib/libtls/tls_cert.h b/lib/libtls/tls_cert.h new file mode 100644 index 00000000..e6d7935f --- /dev/null +++ b/lib/libtls/tls_cert.h @@ -0,0 +1,105 @@ +#ifndef _TLS_TLS_CERT_H_ +#define _TLS_TLS_CERT_H_ + +#define TLS_CERT_GNAME_DNS 1 +#define TLS_CERT_GNAME_IPv4 2 +#define TLS_CERT_GNAME_IPv6 3 +#define TLS_CERT_GNAME_EMAIL 4 +#define TLS_CERT_GNAME_URI 5 + +#define TLS_KU_DIGITAL_SIGNATURE (1 << 0) +#define TLS_KU_NON_REPUDIATION (1 << 1) +#define TLS_KU_KEY_ENCIPHERMENT (1 << 2) +#define TLS_KU_DATA_ENCIPHERMENT (1 << 3) +#define TLS_KU_KEY_AGREEMENT (1 << 4) +#define TLS_KU_KEY_CERT_SIGN (1 << 5) +#define TLS_KU_CRL_SIGN (1 << 6) +#define TLS_KU_ENCIPHER_ONLY (1 << 7) +#define TLS_KU_DECIPHER_ONLY (1 << 8) + +#define TLS_XKU_SSL_SERVER (1 << 0) +#define TLS_XKU_SSL_CLIENT (1 << 1) +#define TLS_XKU_SMIME (1 << 2) +#define TLS_XKU_CODE_SIGN (1 << 3) +#define TLS_XKU_OCSP_SIGN (1 << 4) +#define TLS_XKU_SGC (1 << 5) +#define TLS_XKU_TIMESTAMP (1 << 6) +#define TLS_XKU_DVCS (1 << 7) + +#define TLS_EXT_BASIC (1 << 0) +#define TLS_EXT_KEY_USAGE (1 << 1) +#define TLS_EXT_EXTENDED_KEY_USAGE (1 << 2) +#define TLS_EXT_SUBJECT_ALT_NAME (1 << 3) + +/* + * GeneralName + */ +struct tls_cert_general_name { + const void *name_value; + int name_type; +}; + +/* + * DistinguishedName + */ +struct tls_cert_dname { + const char *common_name; + const char *country_name; + const char *state_or_province_name; + const char *locality_name; + const char *street_address; + const char *organization_name; + const char *organizational_unit_name; +}; + +struct tls_cert { + /* Version number from cert: 0:v1, 1:v2, 2:v3 */ + int version; + + /* did it pass verify? useful when noverifycert is on. */ + int successful_verify; + + /* DistringuishedName for subject */ + struct tls_cert_dname subject; + + /* DistringuishedName for issuer */ + struct tls_cert_dname issuer; + + /* decimal number */ + const char *serial; + + /* Validity times */ + time_t not_before; + time_t not_after; + + uint32_t ext_set; + uint32_t ext_crit; + + /* BasicConstraints extension */ + int basic_constraints_ca; + int basic_constraints_pathlen; + + /* KeyUsage extension */ + uint32_t key_usage_flags; + + /* ExtendedKeyUsage extension */ + uint32_t extended_key_usage_flags; + + /* SubjectAltName extension */ + struct tls_cert_general_name *subject_alt_names; + int subject_alt_name_count; + + /* Fingerprint as raw hash */ + const unsigned char *fingerprint; + size_t fingerprint_size; +}; + +int tls_get_peer_cert(struct tls *ctx, struct tls_cert **cert_p, const char *fingerprint_algo); +void tls_cert_free(struct tls_cert *cert); + +#ifdef TLS_CERT_INTERNAL_FUNCS +int tls_parse_cert(struct tls *ctx, struct tls_cert **cert_p, + const char *fingerprint_algo, X509 *x509); +#endif + +#endif diff --git a/lib/libtls/tls_client.c b/lib/libtls/tls_client.c new file mode 100644 index 00000000..2100d17b --- /dev/null +++ b/lib/libtls/tls_client.c @@ -0,0 +1,287 @@ +/* $OpenBSD: tls_client.c,v 1.16 2015/03/21 15:35:15 sthen Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * + * 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 + +#include +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +struct tls * +tls_client(void) +{ + struct tls *ctx; + + 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, + * otherwise use the default. + */ + if ((p = (char *)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); +} + +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) +{ + union tls_addr addrbuf; + int rv = -1; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_errorx(ctx, "not a client context"); + goto err; + } + + if (fd_read < 0 || fd_write < 0) { + tls_set_errorx(ctx, "invalid file descriptors"); + goto err; + } + + if (servername != NULL) { + if ((ctx->servername = strdup(servername)) == NULL) { + tls_set_errorx(ctx, "out of memory"); + goto err; + } + } + + 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) != 0) + goto err; + if (tls_configure_keypair(ctx, 0) != 0) + goto err; + + if (ctx->config->verify_name) { + if (servername == NULL) { + tls_set_errorx(ctx, "server name not specified"); + goto err; + } + } + + if (ctx->config->verify_cert && + (tls_configure_ssl_verify(ctx, SSL_VERIFY_PEER) == -1)) + goto err; + + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_verify_callback) != 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 (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; + } + if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) { + tls_set_errorx(ctx, "ssl OCSP extension setup failure"); + goto err; + } + + /* + * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not + * permitted in "HostName". + */ + if (servername != NULL && + inet_pton(AF_INET, servername, &addrbuf) != 1 && + inet_pton(AF_INET6, servername, &addrbuf) != 1) { + if (SSL_set_tlsext_host_name(ctx->ssl_conn, servername) == 0) { + tls_set_errorx(ctx, "server name indication failure"); + goto err; + } + } + + rv = 0; + + err: + return (rv); +} + +int +tls_handshake_client(struct tls *ctx) +{ + X509 *cert = NULL; + int ssl_ret; + int rv = -1; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_errorx(ctx, "not a client context"); + goto err; + } + + 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 ((rv = tls_check_name(ctx, cert, + ctx->servername)) != 0) { + if (rv != -2) + tls_set_errorx(ctx, "name `%s' not present in" + " server certificate", ctx->servername); + goto err; + } + } + + ctx->state |= TLS_HANDSHAKE_COMPLETE; + rv = 0; + + err: + X509_free(cert); + + return (rv); +} diff --git a/lib/libtls/tls_compat.c b/lib/libtls/tls_compat.c new file mode 100644 index 00000000..c3e701f3 --- /dev/null +++ b/lib/libtls/tls_compat.c @@ -0,0 +1,440 @@ +/* + * Compatibility with various libssl implementations. + * + * Copyright (c) 2015 Marko Kreen + * + * Permission to use, copy, modify, and/or 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 +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +#ifndef SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE +#undef SSLerr +#undef X509err +#endif + +#ifndef SSLerr +#define SSLerr(a,b) do {} while (0) +#define X509err(a,b) do {} while (0) +#endif + +#ifndef SSL_CTX_set_dh_auto +#define DH_CLEANUP + +/* + * SKIP primes, used by OpenSSL and PostgreSQL. + * + * https://tools.ietf.org/html/draft-ietf-ipsec-skip-06 + */ + +static const char file_dh1024[] = +"-----BEGIN DH PARAMETERS-----\n" +"MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY\n" +"jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6\n" +"ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC\n" +"-----END DH PARAMETERS-----\n"; + +static const char file_dh2048[] = +"-----BEGIN DH PARAMETERS-----\n" +"MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n" +"89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n" +"T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n" +"zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n" +"Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n" +"CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n" +"-----END DH PARAMETERS-----\n"; + +static const char file_dh4096[] = +"-----BEGIN DH PARAMETERS-----\n" +"MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ\n" +"l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt\n" +"Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS\n" +"Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98\n" +"VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc\n" +"alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM\n" +"sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9\n" +"ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte\n" +"OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH\n" +"AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n" +"KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n" +"-----END DH PARAMETERS-----\n"; + + +static DH *dh1024, *dh2048, *dh4096; + +static DH *load_dh_buffer(struct tls *ctx, DH **dhp, const char *buf) +{ + BIO *bio; + DH *dh = *dhp; + if (dh == NULL) { + bio = BIO_new_mem_buf((char *)buf, strlen(buf)); + if (bio) { + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + BIO_free(bio); + } + *dhp = dh; + } + if (ctx) + ctx->used_dh_bits = DH_size(dh) * 8; + return dh; +} + +static DH *dh_auto_cb(SSL *s, int is_export, int keylength) +{ + EVP_PKEY *pk; + int bits; + struct tls *ctx = SSL_get_app_data(s); + + pk = SSL_get_privatekey(s); + if (!pk) + return load_dh_buffer(ctx, &dh2048, file_dh2048); + + bits = EVP_PKEY_bits(pk); + if (bits >= 3072) + return load_dh_buffer(ctx, &dh4096, file_dh4096); + if (bits >= 1536) + return load_dh_buffer(ctx, &dh2048, file_dh2048); + return load_dh_buffer(ctx, &dh1024, file_dh1024); +} + +static DH *dh_legacy_cb(SSL *s, int is_export, int keylength) +{ + struct tls *ctx = SSL_get_app_data(s); + return load_dh_buffer(ctx, &dh1024, file_dh1024); +} + +long SSL_CTX_set_dh_auto(SSL_CTX *ctx, int onoff) +{ + if (onoff == 0) + return 1; + if (onoff == 2) { + SSL_CTX_set_tmp_dh_callback(ctx, dh_legacy_cb); + } else { + SSL_CTX_set_tmp_dh_callback(ctx, dh_auto_cb); + } + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); + return 1; +} + +#endif + +#ifndef SSL_CTX_set_ecdh_auto +#define ECDH_CLEANUP + +/* + * Use same curve as EC key, fallback to NIST P-256. + */ + +static EC_KEY *ecdh_cache; + +#ifdef USE_LIBSSL_INTERNALS +static EC_KEY *ecdh_auto_cb(SSL *ssl, int is_export, int keylength) +{ + int last_nid; + int nid = 0; + EVP_PKEY *pk; + EC_KEY *ec; + struct tls *ctx = SSL_get_app_data(ssl); + + /* pick curve from EC key */ + pk = SSL_get_privatekey(ssl); + if (pk && EVP_PKEY_id(pk) == EVP_PKEY_EC) { + ec = EVP_PKEY_get1_EC_KEY(pk); + if (ec) { + nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + EC_KEY_free(ec); + } + } + + /* ssl->tlsext_ellipticcurvelist is empty, nothing else to do... */ + if (nid == 0) + nid = NID_X9_62_prime256v1; + + if (ctx) + ctx->used_ecdh_nid = nid; + + if (ecdh_cache) { + last_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ecdh_cache)); + if (last_nid == nid) + return ecdh_cache; + EC_KEY_free(ecdh_cache); + ecdh_cache = NULL; + } + + ecdh_cache = EC_KEY_new_by_curve_name(nid); + return ecdh_cache; +} +#endif + +long SSL_CTX_set_ecdh_auto(SSL_CTX *ctx, int onoff) +{ + if (onoff) { + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); +#ifdef USE_LIBSSL_INTERNALS + SSL_CTX_set_tmp_ecdh_callback(ctx, ecdh_auto_cb); +#endif + } + return 1; +} + +#endif + +void tls_compat_cleanup(void) +{ +#ifdef DH_CLEANUP + if (dh1024) { DH_free(dh1024); dh1024 = NULL; } + if (dh2048) { DH_free(dh2048); dh2048 = NULL; } + if (dh4096) { DH_free(dh4096); dh4096 = NULL; } +#endif +#ifdef ECDH_CLEANUP + if (ecdh_cache) { + EC_KEY_free(ecdh_cache); + ecdh_cache = NULL; + } +#endif +} + +#ifndef HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM + +/* + * Load certs for public key from memory. + */ + +int +SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *data, int data_len) +{ + pem_password_cb *psw_fn = NULL; + void *psw_arg = NULL; + X509 *cert; + BIO *bio = NULL; + int ok; + +#ifdef USE_LIBSSL_INTERNALS + psw_fn = ctx->default_passwd_callback; + psw_arg = ctx->default_passwd_callback_userdata; +#endif + + ERR_clear_error(); + + /* Read from memory */ + bio = BIO_new_mem_buf(data, data_len); + if (!bio) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); + goto failed; + } + + /* Load primary cert */ + cert = PEM_read_bio_X509_AUX(bio, NULL, psw_fn, psw_arg); + if (!cert) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); + goto failed; + } + + /* Increments refcount */ + ok = SSL_CTX_use_certificate(ctx, cert); + X509_free(cert); + if (!ok || ERR_peek_error()) + goto failed; + + /* Load extra certs */ + ok = SSL_CTX_clear_extra_chain_certs(ctx); + while (ok) { + cert = PEM_read_bio_X509(bio, NULL, psw_fn, psw_arg); + if (!cert) { + /* Is it EOF? */ + unsigned long err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) != ERR_LIB_PEM) + break; + if (ERR_GET_REASON(err) != PEM_R_NO_START_LINE) + break; + + /* On EOF do successful exit */ + BIO_free(bio); + ERR_clear_error(); + return 1; + } + /* Does not increment refcount */ + ok = SSL_CTX_add_extra_chain_cert(ctx, cert); + if (!ok) + X509_free(cert); + } + failed: + if (bio) + BIO_free(bio); + return 0; +} + +#endif + +#ifndef HAVE_SSL_CTX_LOAD_VERIFY_MEM + +/* + * Load CA certs for verification from memory. + */ + +int SSL_CTX_load_verify_mem(SSL_CTX *ctx, void *data, int data_len) +{ + STACK_OF(X509_INFO) *stack = NULL; + X509_STORE *store; + X509_INFO *info; + int nstack, i, ret = 0, got = 0; + BIO *bio; + + /* Read from memory */ + bio = BIO_new_mem_buf(data, data_len); + if (!bio) + goto failed; + + /* Parse X509_INFO records */ + stack = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); + if (!stack) + goto failed; + + /* Loop over stack, add certs and revocation records to store */ + store = SSL_CTX_get_cert_store(ctx); + nstack = sk_X509_INFO_num(stack); + for (i = 0; i < nstack; i++) { + info = sk_X509_INFO_value(stack, i); + if (info->x509 && !X509_STORE_add_cert(store, info->x509)) + goto failed; + if (info->crl && !X509_STORE_add_crl(store, info->crl)) + goto failed; + if (info->x509 || info->crl) + got = 1; + } + ret = got; + failed: + if (bio) + BIO_free(bio); + if (stack) + sk_X509_INFO_pop_free(stack, X509_INFO_free); + if (!ret) + X509err(X509_F_X509_LOAD_CERT_CRL_FILE, ERR_R_PEM_LIB); + return ret; +} + +#endif + +#ifndef HAVE_ASN1_TIME_PARSE + +static int +parse2num(const char **str_p, int min, int max) +{ + const char *s = *str_p; + if (s && s[0] >= '0' && s[0] <= '9' && s[1] >= '0' && s[1] <= '9') { + int val = (s[0] - '0') * 10 + (s[1] - '0'); + if (val >= min && val <= max) { + *str_p += 2; + return val; + } + } + *str_p = NULL; + return 0; +} + +int +asn1_time_parse(const char *src, size_t len, struct tm *tm, int mode) +{ + char buf[16]; + const char *s = buf; + int utctime; + int year; + + memset(tm, 0, sizeof *tm); + + if (mode != 0) + return -1; + + /* + * gentime: YYYYMMDDHHMMSSZ + * utctime: YYMMDDHHMM(SS)(Z) + */ + if (len == 15) { + utctime = 0; + } else if (len > 8 && len < 15) { + utctime = 1; + } else { + return -1; + } + memcpy(buf, src, len); + buf[len] = '\0'; + + year = parse2num(&s, 0, 99); + if (utctime) { + if (year < 50) + year = 2000 + year; + else + year = 1900 + year; + } else { + year = year*100 + parse2num(&s, 0, 99); + } + tm->tm_year = year - 1900; + tm->tm_mon = parse2num(&s, 1, 12) - 1; + tm->tm_mday = parse2num(&s, 1, 31); + tm->tm_hour = parse2num(&s, 0, 23); + tm->tm_min = parse2num(&s, 0, 59); + if (utctime) { + if (s && s[0] != 'Z' && s[0] != '\0') + tm->tm_sec = parse2num(&s, 0, 61); + } else { + tm->tm_sec = parse2num(&s, 0, 61); + } + + if (s) { + if (s[0] == '\0') + goto good; + if (s[0] == 'Z' && s[1] == '\0') + goto good; + } + return -1; + good: + return utctime ? V_ASN1_UTCTIME : V_ASN1_GENERALIZEDTIME; +} + +#endif /* HAVE_ASN1_TIME_PARSE */ + +int +tls_asn1_parse_time(struct tls *ctx, const ASN1_TIME *asn1time, time_t *dst) +{ + struct tm tm; + int res; + time_t tval; + + *dst = 0; + if (!asn1time) + return 0; + if (asn1time->type != V_ASN1_GENERALIZEDTIME && + asn1time->type != V_ASN1_UTCTIME) { + tls_set_errorx(ctx, "Invalid time object type: %d", asn1time->type); + return -1; + } + + res = asn1_time_parse((char*)asn1time->data, asn1time->length, &tm, 0); + if (res == -1) { + tls_set_errorx(ctx, "Invalid asn1 time"); + return -1; + } + + tval = timegm(&tm); + if (tval == (time_t)-1) { + tls_set_error(ctx, "Cannot convert asn1 time"); + return -1; + } + *dst = tval; + return 0; +} diff --git a/lib/libtls/tls_compat.h b/lib/libtls/tls_compat.h new file mode 100644 index 00000000..e7a0efb8 --- /dev/null +++ b/lib/libtls/tls_compat.h @@ -0,0 +1,47 @@ +#ifndef _TLS_COMPAT_H_ +#define _TLS_COMPAT_H_ + +#include + +/* OpenSSL 1.1+ has hidden struct fields */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + +#define USE_LIBSSL_INTERNALS + +#define X509_get_key_usage(x509) ((x509)->ex_kusage) +#define X509_get_extended_key_usage(x509) ((x509)->ex_xkusage) +#define SSL_CTX_get0_param(ssl_ctx) ((ssl_ctx)->param) +#endif + +/* ecdh_auto is broken - ignores main EC key */ +#undef SSL_CTX_set_ecdh_auto + +/* dh_auto seems fine, but use ours to get DH info */ +#undef SSL_CTX_set_dh_auto + +#ifndef SSL_CTX_set_dh_auto +long SSL_CTX_set_dh_auto(SSL_CTX *ctx, int onoff); +#endif + +#ifndef SSL_CTX_set_ecdh_auto +long SSL_CTX_set_ecdh_auto(SSL_CTX *ctx, int onoff); +#endif + +#ifndef HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM +int SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len); +#endif + +#ifndef HAVE_SSL_CTX_LOAD_VERIFY_MEM +int SSL_CTX_load_verify_mem(SSL_CTX *ctx, void *buf, int len); +#endif + +/* BoringSSL has no OCSP support */ +#ifdef OPENSSL_IS_BORINGSSL +#define SSL_CTX_set_tlsext_status_cb(a,b) (1) +#define SSL_set_tlsext_status_type(a,b) (1) +#endif + +void tls_compat_cleanup(void); + +#endif + diff --git a/lib/libtls/tls_config.c b/lib/libtls/tls_config.c new file mode 100644 index 00000000..10dbba1e --- /dev/null +++ b/lib/libtls/tls_config.c @@ -0,0 +1,365 @@ +/* $OpenBSD: tls_config.c,v 1.8 2015/02/22 14:59:37 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * + * 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 + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +static inline void +explicit_bzero(void *dest, int size) +{ + memset(dest, 0, size); +} + +static int +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; +} + +static void * +memdup(const void *in, size_t len) +{ + void *out; + + if ((out = malloc(len)) == NULL) + return NULL; + memcpy(out, in, len); + return out; +} + +static int +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; +} + +struct tls_config * +tls_config_new(void) +{ + struct tls_config *config; + + if ((config = calloc(1, sizeof(*config))) == NULL) + return (NULL); + + /* + * Default configuration. + */ + if (tls_config_set_ca_file(config, _PATH_SSL_CA_FILE) != 0) + goto err; + if (tls_config_set_dheparams(config, "none") != 0) + goto err; + if (tls_config_set_ecdhecurve(config, "auto") != 0) + goto err; + if (tls_config_set_ciphers(config, "secure") != 0) + goto err; + + tls_config_set_protocols(config, TLS_PROTOCOLS_DEFAULT); + tls_config_set_verify_depth(config, 6); + + tls_config_prefer_ciphers_server(config); + + tls_config_verify(config); + + return (config); + + err: + tls_config_free(config); + return (NULL); +} + +void +tls_config_free(struct tls_config *config) +{ + if (config == NULL) + return; + + tls_config_clear_keys(config); + + free((char *)config->ca_file); + free((char *)config->ca_path); + free((char *)config->cert_file); + free(config->cert_mem); + free((char *)config->ciphers); + free((char *)config->key_file); + free(config->key_mem); + + free(config); +} + +void +tls_config_clear_keys(struct tls_config *config) +{ + tls_config_set_ca_mem(config, NULL, 0); + tls_config_set_cert_mem(config, NULL, 0); + tls_config_set_key_mem(config, NULL, 0); +} + +int +tls_config_parse_protocols(uint32_t *protocols, const char *protostr) +{ + uint32_t proto, protos = 0; + char *s, *p, *q; + int negate; + + 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_0; + else if (strcasecmp(p, "tlsv1.1") == 0) + proto = TLS_PROTOCOL_TLSv1_1; + else if (strcasecmp(p, "tlsv1.2") == 0) + proto = TLS_PROTOCOL_TLSv1_2; + + if (proto == 0) { + free(s); + return (-1); + } + + if (negate) + protos &= ~proto; + else + protos |= proto; + } + + *protocols = protos; + + free(s); + + return (0); +} + +int +tls_config_set_ca_file(struct tls_config *config, const char *ca_file) +{ + return set_string(&config->ca_file, ca_file); +} + +int +tls_config_set_ca_path(struct tls_config *config, const char *ca_path) +{ + return 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 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 set_string(&config->cert_file, cert_file); +} + +int +tls_config_set_cert_mem(struct tls_config *config, const uint8_t *cert, + size_t len) +{ + return set_mem(&config->cert_mem, &config->cert_len, cert, len); +} + +int +tls_config_set_ciphers(struct tls_config *config, const char *ciphers) +{ + if (ciphers == NULL || + strcasecmp(ciphers, "default") == 0 || + strcasecmp(ciphers, "secure") == 0) + ciphers = TLS_CIPHERS_DEFAULT; + else if (strcasecmp(ciphers, "compat") == 0 || + strcasecmp(ciphers, "legacy") == 0) + ciphers = TLS_CIPHERS_COMPAT; + else if (strcasecmp(ciphers, "insecure") == 0 || + strcasecmp(ciphers, "all") == 0) + ciphers = TLS_CIPHERS_ALL; + else if (strcasecmp(ciphers, "normal") == 0) + ciphers = TLS_CIPHERS_NORMAL; + else if (strcasecmp(ciphers, "fast") == 0) + ciphers = TLS_CIPHERS_FAST; + + return set_string(&config->ciphers, ciphers); +} + +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 + return (-1); + + config->dheparams = keylen; + + return (0); +} + +int +tls_config_set_ecdhecurve(struct tls_config *config, const char *name) +{ + int nid; + + if (name == NULL || strcasecmp(name, "none") == 0) + nid = NID_undef; + else if (strcasecmp(name, "auto") == 0) + nid = -1; + else if ((nid = OBJ_txt2nid(name)) == NID_undef) + return (-1); + + config->ecdhecurve = nid; + + return (0); +} + +int +tls_config_set_key_file(struct tls_config *config, const char *key_file) +{ + return set_string(&config->key_file, key_file); +} + +int +tls_config_set_key_mem(struct tls_config *config, const uint8_t *key, + size_t len) +{ + if (config->key_mem) + explicit_bzero(config->key_mem, config->key_len); + return set_mem(&config->key_mem, &config->key_len, key, len); +} + +int +tls_config_set_ocsp_stapling_file(struct tls_config *config, const char *blob_file) +{ + if (blob_file != NULL) + tls_config_set_ocsp_stapling_mem(config, NULL, 0); + + return set_string(&config->ocsp_file, blob_file); +} + +int +tls_config_set_ocsp_stapling_mem(struct tls_config *config, const uint8_t *blob, size_t len) +{ + if (blob != NULL) + tls_config_set_ocsp_stapling_file(config, NULL); + + return set_mem(&config->ocsp_mem, &config->ocsp_len, blob, len); +} + +void +tls_config_set_protocols(struct tls_config *config, uint32_t protocols) +{ + config->protocols = protocols; +} + +void +tls_config_set_verify_depth(struct tls_config *config, int verify_depth) +{ + config->verify_depth = verify_depth; +} + +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_verify_client(struct tls_config *config) +{ + config->verify_client = 1; +} + +void +tls_config_verify_client_optional(struct tls_config *config) +{ + config->verify_client = 2; +} diff --git a/lib/libtls/tls_conninfo.c b/lib/libtls/tls_conninfo.c new file mode 100644 index 00000000..b9f202cf --- /dev/null +++ b/lib/libtls/tls_conninfo.c @@ -0,0 +1,216 @@ +/* $OpenBSD: tls_peer.c,v 1.3 2015/09/11 13:22:39 beck Exp $ */ +/* + * Copyright (c) 2015 Joel Sing + * Copyright (c) 2015 Bob Beck + * + * 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 + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +static 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 = realloc(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) +{ + unsigned char d[EVP_MAX_MD_SIZE]; + char *dhex = NULL; + unsigned int dlen; + int rv = -1; + + *hash = NULL; + if (ctx->ssl_peer_cert == NULL) + return (0); + + if (X509_digest(ctx->ssl_peer_cert, EVP_sha256(), d, &dlen) != 1) { + tls_set_errorx(ctx, "digest failed"); + goto err; + } + + if (tls_hex_string(d, dlen, &dhex, NULL) != 0) { + tls_set_errorx(ctx, "digest hex string failed"); + goto err; + } + + if (asprintf(hash, "SHA256:%s", dhex) == -1) { + tls_set_errorx(ctx, "out of memory"); + *hash = NULL; + goto err; + } + + rv = 0; + +err: + free(dhex); + + return (rv); +} + +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; + int rv = -1; + + memset(&before_tm, 0, sizeof(before_tm)); + memset(&after_tm, 0, sizeof(after_tm)); + + if (ctx->ssl_peer_cert != NULL) { + 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((char*)before->data, before->length, &before_tm, 0) == -1) + goto err; + if (asn1_time_parse((char*)after->data, after->length, &after_tm, 0) == -1) + goto err; + if ((*notbefore = timegm(&before_tm)) == -1) + goto err; + if ((*notafter = timegm(&after_tm)) == -1) + goto err; + } + rv = 0; + err: + return (rv); +} + +int +tls_get_conninfo(struct tls *ctx) { + const char * tmp; + + tls_free_conninfo(ctx->conninfo); + + if (ctx->ssl_peer_cert != NULL) { + 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; + } + if ((tmp = SSL_get_version(ctx->ssl_conn)) == NULL) + goto err; + ctx->conninfo->version = strdup(tmp); + if (ctx->conninfo->version == NULL) + goto err; + if ((tmp = SSL_get_cipher(ctx->ssl_conn)) == NULL) + goto err; + ctx->conninfo->cipher = strdup(tmp); + if (ctx->conninfo->cipher == NULL) + goto err; + return (0); +err: + tls_free_conninfo(ctx->conninfo); + return (-1); +} + +void +tls_free_conninfo(struct tls_conninfo *conninfo) { + if (conninfo != NULL) { + free(conninfo->hash); + conninfo->hash = NULL; + free(conninfo->subject); + conninfo->subject = NULL; + free(conninfo->issuer); + conninfo->issuer = NULL; + free(conninfo->version); + conninfo->version = NULL; + free(conninfo->cipher); + conninfo->cipher = NULL; + } +} + +const char * +tls_conn_cipher(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->cipher); +} + +const char * +tls_conn_version(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->version); +} diff --git a/lib/libtls/tls_internal.h b/lib/libtls/tls_internal.h new file mode 100644 index 00000000..7200938c --- /dev/null +++ b/lib/libtls/tls_internal.h @@ -0,0 +1,199 @@ +/* $OpenBSD: tls_internal.h,v 1.11 2015/02/22 14:50:41 jsing Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas + * Copyright (c) 2014 Joel Sing + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define _PATH_SSL_CA_FILE "/etc/ssl/certs/ca-certificates.crt" + +/* + * Anything that is not completely broken. + * + * Also fixes 3DES ordering bug in older OpenSSLs. + */ +#define TLS_CIPHERS_COMPAT "HIGH:+3DES:!aNULL" + +/* + * All==insecure. + */ +#define TLS_CIPHERS_ALL "ALL:!aNULL:!eNULL" + +/* + * TLSv1.2+AEAD+ECDHE/DHE. CBC modes are dubious due to spec bugs in TLS. + */ +#define TLS_CIPHERS_DEFAULT "HIGH+EECDH:HIGH+EDH:!SSLv3:!SHA384:!SHA256:!DSS:!aNULL" + +/* + * Compact subset of reasonable suites only. + * + * Priorities, in order: + * - ECDHE > DHE > RSA + * - AESGCM > CBC + * - TLSv1.2 > TLSv1.0 + * - AES256 > AES128. + */ +#define TLS_CIPHERS_NORMAL "HIGH+EECDH:HIGH+EDH:HIGH+RSA:+SHA384:+SHA256:+SSLv3:+EDH:+RSA:-3DES:3DES+RSA:!CAMELLIA:!DSS:!aNULL" + +/* + * Prefer performance if it does not affect security. + * + * Same as "normal" but prefers AES128 to AES256. + */ +#define TLS_CIPHERS_FAST "HIGH+EECDH:HIGH+EDH:HIGH+RSA:+AES256:+SHA256:+SHA384:+SSLv3:+EDH:+RSA:-3DES:3DES+RSA:!CAMELLIA:!DSS:!aNULL" + +union tls_addr { + struct in_addr ip4; + struct in6_addr ip6; +}; + +struct tls_config { + const char *ca_file; + const char *ca_path; + char *ca_mem; + size_t ca_len; + const char *cert_file; + char *cert_mem; + size_t cert_len; + const char *ciphers; + int ciphers_server; + int dheparams; + int ecdhecurve; + const char *key_file; + char *key_mem; + size_t key_len; + const char *ocsp_file; + char *ocsp_mem; + size_t ocsp_len; + uint32_t protocols; + int verify_cert; + int verify_client; + int verify_depth; + int verify_name; + int verify_time; +}; + +struct tls_conninfo { + char *issuer; + char *subject; + char *hash; + char *serial; + char *fingerprint; + char *version; + char *cipher; + time_t notbefore; + time_t notafter; +}; + +#define TLS_CLIENT (1 << 0) +#define TLS_SERVER (1 << 1) +#define TLS_SERVER_CONN (1 << 2) +#define TLS_OCSP_CLIENT (1 << 3) + +#define TLS_EOF_NO_CLOSE_NOTIFY (1 << 0) +#define TLS_HANDSHAKE_COMPLETE (1 << 1) +#define TLS_DO_ABORT (1 << 8) + +struct tls_ocsp_query; +struct tls_ocsp_info; + +struct tls { + struct tls_config *config; + uint32_t flags; + uint32_t state; + + char *errmsg; + int errnum; + + char *servername; + int socket; + + SSL *ssl_conn; + SSL_CTX *ssl_ctx; + X509 *ssl_peer_cert; + struct tls_conninfo *conninfo; + + int used_dh_bits; + int used_ecdh_nid; + + const char *ocsp_result; + struct tls_ocsp_info *ocsp_info; + + struct tls_ocsp_query *ocsp_query; +}; + +struct tls_ocsp_info { + int response_status; + int cert_status; + int crl_reason; + time_t this_update; + time_t next_update; + time_t revocation_time; +}; + +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 tls_configure_keypair(struct tls *ctx, int); +int tls_configure_server(struct tls *ctx); +int tls_configure_ssl(struct tls *ctx); +int tls_configure_ssl_verify(struct tls *ctx, int verify); +int tls_handshake_client(struct tls *ctx); +int tls_handshake_server(struct tls *ctx); +int tls_host_port(const char *hostport, char **host, char **port); +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_error_libssl(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_get_conninfo(struct tls *ctx); +void tls_free_conninfo(struct tls_conninfo *conninfo); + +int tls_ocsp_verify_callback(SSL *ssl, void *arg); +int tls_ocsp_stapling_callback(SSL *ssl, void *arg); +void tls_ocsp_client_free(struct tls *ctx); +void tls_ocsp_info_free(struct tls_ocsp_info *info); + +int tls_asn1_parse_time(struct tls *ctx, const ASN1_TIME *asn1time, time_t *dst); + +int asn1_time_parse(const char *, size_t, struct tm *, int); + +#endif /* HEADER_TLS_INTERNAL_H */ diff --git a/lib/libtls/tls_ocsp.c b/lib/libtls/tls_ocsp.c new file mode 100644 index 00000000..0d90484c --- /dev/null +++ b/lib/libtls/tls_ocsp.c @@ -0,0 +1,969 @@ +/* + * Copyright (c) 2015 Marko Kreen + * + * 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 + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +#ifndef OPENSSL_NO_OCSP + +#include + +#define MAXAGE_SEC (14*24*60*60) +#define JITTER_SEC (60) + +#define USE_NONCE 0 + +#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10002000 +#define BUGGY_VERIFY +#endif + +/* + * State for request. + */ + +struct tls_ocsp_query { + /* responder location */ + char *ocsp_url; + + /* request blob */ + uint8_t *request_data; + size_t request_size; + + /* network state */ + BIO *bio; + SSL_CTX *ssl_ctx; + OCSP_REQ_CTX *http_req; + + /* cert data, this struct does not own these */ + X509 *main_cert; + STACK_OF(X509) *extra_certs; + SSL_CTX *cert_ssl_ctx; +}; + +/* + * Extract OCSP response info. + */ + +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_info *info; + int res; + + info = calloc(1, sizeof (struct tls_ocsp_info)); + if (!info) { + tls_set_error(ctx, "calloc"); + return -1; + } + info->response_status = response_status; + info->cert_status = cert_status; + info->crl_reason = crl_reason; + + res = tls_asn1_parse_time(ctx, revtime, &info->revocation_time); + if (res == 0) + res = tls_asn1_parse_time(ctx, thisupd, &info->this_update); + if (res == 0) + res = tls_asn1_parse_time(ctx, nextupd, &info->next_update); + + if (res == 0) { + ctx->ocsp_info = info; + } else { + tls_ocsp_info_free(info); + } + return res; +} + +static void +tls_ocsp_fill_result(struct tls *ctx, int res) +{ + struct tls_ocsp_info *info = ctx->ocsp_info; + if (res < 0) { + ctx->ocsp_result = "error"; + } else if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + ctx->ocsp_result = OCSP_response_status_str(info->response_status); + } else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) { + ctx->ocsp_result = OCSP_cert_status_str(info->cert_status); + } else { + ctx->ocsp_result = OCSP_crl_reason_str(info->crl_reason); + } +} + +void +tls_ocsp_info_free(struct tls_ocsp_info *info) +{ + free(info); +} + +int +tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, + int *crl_reason, time_t *this_update, + time_t *next_update, time_t *revoction_time, + const char **result_text) +{ + static const struct tls_ocsp_info no_ocsp = { -1, -1, -1, 0, 0, 0 }; + const struct tls_ocsp_info *info = ctx->ocsp_info; + const char *ocsp_result = ctx->ocsp_result; + int ret = 0; + + if (!info) { + info = &no_ocsp; + ret = -1; + } + + if (!ocsp_result) { + ret = TLS_NO_OCSP; + ocsp_result = "no-ocsp"; + } + + if (response_status) + *response_status = info->response_status; + if (cert_status) + *cert_status = info->cert_status; + if (crl_reason) + *crl_reason = info->crl_reason; + if (this_update) + *this_update = info->this_update; + if (next_update) + *next_update = info->next_update; + if (revoction_time) + *revoction_time = info->revocation_time; + if (result_text) + *result_text = ocsp_result; + + return ret; +} + +/* + * Verify stapled response + */ + +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; + X509_OBJECT tmpobj; + OCSP_CERTID *cid = NULL; + X509_STORE *store; + int ok; + + issuer_name = X509_get_issuer_name(main_cert); + if (!issuer_name) + return NULL; + + if (extra_certs) { + issuer = X509_find_by_subject(extra_certs, issuer_name); + if (issuer) + return OCSP_cert_to_id(NULL, main_cert, issuer); + } + + store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return NULL; + ok = X509_STORE_CTX_init(&storectx, store, main_cert, extra_certs); + if (ok != 1) + return NULL; + ok = X509_STORE_get_by_subject(&storectx, X509_LU_X509, issuer_name, &tmpobj); + if (ok == 1) { + cid = OCSP_cert_to_id(NULL, main_cert, tmpobj.data.x509); + X509_free(tmpobj.data.x509); + } + X509_STORE_CTX_cleanup(&storectx); + return cid; +} + +static int +tls_ocsp_verify_response(struct tls *ctx, X509 *main_cert, STACK_OF(X509) *extra_certs, + SSL_CTX *ssl_ctx, OCSP_RESPONSE *resp) +{ + OCSP_BASICRESP *br = NULL; + STACK_OF(X509) *ocsp_chain = 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 ssl_res, ret = -1; + unsigned long flags; +#ifdef BUGGY_VERIFY + STACK_OF(X509) *tmpchain = NULL; +#endif + + br = OCSP_response_get1_basic(resp); + if (!br) { + tls_set_errorx(ctx, "ocsp error: cannot load"); + goto error; + } + +#ifdef BUGGY_VERIFY + /* + * There may be OCSP-subCA in OCSP response that chains to subCA + * in main TLS headers. Need to present both chains to verify. + * + * OCSP_basic_verify() bugs: + * - Does not use br->certs when building chain. + * - Does not use main_certs when validating br->certs. + */ + if (br->certs && extra_certs) { + int i; + + combined = sk_X509_new_null(); + if (!combined) { + tls_set_errorx(ctx, "ocsp error: cannot merge"); + goto error; + } + for (i = 0; i < sk_X509_num(br->certs); i++) { + ssl_res = sk_X509_push(combined, sk_X509_value(br->certs, i)); + if (ssl_res == 0) { + tls_set_errorx(ctx, "ocsp error: cannot merge"); + goto error; + } + } + for (i = 0; i < sk_X509_num(extra_certs); i++) { + ssl_res = sk_X509_push(combined, sk_X509_value(extra_certs, i)); + if (ssl_res == 0) { + tls_set_errorx(ctx, "ocsp error: cannot merge"); + goto error; + } + } + + /* OCSP_TRUSTOTHER would skip br->certs checks */ + flags = 0; + flags = OCSP_TRUSTOTHER; + ocsp_chain = combined; + + /* Bug: does not use main_certs when validating br->certs */ + if ((flags & OCSP_TRUSTOTHER) == 0) { + tmpchain = br->certs; + br->certs = combined; + } + } else if (br->certs && !extra_certs) { + /* can this happen? */ + flags = 0; + ocsp_chain = br->certs; + } else +#endif + { + /* + * Skip validation of 'extra_certs' as this should be done + * already as part of main handshake. + */ + flags = OCSP_TRUSTOTHER; + ocsp_chain = extra_certs; + } + + /* now verify */ + ssl_res = OCSP_basic_verify(br, ocsp_chain, SSL_CTX_get_cert_store(ssl_ctx), flags); + +#ifdef BUGGY_VERIFY + /* replace back */ + if (tmpchain) { + br->certs = tmpchain; + tmpchain = NULL; + } +#endif + + if (ssl_res != 1) { + tls_set_error_libssl(ctx, "ocsp verify failed"); + goto error; + } + + /* signature OK, look inside */ + response_status = OCSP_response_status(resp); + if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + tls_set_errorx(ctx, "ocsp verify failed: unsuccessful response - %s", + OCSP_response_status_str(response_status)); + goto error; + } + + cid = tls_ocsp_get_certid(main_cert, extra_certs, ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "ocsp verify failed: no issuer cert"); + goto error; + } + + ssl_res = OCSP_resp_find_status(br, cid, &cert_status, &crl_reason, &revtime, &thisupd, &nextupd); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: no result for cert"); + goto error; + } + + ssl_res = OCSP_check_validity(thisupd, nextupd, JITTER_SEC, MAXAGE_SEC); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: bad age"); + goto error; + } + + ssl_res = tls_ocsp_fill_info(ctx, response_status, cert_status, crl_reason, revtime, thisupd, nextupd); + if (ssl_res != 0) + goto error; + + /* 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 error; + } + + ret = 0; + +error: + sk_X509_free(combined); + OCSP_CERTID_free(cid); + OCSP_BASICRESP_free(br); + return ret; +} + +/* + * Same callback on client-side has different error proto: + * 1=OK, 0=bad, -1=internal error + */ + +int +tls_ocsp_verify_callback(SSL *ssl, void *arg) +{ + OCSP_RESPONSE *resp = NULL; + STACK_OF(X509) *extra_certs = NULL; + X509 *peer = NULL; + const unsigned char *raw = NULL; + int size, res = -1; + struct tls *ctx; + + (void)arg; + ctx = SSL_get_app_data(ssl); + if (!ctx) + return -1; + + size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw); + if (size <= 0) + return 1; + + peer = SSL_get_peer_certificate(ssl); + if (!peer) { + tls_set_errorx(ctx, "ocsp verify failed: no peer cert"); + goto error; + } + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + tls_set_errorx(ctx, "ocsp verify failed: parse failed"); + goto error; + } + + extra_certs = SSL_get_peer_cert_chain(ssl); + res = tls_ocsp_verify_response(ctx, peer, extra_certs, ctx->ssl_ctx, resp); +error: + tls_ocsp_fill_result(ctx, res); + OCSP_RESPONSE_free(resp); + X509_free(peer); + return (res == 0) ? 1 : 0; +} + +/* + * Staple OCSP response to server handshake. + */ + +int +tls_ocsp_stapling_callback(SSL *ssl, void *arg) +{ + struct tls *ctx; + char *mem, *fmem = NULL; + uint8_t *xmem; + size_t len; + int ret = SSL_TLSEXT_ERR_ALERT_FATAL; + + (void)arg; + ctx = SSL_get_app_data(ssl); + if (!ctx) + return SSL_TLSEXT_ERR_NOACK; + + if (ctx->config->ocsp_file) { + fmem = mem = (char*)tls_load_file(ctx->config->ocsp_file, &len, NULL); + if (!mem) + goto err; + } else { + mem = ctx->config->ocsp_mem; + len = ctx->config->ocsp_len; + if (!mem) + return SSL_TLSEXT_ERR_NOACK; + } + xmem = OPENSSL_malloc(len); + if (xmem) { + memcpy(xmem, mem, len); + if (SSL_set_tlsext_status_ocsp_resp(ctx->ssl_conn, xmem, len) != 1) { + OPENSSL_free(xmem); + goto err; + } + ret = SSL_TLSEXT_ERR_OK; + } +err: + free(fmem); + return ret; +} + +/* + * Query OCSP responder over HTTP(S). + */ + +void +tls_ocsp_client_free(struct tls *ctx) +{ + struct tls_ocsp_query *q; + if (!ctx) + return; + q = ctx->ocsp_query; + if (q) { + if (q->http_req) + OCSP_REQ_CTX_free(q->http_req); + BIO_free_all(q->bio); + SSL_CTX_free(q->ssl_ctx); + + free(q->ocsp_url); + free(q->request_data); + free(q); + + ctx->ocsp_query = NULL; + } +} + +static struct tls * +tls_ocsp_client_new(void) +{ + struct tls *ctx; + + ctx = tls_new(); + if (!ctx) + return NULL; + ctx->flags = TLS_OCSP_CLIENT; + + ctx->ocsp_query = calloc(1, sizeof (struct tls_ocsp_query)); + if (!ctx->ocsp_query) { + tls_free(ctx); + return NULL; + } + return ctx; +} + +static int +tls_build_ocsp_request(struct tls *ctx) +{ + struct tls_ocsp_query *q; + int ok, ret = -1; + OCSP_REQUEST *req = NULL; + OCSP_CERTID *cid = NULL; + OCSP_ONEREQ *onereq = NULL; + BIO *mem = NULL; + void *data; + + q = ctx->ocsp_query; + + cid = tls_ocsp_get_certid(q->main_cert, q->extra_certs, q->cert_ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "Cannot create cert-id"); + goto failed; + } + + req = OCSP_REQUEST_new(); + if (!req) { + tls_set_error_libssl(ctx, "Cannot create request"); + goto failed; + } + + onereq = OCSP_request_add0_id(req, cid); + if (!onereq) { + tls_set_error_libssl(ctx, "Cannot add cert-id to request"); + goto failed; + } + cid = NULL; + + /* + * Now render it. + */ + + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_errorx(ctx, "BIO_new"); + goto failed; + } + + ok = i2d_OCSP_REQUEST_bio(mem, req); + if (!ok) { + tls_set_error_libssl(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + q->request_size = BIO_get_mem_data(mem, &data); + q->request_data = malloc(q->request_size); + if (!q->request_data) { + tls_set_error(ctx, "Failed to allocate request data"); + goto failed; + } + memcpy(q->request_data, data, q->request_size); + + req = NULL; + ret = 0; +failed: + OCSP_CERTID_free(cid); + OCSP_REQUEST_free(req); + BIO_free(mem); + return ret; +} + +static int +tls_ocsp_setup(struct tls **ocsp_ctx_p, struct tls_config *config, struct tls *target) +{ + struct tls *ctx; + struct tls_ocsp_query *q; + int ret; + STACK_OF(OPENSSL_STRING) *ocsp_urls; + + ctx = tls_ocsp_client_new(); + if (!ctx) + return -1; + + *ocsp_ctx_p = ctx; + q = ctx->ocsp_query; + + if (config) { + /* create ctx->ssl_ctx */ + ctx->flags = TLS_SERVER; + ret = tls_configure(ctx, config); + ctx->flags = TLS_OCSP_CLIENT; + if (ret != 0) + return ret; + + q->main_cert = SSL_get_certificate(ctx->ssl_conn); + q->cert_ssl_ctx = ctx->ssl_ctx; + SSL_CTX_get_extra_chain_certs(ctx->ssl_ctx, &q->extra_certs); + } else { + /* steal state from target struct */ + q->main_cert = SSL_get_peer_certificate(target->ssl_conn); + q->extra_certs = SSL_get_peer_cert_chain(target->ssl_conn); + q->cert_ssl_ctx = target->ssl_ctx; + X509_free(q->main_cert); /* unref */ + } + + if (!q->main_cert) { + tls_set_errorx(ctx, "No cert"); + return -1; + } + + ocsp_urls = X509_get1_ocsp(q->main_cert); + if (!ocsp_urls) + return TLS_NO_OCSP; + q->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0)); + if (!q->ocsp_url) { + tls_set_errorx(ctx, "Cannot copy URL"); + goto failed; + } + + ret = tls_build_ocsp_request(ctx); + if (ret != 0) + goto failed; + + *ocsp_ctx_p = ctx; + +failed: + X509_email_free(ocsp_urls); + return ret; +} + +static int +tls_ocsp_process_response_parsed(struct tls *ctx, struct tls_config *config, OCSP_RESPONSE *resp) +{ + struct tls_ocsp_query *q = ctx->ocsp_query; + BIO *mem = NULL; + size_t len; + unsigned char *data; + int ret = -1, ok, res; + + res = tls_ocsp_verify_response(ctx, q->main_cert, q->extra_certs, q->cert_ssl_ctx, resp); + if (res < 0) + goto failed; + + /* Update blob in config */ + if (config) { + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_error_libssl(ctx, "BIO_new"); + goto failed; + } + ok = i2d_OCSP_RESPONSE_bio(mem, resp); + if (!ok) { + tls_set_error_libssl(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + len = BIO_get_mem_data(mem, &data); + res = tls_config_set_ocsp_stapling_mem(config, data, len); + if (res < 0) + goto failed; + } + ret = 0; +failed: + BIO_free(mem); + tls_ocsp_fill_result(ctx, ret); + return ret; +} + +static int +tls_ocsp_create_request(struct tls **ocsp_ctx_p, + struct tls_config *config, struct tls *target, + char **ocsp_url, + void **request_blob, size_t *request_size) +{ + int res; + struct tls_ocsp_query *q; + + res = tls_ocsp_setup(ocsp_ctx_p, config, target); + if (res != 0) + return res; + q = (*ocsp_ctx_p)->ocsp_query; + + *ocsp_url = q->ocsp_url; + *request_blob = q->request_data; + *request_size = q->request_size; + + return 0; +} + +/* + * Public API for request blobs. + */ + +int +tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, NULL, target, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, + struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, config, NULL, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size) +{ + int ret; + OCSP_RESPONSE *resp; + const unsigned char *raw = response_blob; + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + ctx->ocsp_result = "parse-failed"; + tls_set_error_libssl(ctx, "parse failed"); + return -1; + } + ret = tls_ocsp_process_response_parsed(ctx, ctx->config, resp); + OCSP_RESPONSE_free(resp); + return ret; +} + +/* + * Network processing + */ + +static int +tls_ocsp_build_http_req(struct tls *ctx) +{ + struct tls_ocsp_query *q = ctx->ocsp_query; + int ok; + OCSP_REQUEST *req; + OCSP_REQ_CTX *sreq; + const unsigned char *data; + int ret=-1, https=0; + char *host=NULL, *port=NULL, *path=NULL; + + ok = OCSP_parse_url(q->ocsp_url, &host, &port, &path, &https); + if (ok != 1) { + tls_set_error_libssl(ctx, "Cannot parse URL"); + goto failed; + } + + sreq = OCSP_sendreq_new(q->bio, path, NULL, -1); + if (!sreq) { + tls_set_error_libssl(ctx, "OCSP HTTP request setup failed"); + goto failed; + } + q->http_req = sreq; + + ok = OCSP_REQ_CTX_add1_header(sreq, "Host", host); + + /* add payload after headers */ + if (ok) { + data = q->request_data; + req = d2i_OCSP_REQUEST(NULL, &data, q->request_size); + if (req) + ok = OCSP_REQ_CTX_set1_req(sreq, req); + else + ok = false; + OCSP_REQUEST_free(req); + } + + if (ok) + ret = 0; +failed: + free(host); + free(port); + free(path); + return ret; +} + +static int +tls_ocsp_connection_setup(struct tls *ctx) +{ + SSL *ssl = NULL; + int ret = -1, ok, https=0; + struct tls_ocsp_query *q = ctx->ocsp_query; + union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; + char *host=NULL, *port=NULL, *path=NULL; + + ok = OCSP_parse_url(q->ocsp_url, &host, &port, &path, &https); + if (ok != 1) { + tls_set_error_libssl(ctx, "Cannot parse URL"); + goto failed; + } + + if (https) { + q->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if (!q->ssl_ctx) { + tls_set_error_libssl(ctx, "Cannot init SSL"); + goto failed; + } + + SSL_CTX_set_options(q->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_clear_options(q->ssl_ctx, SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2); + SSL_CTX_set_cipher_list(q->ssl_ctx, TLS_CIPHERS_COMPAT); + SSL_CTX_set_mode(q->ssl_ctx, SSL_MODE_AUTO_RETRY); + + q->bio = BIO_new_ssl_connect(q->ssl_ctx); + if (q->bio) { + if (inet_pton(AF_INET, host, &addrbuf) != 1 && + inet_pton(AF_INET6, host, &addrbuf) != 1) { + if (!BIO_get_ssl(q->bio, &ssl)) { + tls_set_errorx(ctx, "cannot get ssl struct"); + goto failed; + } + if (SSL_set_tlsext_host_name(ssl, host) == 0) { + tls_set_errorx(ctx, "server name indication failure"); + goto failed; + } + } + } + } else { + q->bio = BIO_new(BIO_s_connect()); + } + + if (!q->bio) { + tls_set_error_libssl(ctx, "Cannot connect"); + goto failed; + } + + BIO_set_conn_hostname(q->bio, host); + BIO_set_conn_port(q->bio, port); + BIO_set_nbio(q->bio, 1); + ret = 0; +failed: + free(host); + free(port); + free(path); + return ret; +} + +static int +tls_ocsp_evloop(struct tls *ctx, int *fd_p, struct tls_config *config) +{ + struct tls_ocsp_query *q = ctx->ocsp_query; + OCSP_RESPONSE *ocsp_resp = NULL; + int ret, ok; + + if (q->http_req == NULL) { + ok = BIO_do_connect(q->bio); + if (ok != 1 && !BIO_should_retry(q->bio)) { + tls_set_error_libssl(ctx, "Connection failure"); + goto error; + } + + *fd_p = BIO_get_fd(q->bio, NULL); + if (*fd_p < 0) { + tls_set_error_libssl(ctx, "Cannot get FD"); + goto error; + } + + if (ok != 1) + return TLS_WANT_POLLOUT; + + ret = tls_ocsp_build_http_req(ctx); + if (ret != 0) + goto error; + } + + ok = OCSP_sendreq_nbio(&ocsp_resp, q->http_req); + if (ok == 1) { + ret = tls_ocsp_process_response_parsed(ctx, config, ocsp_resp); + return ret; + } else if (ok == 0) { + tls_set_error_libssl(ctx, "OCSP request failed"); + goto error; + } else if (BIO_should_read(q->bio)) { + return TLS_WANT_POLLIN; + } else if (BIO_should_write(q->bio)) { + return TLS_WANT_POLLOUT; + } + + tls_set_error_libssl(ctx, "Unexpected request error"); +error: + tls_ocsp_fill_result(ctx, -1); + return -1; +} + +static int +tls_ocsp_do_poll(struct tls *ctx, int errcode, int fd) +{ + struct pollfd pfd; + int res; + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + + if (errcode == TLS_WANT_POLLIN) { + pfd.events = POLLIN; + } else if (errcode == TLS_WANT_POLLOUT) { + pfd.events = POLLOUT; + } else { + tls_set_error(ctx, "bad code to poll"); + return -1; + } + res = poll(&pfd, 1, -1); + if (res == 1) { + return 0; + } else if (res == 0) { + tls_set_errorx(ctx, "poll timed out"); + return -1; + } + tls_set_error(ctx, "poll error"); + return -1; +} + +static int +tls_ocsp_query_async(struct tls **ocsp_ctx_p, int *fd_p, struct tls_config *config, struct tls *target) +{ + struct tls *ctx = *ocsp_ctx_p; + int ret; + + if (!ctx) { + ret = tls_ocsp_setup(&ctx, config, target); + if (ret != 0) + goto failed; + ret = tls_ocsp_connection_setup(ctx); + if (ret != 0) + goto failed; + *ocsp_ctx_p = ctx; + } + return tls_ocsp_evloop(ctx, fd_p, config); +failed: + tls_free(ctx); + return -1; +} + +static int +tls_ocsp_common_query(struct tls **ocsp_ctx_p, int *fd_p, struct tls_config *config, struct tls *target) +{ + struct tls *ctx = NULL; + int ret, fd; + fd = -1; + + /* async path */ + if (fd_p) + return tls_ocsp_query_async(ocsp_ctx_p, fd_p, config, target); + + /* sync path */ + while (1) { + ret = tls_ocsp_query_async(&ctx, &fd, config, target); + if (ret != TLS_WANT_POLLIN && ret != TLS_WANT_POLLOUT) + break; + ret = tls_ocsp_do_poll(ctx, ret, fd); + if (ret != 0) + break; + } + *ocsp_ctx_p = ctx; + return ret; +} + +/* + * Public API. + */ + +int +tls_ocsp_check_peer(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls *target) +{ + return tls_ocsp_common_query(ocsp_ctx_p, async_fd_p, NULL, target); +} + +int +tls_ocsp_refresh_stapling(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls_config *config) +{ + return tls_ocsp_common_query(ocsp_ctx_p, async_fd_p, config, NULL); +} + +#else /* No OCSP */ + +void tls_ocsp_info_free(struct tls_ocsp_info *info) {} +void tls_ocsp_client_free(struct tls *ctx) {} + +int +tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, + int *crl_reason, time_t *this_update, + time_t *next_update, time_t *revoction_time, + const char **result_text) +{ + if (response_status) *response_status = -1; + if (cert_status) *cert_status = -1; + if (crl_reason) *crl_reason = -1; + if (result_text) *result_text = "OCSP not supported"; + if (this_update) *this_update = 0; + if (next_update) *next_update = 0; + if (revoction_time) *revoction_time = 0; + return TLS_NO_OCSP; +} + +int +tls_ocsp_check_peer(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls *target) +{ + *ocsp_ctx_p = NULL; + return TLS_NO_OCSP; +} + +int +tls_ocsp_refresh_stapling(struct tls **ocsp_ctx_p, int *async_fd_p, struct tls_config *config) +{ + *ocsp_ctx_p = NULL; + return TLS_NO_OCSP; +} + +#endif /* OPENSSL_NO_OCSP */ diff --git a/lib/libtls/tls_peer.c b/lib/libtls/tls_peer.c new file mode 100644 index 00000000..d7f4303a --- /dev/null +++ b/lib/libtls/tls_peer.c @@ -0,0 +1,83 @@ +/* $OpenBSD: tls_peer.c,v 1.3 2015/09/11 13:22:39 beck Exp $ */ +/* + * Copyright (c) 2015 Joel Sing + * Copyright (c) 2015 Bob Beck + * + * 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 + +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +const char * +tls_peer_cert_hash(struct tls *ctx) +{ + if (ctx->conninfo) + return (ctx->conninfo->hash); + return NULL; +} +const char * +tls_peer_cert_issuer(struct tls *ctx) +{ + if (ctx->conninfo) + return (ctx->conninfo->issuer); + return NULL; +} + +const char * +tls_peer_cert_subject(struct tls *ctx) +{ + if (ctx->conninfo) + return (ctx->conninfo->subject); + return NULL; +} + +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) +{ + if (ctx->ssl_peer_cert == NULL) + return (0); + + return (tls_check_name(ctx, ctx->ssl_peer_cert, name) == 0); +} + +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); +} diff --git a/lib/libtls/tls_server.c b/lib/libtls/tls_server.c new file mode 100644 index 00000000..d4a12cd2 --- /dev/null +++ b/lib/libtls/tls_server.c @@ -0,0 +1,193 @@ +/* $OpenBSD: tls_server.c,v 1.6 2015/03/31 12:21:27 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * + * 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 +#include +#include +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +struct tls * +tls_server(void) +{ + struct tls *ctx; + + 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; + (void)ctx; + + if ((conn_ctx = tls_new()) == NULL) + return (NULL); + + conn_ctx->flags |= TLS_SERVER_CONN; + + return (conn_ctx); +} + +int +tls_configure_server(struct tls *ctx) +{ + EC_KEY *ecdh_key; + unsigned char sid[SSL_MAX_SSL_SESSION_ID_LENGTH]; + + if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { + tls_set_errorx(ctx, "ssl context failure"); + goto err; + } + + if (tls_configure_ssl(ctx) != 0) + goto err; + if (tls_configure_keypair(ctx, 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, verify) == -1) + goto err; + } + + if (ctx->config->dheparams == -1) + SSL_CTX_set_dh_auto(ctx->ssl_ctx, 1); + else if (ctx->config->dheparams == 1024) + SSL_CTX_set_dh_auto(ctx->ssl_ctx, 2); + + if (ctx->config->ecdhecurve == -1) { + SSL_CTX_set_ecdh_auto(ctx->ssl_ctx, 1); + } else if (ctx->config->ecdhecurve != NID_undef) { + if ((ecdh_key = EC_KEY_new_by_curve_name( + ctx->config->ecdhecurve)) == NULL) { + tls_set_errorx(ctx, "failed to set ECDHE curve"); + goto err; + } + SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_SINGLE_ECDH_USE); + SSL_CTX_set_tmp_ecdh(ctx->ssl_ctx, ecdh_key); + EC_KEY_free(ecdh_key); + } + + if (ctx->config->ciphers_server == 1) + SSL_CTX_set_options(ctx->ssl_ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_stapling_callback) != 1) { + tls_set_errorx(ctx, "ssl OCSP stapling setup failure"); + goto err; + } + + /* + * Set session ID context to a random value. We don't support + * persistent caching of sessions so it is OK to set a temporary + * session ID context that is valid during run time. + */ + if (!RAND_bytes(sid, sizeof(sid))) { + tls_set_errorx(ctx, "failed to generate session id"); + goto err; + } + if (!SSL_CTX_set_session_id_context(ctx->ssl_ctx, sid, sizeof(sid))) { + tls_set_errorx(ctx, "failed to set session id context"); + goto err; + } + + return (0); + + err: + return (-1); +} + +int +tls_accept_socket(struct tls *ctx, struct tls **cctx, int socket) +{ + return (tls_accept_fds(ctx, cctx, socket, socket)); +} + +int +tls_accept_fds(struct tls *ctx, struct tls **cctx, int fd_read, int fd_write) +{ + 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; + } + 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_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; + } + + 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); +} diff --git a/lib/libtls/tls_util.c b/lib/libtls/tls_util.c new file mode 100644 index 00000000..8c6b7661 --- /dev/null +++ b/lib/libtls/tls_util.c @@ -0,0 +1,226 @@ +/* $OpenBSD: tls_util.c,v 1.1 2014/10/31 13:46:17 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing + * Copyright (c) 2015 Reyk Floeter + * + * 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 +#include + +#include + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +const char * +tls_backend_version(void) +{ + return SSLeay_version(SSLEAY_VERSION); +} + +/* + * 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 fail; + + 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 seperator. */ + 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) + goto fail; + if (asprintf(port, "%s", p) == -1) + goto fail; + + rv = 0; + goto done; + + fail: + free(*host); + *host = NULL; + free(*port); + *port = NULL; + rv = -1; + + done: + free(s); + + return (rv); +} + +static int +tls_password_cb(char *buf, int size, int rwflag, void *u) +{ + size_t len; + if (u == NULL) { + memset(buf, 0, size); + return (0); + } + len = snprintf(buf, size, "%s", u); + if (len >= (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; + uint8_t *buf = NULL; + char *data; + struct stat st; + size_t size; + int fd = -1; + + *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 fail; + size = (size_t)st.st_size; + if ((buf = calloc(1, size + 1)) == NULL) + goto fail; + if (read(fd, buf, size) != (ssize_t)size) + goto fail; + close(fd); + goto done; + } + + /* Or read the (possibly) encrypted key from file */ + if ((fp = fdopen(fd, "r")) == NULL) + goto fail; + fd = -1; + + key = PEM_read_PrivateKey(fp, NULL, tls_password_cb, password); + fclose(fp); + if (key == NULL) + goto fail; + + /* Write unencrypted key to memory buffer */ + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto fail; + if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) + goto fail; + if ((size = BIO_get_mem_data(bio, &data)) <= 0) + goto fail; + if ((buf = calloc(1, size)) == NULL) + goto fail; + memcpy(buf, data, size); + + BIO_free_all(bio); + EVP_PKEY_free(key); + + done: + *len = size; + return (buf); + + fail: + free(buf); + if (fd != -1) + close(fd); + if (bio != NULL) + BIO_free_all(bio); + if (key != NULL) + EVP_PKEY_free(key); + + return (NULL); +} + +ssize_t +tls_get_connection_info(struct tls *ctx, char *buf, size_t buflen) +{ + SSL *conn = ctx->ssl_conn; + const char *ocsp_pfx = "", *ocsp_info = ""; + const char *proto = "-", *cipher = "-"; + char dh[64]; + int used_dh_bits = ctx->used_dh_bits, used_ecdh_nid = ctx->used_ecdh_nid; + + if (conn != NULL) { + proto = SSL_get_version(conn); + cipher = SSL_get_cipher(conn); + +#ifdef SSL_get_server_tmp_key + if (ctx->flags & TLS_CLIENT) { + EVP_PKEY *pk = NULL; + int ok = SSL_get_server_tmp_key(conn, &pk); + int pk_type = EVP_PKEY_id(pk); + if (ok && pk) { + if (pk_type == EVP_PKEY_DH) { + DH *dh = EVP_PKEY_get0(pk); + used_dh_bits = DH_size(dh) * 8; + } else if (pk_type == EVP_PKEY_EC) { + EC_KEY *ecdh = EVP_PKEY_get0(pk); + const EC_GROUP *eg = EC_KEY_get0_group(ecdh); + used_ecdh_nid = EC_GROUP_get_curve_name(eg); + } + EVP_PKEY_free(pk); + } + } +#endif + } + + if (used_dh_bits) { + snprintf(dh, sizeof dh, "/DH=%d", used_dh_bits); + } else if (used_ecdh_nid) { + snprintf(dh, sizeof dh, "/ECDH=%s", OBJ_nid2sn(used_ecdh_nid)); + } else { + dh[0] = 0; + } + + if (ctx->ocsp_result) { + ocsp_info = ctx->ocsp_result; + ocsp_pfx = "/OCSP="; + } + + return snprintf(buf, buflen, "%s/%s%s%s%s", proto, cipher, dh, ocsp_pfx, ocsp_info); +} diff --git a/lib/libtls/tls_verify.c b/lib/libtls/tls_verify.c new file mode 100644 index 00000000..85acc319 --- /dev/null +++ b/lib/libtls/tls_verify.c @@ -0,0 +1,256 @@ +/* $OpenBSD: tls_verify.c,v 1.7 2015/02/11 06:46:33 jsing Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas + * + * 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 + +#include "tls_internal.h" +#include "tls_compat.h" +#include "tls.h" + +static int tls_match_name(const char *cert_name, const char *name); +static int tls_check_subject_altname(struct tls *ctx, X509 *cert, + const char *name); +static int tls_check_common_name(struct tls *ctx, X509 *cert, const char *name); + +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. */ +static int +tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union tls_addr addrbuf; + int addrlen, type; + int count, i; + int rv = -1; + + altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, + NULL, NULL); + if (altname_stack == NULL) + return -1; + + 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 != type) + continue; + + if (type == GEN_DNS) { + void *data; + int format, len; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_data(altname->d.dNSName); + len = ASN1_STRING_length(altname->d.dNSName); + + if (len < 0 || len != (int)strlen(data)) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "NUL byte in subjectAltName, " + "probably a malicious certificate", + name); + rv = -2; + break; + } + + /* + * 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_error(ctx, + "error verifying name '%s': " + "a dNSName of \" \" must not be " + "used", name); + rv = -2; + break; + } + + if (tls_match_name(data, name) == 0) { + rv = 0; + break; + } + } else { +#ifdef DEBUG + fprintf(stdout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); +#endif + } + + } else if (type == GEN_IPADD) { + unsigned char *data; + int datalen; + + datalen = ASN1_STRING_length(altname->d.iPAddress); + data = ASN1_STRING_data(altname->d.iPAddress); + + if (datalen < 0) { + tls_set_errorx(ctx, + "Unexpected negative length for an " + "IP address: %d", datalen); + rv = -2; + break; + } + + /* + * 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) { + rv = 0; + break; + } + } + } + + 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) +{ + X509_NAME *subject_name; + char *common_name = NULL; + union tls_addr addrbuf; + int common_name_len; + int rv = -1; + + subject_name = X509_get_subject_name(cert); + if (subject_name == NULL) + goto out; + + common_name_len = X509_NAME_get_text_by_NID(subject_name, + NID_commonName, NULL, 0); + if (common_name_len < 0) + goto out; + + common_name = calloc(common_name_len + 1, 1); + if (common_name == NULL) + goto out; + + X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len != (int)strlen(common_name)) { + tls_set_errorx(ctx, "error verifying name '%s': " + "NUL byte in Common Name field, " + "probably a malicious certificate", name); + rv = -2; + goto out; + } + + if (inet_pton(AF_INET, name, &addrbuf) == 1 || + inet_pton(AF_INET6, name, &addrbuf) == 1) { + /* + * We don't want to attempt wildcard matching against IP + * addresses, so perform a simple comparison here. + */ + if (strcmp(common_name, name) == 0) + rv = 0; + else + rv = -1; + goto out; + } + + if (tls_match_name(common_name, name) == 0) + rv = 0; + out: + free(common_name); + return rv; +} + +int +tls_check_name(struct tls *ctx, X509 *cert, const char *name) +{ + int rv; + + rv = tls_check_subject_altname(ctx, cert, name); + if (rv == 0 || rv == -2) + return rv; + + return tls_check_common_name(ctx, cert, name); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9939670f..f8733991 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,4 @@ +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") set(machine_library machinarium)