diff options
author | Joel Sing <jsing@cvs.openbsd.org> | 2014-10-31 13:46:18 +0000 |
---|---|---|
committer | Joel Sing <jsing@cvs.openbsd.org> | 2014-10-31 13:46:18 +0000 |
commit | f44b598f55e4b6375817972d506ad93e63c93bd1 (patch) | |
tree | 853c2c67e6a38e22e1a866a41d78d694c9411397 /lib/libtls | |
parent | c3c9d9dbad2cd6645037029656c4a7339bda69da (diff) |
Rename libressl to libtls to avoid confusion and to make it easier to
distinguish between LibreSSL (the project) and libressl (the library).
Discussed with many.
Diffstat (limited to 'lib/libtls')
-rw-r--r-- | lib/libtls/Makefile | 58 | ||||
-rw-r--r-- | lib/libtls/shlib_version | 2 | ||||
-rw-r--r-- | lib/libtls/tls.c | 300 | ||||
-rw-r--r-- | lib/libtls/tls.h | 74 | ||||
-rw-r--r-- | lib/libtls/tls_client.c | 212 | ||||
-rw-r--r-- | lib/libtls/tls_config.c | 201 | ||||
-rw-r--r-- | lib/libtls/tls_init.3 | 316 | ||||
-rw-r--r-- | lib/libtls/tls_internal.h | 72 | ||||
-rw-r--r-- | lib/libtls/tls_server.c | 134 | ||||
-rw-r--r-- | lib/libtls/tls_util.c | 81 | ||||
-rw-r--r-- | lib/libtls/tls_verify.c | 225 |
11 files changed, 1675 insertions, 0 deletions
diff --git a/lib/libtls/Makefile b/lib/libtls/Makefile new file mode 100644 index 00000000000..b83a6de2ce0 --- /dev/null +++ b/lib/libtls/Makefile @@ -0,0 +1,58 @@ +# $OpenBSD: Makefile,v 1.1 2014/10/31 13:46:17 jsing Exp $ + +CFLAGS+= -Wall -Werror -Wimplicit +CFLAGS+= -DLIBRESSL_INTERNAL + +LIB= tls + +DPADD= ${LIBCRYPTO} ${LIBSSL} + +HDRS= tls.h + +SRCS= tls.c \ + tls_client.c \ + tls_config.c \ + tls_server.c \ + tls_util.c \ + tls_verify.c + +MAN= tls_init.3 + +MLINKS+=tls_init.3 tls_config_new.3 +MLINKS+=tls_init.3 tls_config_free.3 +MLINKS+=tls_init.3 tls_config_set_ca_file.3 +MLINKS+=tls_init.3 tls_config_set_ca_path.3 +MLINKS+=tls_init.3 tls_config_set_cert_file.3 +MLINKS+=tls_init.3 tls_config_set_cert_mem.3 +MLINKS+=tls_init.3 tls_config_set_ciphers.3 +MLINKS+=tls_init.3 tls_config_set_ecdhcurve.3 +MLINKS+=tls_init.3 tls_config_set_key_file.3 +MLINKS+=tls_init.3 tls_config_set_key_mem.3 +MLINKS+=tls_init.3 tls_config_set_protocols.3 +MLINKS+=tls_init.3 tls_config_set_verify_depth.3 +MLINKS+=tls_init.3 tls_config_clear_keys.3 +MLINKS+=tls_init.3 tls_config_insecure_noverifyhost.3 +MLINKS+=tls_init.3 tls_config_insecure_noverifycert.3 +MLINKS+=tls_init.3 tls_config_verify.3 +MLINKS+=tls_init.3 tls_client.3 +MLINKS+=tls_init.3 tls_server.3 +MLINKS+=tls_init.3 tls_configure.3 +MLINKS+=tls_init.3 tls_error.3 +MLINKS+=tls_init.3 tls_reset.3 +MLINKS+=tls_init.3 tls_free.3 +MLINKS+=tls_init.3 tls_close.3 +MLINKS+=tls_init.3 tls_connect.3 +MLINKS+=tls_init.3 tls_connect_socket.3 +MLINKS+=tls_init.3 tls_read.3 +MLINKS+=tls_init.3 tls_write.3 + +includes: + @cd ${.CURDIR}; for i in $(HDRS); do \ + j="cmp -s $$i ${DESTDIR}/usr/include/$$i || \ + ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m 444 $$i\ + ${DESTDIR}/usr/include/"; \ + echo $$j; \ + eval "$$j"; \ + done; + +.include <bsd.lib.mk> diff --git a/lib/libtls/shlib_version b/lib/libtls/shlib_version new file mode 100644 index 00000000000..1edea46de91 --- /dev/null +++ b/lib/libtls/shlib_version @@ -0,0 +1,2 @@ +major=1 +minor=0 diff --git a/lib/libtls/tls.c b/lib/libtls/tls.c new file mode 100644 index 00000000000..a7f612e40ba --- /dev/null +++ b/lib/libtls/tls.c @@ -0,0 +1,300 @@ +/* $OpenBSD: tls.c,v 1.1 2014/10/31 13:46:17 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 <sys/socket.h> + +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +static struct tls_config *tls_config_default; + +int +tls_init(void) +{ + static int tls_initialised = 0; + + if (tls_initialised) + return (0); + + SSL_load_error_strings(); + SSL_library_init(); + + if ((tls_config_default = tls_config_new()) == NULL) + return (-1); + + tls_initialised = 1; + + return (0); +} + +const char * +tls_error(struct tls *ctx) +{ + return ctx->errmsg; +} + +int +tls_set_error(struct tls *ctx, char *fmt, ...) +{ + va_list ap; + int rv; + + ctx->err = errno; + free(ctx->errmsg); + ctx->errmsg = NULL; + + va_start(ap, fmt); + rv = vasprintf(&ctx->errmsg, fmt, ap); + va_end(ap); + + return (rv); +} + +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) +{ + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + BIO *bio = NULL; + + if (ctx->config->cert_mem != NULL) { + if (SSL_CTX_use_certificate_chain(ctx->ssl_ctx, + ctx->config->cert_mem, ctx->config->cert_len) != 1) { + tls_set_error(ctx, "failed to load certificate"); + goto err; + } + cert = NULL; + } + if (ctx->config->key_mem != NULL) { + if ((bio = BIO_new_mem_buf(ctx->config->key_mem, + ctx->config->key_len)) == NULL) { + tls_set_error(ctx, "failed to create buffer"); + goto err; + } + if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, + NULL)) == NULL) { + tls_set_error(ctx, "failed to read private key"); + goto err; + } + if (SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey) != 1) { + tls_set_error(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_error(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_error(ctx, "failed to load private key file"); + goto err; + } + } + + if (SSL_CTX_check_private_key(ctx->ssl_ctx) != 1) { + tls_set_error(ctx, "private/public key mismatch"); + goto err; + } + + return (0); + +err: + EVP_PKEY_free(pkey); + X509_free(cert); + BIO_free(bio); + + return (1); +} + +int +tls_configure_ssl(struct tls *ctx) +{ + 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_error(ctx, "failed to set ciphers"); + goto err; + } + } + + 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); + + ctx->ssl_conn = NULL; + ctx->ssl_ctx = NULL; + + ctx->socket = -1; + + ctx->err = 0; + free(ctx->errmsg); + ctx->errmsg = NULL; +} + +int +tls_read(struct tls *ctx, void *buf, size_t buflen, size_t *outlen) +{ + int ret, ssl_err; + + ret = SSL_read(ctx->ssl_conn, buf, buflen); + if (ret > 0) { + *outlen = (size_t)ret; + return (0); + } + + ssl_err = SSL_get_error(ctx->ssl_conn, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + return (TLS_READ_AGAIN); + case SSL_ERROR_WANT_WRITE: + return (TLS_WRITE_AGAIN); + default: + tls_set_error(ctx, "read failed (%i)", ssl_err); + return (-1); + } +} + +int +tls_write(struct tls *ctx, const void *buf, size_t buflen, size_t *outlen) +{ + int ret, ssl_err; + + ret = SSL_write(ctx->ssl_conn, buf, buflen); + if (ret > 0) { + *outlen = (size_t)ret; + return (0); + } + + ssl_err = SSL_get_error(ctx->ssl_conn, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + return (TLS_READ_AGAIN); + case SSL_ERROR_WANT_WRITE: + return (TLS_WRITE_AGAIN); + default: + tls_set_error(ctx, "write failed (%i)", ssl_err); + return (-1); + } +} + +int +tls_close(struct tls *ctx) +{ + /* XXX - handle case where multiple calls are required. */ + if (ctx->ssl_conn != NULL) { + if (SSL_shutdown(ctx->ssl_conn) == -1) { + tls_set_error(ctx, "SSL shutdown failed"); + goto err; + } + } + + if (ctx->socket != -1) { + if (shutdown(ctx->socket, SHUT_RDWR) != 0) { + tls_set_error(ctx, "shutdown"); + goto err; + } + if (close(ctx->socket) != 0) { + tls_set_error(ctx, "close"); + goto err; + } + ctx->socket = -1; + } + + return (0); + +err: + return (-1); +} diff --git a/lib/libtls/tls.h b/lib/libtls/tls.h new file mode 100644 index 00000000000..0fa776e584c --- /dev/null +++ b/lib/libtls/tls.h @@ -0,0 +1,74 @@ +/* $OpenBSD: tls.h,v 1.1 2014/10/31 13:46:17 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 + +#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_DEFAULT TLS_PROTOCOL_TLSv1 + +#define TLS_READ_AGAIN -2 +#define TLS_WRITE_AGAIN -3 + +struct tls; +struct tls_config; + +int tls_init(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_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_ecdhcurve(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); +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_clear_keys(struct tls_config *config); +void tls_config_insecure_noverifyhost(struct tls_config *config); +void tls_config_insecure_noverifycert(struct tls_config *config); +void tls_config_verify(struct tls_config *config); + +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_socket(struct tls *ctx, struct tls **cctx, int socket); +int tls_connect(struct tls *ctx, const char *host, const char *port); +int tls_connect_socket(struct tls *ctx, int s, const char *hostname); +int tls_read(struct tls *ctx, void *buf, size_t buflen, size_t *outlen); +int tls_write(struct tls *ctx, const void *buf, size_t buflen, size_t *outlen); +int tls_close(struct tls *ctx); + +#endif /* HEADER_TLS_H */ diff --git a/lib/libtls/tls_client.c b/lib/libtls/tls_client.c new file mode 100644 index 00000000000..853766f87b6 --- /dev/null +++ b/lib/libtls/tls_client.c @@ -0,0 +1,212 @@ +/* $OpenBSD: tls_client.c,v 1.1 2014/10/31 13:46:17 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 <sys/types.h> +#include <sys/socket.h> + +#include <arpa/inet.h> + +#include <netdb.h> +#include <stdlib.h> +#include <unistd.h> + +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.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) +{ + 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_error(ctx, "not a client context"); + goto err; + } + + if (host == NULL) { + tls_set_error(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_error(ctx, "memory allocation failure"); + goto err; + } + if (ret != 0) + port = HTTPS_PORT; + } + + h = (hs != NULL) ? hs : host; + p = (ps != NULL) ? ps : port; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((ret = getaddrinfo(h, p, &hints, &res0)) != 0) { + tls_set_error(ctx, "%s", gai_strerror(ret)); + goto err; + } + 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 (tls_connect_socket(ctx, s, h) != 0) { + close(s); + goto err; + } + + rv = 0; + +err: + + free(hs); + free(ps); + + return (rv); +} + +int +tls_connect_socket(struct tls *ctx, int socket, const char *hostname) +{ + union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; + X509 *cert = NULL; + int ret; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_error(ctx, "not a client context"); + goto err; + } + + ctx->socket = socket; + + if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { + tls_set_error(ctx, "ssl context failure"); + goto err; + } + + if (tls_configure_ssl(ctx) != 0) + goto err; + + if (ctx->config->verify_host) { + if (hostname == NULL) { + tls_set_error(ctx, "server name not specified"); + goto err; + } + } + + if (ctx->config->verify_cert) { + SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL); + + if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, + ctx->config->ca_file, ctx->config->ca_path) != 1) { + tls_set_error(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); + } + + if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { + tls_set_error(ctx, "ssl connection failure"); + goto err; + } + if (SSL_set_fd(ctx->ssl_conn, ctx->socket) != 1) { + tls_set_error(ctx, "ssl file descriptor failure"); + goto err; + } + + /* + * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not + * permitted in "HostName". + */ + if (hostname != NULL && + inet_pton(AF_INET, hostname, &addrbuf) != 1 && + inet_pton(AF_INET6, hostname, &addrbuf) != 1) { + if (SSL_set_tlsext_host_name(ctx->ssl_conn, hostname) == 0) { + tls_set_error(ctx, "SNI host name failed"); + goto err; + } + } + + if ((ret = SSL_connect(ctx->ssl_conn)) != 1) { + tls_set_error(ctx, "SSL connect failed: %i", + SSL_get_error(ctx->ssl_conn, ret)); + goto err; + } + + if (ctx->config->verify_host) { + cert = SSL_get_peer_certificate(ctx->ssl_conn); + if (cert == NULL) { + tls_set_error(ctx, "no server certificate"); + goto err; + } + if (tls_check_hostname(cert, hostname) != 0) { + tls_set_error(ctx, "host `%s' not present in" + " server certificate", hostname); + goto err; + } + } + + return (0); + +err: + X509_free(cert); + + return (-1); +} diff --git a/lib/libtls/tls_config.c b/lib/libtls/tls_config.c new file mode 100644 index 00000000000..0e435f616aa --- /dev/null +++ b/lib/libtls/tls_config.c @@ -0,0 +1,201 @@ +/* $OpenBSD: tls_config.c,v 1.1 2014/10/31 13:46:17 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 <errno.h> +#include <stdlib.h> + +#include <tls.h> +#include "tls_internal.h" + +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) { + tls_config_free(config); + return (NULL); + } + tls_config_set_ecdhcurve(config, "auto"); + tls_config_set_protocols(config, TLS_PROTOCOLS_DEFAULT); + tls_config_set_verify_depth(config, 6); + + tls_config_verify(config); + + return (config); +} + +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_cert_mem(config, NULL, 0); + tls_config_set_key_mem(config, NULL, 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_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) +{ + return set_string(&config->ciphers, ciphers); +} + +int +tls_config_set_ecdhcurve(struct tls_config *config, const char *name) +{ + int nid; + + if (name == NULL) + nid = NID_undef; + else if (strcasecmp(name, "auto") == 0) + nid = -1; + else if ((nid = OBJ_txt2nid(name)) == NID_undef) + return (-1); + + config->ecdhcurve = 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); +} + +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_insecure_noverifyhost(struct tls_config *config) +{ + config->verify_host = 0; +} + +void +tls_config_insecure_noverifycert(struct tls_config *config) +{ + config->verify_cert = 0; +} + +void +tls_config_verify(struct tls_config *config) +{ + config->verify_host = 1; + config->verify_cert = 1; +} diff --git a/lib/libtls/tls_init.3 b/lib/libtls/tls_init.3 new file mode 100644 index 00000000000..faa9b995393 --- /dev/null +++ b/lib/libtls/tls_init.3 @@ -0,0 +1,316 @@ +.\" $OpenBSD: tls_init.3,v 1.1 2014/10/31 13:46:17 jsing Exp $ +.\" +.\" Copyright (c) 2014 Ted Unangst <tedu@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. +.\" +.Dd $Mdocdate: October 31 2014 $ +.Dt TLS 3 +.Os +.Sh NAME +.Nm tls_init , +.Nm tls_error , +.Nm tls_config_new , +.Nm tls_config_free , +.Nm tls_config_set_ca_file , +.Nm tls_config_set_ca_path , +.Nm tls_config_set_cert_file , +.Nm tls_config_set_cert_mem , +.Nm tls_config_set_ciphers , +.Nm tls_config_set_ecdhcurve , +.Nm tls_config_set_key_file , +.Nm tls_config_set_key_mem , +.Nm tls_config_set_protocols , +.Nm tls_config_set_verify_depth , +.Nm tls_config_clear_keys , +.Nm tls_config_insecure_noverifyhost , +.Nm tls_config_insecure_noverifycert , +.Nm tls_config_verify , +.Nm tls_client , +.Nm tls_server , +.Nm tls_configure , +.Nm tls_reset , +.Nm tls_close , +.Nm tls_free , +.Nm tls_connect , +.Nm tls_connect_socket , +.Nm tls_read , +.Nm tls_write , +.Nd tls TLS client and server API +.Sh SYNOPSIS +.In tls.h +.Ft "int" +.Fn tls_init "void" +.Ft "const char *" +.Fn tls_error "struct tls *ctx" +.Ft "struct tls_config *" +.Fn tls_config_new "void" +.Ft "void" +.Fn tls_config_free "struct tls_config *config" +.Ft "int" +.Fn tls_config_set_ca_file "struct tls_config *config" "const char *ca_file" +.Ft "int" +.Fn tls_config_set_ca_path "struct tls_config *config" "const char *ca_path" +.Ft "int" +.Fn tls_config_set_cert_file "struct tls_config *config" "const char *cert_file" +.Ft "int" +.Fn tls_config_set_cert_mem "struct tls_config *config" "const uint8_t *cert" "size_t len" +.Ft "int" +.Fn tls_config_set_ciphers "struct tls_config *config" "const char *ciphers" +.Ft "int" +.Fn tls_config_set_ecdhcurve "struct tls_config *config" "const char *name" +.Ft "int" +.Fn tls_config_set_key_file "struct tls_config *config" "const char *key_file" +.Ft "int" +.Fn tls_config_set_key_mem "struct tls_config *config" "const uint8_t *key" "size_t len" +.Ft "int" +.Fn tls_config_set_protocols "struct tls_config *config" "uint32_t protocols" +.Ft "int" +.Fn tls_config_set_verify_depth "struct tls_config *config" "int verify_depth" +.Ft "void" +.Fn tls_config_clear_keys "struct tls_config *config" +.Ft "void" +.Fn tls_config_insecure_noverifyhost "struct tls_config *config" +.Ft "void" +.Fn tls_config_insecure_noverifycert "struct tls_config *config" +.Ft "void" +.Fn tls_config_verify "struct tls_config *config" +.Ft "struct tls *" +.Fn tls_client void +.Ft "struct tls *" +.Fn tls_server void +.Ft "int" +.Fn tls_configure "struct tls *ctx" "struct tls_config *config" +.Ft "void" +.Fn tls_reset "struct tls *ctx" +.Ft "int" +.Fn tls_close "struct tls *ctx" +.Ft "void" +.Fn tls_free "struct tls *ctx" +.Ft "int" +.Fn tls_connect "struct tls *ctx" "const char *host" "const char *port" +.Ft "int" +.Fn tls_connect_socket "struct tls *ctx" "int s" "const char *hostname" +.Ft "int" +.Fn tls_read "struct tls *ctx" "void *buf" "size_t buflen" "size_t *outlen" +.Ft "int" +.Fn tls_write "struct tls *ctx" "const void *buf" "size_t buflen" +.Sh DESCRIPTION +The +.Nm tls +family of functions establishes a secure communications channel +using the TLS socket protocol. +Both clients and servers are supported. +.Pp +The +.Fn tls_init +function should be called once before any function is used. +.Pp +Before a connection is created, a configuration must be created. +The +.Fn tls_config_new +function returns a new default configuration that can be used for future +connections. +Several functions exist to change the options of the configuration; see below. +.Pp +A +.Em tls +connection is represented as a +.Em context . +A new +.Em context +is created by either the +.Fn tls_client +or +.Fn tls_server +functions. +The context can then be configured with the function +.Fn tls_configure . +The same +.Em tls_config +object can be used to configure multiple contexts. +.Pp +A client connection is initiated after configuration by calling +.Fn tls_connect . +This function will create a new socket, connect to the specified host and +port, and then establish a secure connection. +An already existing socket can be upgraded to a secure connection by calling +.Fn tls_connect_socket . +.Pp +Two functions are provided for input and output, +.Fn tls_read +and +.Fn tls_write . +.Pp +After use, a tls +.Em context +should be closed with +.Fn tls_close , +and then freed by calling +.Fn tls_free . +When no more contexts are to be created, the +.Em tls_config +object should be freed by calling +.Fn tls_config_free . +.Sh FUNCTIONS +The +.Fn tls_init +function initializes global data structures. +It should be called once before any other functions. +.Pp +The following functions create and free configuration objects. +.Bl -bullet -offset four +.It +.Fn tls_config_new +allocates a new default configuration object. +.It +.Fn tls_config_free +frees a configuration object. +.El +.Pp +The following functions modify a configuration by setting parameters. +Configuration options may apply to only clients or only servers or both. +.Bl -bullet -offset four +.It +.Fn tls_config_set_ca_file +sets the filename used to load a file +containing the root certificates. +.Em (Client) +.It +.Fn tls_config_set_ca_path +sets the path (directory) which should be searched for root +certificates. +.Em (Client) +.It +.Fn tls_config_set_cert_file +sets file from which the public certificate will be read. +.Em (Client and server) +.It +.Fn tls_config_set_cert_mem +sets the public certificate directly from memory. +.Em (Client and server) +.It +.Fn tls_config_set_ciphers +sets the list of ciphers that may be used. +.Em (Client and server) +.It +.Fn tls_config_set_key_file +sets the file from which the private key will be read. +.Em (Server) +.It +.Fn tls_config_set_key_mem +directly sets the private key from memory. +.Em (Server) +.It +.Fn tls_config_set_protocols +sets which versions of the protocol may be used. +Possible values are the bitwise OR of: +.Pp +.Bl -tag -width "TLS_PROTOCOL_TLSv1_2" -offset indent -compact +.It Dv TLS_PROTOCOL_TLSv1_0 +.It Dv TLS_PROTOCOL_TLSv1_1 +.It Dv TLS_PROTOCOL_TLSv1_2 +.El +.Pp +Additionally, the values +.Dv TLS_PROTOCOL_TLSv1 +(all TLS versions) and +.Dv TLS_PROTOCOLS_DEFAULT +(currently all TLS versions) may be used. +.Em (Client and server) +.It +.Fn tls_config_clear_keys +clears any secret keys from memory. +.Em (Server) +.It +.Fn tls_config_insecure_noverifyhost +disables hostname verification. +Be careful when using this option. +.Em (Client) +.It +.Fn tls_config_insecure_noverifycert +disables certificate verification. +Be extremely careful when using this option. +.Em (Client) +.It +.Fn tls_config_verify +reenables hostname and certificate verification. +.Em (Client) +.El +.Pp +The following functions create, prepare, and free a connection context. +.Bl -bullet -offset four +.It +.Fn tls_client +creates a new tls context for client connections. +.It +.Fn tls_server +creates a new tls context for server connections. +.It +.Fn tls_configure +readies a tls context for use by applying the configuration +options. +.It +.Fn tls_close +closes a connection after use. +.It +.Fn tls_free +frees a tls context after use. +.El +.Pp +The following functions initiate a connection and perform input and output +operations. +.Bl -bullet -offset four +.It +.Fn tls_connect +connects a client context to the server named by +.Fa host . +The +.Fa port +may be numeric or a service name. +If it is NULL then a host of the format "hostname:port" is permitted. +.It +.Fn tls_connect_socket +connects a client context to an already established socket connection. +.It +.Fn tls_read +reads +.Fa buflen +bytes of data from the socket into +.Fa buf . +The amount of data read is returned in +.Fa outlen . +.It +.Fn tls_write +writes +.Fa buflen +bytes of data from +.Fa buf +to the socket. +The amount of data written is returned in +.Fa outlen . +.El +.Sh RETURN VALUES +Functions that return +.Vt int +will return 0 on success and -1 on error. +Functions that return a pointer will return NULL on error. +.\" .Sh ERRORS +.\" .Sh SEE ALSO +.Sh HISTORY +The +.Nm tls +API first appeared in +.Ox 5.6 +as a response to the unnecessary challenges other APIs present in +order to use them safely. diff --git a/lib/libtls/tls_internal.h b/lib/libtls/tls_internal.h new file mode 100644 index 00000000000..da696e228d3 --- /dev/null +++ b/lib/libtls/tls_internal.h @@ -0,0 +1,72 @@ +/* $OpenBSD: tls_internal.h,v 1.1 2014/10/31 13:46:17 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 <openssl/ssl.h> + +#define HTTPS_PORT "443" + +#define _PATH_SSL_CA_FILE "/etc/ssl/cert.pem" + +struct tls_config { + const char *ca_file; + const char *ca_path; + const char *cert_file; + char *cert_mem; + size_t cert_len; + const char *ciphers; + int ecdhcurve; + const char *key_file; + char *key_mem; + size_t key_len; + uint32_t protocols; + int verify_cert; + int verify_host; + int verify_depth; +}; + +#define TLS_CLIENT (1 << 0) +#define TLS_SERVER (1 << 1) +#define TLS_SERVER_CONN (1 << 2) + +struct tls { + struct tls_config *config; + uint64_t flags; + + int err; + char *errmsg; + + int socket; + + SSL *ssl_conn; + SSL_CTX *ssl_ctx; +}; + +struct tls *tls_new(void); +struct tls *tls_server_conn(struct tls *ctx); + +int tls_check_hostname(X509 *cert, const char *host); +int tls_configure_keypair(struct tls *ctx); +int tls_configure_server(struct tls *ctx); +int tls_configure_ssl(struct tls *ctx); +int tls_host_port(const char *hostport, char **host, char **port); +int tls_set_error(struct tls *ctx, char *fmt, ...); + +#endif /* HEADER_TLS_INTERNAL_H */ diff --git a/lib/libtls/tls_server.c b/lib/libtls/tls_server.c new file mode 100644 index 00000000000..001f19ded4d --- /dev/null +++ b/lib/libtls/tls_server.c @@ -0,0 +1,134 @@ +/* $OpenBSD: tls_server.c,v 1.1 2014/10/31 13:46:17 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 <tls.h> +#include "tls_internal.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; + + 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; + + if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { + tls_set_error(ctx, "ssl context failure"); + goto err; + } + + if (tls_configure_ssl(ctx) != 0) + goto err; + if (tls_configure_keypair(ctx) != 0) + goto err; + + if (ctx->config->ecdhcurve == -1) { + SSL_CTX_set_ecdh_auto(ctx->ssl_ctx, 1); + } else if (ctx->config->ecdhcurve != NID_undef) { + if ((ecdh_key = EC_KEY_new_by_curve_name( + ctx->config->ecdhcurve)) == NULL) { + tls_set_error(ctx, "failed to set ECDH 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); + } + + return (0); + +err: + return (-1); +} + +int +tls_accept_socket(struct tls *ctx, struct tls **cctx, int socket) +{ + struct tls *conn_ctx = *cctx; + int ret, ssl_err; + + if ((ctx->flags & TLS_SERVER) == 0) { + tls_set_error(ctx, "not a server context"); + goto err; + } + + if (conn_ctx == NULL) { + if ((conn_ctx = tls_server_conn(ctx)) == NULL) { + tls_set_error(ctx, "connection context failure"); + goto err; + } + *cctx = conn_ctx; + + conn_ctx->socket = socket; + + if ((conn_ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { + tls_set_error(ctx, "ssl failure"); + goto err; + } + + if (SSL_set_fd(conn_ctx->ssl_conn, socket) != 1) { + tls_set_error(ctx, "ssl set fd failure"); + goto err; + } + SSL_set_app_data(conn_ctx->ssl_conn, conn_ctx); + } + + if ((ret = SSL_accept(conn_ctx->ssl_conn)) != 1) { + ssl_err = SSL_get_error(conn_ctx->ssl_conn, ret); + switch (ssl_err) { + case SSL_ERROR_WANT_READ: + return (TLS_READ_AGAIN); + case SSL_ERROR_WANT_WRITE: + return (TLS_WRITE_AGAIN); + default: + tls_set_error(ctx, "ssl accept failure (%i)", + ssl_err); + goto err; + } + } + + return (0); + +err: + return (-1); +} diff --git a/lib/libtls/tls_util.c b/lib/libtls/tls_util.c new file mode 100644 index 00000000000..2adfb674b8f --- /dev/null +++ b/lib/libtls/tls_util.c @@ -0,0 +1,81 @@ +/* $OpenBSD: tls_util.c,v 1.1 2014/10/31 13:46:17 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 <stdlib.h> + +#include "tls_internal.h" + +/* + * 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); +} diff --git a/lib/libtls/tls_verify.c b/lib/libtls/tls_verify.c new file mode 100644 index 00000000000..fa0010922fc --- /dev/null +++ b/lib/libtls/tls_verify.c @@ -0,0 +1,225 @@ +/* $OpenBSD: tls_verify.c,v 1.1 2014/10/31 13:46:17 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 <sys/socket.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <string.h> + +#include <openssl/x509v3.h> + +#include "tls_internal.h" + +int tls_match_hostname(const char *cert_hostname, const char *hostname); +int tls_check_subject_altname(X509 *cert, const char *host); +int tls_check_common_name(X509 *cert, const char *host); + +int +tls_match_hostname(const char *cert_hostname, const char *hostname) +{ + const char *cert_domain, *domain, *next_dot; + + if (strcasecmp(cert_hostname, hostname) == 0) + return 0; + + /* Wildcard match? */ + if (cert_hostname[0] == '*') { + /* + * Valid wildcards: + * - "*.domain.tld" + * - "*.sub.domain.tld" + * - etc. + * Reject "*.tld". + * No attempt to prevent the use of eg. "*.co.uk". + */ + cert_domain = &cert_hostname[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(hostname, '.'); + + /* No wildcard match against a hostname with no domain part. */ + if (domain == NULL || strlen(domain) == 1) + return -1; + + if (strcasecmp(cert_domain, domain) == 0) + return 0; + } + + return -1; +} + +int +tls_check_subject_altname(X509 *cert, const char *host) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union { struct in_addr ip4; struct in6_addr ip6; } 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, host, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 4; + } else if (inet_pton(AF_INET6, host, &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) { + unsigned char *data; + int format; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_data(altname->d.dNSName); + + if (ASN1_STRING_length(altname->d.dNSName) != + (int)strlen(data)) { + fprintf(stdout, "%s: NUL byte in " + "subjectAltName, probably a " + "malicious certificate.\n", + getprogname()); + rv = -2; + break; + } + + if (tls_match_hostname(data, host) == 0) { + rv = 0; + break; + } + } else + fprintf(stdout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); + + } 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 == addrlen && + memcmp(data, &addrbuf, addrlen) == 0) { + rv = 0; + break; + } + } + } + + sk_GENERAL_NAME_free(altname_stack); + return rv; +} + +int +tls_check_common_name(X509 *cert, const char *host) +{ + X509_NAME *name; + char *common_name = NULL; + int common_name_len; + int rv = -1; + union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; + + name = X509_get_subject_name(cert); + if (name == NULL) + goto out; + + common_name_len = X509_NAME_get_text_by_NID(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(name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len != (int)strlen(common_name)) { + fprintf(stdout, "%s: NUL byte in Common Name field, " + "probably a malicious certificate.\n", getprogname()); + rv = -2; + goto out; + } + + if (inet_pton(AF_INET, host, &addrbuf) == 1 || + inet_pton(AF_INET6, host, &addrbuf) == 1) { + /* + * We don't want to attempt wildcard matching against IP + * addresses, so perform a simple comparison here. + */ + if (strcmp(common_name, host) == 0) + rv = 0; + else + rv = -1; + goto out; + } + + if (tls_match_hostname(common_name, host) == 0) + rv = 0; +out: + free(common_name); + return rv; +} + +int +tls_check_hostname(X509 *cert, const char *host) +{ + int rv; + + rv = tls_check_subject_altname(cert, host); + if (rv == 0 || rv == -2) + return rv; + + return tls_check_common_name(cert, host); +} |