diff options
Diffstat (limited to 'tls_config.c')
-rw-r--r-- | tls_config.c | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/tls_config.c b/tls_config.c new file mode 100644 index 0000000..ed47170 --- /dev/null +++ b/tls_config.c @@ -0,0 +1,907 @@ +/* $OpenBSD: tls_config.c,v 1.58 2020/01/20 08:39:21 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/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> + +#include <tls.h> + +#include "tls_internal.h" + +static const char default_ca_file[] = TLS_DEFAULT_CA_FILE; + +const char * +tls_default_ca_cert_file(void) +{ + return default_ca_file; +} + +int +tls_config_load_file(struct tls_error *error, const char *filetype, + const char *filename, char **buf, size_t *len) +{ + struct stat st; + int fd = -1; + ssize_t n; + + free(*buf); + *buf = NULL; + *len = 0; + + if ((fd = open(filename, O_RDONLY)) == -1) { + tls_error_set(error, "failed to open %s file '%s'", + filetype, filename); + goto err; + } + if (fstat(fd, &st) != 0) { + tls_error_set(error, "failed to stat %s file '%s'", + filetype, filename); + goto err; + } + if (st.st_size < 0) + goto err; + *len = (size_t)st.st_size; + if ((*buf = malloc(*len)) == NULL) { + tls_error_set(error, "failed to allocate buffer for " + "%s file", filetype); + goto err; + } + n = read(fd, *buf, *len); + if (n < 0 || (size_t)n != *len) { + tls_error_set(error, "failed to read %s file '%s'", + filetype, filename); + goto err; + } + close(fd); + return 0; + + err: + if (fd != -1) + close(fd); + freezero(*buf, *len); + *buf = NULL; + *len = 0; + + return -1; +} + +struct tls_config * +tls_config_new_internal(void) +{ + struct tls_config *config; + unsigned char sid[TLS_MAX_SESSION_ID_LENGTH]; + + if ((config = calloc(1, sizeof(*config))) == NULL) + return (NULL); + + if (pthread_mutex_init(&config->mutex, NULL) != 0) + goto err; + + config->refcount = 1; + config->session_fd = -1; + + if ((config->keypair = tls_keypair_new()) == NULL) + goto err; + + /* + * Default configuration. + */ + if (tls_config_set_dheparams(config, "none") != 0) + goto err; + if (tls_config_set_ecdhecurves(config, "default") != 0) + goto err; + if (tls_config_set_ciphers(config, "secure") != 0) + goto err; + + if (tls_config_set_protocols(config, TLS_PROTOCOLS_DEFAULT) != 0) + goto err; + if (tls_config_set_verify_depth(config, 6) != 0) + goto err; + + /* + * Set session ID context to a random value. For the simple case + * of a single process server this is good enough. For multiprocess + * servers the session ID needs to be set by the caller. + */ + arc4random_buf(sid, sizeof(sid)); + if (tls_config_set_session_id(config, sid, sizeof(sid)) != 0) + goto err; + config->ticket_keyrev = arc4random(); + config->ticket_autorekey = 1; + + tls_config_prefer_ciphers_server(config); + + tls_config_verify(config); + + return (config); + + err: + tls_config_free(config); + return (NULL); +} + +struct tls_config * +tls_config_new(void) +{ + if (tls_init() == -1) + return (NULL); + + return tls_config_new_internal(); +} + +void +tls_config_free(struct tls_config *config) +{ + struct tls_keypair *kp, *nkp; + int refcount; + + if (config == NULL) + return; + + pthread_mutex_lock(&config->mutex); + refcount = --config->refcount; + pthread_mutex_unlock(&config->mutex); + + if (refcount > 0) + return; + + for (kp = config->keypair; kp != NULL; kp = nkp) { + nkp = kp->next; + tls_keypair_free(kp); + } + + free(config->error.msg); + + free(config->alpn); + free((char *)config->ca_mem); + free((char *)config->ca_path); + free((char *)config->ciphers); + free((char *)config->crl_mem); + free(config->ecdhecurves); + + free(config); +} + +static void +tls_config_keypair_add(struct tls_config *config, struct tls_keypair *keypair) +{ + struct tls_keypair *kp; + + kp = config->keypair; + while (kp->next != NULL) + kp = kp->next; + + kp->next = keypair; +} + +const char * +tls_config_error(struct tls_config *config) +{ + return config->error.msg; +} + +void +tls_config_clear_keys(struct tls_config *config) +{ + struct tls_keypair *kp; + + for (kp = config->keypair; kp != NULL; kp = kp->next) + tls_keypair_clear_key(kp); +} + +int +tls_config_parse_protocols(uint32_t *protocols, const char *protostr) +{ + uint32_t proto, protos = 0; + char *s, *p, *q; + int negate; + + if (protostr == NULL) { + *protocols = TLS_PROTOCOLS_DEFAULT; + return (0); + } + + if ((s = strdup(protostr)) == NULL) + return (-1); + + q = s; + while ((p = strsep(&q, ",:")) != NULL) { + while (*p == ' ' || *p == '\t') + p++; + + negate = 0; + if (*p == '!') { + negate = 1; + p++; + } + + if (negate && protos == 0) + protos = TLS_PROTOCOLS_ALL; + + proto = 0; + if (strcasecmp(p, "all") == 0 || + strcasecmp(p, "legacy") == 0) + proto = TLS_PROTOCOLS_ALL; + else if (strcasecmp(p, "default") == 0 || + strcasecmp(p, "secure") == 0) + proto = TLS_PROTOCOLS_DEFAULT; + if (strcasecmp(p, "tlsv1") == 0) + proto = TLS_PROTOCOL_TLSv1; + else if (strcasecmp(p, "tlsv1.0") == 0) + proto = TLS_PROTOCOL_TLSv1_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; + else if (strcasecmp(p, "tlsv1.3") == 0) + proto = TLS_PROTOCOL_TLSv1_3; + + if (proto == 0) { + free(s); + return (-1); + } + + if (negate) + protos &= ~proto; + else + protos |= proto; + } + + *protocols = protos; + + free(s); + + return (0); +} + +static int +tls_config_parse_alpn(struct tls_config *config, const char *alpn, + char **alpn_data, size_t *alpn_len) +{ + size_t buf_len, i, len; + char *buf = NULL; + char *s = NULL; + char *p, *q; + + free(*alpn_data); + *alpn_data = NULL; + *alpn_len = 0; + + if ((buf_len = strlen(alpn) + 1) > 65535) { + tls_config_set_errorx(config, "alpn too large"); + goto err; + } + + if ((buf = malloc(buf_len)) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + + if ((s = strdup(alpn)) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + + i = 0; + q = s; + while ((p = strsep(&q, ",")) != NULL) { + if ((len = strlen(p)) == 0) { + tls_config_set_errorx(config, + "alpn protocol with zero length"); + goto err; + } + if (len > 255) { + tls_config_set_errorx(config, + "alpn protocol too long"); + goto err; + } + buf[i++] = len & 0xff; + memcpy(&buf[i], p, len); + i += len; + } + + free(s); + + *alpn_data = buf; + *alpn_len = buf_len; + + return (0); + + err: + free(buf); + free(s); + + return (-1); +} + +int +tls_config_set_alpn(struct tls_config *config, const char *alpn) +{ + return tls_config_parse_alpn(config, alpn, &config->alpn, + &config->alpn_len); +} + +static int +tls_config_add_keypair_file_internal(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + struct tls_keypair *keypair; + + if ((keypair = tls_keypair_new()) == NULL) + return (-1); + if (tls_keypair_set_cert_file(keypair, &config->error, cert_file) != 0) + goto err; + if (tls_keypair_set_key_file(keypair, &config->error, key_file) != 0) + goto err; + if (ocsp_file != NULL && + tls_keypair_set_ocsp_staple_file(keypair, &config->error, + ocsp_file) != 0) + goto err; + + tls_config_keypair_add(config, keypair); + + return (0); + + err: + tls_keypair_free(keypair); + return (-1); +} + +static int +tls_config_add_keypair_mem_internal(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + struct tls_keypair *keypair; + + if ((keypair = tls_keypair_new()) == NULL) + return (-1); + if (tls_keypair_set_cert_mem(keypair, &config->error, cert, cert_len) != 0) + goto err; + if (tls_keypair_set_key_mem(keypair, &config->error, key, key_len) != 0) + goto err; + if (staple != NULL && + tls_keypair_set_ocsp_staple_mem(keypair, &config->error, staple, + staple_len) != 0) + goto err; + + tls_config_keypair_add(config, keypair); + + return (0); + + err: + tls_keypair_free(keypair); + return (-1); +} + +int +tls_config_add_keypair_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len) +{ + return tls_config_add_keypair_mem_internal(config, cert, cert_len, key, + key_len, NULL, 0); +} + +int +tls_config_add_keypair_file(struct tls_config *config, + const char *cert_file, const char *key_file) +{ + return tls_config_add_keypair_file_internal(config, cert_file, + key_file, NULL); +} + +int +tls_config_add_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, const uint8_t *staple, + size_t staple_len) +{ + return tls_config_add_keypair_mem_internal(config, cert, cert_len, key, + key_len, staple, staple_len); +} + +int +tls_config_add_keypair_ocsp_file(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + return tls_config_add_keypair_file_internal(config, cert_file, + key_file, ocsp_file); +} + +int +tls_config_set_ca_file(struct tls_config *config, const char *ca_file) +{ + return tls_config_load_file(&config->error, "CA", ca_file, + &config->ca_mem, &config->ca_len); +} + +int +tls_config_set_ca_path(struct tls_config *config, const char *ca_path) +{ + return tls_set_string(&config->ca_path, ca_path); +} + +int +tls_config_set_ca_mem(struct tls_config *config, const uint8_t *ca, size_t len) +{ + return tls_set_mem(&config->ca_mem, &config->ca_len, ca, len); +} + +int +tls_config_set_cert_file(struct tls_config *config, const char *cert_file) +{ + return tls_keypair_set_cert_file(config->keypair, &config->error, + cert_file); +} + +int +tls_config_set_cert_mem(struct tls_config *config, const uint8_t *cert, + size_t len) +{ + return tls_keypair_set_cert_mem(config->keypair, &config->error, + cert, len); +} + +int +tls_config_set_ciphers(struct tls_config *config, const char *ciphers) +{ + SSL_CTX *ssl_ctx = NULL; + + if (ciphers == NULL || + strcasecmp(ciphers, "default") == 0 || + strcasecmp(ciphers, "secure") == 0) + ciphers = TLS_CIPHERS_DEFAULT; + else if (strcasecmp(ciphers, "compat") == 0) + ciphers = TLS_CIPHERS_COMPAT; + else if (strcasecmp(ciphers, "legacy") == 0) + ciphers = TLS_CIPHERS_LEGACY; + else if (strcasecmp(ciphers, "all") == 0 || + strcasecmp(ciphers, "insecure") == 0) + ciphers = TLS_CIPHERS_ALL; + + if ((ssl_ctx = SSL_CTX_new(SSLv23_method())) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers) != 1) { + tls_config_set_errorx(config, "no ciphers for '%s'", ciphers); + goto err; + } + + SSL_CTX_free(ssl_ctx); + return tls_set_string(&config->ciphers, ciphers); + + err: + SSL_CTX_free(ssl_ctx); + return -1; +} + +int +tls_config_set_crl_file(struct tls_config *config, const char *crl_file) +{ + return tls_config_load_file(&config->error, "CRL", crl_file, + &config->crl_mem, &config->crl_len); +} + +int +tls_config_set_crl_mem(struct tls_config *config, const uint8_t *crl, + size_t len) +{ + return tls_set_mem(&config->crl_mem, &config->crl_len, crl, len); +} + +int +tls_config_set_dheparams(struct tls_config *config, const char *params) +{ + int keylen; + + if (params == NULL || strcasecmp(params, "none") == 0) + keylen = 0; + else if (strcasecmp(params, "auto") == 0) + keylen = -1; + else if (strcasecmp(params, "legacy") == 0) + keylen = 1024; + else { + tls_config_set_errorx(config, "invalid dhe param '%s'", params); + return (-1); + } + + config->dheparams = keylen; + + return (0); +} + +int +tls_config_set_ecdhecurve(struct tls_config *config, const char *curve) +{ + if (curve == NULL || + strcasecmp(curve, "none") == 0 || + strcasecmp(curve, "auto") == 0) { + curve = TLS_ECDHE_CURVES; + } else if (strchr(curve, ',') != NULL || strchr(curve, ':') != NULL) { + tls_config_set_errorx(config, "invalid ecdhe curve '%s'", + curve); + return (-1); + } + + return tls_config_set_ecdhecurves(config, curve); +} + +int +tls_config_set_ecdhecurves(struct tls_config *config, const char *curves) +{ + int *curves_list = NULL, *curves_new; + size_t curves_num = 0; + char *cs = NULL; + char *p, *q; + int rv = -1; + int nid; + + free(config->ecdhecurves); + config->ecdhecurves = NULL; + config->ecdhecurves_len = 0; + + if (curves == NULL || strcasecmp(curves, "default") == 0) + curves = TLS_ECDHE_CURVES; + + if ((cs = strdup(curves)) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + + q = cs; + while ((p = strsep(&q, ",:")) != NULL) { + while (*p == ' ' || *p == '\t') + p++; + + nid = OBJ_sn2nid(p); + if (nid == NID_undef) + nid = OBJ_ln2nid(p); + if (nid == NID_undef) + nid = EC_curve_nist2nid(p); + if (nid == NID_undef) { + tls_config_set_errorx(config, + "invalid ecdhe curve '%s'", p); + goto err; + } + + if ((curves_new = reallocarray(curves_list, curves_num + 1, + sizeof(int))) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + curves_list = curves_new; + curves_list[curves_num] = nid; + curves_num++; + } + + config->ecdhecurves = curves_list; + config->ecdhecurves_len = curves_num; + curves_list = NULL; + + rv = 0; + + err: + free(cs); + free(curves_list); + + return (rv); +} + +int +tls_config_set_key_file(struct tls_config *config, const char *key_file) +{ + return tls_keypair_set_key_file(config->keypair, &config->error, + key_file); +} + +int +tls_config_set_key_mem(struct tls_config *config, const uint8_t *key, + size_t len) +{ + return tls_keypair_set_key_mem(config->keypair, &config->error, + key, len); +} + +static int +tls_config_set_keypair_file_internal(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + if (tls_config_set_cert_file(config, cert_file) != 0) + return (-1); + if (tls_config_set_key_file(config, key_file) != 0) + return (-1); + if (ocsp_file != NULL && + tls_config_set_ocsp_staple_file(config, ocsp_file) != 0) + return (-1); + + return (0); +} + +static int +tls_config_set_keypair_mem_internal(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + if (tls_config_set_cert_mem(config, cert, cert_len) != 0) + return (-1); + if (tls_config_set_key_mem(config, key, key_len) != 0) + return (-1); + if ((staple != NULL) && + (tls_config_set_ocsp_staple_mem(config, staple, staple_len) != 0)) + return (-1); + + return (0); +} + +int +tls_config_set_keypair_file(struct tls_config *config, + const char *cert_file, const char *key_file) +{ + return tls_config_set_keypair_file_internal(config, cert_file, key_file, + NULL); +} + +int +tls_config_set_keypair_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len) +{ + return tls_config_set_keypair_mem_internal(config, cert, cert_len, + key, key_len, NULL, 0); +} + +int +tls_config_set_keypair_ocsp_file(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + return tls_config_set_keypair_file_internal(config, cert_file, key_file, + ocsp_file); +} + +int +tls_config_set_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + return tls_config_set_keypair_mem_internal(config, cert, cert_len, + key, key_len, staple, staple_len); +} + + +int +tls_config_set_protocols(struct tls_config *config, uint32_t protocols) +{ + config->protocols = protocols; + + return (0); +} + +int +tls_config_set_session_fd(struct tls_config *config, int session_fd) +{ + struct stat sb; + mode_t mugo; + + if (session_fd == -1) { + config->session_fd = session_fd; + return (0); + } + + if (fstat(session_fd, &sb) == -1) { + tls_config_set_error(config, "failed to stat session file"); + return (-1); + } + if (!S_ISREG(sb.st_mode)) { + tls_config_set_errorx(config, + "session file is not a regular file"); + return (-1); + } + + if (sb.st_uid != getuid()) { + tls_config_set_errorx(config, "session file has incorrect " + "owner (uid %i != %i)", sb.st_uid, getuid()); + return (-1); + } + mugo = sb.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO); + if (mugo != (S_IRUSR|S_IWUSR)) { + tls_config_set_errorx(config, "session file has incorrect " + "permissions (%o != 600)", mugo); + return (-1); + } + + config->session_fd = session_fd; + + return (0); +} + +int +tls_config_set_verify_depth(struct tls_config *config, int verify_depth) +{ + config->verify_depth = verify_depth; + + return (0); +} + +void +tls_config_prefer_ciphers_client(struct tls_config *config) +{ + config->ciphers_server = 0; +} + +void +tls_config_prefer_ciphers_server(struct tls_config *config) +{ + config->ciphers_server = 1; +} + +void +tls_config_insecure_noverifycert(struct tls_config *config) +{ + config->verify_cert = 0; +} + +void +tls_config_insecure_noverifyname(struct tls_config *config) +{ + config->verify_name = 0; +} + +void +tls_config_insecure_noverifytime(struct tls_config *config) +{ + config->verify_time = 0; +} + +void +tls_config_verify(struct tls_config *config) +{ + config->verify_cert = 1; + config->verify_name = 1; + config->verify_time = 1; +} + +void +tls_config_ocsp_require_stapling(struct tls_config *config) +{ + config->ocsp_require_stapling = 1; +} + +void +tls_config_verify_client(struct tls_config *config) +{ + config->verify_client = 1; +} + +void +tls_config_verify_client_optional(struct tls_config *config) +{ + config->verify_client = 2; +} + +void +tls_config_skip_private_key_check(struct tls_config *config) +{ + config->skip_private_key_check = 1; +} + +int +tls_config_set_ocsp_staple_file(struct tls_config *config, const char *staple_file) +{ + return tls_keypair_set_ocsp_staple_file(config->keypair, &config->error, + staple_file); +} + +int +tls_config_set_ocsp_staple_mem(struct tls_config *config, const uint8_t *staple, + size_t len) +{ + return tls_keypair_set_ocsp_staple_mem(config->keypair, &config->error, + staple, len); +} + +int +tls_config_set_session_id(struct tls_config *config, + const unsigned char *session_id, size_t len) +{ + if (len > TLS_MAX_SESSION_ID_LENGTH) { + tls_config_set_errorx(config, "session ID too large"); + return (-1); + } + memset(config->session_id, 0, sizeof(config->session_id)); + memcpy(config->session_id, session_id, len); + return (0); +} + +int +tls_config_set_session_lifetime(struct tls_config *config, int lifetime) +{ + if (lifetime > TLS_MAX_SESSION_TIMEOUT) { + tls_config_set_errorx(config, "session lifetime too large"); + return (-1); + } + if (lifetime != 0 && lifetime < TLS_MIN_SESSION_TIMEOUT) { + tls_config_set_errorx(config, "session lifetime too small"); + return (-1); + } + + config->session_lifetime = lifetime; + return (0); +} + +int +tls_config_add_ticket_key(struct tls_config *config, uint32_t keyrev, + unsigned char *key, size_t keylen) +{ + struct tls_ticket_key newkey; + int i; + + if (TLS_TICKET_KEY_SIZE != keylen || + sizeof(newkey.aes_key) + sizeof(newkey.hmac_key) > keylen) { + tls_config_set_errorx(config, + "wrong amount of ticket key data"); + return (-1); + } + + keyrev = htonl(keyrev); + memset(&newkey, 0, sizeof(newkey)); + memcpy(newkey.key_name, &keyrev, sizeof(keyrev)); + memcpy(newkey.aes_key, key, sizeof(newkey.aes_key)); + memcpy(newkey.hmac_key, key + sizeof(newkey.aes_key), + sizeof(newkey.hmac_key)); + newkey.time = time(NULL); + + for (i = 0; i < TLS_NUM_TICKETS; i++) { + struct tls_ticket_key *tk = &config->ticket_keys[i]; + if (memcmp(newkey.key_name, tk->key_name, + sizeof(tk->key_name)) != 0) + continue; + + /* allow re-entry of most recent key */ + if (i == 0 && memcmp(newkey.aes_key, tk->aes_key, + sizeof(tk->aes_key)) == 0 && memcmp(newkey.hmac_key, + tk->hmac_key, sizeof(tk->hmac_key)) == 0) + return (0); + tls_config_set_errorx(config, "ticket key already present"); + return (-1); + } + + memmove(&config->ticket_keys[1], &config->ticket_keys[0], + sizeof(config->ticket_keys) - sizeof(config->ticket_keys[0])); + config->ticket_keys[0] = newkey; + + config->ticket_autorekey = 0; + + return (0); +} + +int +tls_config_ticket_autorekey(struct tls_config *config) +{ + unsigned char key[TLS_TICKET_KEY_SIZE]; + int rv; + + arc4random_buf(key, sizeof(key)); + rv = tls_config_add_ticket_key(config, config->ticket_keyrev++, key, + sizeof(key)); + config->ticket_autorekey = 1; + return (rv); +} |