mirror of https://github.com/yandex/odyssey.git
machinarium: integrate with OpenBSD tls library
This commit is contained in:
parent
b0f195b9b4
commit
b0b8ae0ce8
|
@ -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 "")
|
||||
|
|
|
@ -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})
|
|
@ -0,0 +1,640 @@
|
|||
/* $OpenBSD: tls.c,v 1.11 2015/04/15 16:08:43 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/dh.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/* $OpenBSD: tls.h,v 1.12 2015/03/31 14:03:38 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _HEADER_TLS_H_
|
||||
#define _HEADER_TLS_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
|
@ -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 <jca@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,287 @@
|
|||
/* $OpenBSD: tls_client.c,v 1.16 2015/03/21 15:35:15 sthen Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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 <openssl/err.h>
|
||||
#include <openssl/dh.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef _TLS_COMPAT_H_
|
||||
#define _TLS_COMPAT_H_
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
/* 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
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
/* $OpenBSD: tls_config.c,v 1.8 2015/02/22 14:59:37 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/* $OpenBSD: tls_peer.c,v 1.3 2015/09/11 13:22:39 beck Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2015 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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 <jca@openbsd.org>
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef HEADER_TLS_INTERNAL_H
|
||||
#define HEADER_TLS_INTERNAL_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#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 */
|
|
@ -0,0 +1,969 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Marko Kreen <markokr@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "tls_internal.h"
|
||||
#include "tls_compat.h"
|
||||
#include "tls.h"
|
||||
|
||||
#ifndef OPENSSL_NO_OCSP
|
||||
|
||||
#include <openssl/ocsp.h>
|
||||
|
||||
#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 */
|
|
@ -0,0 +1,83 @@
|
|||
/* $OpenBSD: tls_peer.c,v 1.3 2015/09/11 13:22:39 beck Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2015 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2015 Bob Beck <beck@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "tls_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);
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/* $OpenBSD: tls_server.c,v 1.6 2015/03/31 12:21:27 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/* $OpenBSD: tls_util.c,v 1.1 2014/10/31 13:46:17 jsing Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
|
||||
* Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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 <jca@openbsd.org>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
|
||||
|
||||
set(machine_library machinarium)
|
||||
|
||||
|
|
Loading…
Reference in New Issue