diff options
36 files changed, 6715 insertions, 13 deletions
@@ -36,11 +36,11 @@ GEMEXP_SRCS = ge.c config.c crypto.c dirs.c fcgi.c iri.c log.c mime.c \ GEMEXP_OBJS = ${GEMEXP_SRCS:.c=.o} ${COBJS} -GG_SRCS = gg.c iri.c utf8.c +GG_SRCS = gg.c iri.c log.c utf8.c GG_OBJS = ${GG_SRCS:.c=.o} ${COBJS} -TITAN_SRCS = titan.c iri.c utf8.c +TITAN_SRCS = titan.c iri.c log.c utf8.c TITAN_OBJS = ${TITAN_SRCS:.c=.o} ${COBJS} SRCS = gmid.h log.h parse.y proc.h \ @@ -60,7 +60,9 @@ config.mk config.h: configure include config.mk clean: - rm -f *.[do] compat/*.[do] y.tab.c y.tab.h y.output gmid gemexp gg + rm -f gmid gemexp gg + rm -f *.[do] compat/*.[do] compat/libtls/*.[do] + rm -f y.tab.c y.tab.h y.output rm -f compile_flags.txt ${MAKE} -C regress clean diff --git a/compat/Makefile b/compat/Makefile index 65ec3ef..39fe642 100644 --- a/compat/Makefile +++ b/compat/Makefile @@ -1,9 +1,13 @@ DISTFILES = Makefile \ + arc4random.c \ + arc4random.h \ + chacha_private.h \ err.c \ explicit_bzero.c \ freezero.c \ getdtablecount.c \ getdtablesize.c \ + getentropy.c \ getprogname.c \ imsg-buffer.c \ imsg.c \ @@ -18,6 +22,7 @@ DISTFILES = Makefile \ strlcat.c \ strlcpy.c \ strtonum.c \ + timingsafe_memcmp.c \ tree.h \ vasprintf.c \ vis.c @@ -30,6 +35,7 @@ dist: ${DISTFILES} ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ mkdir -p ${DESTDIR}/vis ${INSTALL} -m 0644 vis/vis.h ${DESTDIR}/vis + ${MAKE} -C libtls DESTDIR=${DESTDIR}/libtls dist .PHONY: all dist include ../config.mk diff --git a/compat/arc4random.c b/compat/arc4random.c new file mode 100644 index 0000000..8ee61b4 --- /dev/null +++ b/compat/arc4random.c @@ -0,0 +1,252 @@ +/* $OpenBSD: arc4random.c,v 1.58 2022/07/31 13:41:45 tb Exp $ */ + +/* + * Copyright (c) 1996, David Mazieres <dm@uun.org> + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * Copyright (c) 2013, Markus Friedl <markus@openbsd.org> + * Copyright (c) 2014, Theo de Raadt <deraadt@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. + */ + +/* + * ChaCha based random number generator for OpenBSD. + */ + +/* OPENBSD ORIGINAL: lib/libc/crypt/arc4random.c */ + +#include "../config.h" + +#include <sys/types.h> + +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/time.h> + +#ifndef HAVE_ARC4RANDOM + +/* + * Always use the getentropy implementation from bsd-getentropy.c, which + * will call a native getentropy if available then fall back as required. + * We use a different name so that OpenSSL cannot call the wrong getentropy. + */ +int _ssh_compat_getentropy(void *, size_t); +#ifdef getentropy +# undef getentropy +#endif +#define getentropy(x, y) (_ssh_compat_getentropy((x), (y))) + +#include "log.h" + +#define KEYSTREAM_ONLY +#include "chacha_private.h" + +#define minimum(a, b) ((a) < (b) ? (a) : (b)) + +#if defined(__GNUC__) || defined(_MSC_VER) +#define inline __inline +#else /* __GNUC__ || _MSC_VER */ +#define inline +#endif /* !__GNUC__ && !_MSC_VER */ + +#define KEYSZ 32 +#define IVSZ 8 +#define BLOCKSZ 64 +#define RSBUFSZ (16*BLOCKSZ) + +#define REKEY_BASE (1024*1024) /* NB. should be a power of 2 */ + +/* Marked MAP_INHERIT_ZERO, so zero'd out in fork children. */ +static struct _rs { + size_t rs_have; /* valid bytes at end of rs_buf */ + size_t rs_count; /* bytes till reseed */ +} *rs; + +/* Maybe be preserved in fork children, if _rs_allocate() decides. */ +static struct _rsx { + chacha_ctx rs_chacha; /* chacha context for random keystream */ + u_char rs_buf[RSBUFSZ]; /* keystream blocks */ +} *rsx; + +static inline int _rs_allocate(struct _rs **, struct _rsx **); +static inline void _rs_forkdetect(void); +#include "arc4random.h" + +static inline void _rs_rekey(u_char *dat, size_t datlen); + +static inline void +_rs_init(u_char *buf, size_t n) +{ + if (n < KEYSZ + IVSZ) + return; + + if (rs == NULL) { + if (_rs_allocate(&rs, &rsx) == -1) + _exit(1); + } + + chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8); + chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ); +} + +static void +_rs_stir(void) +{ + u_char rnd[KEYSZ + IVSZ]; + uint32_t rekey_fuzz = 0; + + if (getentropy(rnd, sizeof rnd) == -1) + _getentropy_fail(); + + if (!rs) + _rs_init(rnd, sizeof(rnd)); + else + _rs_rekey(rnd, sizeof(rnd)); + explicit_bzero(rnd, sizeof(rnd)); /* discard source seed */ + + /* invalidate rs_buf */ + rs->rs_have = 0; + memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); + + /* rekey interval should not be predictable */ + chacha_encrypt_bytes(&rsx->rs_chacha, (uint8_t *)&rekey_fuzz, + (uint8_t *)&rekey_fuzz, sizeof(rekey_fuzz)); + rs->rs_count = REKEY_BASE + (rekey_fuzz % REKEY_BASE); +} + +static inline void +_rs_stir_if_needed(size_t len) +{ + _rs_forkdetect(); + if (!rs || rs->rs_count <= len) + _rs_stir(); + if (rs->rs_count <= len) + rs->rs_count = 0; + else + rs->rs_count -= len; +} + +static inline void +_rs_rekey(u_char *dat, size_t datlen) +{ +#ifndef KEYSTREAM_ONLY + memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf)); +#endif + /* fill rs_buf with the keystream */ + chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf, + rsx->rs_buf, sizeof(rsx->rs_buf)); + /* mix in optional user provided data */ + if (dat) { + size_t i, m; + + m = minimum(datlen, KEYSZ + IVSZ); + for (i = 0; i < m; i++) + rsx->rs_buf[i] ^= dat[i]; + } + /* immediately reinit for backtracking resistance */ + _rs_init(rsx->rs_buf, KEYSZ + IVSZ); + memset(rsx->rs_buf, 0, KEYSZ + IVSZ); + rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ; +} + +static inline void +_rs_random_buf(void *_buf, size_t n) +{ + u_char *buf = (u_char *)_buf; + u_char *keystream; + size_t m; + + _rs_stir_if_needed(n); + while (n > 0) { + if (rs->rs_have > 0) { + m = minimum(n, rs->rs_have); + keystream = rsx->rs_buf + sizeof(rsx->rs_buf) + - rs->rs_have; + memcpy(buf, keystream, m); + memset(keystream, 0, m); + buf += m; + n -= m; + rs->rs_have -= m; + } + if (rs->rs_have == 0) + _rs_rekey(NULL, 0); + } +} + +static inline void +_rs_random_u32(uint32_t *val) +{ + u_char *keystream; + + _rs_stir_if_needed(sizeof(*val)); + if (rs->rs_have < sizeof(*val)) + _rs_rekey(NULL, 0); + keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have; + memcpy(val, keystream, sizeof(*val)); + memset(keystream, 0, sizeof(*val)); + rs->rs_have -= sizeof(*val); +} + +uint32_t +arc4random(void) +{ + uint32_t val; + + _ARC4_LOCK(); + _rs_random_u32(&val); + _ARC4_UNLOCK(); + return val; +} + +/* + * If we are providing arc4random, then we can provide a more efficient + * arc4random_buf(). + */ +# ifndef HAVE_ARC4RANDOM_BUF +void +arc4random_buf(void *buf, size_t n) +{ + _ARC4_LOCK(); + _rs_random_buf(buf, n); + _ARC4_UNLOCK(); +} +# endif /* !HAVE_ARC4RANDOM_BUF */ +#endif /* !HAVE_ARC4RANDOM */ + +/* arc4random_buf() that uses platform arc4random() */ +#if !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM) +void +arc4random_buf(void *_buf, size_t n) +{ + size_t i; + u_int32_t r = 0; + char *buf = (char *)_buf; + + for (i = 0; i < n; i++) { + if (i % 4 == 0) + r = arc4random(); + buf[i] = r & 0xff; + r >>= 8; + } + explicit_bzero(&r, sizeof(r)); +} +#endif /* !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM) */ + diff --git a/compat/arc4random.h b/compat/arc4random.h new file mode 100644 index 0000000..5af3a44 --- /dev/null +++ b/compat/arc4random.h @@ -0,0 +1,89 @@ +/* $OpenBSD: arc4random_linux.h,v 1.12 2019/07/11 10:37:28 inoguchi Exp $ */ + +/* + * Copyright (c) 1996, David Mazieres <dm@uun.org> + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * Copyright (c) 2013, Markus Friedl <markus@openbsd.org> + * Copyright (c) 2014, Theo de Raadt <deraadt@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. + */ + +/* + * Stub functions for portability. From LibreSSL with some adaptations. + */ + +#include <sys/mman.h> + +#include <signal.h> + +/* OpenSSH isn't multithreaded */ +#define _ARC4_LOCK() +#define _ARC4_UNLOCK() +#define _ARC4_ATFORK(f) + +static inline void +_getentropy_fail(void) +{ + fatal("getentropy failed"); +} + +static volatile sig_atomic_t _rs_forked; + +static inline void +_rs_forkhandler(void) +{ + _rs_forked = 1; +} + +static inline void +_rs_forkdetect(void) +{ + static pid_t _rs_pid = 0; + pid_t pid = getpid(); + + if (_rs_pid == 0 || _rs_pid == 1 || _rs_pid != pid || _rs_forked) { + _rs_pid = pid; + _rs_forked = 0; + if (rs) + memset(rs, 0, sizeof(*rs)); + } +} + +static inline int +_rs_allocate(struct _rs **rsp, struct _rsx **rsxp) +{ +#if defined(MAP_ANON) && defined(MAP_PRIVATE) + if ((*rsp = mmap(NULL, sizeof(**rsp), PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) + return (-1); + + if ((*rsxp = mmap(NULL, sizeof(**rsxp), PROT_READ|PROT_WRITE, + MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { + munmap(*rsp, sizeof(**rsp)); + *rsp = NULL; + return (-1); + } +#else + if ((*rsp = calloc(1, sizeof(**rsp))) == NULL) + return (-1); + if ((*rsxp = calloc(1, sizeof(**rsxp))) == NULL) { + free(*rsp); + *rsp = NULL; + return (-1); + } +#endif + + _ARC4_ATFORK(_rs_forkhandler); + return (0); +} diff --git a/compat/chacha_private.h b/compat/chacha_private.h new file mode 100644 index 0000000..cdcb785 --- /dev/null +++ b/compat/chacha_private.h @@ -0,0 +1,224 @@ +/* OPENBSD ORIGINAL: lib/libc/crypt/chacha_private.h */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +/* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */ + +typedef unsigned char u8; +typedef unsigned int u32; + +typedef struct +{ + u32 input[16]; /* could be compressed */ +} chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((u8)(v) & U8C(0xFF)) +#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((u32)((p)[0]) ) | \ + ((u32)((p)[1]) << 8) | \ + ((u32)((p)[2]) << 16) | \ + ((u32)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +static void +chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +static void +chacha_ivsetup(chacha_ctx *x,const u8 *iv) +{ + x->input[12] = 0; + x->input[13] = 0; + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +static void +chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes) +{ + u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + u8 *ctarget = NULL; + u8 tmp[64]; + u_int i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + +#ifndef KEYSTREAM_ONLY + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); +#endif + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; +#ifndef KEYSTREAM_ONLY + m += 64; +#endif + } +} diff --git a/compat/getentropy.c b/compat/getentropy.c new file mode 100644 index 0000000..dc33884 --- /dev/null +++ b/compat/getentropy.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1996, David Mazieres <dm@uun.org> + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * Copyright (c) 2013, Markus Friedl <markus@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 "../config.h" + +#ifndef SSH_RANDOM_DEV +# define SSH_RANDOM_DEV "/dev/urandom" +#endif /* SSH_RANDOM_DEV */ + +#include <sys/types.h> +#ifdef HAVE_SYS_RANDOM_H +# include <sys/random.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef WITH_OPENSSL +#include <openssl/rand.h> +#include <openssl/err.h> +#endif + +#include "log.h" + +int _ssh_compat_getentropy(void *, size_t); + +static int +seed_from_prngd(unsigned char *buf, size_t bytes) +{ + return -1; +} + +int +_ssh_compat_getentropy(void *s, size_t len) +{ +#if defined(WITH_OPENSSL) && defined(OPENSSL_PRNG_ONLY) + if (RAND_bytes(s, len) <= 0) + fatal("Couldn't obtain random bytes (error 0x%lx)", + (unsigned long)ERR_get_error()); +#else + int fd, save_errno; + ssize_t r; + size_t o = 0; + +#ifdef WITH_OPENSSL + if (RAND_bytes(s, len) == 1) + return 0; +#endif +#ifdef HAVE_GETENTROPY + if ((r = getentropy(s, len)) == 0) + return 0; +#endif /* HAVE_GETENTROPY */ +#ifdef HAVE_GETRANDOM + if ((r = getrandom(s, len, 0)) > 0 && (size_t)r == len) + return 0; +#endif /* HAVE_GETRANDOM */ + + if ((fd = open(SSH_RANDOM_DEV, O_RDONLY)) == -1) { + save_errno = errno; + /* Try egd/prngd before giving up. */ + if (seed_from_prngd(s, len) == 0) + return 0; + fatal("Couldn't open %s: %s", SSH_RANDOM_DEV, + strerror(save_errno)); + } + while (o < len) { + r = read(fd, (u_char *)s + o, len - o); + if (r < 0) { + if (errno == EAGAIN || errno == EINTR || + errno == EWOULDBLOCK) + continue; + fatal("read %s: %s", SSH_RANDOM_DEV, strerror(errno)); + } + o += r; + } + close(fd); +#endif /* WITH_OPENSSL */ + return 0; +} diff --git a/compat/libtls/Makefile b/compat/libtls/Makefile new file mode 100644 index 0000000..4af6e93 --- /dev/null +++ b/compat/libtls/Makefile @@ -0,0 +1,27 @@ +DISTFILES = Makefile \ + asn.c \ + by_mem.c \ + openssl.c \ + tls.c \ + tls.h \ + tls_bio_cb.c \ + tls_client.c \ + tls_config.c \ + tls_conninfo.c \ + tls_internal.h \ + tls_keypair.c \ + tls_ocsp.c \ + tls_peer.c \ + tls_server.c \ + tls_util.c \ + tls_verify.c + +all: + false + +dist: ${DISTFILES} + mkdir -p ${DESTDIR}/ + ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/ + +.PHONY: all dist +include ../../config.mk diff --git a/compat/libtls/asn.c b/compat/libtls/asn.c new file mode 100644 index 0000000..4bc428e --- /dev/null +++ b/compat/libtls/asn.c @@ -0,0 +1,177 @@ +/* $OpenBSD: a_time_tm.c,v 1.15 2018/04/25 11:48:21 tb Exp $ */ +/* + * 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 "config.h" + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#define GENTIME_LENGTH 15 +#define UTCTIME_LENGTH 13 + +#define V_ASN1_UTCTIME 23 +#define V_ASN1_GENERALIZEDTIME 24 + +#ifndef HAVE_ASN1_TIME_TM_CMP +int +ASN1_time_tm_cmp(struct tm *tm1, struct tm *tm2) +{ + if (tm1->tm_year < tm2->tm_year) + return (-1); + if (tm1->tm_year > tm2->tm_year) + return (1); + if (tm1->tm_mon < tm2->tm_mon) + return (-1); + if (tm1->tm_mon > tm2->tm_mon) + return (1); + if (tm1->tm_mday < tm2->tm_mday) + return (-1); + if (tm1->tm_mday > tm2->tm_mday) + return (1); + if (tm1->tm_hour < tm2->tm_hour) + return (-1); + if (tm1->tm_hour > tm2->tm_hour) + return (1); + if (tm1->tm_min < tm2->tm_min) + return (-1); + if (tm1->tm_min > tm2->tm_min) + return (1); + if (tm1->tm_sec < tm2->tm_sec) + return (-1); + if (tm1->tm_sec > tm2->tm_sec) + return (1); + return 0; +} +#endif + +#ifndef HAVE_ASN1_TIME_TM_CLAMP_NOTAFTER +int +ASN1_time_tm_clamp_notafter(struct tm *tm) +{ +#ifdef SMALL_TIME_T + struct tm broken_os_epoch_tm; + time_t broken_os_epoch_time = INT_MAX; + + if (gmtime_r(&broken_os_epoch_time, &broken_os_epoch_tm) == NULL) + return 0; + + if (ASN1_time_tm_cmp(tm, &broken_os_epoch_tm) == 1) + memcpy(tm, &broken_os_epoch_tm, sizeof(*tm)); +#endif + return 1; +} +#endif + +/* + * Parse an RFC 5280 format ASN.1 time string. + * + * mode must be: + * 0 if we expect to parse a time as specified in RFC 5280 for an X509 object. + * V_ASN1_UTCTIME if we wish to parse an RFC5280 format UTC time. + * V_ASN1_GENERALIZEDTIME if we wish to parse an RFC5280 format Generalized time. + * + * Returns: + * -1 if the string was invalid. + * V_ASN1_UTCTIME if the string validated as a UTC time string. + * V_ASN1_GENERALIZEDTIME if the string validated as a Generalized time string. + * + * Fills in *tm with the corresponding time if tm is non NULL. + */ +#ifndef HAVE_ASN1_TIME_PARSE +#define ATOI2(ar) ((ar) += 2, ((ar)[-2] - '0') * 10 + ((ar)[-1] - '0')) +int +ASN1_time_parse(const char *bytes, size_t len, struct tm *tm, int mode) +{ + size_t i; + int type = 0; + struct tm ltm; + struct tm *lt; + const char *p; + + if (bytes == NULL) + return (-1); + + /* Constrain to valid lengths. */ + if (len != UTCTIME_LENGTH && len != GENTIME_LENGTH) + return (-1); + + lt = tm; + if (lt == NULL) { + memset(<m, 0, sizeof(ltm)); + lt = <m; + } + + /* Timezone is required and must be GMT (Zulu). */ + if (bytes[len - 1] != 'Z') + return (-1); + + /* Make sure everything else is digits. */ + for (i = 0; i < len - 1; i++) { + if (isdigit((unsigned char)bytes[i])) + continue; + return (-1); + } + + /* + * Validate and convert the time + */ + p = bytes; + switch (len) { + case GENTIME_LENGTH: + if (mode == V_ASN1_UTCTIME) + return (-1); + lt->tm_year = (ATOI2(p) * 100) - 1900; /* cc */ + type = V_ASN1_GENERALIZEDTIME; + /* FALLTHROUGH */ + case UTCTIME_LENGTH: + if (type == 0) { + if (mode == V_ASN1_GENERALIZEDTIME) + return (-1); + type = V_ASN1_UTCTIME; + } + lt->tm_year += ATOI2(p); /* yy */ + if (type == V_ASN1_UTCTIME) { + if (lt->tm_year < 50) + lt->tm_year += 100; + } + lt->tm_mon = ATOI2(p) - 1; /* mm */ + if (lt->tm_mon < 0 || lt->tm_mon > 11) + return (-1); + lt->tm_mday = ATOI2(p); /* dd */ + if (lt->tm_mday < 1 || lt->tm_mday > 31) + return (-1); + lt->tm_hour = ATOI2(p); /* HH */ + if (lt->tm_hour < 0 || lt->tm_hour > 23) + return (-1); + lt->tm_min = ATOI2(p); /* MM */ + if (lt->tm_min < 0 || lt->tm_min > 59) + return (-1); + lt->tm_sec = ATOI2(p); /* SS */ + /* Leap second 60 is not accepted. Reconsider later? */ + if (lt->tm_sec < 0 || lt->tm_sec > 59) + return (-1); + break; + default: + return (-1); + } + + return (type); +} +#endif diff --git a/compat/libtls/by_mem.c b/compat/libtls/by_mem.c new file mode 100644 index 0000000..e290a0c --- /dev/null +++ b/compat/libtls/by_mem.c @@ -0,0 +1,141 @@ +/* $OpenBSD: by_mem.c,v 1.4 2017/01/29 17:49:23 beck Exp $ */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#include "config.h" + +#ifndef HAVE_X509_LOOKUP_MEM + +#include <sys/uio.h> +#include <errno.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> + +#include <openssl/buffer.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/lhash.h> +#include <openssl/x509.h> + +#define X509error(r) ERR_PUT_error(ERR_LIB_X509,(0xfff),(r),__FILE__,__LINE__) + +#define X509_L_MEM 3 + +static int by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); + +static X509_LOOKUP_METHOD *x509_mem_lookup; + +X509_LOOKUP_METHOD * +X509_LOOKUP_mem(void) +{ + if (x509_mem_lookup == NULL) { + x509_mem_lookup = X509_LOOKUP_meth_new("Load cert from memory"); + X509_LOOKUP_meth_set_ctrl(x509_mem_lookup, by_mem_ctrl); + } + return (x509_mem_lookup); +} + +static int +by_mem_ctrl(X509_LOOKUP *lu, int cmd, const char *buf, + long type, char **ret) +{ + STACK_OF(X509_INFO) *inf = NULL; + const struct iovec *iov; + X509_INFO *itmp; + BIO *in = NULL; + int i, count = 0, ok = 0; + + iov = (const struct iovec *)buf; + + if (!(cmd == X509_L_MEM && type == X509_FILETYPE_PEM)) + goto done; + + if ((in = BIO_new_mem_buf(iov->iov_base, iov->iov_len)) == NULL) + goto done; + + if ((inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL)) == NULL) + goto done; + + for (i = 0; i < sk_X509_INFO_num(inf); i++) { + itmp = sk_X509_INFO_value(inf, i); + if (itmp->x509) { + ok = X509_STORE_add_cert(X509_LOOKUP_get_store(lu), itmp->x509); + if (!ok) + goto done; + count++; + } + if (itmp->crl) { + ok = X509_STORE_add_crl(X509_LOOKUP_get_store(lu), itmp->crl); + if (!ok) + goto done; + count++; + } + } + + ok = count != 0; + done: + if (count == 0) + X509error(ERR_R_PEM_LIB); + if (inf != NULL) + sk_X509_INFO_pop_free(inf, X509_INFO_free); + if (in != NULL) + BIO_free(in); + return (ok); +} + +#endif /* HAVE_X509_LOOKUP_MEM */ diff --git a/compat/libtls/openssl.c b/compat/libtls/openssl.c new file mode 100644 index 0000000..3528887 --- /dev/null +++ b/compat/libtls/openssl.c @@ -0,0 +1,212 @@ +#include "config.h" + +#include <sys/types.h> +#include <sys/uio.h> + +#include <limits.h> +#include <unistd.h> +#include <stdio.h> + +#include <openssl/err.h> +#include <openssl/bio.h> +#include <openssl/objects.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/x509_vfy.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> + +#include "log.h" + +#ifndef HAVE_SSL_CTX_LOAD_VERIFY_MEM + +#define X509_LOOKUP_add_mem(x,iov,type) \ + X509_LOOKUP_ctrl((x),X509_L_MEM,(const char *)(iov),\ + (long)(type),NULL) + +#define X509_L_MEM 3 + +#define SSL_ECDH_CURVE "prime256v1" + +X509_LOOKUP_METHOD * +X509_LOOKUP_mem(void); + +static int +X509_STORE_load_mem(X509_STORE *ctx, void *buf, int len) +{ + X509_LOOKUP *lookup; + struct iovec iov; + lookup = X509_STORE_add_lookup(ctx, X509_LOOKUP_mem()); + if (lookup == NULL) + return (0); + iov.iov_base = buf; + iov.iov_len = len; + if (X509_LOOKUP_add_mem(lookup, &iov, X509_FILETYPE_PEM) != 1) + return (0); + return (1); +} + +int +SSL_CTX_load_verify_mem(SSL_CTX *ctx, void *buf, int len) +{ + return (X509_STORE_load_mem(SSL_CTX_get_cert_store(ctx), buf, len)); +} + +#endif /* HAVE_SSL_CTX_LOAD_VERIFY_MEM */ + +#ifndef HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +/* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + +/* + * Read a bio that contains our certificate in "PEM" format, + * possibly followed by a sequence of CA certificates that should be + * sent to the peer in the Certificate message. + */ +static int +ssl_ctx_use_certificate_chain_bio(SSL_CTX *ctx, BIO *in) +{ + int ret = 0; + X509 *x = NULL; + + ERR_clear_error(); /* clear error stack for SSL_CTX_use_certificate() */ + + x = PEM_read_bio_X509_AUX(in, NULL, SSL_CTX_get_default_passwd_cb(ctx), + SSL_CTX_get_default_passwd_cb_userdata(ctx)); + if (x == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB); + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if (ERR_peek_error() != 0) + ret = 0; + /* Key/certificate mismatch doesn't imply ret==0 ... */ + if (ret) { + /* + * If we could set up our certificate, now proceed to + * the CA certificates. + */ + X509 *ca; + STACK_OF(X509) *chain; + int r; + unsigned long err; + + SSL_CTX_get_extra_chain_certs_only(ctx, &chain); + if (chain != NULL) { + sk_X509_pop_free(chain, X509_free); + SSL_CTX_clear_extra_chain_certs(ctx); + } + + while ((ca = PEM_read_bio_X509(in, NULL, + SSL_CTX_get_default_passwd_cb(ctx), + SSL_CTX_get_default_passwd_cb_userdata(ctx))) != NULL) { + r = SSL_CTX_add_extra_chain_cert(ctx, ca); + if (!r) { + X509_free(ca); + ret = 0; + goto end; + } + /* + * Note that we must not free r if it was successfully + * added to the chain (while we must free the main + * certificate, since its reference count is increased + * by SSL_CTX_use_certificate). + */ + } + + /* When the while loop ends, it's usually just EOF. */ + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + ret = 0; /* some real error */ + } + +end: + if (x != NULL) + X509_free(x); + return (ret); +} + +int +SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len) +{ + BIO *in; + int ret = 0; + + in = BIO_new_mem_buf(buf, len); + if (in == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_BUF_LIB); + goto end; + } + + ret = ssl_ctx_use_certificate_chain_bio(ctx, in); + +end: + BIO_free(in); + return (ret); +} + +#endif /* HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM */ diff --git a/compat/libtls/tls.c b/compat/libtls/tls.c new file mode 100644 index 0000000..0daabf5 --- /dev/null +++ b/compat/libtls/tls.c @@ -0,0 +1,903 @@ +/* $OpenBSD: tls.c,v 1.97 2023/06/18 11:43:03 op Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/socket.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/safestack.h> +#include <openssl/ssl.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +static struct tls_config *tls_config_default; + +static int tls_init_rv = -1; + +static void +tls_do_init(void) +{ + OPENSSL_init_ssl(OPENSSL_INIT_NO_LOAD_CONFIG, NULL); + + if (BIO_sock_init() != 1) + return; + + if ((tls_config_default = tls_config_new_internal()) == NULL) + return; + + tls_config_default->refcount++; + + tls_init_rv = 0; +} + +int +tls_init(void) +{ + if (tls_init_rv == -1) + tls_do_init(); + return tls_init_rv; +} + +const char * +tls_error(struct tls *ctx) +{ + return ctx->error.msg; +} + +void +tls_error_clear(struct tls_error *error) +{ + free(error->msg); + error->msg = NULL; + error->num = 0; + error->tls = 0; +} + +static int +tls_error_vset(struct tls_error *error, int errnum, const char *fmt, va_list ap) +{ + char *errmsg = NULL; + int rv = -1; + + tls_error_clear(error); + + error->num = errnum; + error->tls = 1; + + if (vasprintf(&errmsg, fmt, ap) == -1) { + errmsg = NULL; + goto err; + } + + if (errnum == -1) { + error->msg = errmsg; + return (0); + } + + if (asprintf(&error->msg, "%s: %s", errmsg, strerror(errnum)) == -1) { + error->msg = NULL; + goto err; + } + rv = 0; + + err: + free(errmsg); + + return (rv); +} + +int +tls_error_set(struct tls_error *error, const char *fmt, ...) +{ + va_list ap; + int errnum, rv; + + errnum = errno; + + va_start(ap, fmt); + rv = tls_error_vset(error, errnum, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_error_setx(struct tls_error *error, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = tls_error_vset(error, -1, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_config_set_error(struct tls_config *config, const char *fmt, ...) +{ + va_list ap; + int errnum, rv; + + errnum = errno; + + va_start(ap, fmt); + rv = tls_error_vset(&config->error, errnum, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_config_set_errorx(struct tls_config *config, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = tls_error_vset(&config->error, -1, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_set_error(struct tls *ctx, const char *fmt, ...) +{ + va_list ap; + int errnum, rv; + + errnum = errno; + + va_start(ap, fmt); + rv = tls_error_vset(&ctx->error, errnum, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_set_errorx(struct tls *ctx, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = tls_error_vset(&ctx->error, -1, fmt, ap); + va_end(ap); + + return (rv); +} + +int +tls_set_ssl_errorx(struct tls *ctx, const char *fmt, ...) +{ + va_list ap; + int rv; + + /* Only set an error if a more specific one does not already exist. */ + if (ctx->error.tls != 0) + return (0); + + va_start(ap, fmt); + rv = tls_error_vset(&ctx->error, -1, fmt, ap); + va_end(ap); + + return (rv); +} + +struct tls_sni_ctx * +tls_sni_ctx_new(void) +{ + return (calloc(1, sizeof(struct tls_sni_ctx))); +} + +void +tls_sni_ctx_free(struct tls_sni_ctx *sni_ctx) +{ + if (sni_ctx == NULL) + return; + + SSL_CTX_free(sni_ctx->ssl_ctx); + X509_free(sni_ctx->ssl_cert); + + free(sni_ctx); +} + +struct tls * +tls_new(void) +{ + struct tls *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + tls_reset(ctx); + + if (tls_configure(ctx, tls_config_default) == -1) { + free(ctx); + return NULL; + } + + return (ctx); +} + +int +tls_configure(struct tls *ctx, struct tls_config *config) +{ + if (config == NULL) + config = tls_config_default; + + config->refcount++; + + tls_config_free(ctx->config); + + ctx->config = config; + ctx->keypair = config->keypair; + + if ((ctx->flags & TLS_SERVER) != 0) + return (tls_configure_server(ctx)); + + return (0); +} + +int +tls_cert_hash(X509 *cert, char **hash) +{ + char d[EVP_MAX_MD_SIZE], *dhex = NULL; + int dlen, rv = -1; + + free(*hash); + *hash = NULL; + + if (X509_digest(cert, EVP_sha256(), d, &dlen) != 1) + goto err; + + if (tls_hex_string(d, dlen, &dhex, NULL) != 0) + goto err; + + if (asprintf(hash, "SHA256:%s", dhex) == -1) { + *hash = NULL; + goto err; + } + + rv = 0; + err: + free(dhex); + + return (rv); +} + +int +tls_cert_pubkey_hash(X509 *cert, char **hash) +{ + char d[EVP_MAX_MD_SIZE], *dhex = NULL; + int dlen, rv = -1; + + free(*hash); + *hash = NULL; + + if (X509_pubkey_digest(cert, EVP_sha256(), d, &dlen) != 1) + goto err; + + if (tls_hex_string(d, dlen, &dhex, NULL) != 0) + goto err; + + if (asprintf(hash, "SHA256:%s", dhex) == -1) { + *hash = NULL; + goto err; + } + + rv = 0; + + err: + free(dhex); + + return (rv); +} + +static int +tls_keypair_to_pkey(struct tls *ctx, struct tls_keypair *keypair, EVP_PKEY **pkey) +{ + BIO *bio = NULL; + X509 *x509 = NULL; + char *mem; + size_t len; + int ret = -1; + + *pkey = NULL; + + if (ctx->config->use_fake_private_key) { + mem = keypair->cert_mem; + len = keypair->cert_len; + } else { + mem = keypair->key_mem; + len = keypair->key_len; + } + + if (mem == NULL) + return (0); + + if (len > INT_MAX) { + tls_set_errorx(ctx, ctx->config->use_fake_private_key ? + "cert too long" : "key too long"); + goto err; + } + + if ((bio = BIO_new_mem_buf(mem, len)) == NULL) { + tls_set_errorx(ctx, "failed to create buffer"); + goto err; + } + + if (ctx->config->use_fake_private_key) { + if ((x509 = PEM_read_bio_X509(bio, NULL, tls_password_cb, + NULL)) == NULL) { + tls_set_errorx(ctx, "failed to read X509 certificate"); + goto err; + } + if ((*pkey = X509_get_pubkey(x509)) == NULL) { + tls_set_errorx(ctx, "failed to retrieve pubkey"); + goto err; + } + } else { + if ((*pkey = PEM_read_bio_PrivateKey(bio, NULL, tls_password_cb, + NULL)) == NULL) { + tls_set_errorx(ctx, "failed to read private key"); + goto err; + } + } + + ret = 0; + err: + BIO_free(bio); + X509_free(x509); + return (ret); +} + +static int +tls_keypair_setup_pkey(struct tls *ctx, struct tls_keypair *keypair, EVP_PKEY *pkey) +{ + RSA *rsa = NULL; + EC_KEY *eckey = NULL; + int ret = -1; + + /* Only install the pubkey hash if fake private keys are used. */ + if (!ctx->config->skip_private_key_check) + return (0); + + if (keypair->pubkey_hash == NULL) { + tls_set_errorx(ctx, "public key hash not set"); + goto err; + } + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL || + RSA_set_ex_data(rsa, 0, keypair->pubkey_hash) == 0 || + EVP_PKEY_set1_RSA(pkey, rsa) == 0) { + tls_set_errorx(ctx, "RSA key setup failure"); + goto err; + } + break; + case EVP_PKEY_EC: + if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL || + EC_KEY_set_ex_data(eckey, 0, keypair->pubkey_hash) == 0 || + EVP_PKEY_set1_EC_KEY(pkey, eckey) == 0) { + tls_set_errorx(ctx, "EC key setup failure"); + goto err; + } + break; + default: + tls_set_errorx(ctx, "incorrect key type"); + goto err; + } + + ret = 0; + + err: + RSA_free(rsa); + EC_KEY_free(eckey); + return (ret); +} + +int +tls_configure_ssl_keypair(struct tls *ctx, SSL_CTX *ssl_ctx, + struct tls_keypair *keypair, int required) +{ + EVP_PKEY *pkey = NULL; + + if (!required && + keypair->cert_mem == NULL && + keypair->key_mem == NULL) + return(0); + + if (keypair->cert_mem != NULL) { + if (keypair->cert_len > INT_MAX) { + tls_set_errorx(ctx, "certificate too long"); + goto err; + } + + if (SSL_CTX_use_certificate_chain_mem(ssl_ctx, + keypair->cert_mem, keypair->cert_len) != 1) { + tls_set_errorx(ctx, "failed to load certificate"); + goto err; + } + } + + if (tls_keypair_to_pkey(ctx, keypair, &pkey) == -1) + goto err; + if (pkey != NULL) { + if (tls_keypair_setup_pkey(ctx, keypair, pkey) == -1) + goto err; + if (SSL_CTX_use_PrivateKey(ssl_ctx, pkey) != 1) { + tls_set_errorx(ctx, "failed to load private key"); + goto err; + } + EVP_PKEY_free(pkey); + pkey = NULL; + } + + if (!ctx->config->skip_private_key_check && + SSL_CTX_check_private_key(ssl_ctx) != 1) { + tls_set_errorx(ctx, "private/public key mismatch"); + goto err; + } + + return (0); + + err: + EVP_PKEY_free(pkey); + + return (-1); +} + +int +tls_configure_ssl(struct tls *ctx, SSL_CTX *ssl_ctx) +{ + SSL_CTX_clear_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv3); + + SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1); + SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1_1); + SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1_2); + SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TLSv1_3); + + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_0) == 0) + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1); + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_1) == 0) + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_1); + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_2) == 0) + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2); + if ((ctx->config->protocols & TLS_PROTOCOL_TLSv1_3) == 0) + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); + + if (ctx->config->alpn != NULL) { + if (SSL_CTX_set_alpn_protos(ssl_ctx, ctx->config->alpn, + ctx->config->alpn_len) != 0) { + tls_set_errorx(ctx, "failed to set alpn"); + goto err; + } + } + + if (ctx->config->ciphers != NULL) { + if (SSL_CTX_set_cipher_list(ssl_ctx, + ctx->config->ciphers) != 1) { + tls_set_errorx(ctx, "failed to set ciphers"); + goto err; + } + } + + if (ctx->config->verify_time == 0) { + X509_VERIFY_PARAM_set_flags(SSL_CTX_get0_param(ssl_ctx), + X509_V_FLAG_NO_CHECK_TIME); + } + + /* Disable any form of session caching by default */ + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TICKET); + + return (0); + + err: + return (-1); +} + +static int +tls_ssl_cert_verify_cb(X509_STORE_CTX *x509_ctx, void *arg) +{ + struct tls *ctx = arg; + int x509_err; + + if (ctx->config->verify_cert == 0) + return (1); + + if ((X509_verify_cert(x509_ctx)) < 0) { + tls_set_errorx(ctx, "X509 verify cert failed"); + return (0); + } + + x509_err = X509_STORE_CTX_get_error(x509_ctx); + if (x509_err == X509_V_OK) + return (1); + + tls_set_errorx(ctx, "certificate verification failed: %s", + X509_verify_cert_error_string(x509_err)); + + return (0); +} + +int +tls_configure_ssl_verify(struct tls *ctx, SSL_CTX *ssl_ctx, int verify) +{ + size_t ca_len = ctx->config->ca_len; + char *ca_mem = ctx->config->ca_mem; + char *crl_mem = ctx->config->crl_mem; + size_t crl_len = ctx->config->crl_len; + char *ca_free = NULL; + STACK_OF(X509_INFO) *xis = NULL; + X509_STORE *store; + X509_INFO *xi; + BIO *bio = NULL; + int rv = -1; + int i; + + SSL_CTX_set_verify(ssl_ctx, verify, NULL); + SSL_CTX_set_cert_verify_callback(ssl_ctx, tls_ssl_cert_verify_cb, ctx); + + if (ctx->config->verify_depth >= 0) + SSL_CTX_set_verify_depth(ssl_ctx, ctx->config->verify_depth); + + if (ctx->config->verify_cert == 0) + goto done; + + /* If no CA has been specified, attempt to load the default. */ + if (ctx->config->ca_mem == NULL && ctx->config->ca_path == NULL) { + if (tls_config_load_file(&ctx->error, "CA", tls_default_ca_cert_file(), + &ca_mem, &ca_len) != 0) + goto err; + ca_free = ca_mem; + } + + if (ca_mem != NULL) { + if (ca_len > INT_MAX) { + tls_set_errorx(ctx, "ca too long"); + goto err; + } + if (SSL_CTX_load_verify_mem(ssl_ctx, ca_mem, ca_len) != 1) { + tls_set_errorx(ctx, "ssl verify memory setup failure"); + goto err; + } + } else if (SSL_CTX_load_verify_locations(ssl_ctx, NULL, + ctx->config->ca_path) != 1) { + tls_set_errorx(ctx, "ssl verify locations failure"); + goto err; + } + + if (crl_mem != NULL) { + if (crl_len > INT_MAX) { + tls_set_errorx(ctx, "crl too long"); + goto err; + } + if ((bio = BIO_new_mem_buf(crl_mem, crl_len)) == NULL) { + tls_set_errorx(ctx, "failed to create buffer"); + goto err; + } + if ((xis = PEM_X509_INFO_read_bio(bio, NULL, tls_password_cb, + NULL)) == NULL) { + tls_set_errorx(ctx, "failed to parse crl"); + goto err; + } + store = SSL_CTX_get_cert_store(ssl_ctx); + for (i = 0; i < sk_X509_INFO_num(xis); i++) { + xi = sk_X509_INFO_value(xis, i); + if (xi->crl == NULL) + continue; + if (!X509_STORE_add_crl(store, xi->crl)) { + tls_set_error(ctx, "failed to add crl"); + goto err; + } + } + X509_STORE_set_flags(store, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + + done: + rv = 0; + + err: + sk_X509_INFO_pop_free(xis, X509_INFO_free); + BIO_free(bio); + free(ca_free); + + return (rv); +} + +void +tls_free(struct tls *ctx) +{ + if (ctx == NULL) + return; + + tls_reset(ctx); + + free(ctx); +} + +void +tls_reset(struct tls *ctx) +{ + struct tls_sni_ctx *sni, *nsni; + + tls_config_free(ctx->config); + ctx->config = NULL; + + SSL_CTX_free(ctx->ssl_ctx); + SSL_free(ctx->ssl_conn); + X509_free(ctx->ssl_peer_cert); + + ctx->ssl_conn = NULL; + ctx->ssl_ctx = NULL; + ctx->ssl_peer_cert = NULL; + /* X509 objects in chain are freed with the SSL */ + ctx->ssl_peer_chain = NULL; + + ctx->socket = -1; + ctx->state = 0; + + free(ctx->servername); + ctx->servername = NULL; + + free(ctx->error.msg); + ctx->error.msg = NULL; + ctx->error.num = -1; + + tls_conninfo_free(ctx->conninfo); + ctx->conninfo = NULL; + + tls_ocsp_free(ctx->ocsp); + ctx->ocsp = NULL; + + for (sni = ctx->sni_ctx; sni != NULL; sni = nsni) { + nsni = sni->next; + tls_sni_ctx_free(sni); + } + ctx->sni_ctx = NULL; + + ctx->read_cb = NULL; + ctx->write_cb = NULL; + ctx->cb_arg = NULL; +} + +int +tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret, const char *prefix) +{ + const char *errstr = "unknown error"; + unsigned long err; + int ssl_err; + + ssl_err = SSL_get_error(ssl_conn, ssl_ret); + switch (ssl_err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + return (0); + + case SSL_ERROR_WANT_READ: + return (TLS_WANT_POLLIN); + + case SSL_ERROR_WANT_WRITE: + return (TLS_WANT_POLLOUT); + + case SSL_ERROR_SYSCALL: + if ((err = ERR_peek_error()) != 0) { + errstr = ERR_error_string(err, NULL); + } else if (ssl_ret == 0) { + if ((ctx->state & TLS_HANDSHAKE_COMPLETE) != 0) { + ctx->state |= TLS_EOF_NO_CLOSE_NOTIFY; + return (0); + } + errstr = "unexpected EOF"; + } else if (ssl_ret == -1) { + errstr = strerror(errno); + } + tls_set_ssl_errorx(ctx, "%s failed: %s", prefix, errstr); + return (-1); + + case SSL_ERROR_SSL: + if ((err = ERR_peek_error()) != 0) { + errstr = ERR_error_string(err, NULL); + } + tls_set_ssl_errorx(ctx, "%s failed: %s", prefix, errstr); + return (-1); + + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_WANT_X509_LOOKUP: + default: + tls_set_ssl_errorx(ctx, "%s failed (%d)", prefix, ssl_err); + return (-1); + } +} + +int +tls_handshake(struct tls *ctx) +{ + int rv = -1; + + tls_error_clear(&ctx->error); + + if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) { + tls_set_errorx(ctx, "invalid operation for context"); + goto out; + } + + if ((ctx->state & TLS_HANDSHAKE_COMPLETE) != 0) { + tls_set_errorx(ctx, "handshake already completed"); + goto out; + } + + if ((ctx->flags & TLS_CLIENT) != 0) + rv = tls_handshake_client(ctx); + else if ((ctx->flags & TLS_SERVER_CONN) != 0) + rv = tls_handshake_server(ctx); + + if (rv == 0) { + ctx->ssl_peer_cert = SSL_get_peer_certificate(ctx->ssl_conn); + ctx->ssl_peer_chain = SSL_get_peer_cert_chain(ctx->ssl_conn); + if (tls_conninfo_populate(ctx) == -1) + rv = -1; + if (ctx->ocsp == NULL) + ctx->ocsp = tls_ocsp_setup_from_peer(ctx); + } + out: + /* Prevent callers from performing incorrect error handling */ + errno = 0; + return (rv); +} + +ssize_t +tls_read(struct tls *ctx, void *buf, size_t buflen) +{ + ssize_t rv = -1; + int ssl_ret; + + tls_error_clear(&ctx->error); + + if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0) { + if ((rv = tls_handshake(ctx)) != 0) + goto out; + } + + if (buflen > INT_MAX) { + tls_set_errorx(ctx, "buflen too long"); + goto out; + } + + ERR_clear_error(); + if ((ssl_ret = SSL_read(ctx->ssl_conn, buf, buflen)) > 0) { + rv = (ssize_t)ssl_ret; + goto out; + } + rv = (ssize_t)tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "read"); + + out: + /* Prevent callers from performing incorrect error handling */ + errno = 0; + return (rv); +} + +ssize_t +tls_write(struct tls *ctx, const void *buf, size_t buflen) +{ + ssize_t rv = -1; + int ssl_ret; + + tls_error_clear(&ctx->error); + + if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0) { + if ((rv = tls_handshake(ctx)) != 0) + goto out; + } + + if (buflen > INT_MAX) { + tls_set_errorx(ctx, "buflen too long"); + goto out; + } + + ERR_clear_error(); + if ((ssl_ret = SSL_write(ctx->ssl_conn, buf, buflen)) > 0) { + rv = (ssize_t)ssl_ret; + goto out; + } + rv = (ssize_t)tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "write"); + + out: + /* Prevent callers from performing incorrect error handling */ + errno = 0; + return (rv); +} + +int +tls_close(struct tls *ctx) +{ + int ssl_ret; + int rv = 0; + + tls_error_clear(&ctx->error); + + if ((ctx->flags & (TLS_CLIENT | TLS_SERVER_CONN)) == 0) { + tls_set_errorx(ctx, "invalid operation for context"); + rv = -1; + goto out; + } + + if (ctx->state & TLS_SSL_NEEDS_SHUTDOWN) { + ERR_clear_error(); + ssl_ret = SSL_shutdown(ctx->ssl_conn); + if (ssl_ret < 0) { + rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, + "shutdown"); + if (rv == TLS_WANT_POLLIN || rv == TLS_WANT_POLLOUT) + goto out; + } + ctx->state &= ~TLS_SSL_NEEDS_SHUTDOWN; + } + + if (ctx->socket != -1) { + if (shutdown(ctx->socket, SHUT_RDWR) != 0) { + if (rv == 0 && + errno != ENOTCONN && errno != ECONNRESET) { + tls_set_error(ctx, "shutdown"); + rv = -1; + } + } + if (close(ctx->socket) != 0) { + if (rv == 0) { + tls_set_error(ctx, "close"); + rv = -1; + } + } + ctx->socket = -1; + } + + if ((ctx->state & TLS_EOF_NO_CLOSE_NOTIFY) != 0) { + tls_set_errorx(ctx, "EOF without close notify"); + rv = -1; + } + + out: + /* Prevent callers from performing incorrect error handling */ + errno = 0; + return (rv); +} diff --git a/compat/libtls/tls.h b/compat/libtls/tls.h new file mode 100644 index 0000000..b94a6fa --- /dev/null +++ b/compat/libtls/tls.h @@ -0,0 +1,219 @@ +/* $OpenBSD: tls.h,v 1.62 2022/03/24 15:56:34 tb 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +#include <stddef.h> +#include <stdint.h> + +#define TLS_API 20200120 + +#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_3 (1 << 4) + +#define TLS_PROTOCOL_TLSv1 \ + (TLS_PROTOCOL_TLSv1_0|TLS_PROTOCOL_TLSv1_1|\ + TLS_PROTOCOL_TLSv1_2|TLS_PROTOCOL_TLSv1_3) + +#define TLS_PROTOCOLS_ALL TLS_PROTOCOL_TLSv1 +#define TLS_PROTOCOLS_DEFAULT (TLS_PROTOCOL_TLSv1_2|TLS_PROTOCOL_TLSv1_3) + +#define TLS_WANT_POLLIN -2 +#define TLS_WANT_POLLOUT -3 + +/* RFC 6960 Section 2.3 */ +#define TLS_OCSP_RESPONSE_SUCCESSFUL 0 +#define TLS_OCSP_RESPONSE_MALFORMED 1 +#define TLS_OCSP_RESPONSE_INTERNALERROR 2 +#define TLS_OCSP_RESPONSE_TRYLATER 3 +#define TLS_OCSP_RESPONSE_SIGREQUIRED 4 +#define TLS_OCSP_RESPONSE_UNAUTHORIZED 5 + +/* RFC 6960 Section 2.2 */ +#define TLS_OCSP_CERT_GOOD 0 +#define TLS_OCSP_CERT_REVOKED 1 +#define TLS_OCSP_CERT_UNKNOWN 2 + +/* RFC 5280 Section 5.3.1 */ +#define TLS_CRL_REASON_UNSPECIFIED 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_WITHDRAWN 9 +#define TLS_CRL_REASON_AA_COMPROMISE 10 + +#define TLS_MAX_SESSION_ID_LENGTH 32 +#define TLS_TICKET_KEY_SIZE 48 + +struct tls; +struct tls_config; + +typedef ssize_t (*tls_read_cb)(struct tls *_ctx, void *_buf, size_t _buflen, + void *_cb_arg); +typedef ssize_t (*tls_write_cb)(struct tls *_ctx, const void *_buf, + size_t _buflen, void *_cb_arg); + +int tls_init(void); + +const char *tls_config_error(struct tls_config *_config); +const char *tls_error(struct tls *_ctx); + +struct tls_config *tls_config_new(void); +void tls_config_free(struct tls_config *_config); + +const char *tls_default_ca_cert_file(void); + +int tls_config_add_keypair_file(struct tls_config *_config, + const char *_cert_file, const char *_key_file); +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); +int tls_config_add_keypair_ocsp_file(struct tls_config *_config, + const char *_cert_file, const char *_key_file, + const char *_ocsp_staple_file); +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); +int tls_config_set_alpn(struct tls_config *_config, const char *_alpn); +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_crl_file(struct tls_config *_config, const char *_crl_file); +int tls_config_set_crl_mem(struct tls_config *_config, const uint8_t *_crl, + size_t _len); +int tls_config_set_dheparams(struct tls_config *_config, const char *_params); +int tls_config_set_ecdhecurve(struct tls_config *_config, const char *_curve); +int tls_config_set_ecdhecurves(struct tls_config *_config, const char *_curves); +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_keypair_file(struct tls_config *_config, + const char *_cert_file, const char *_key_file); +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); +int tls_config_set_keypair_ocsp_file(struct tls_config *_config, + const char *_cert_file, const char *_key_file, const char *_staple_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); +int tls_config_set_ocsp_staple_mem(struct tls_config *_config, + const uint8_t *_staple, size_t _len); +int tls_config_set_ocsp_staple_file(struct tls_config *_config, + const char *_staple_file); +int tls_config_set_protocols(struct tls_config *_config, uint32_t _protocols); +int tls_config_set_session_fd(struct tls_config *_config, int _session_fd); +int 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_ocsp_require_stapling(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); + +int tls_config_set_session_id(struct tls_config *_config, + const unsigned char *_session_id, size_t _len); +int tls_config_set_session_lifetime(struct tls_config *_config, int _lifetime); +int tls_config_add_ticket_key(struct tls_config *_config, uint32_t _keyrev, + unsigned char *_key, size_t _keylen); + +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_accept_cbs(struct tls *_ctx, struct tls **_cctx, + tls_read_cb _read_cb, tls_write_cb _write_cb, void *_cb_arg); +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_connect_cbs(struct tls *_ctx, tls_read_cb _read_cb, + tls_write_cb _write_cb, void *_cb_arg, 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 uint8_t *tls_peer_cert_chain_pem(struct tls *_ctx, size_t *_len); + +const char *tls_conn_alpn_selected(struct tls *_ctx); +const char *tls_conn_cipher(struct tls *_ctx); +int tls_conn_cipher_strength(struct tls *_ctx); +const char *tls_conn_servername(struct tls *_ctx); +int tls_conn_session_resumed(struct tls *_ctx); +const char *tls_conn_version(struct tls *_ctx); + +uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password); +void tls_unload_file(uint8_t *_buf, size_t len); + +int tls_ocsp_process_response(struct tls *_ctx, const unsigned char *_response, + size_t _size); +int tls_peer_ocsp_cert_status(struct tls *_ctx); +int tls_peer_ocsp_crl_reason(struct tls *_ctx); +time_t tls_peer_ocsp_next_update(struct tls *_ctx); +int tls_peer_ocsp_response_status(struct tls *_ctx); +const char *tls_peer_ocsp_result(struct tls *_ctx); +time_t tls_peer_ocsp_revocation_time(struct tls *_ctx); +time_t tls_peer_ocsp_this_update(struct tls *_ctx); +const char *tls_peer_ocsp_url(struct tls *_ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_TLS_H */ diff --git a/compat/libtls/tls_bio_cb.c b/compat/libtls/tls_bio_cb.c new file mode 100644 index 0000000..0bab70d --- /dev/null +++ b/compat/libtls/tls_bio_cb.c @@ -0,0 +1,169 @@ +/* $OpenBSD: tls_bio_cb.c,v 1.21 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2016 Tobias Pape <tobias@netshed.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/bio.h> + +#include <tls.h> +#include "tls_internal.h" + +static int bio_cb_write(BIO *bio, const char *buf, int num); +static int bio_cb_read(BIO *bio, char *buf, int size); +static int bio_cb_puts(BIO *bio, const char *str); +static long bio_cb_ctrl(BIO *bio, int cmd, long num, void *ptr); + +static BIO_METHOD *bio_cb_method; + +static void +bio_cb_method_init(void) +{ + BIO_METHOD *bio_method; + + if (bio_cb_method != NULL) + return; + + bio_method = BIO_meth_new(BIO_TYPE_MEM, "libtls_callbacks"); + if (bio_method == NULL) + return; + + BIO_meth_set_write(bio_method, bio_cb_write); + BIO_meth_set_read(bio_method, bio_cb_read); + BIO_meth_set_puts(bio_method, bio_cb_puts); + BIO_meth_set_ctrl(bio_method, bio_cb_ctrl); + + bio_cb_method = bio_method; +} + +static BIO_METHOD * +bio_s_cb(void) +{ + if (bio_cb_method != NULL) + return (bio_cb_method); + + bio_cb_method_init(); + + return (bio_cb_method); +} + +static int +bio_cb_puts(BIO *bio, const char *str) +{ + return (bio_cb_write(bio, str, strlen(str))); +} + +static long +bio_cb_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + long ret = 1; + + switch (cmd) { + case BIO_CTRL_GET_CLOSE: + ret = (long)BIO_get_shutdown(bio); + break; + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(bio, (int)num); + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + break; + case BIO_CTRL_INFO: + case BIO_CTRL_GET: + case BIO_CTRL_SET: + default: + ret = BIO_ctrl(BIO_next(bio), cmd, num, ptr); + } + + return (ret); +} + +static int +bio_cb_write(BIO *bio, const char *buf, int num) +{ + struct tls *ctx = BIO_get_data(bio); + int rv; + + BIO_clear_retry_flags(bio); + rv = (ctx->write_cb)(ctx, buf, num, ctx->cb_arg); + if (rv == TLS_WANT_POLLIN) { + BIO_set_retry_read(bio); + rv = -1; + } else if (rv == TLS_WANT_POLLOUT) { + BIO_set_retry_write(bio); + rv = -1; + } + return (rv); +} + +static int +bio_cb_read(BIO *bio, char *buf, int size) +{ + struct tls *ctx = BIO_get_data(bio); + int rv; + + BIO_clear_retry_flags(bio); + rv = (ctx->read_cb)(ctx, buf, size, ctx->cb_arg); + if (rv == TLS_WANT_POLLIN) { + BIO_set_retry_read(bio); + rv = -1; + } else if (rv == TLS_WANT_POLLOUT) { + BIO_set_retry_write(bio); + rv = -1; + } + return (rv); +} + +int +tls_set_cbs(struct tls *ctx, tls_read_cb read_cb, tls_write_cb write_cb, + void *cb_arg) +{ + const BIO_METHOD *bio_cb; + BIO *bio; + int rv = -1; + + if (read_cb == NULL || write_cb == NULL) { + tls_set_errorx(ctx, "no callbacks provided"); + goto err; + } + + ctx->read_cb = read_cb; + ctx->write_cb = write_cb; + ctx->cb_arg = cb_arg; + + if ((bio_cb = bio_s_cb()) == NULL) { + tls_set_errorx(ctx, "failed to create callback method"); + goto err; + } + if ((bio = BIO_new(bio_cb)) == NULL) { + tls_set_errorx(ctx, "failed to create callback i/o"); + goto err; + } + BIO_set_data(bio, ctx); + BIO_set_init(bio, 1); + + SSL_set_bio(ctx->ssl_conn, bio, bio); + + rv = 0; + + err: + return (rv); +} diff --git a/compat/libtls/tls_client.c b/compat/libtls/tls_client.c new file mode 100644 index 0000000..4a7018d --- /dev/null +++ b/compat/libtls/tls_client.c @@ -0,0 +1,487 @@ +/* $OpenBSD: tls_client.c,v 1.49 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <limits.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/err.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +struct tls * +tls_client(void) +{ + struct tls *ctx; + + if (tls_init() == -1) + return (NULL); + + if ((ctx = tls_new()) == NULL) + return (NULL); + + ctx->flags |= TLS_CLIENT; + + return (ctx); +} + +int +tls_connect(struct tls *ctx, const char *host, const char *port) +{ + return tls_connect_servername(ctx, host, port, NULL); +} + +int +tls_connect_servername(struct tls *ctx, const char *host, const char *port, + const char *servername) +{ + struct addrinfo hints, *res, *res0; + const char *h = NULL, *p = NULL; + char *hs = NULL, *ps = NULL; + int rv = -1, s = -1, ret; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_errorx(ctx, "not a client context"); + goto err; + } + + if (host == NULL) { + tls_set_errorx(ctx, "host not specified"); + goto err; + } + + /* If port is NULL, try to extract a port from the specified host. */ + if (port == NULL) { + ret = tls_host_port(host, &hs, &ps); + if (ret == -1) { + tls_set_errorx(ctx, "memory allocation failure"); + goto err; + } + if (ret != 0) { + tls_set_errorx(ctx, "no port provided"); + goto err; + } + } + + h = (hs != NULL) ? hs : host; + p = (ps != NULL) ? ps : port; + + /* + * First check if the host is specified as a numeric IP address, + * either IPv4 or IPv6, before trying to resolve the host. + * The AI_ADDRCONFIG resolver option will not return IPv4 or IPv6 + * records if it is not configured on an interface; not considering + * loopback addresses. Checking the numeric addresses first makes + * sure that connection attempts to numeric addresses and especially + * 127.0.0.1 or ::1 loopback addresses are always possible. + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + + /* try as an IPv4 literal */ + hints.ai_family = AF_INET; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(h, p, &hints, &res0) != 0) { + /* try again as an IPv6 literal */ + hints.ai_family = AF_INET6; + if (getaddrinfo(h, p, &hints, &res0) != 0) { + /* last try, with name resolution and save the error */ + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_ADDRCONFIG; + if ((s = getaddrinfo(h, p, &hints, &res0)) != 0) { + tls_set_error(ctx, "%s", gai_strerror(s)); + goto err; + } + } + } + + /* It was resolved somehow; now try connecting to what we got */ + s = -1; + for (res = res0; res; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == -1) { + tls_set_error(ctx, "socket"); + continue; + } + if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { + tls_set_error(ctx, "connect"); + close(s); + s = -1; + continue; + } + + break; /* Connected. */ + } + freeaddrinfo(res0); + + if (s == -1) + goto err; + + if (servername == NULL) + servername = h; + + if (tls_connect_socket(ctx, s, servername) != 0) { + close(s); + goto err; + } + + ctx->socket = s; + + rv = 0; + + err: + free(hs); + free(ps); + + return (rv); +} + +static int +tls_client_read_session(struct tls *ctx) +{ + int sfd = ctx->config->session_fd; + uint8_t *session = NULL; + size_t session_len = 0; + SSL_SESSION *ss = NULL; + BIO *bio = NULL; + struct stat sb; + ssize_t n; + int rv = -1; + + if (fstat(sfd, &sb) == -1) { + tls_set_error(ctx, "failed to stat session file"); + goto err; + } + if (sb.st_size < 0 || sb.st_size > INT_MAX) { + tls_set_errorx(ctx, "invalid session file size"); + goto err; + } + session_len = (size_t)sb.st_size; + + /* A zero size file means that we do not yet have a valid session. */ + if (session_len == 0) + goto done; + + if ((session = malloc(session_len)) == NULL) + goto err; + + n = pread(sfd, session, session_len, 0); + if (n < 0 || (size_t)n != session_len) { + tls_set_error(ctx, "failed to read session file"); + goto err; + } + if ((bio = BIO_new_mem_buf(session, session_len)) == NULL) + goto err; + if ((ss = PEM_read_bio_SSL_SESSION(bio, NULL, tls_password_cb, + NULL)) == NULL) { + tls_set_errorx(ctx, "failed to parse session"); + goto err; + } + + if (SSL_set_session(ctx->ssl_conn, ss) != 1) { + tls_set_errorx(ctx, "failed to set session"); + goto err; + } + + done: + rv = 0; + + err: + freezero(session, session_len); + SSL_SESSION_free(ss); + BIO_free(bio); + + return rv; +} + +static int +tls_client_write_session(struct tls *ctx) +{ + int sfd = ctx->config->session_fd; + SSL_SESSION *ss = NULL; + BIO *bio = NULL; + long data_len; + char *data; + off_t offset; + size_t len; + ssize_t n; + int rv = -1; + + if ((ss = SSL_get1_session(ctx->ssl_conn)) == NULL) { + if (ftruncate(sfd, 0) == -1) { + tls_set_error(ctx, "failed to truncate session file"); + goto err; + } + goto done; + } + + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto err; + if (PEM_write_bio_SSL_SESSION(bio, ss) == 0) + goto err; + if ((data_len = BIO_get_mem_data(bio, &data)) <= 0) + goto err; + + len = (size_t)data_len; + offset = 0; + + if (ftruncate(sfd, len) == -1) { + tls_set_error(ctx, "failed to truncate session file"); + goto err; + } + while (len > 0) { + if ((n = pwrite(sfd, data + offset, len, offset)) == -1) { + tls_set_error(ctx, "failed to write session file"); + goto err; + } + offset += n; + len -= n; + } + + done: + rv = 0; + + err: + SSL_SESSION_free(ss); + BIO_free_all(bio); + + return (rv); +} + +static int +tls_connect_common(struct tls *ctx, const char *servername) +{ + union tls_addr addrbuf; + size_t servername_len; + int rv = -1; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_errorx(ctx, "not a client context"); + goto err; + } + + if (servername != NULL) { + if ((ctx->servername = strdup(servername)) == NULL) { + tls_set_errorx(ctx, "out of memory"); + goto err; + } + + /* + * If there's a trailing dot, remove it. While an FQDN includes + * the terminating dot representing the zero-length label of + * the root (RFC 8499, section 2), the SNI explicitly does not + * include it (RFC 6066, section 3). + */ + servername_len = strlen(ctx->servername); + if (servername_len > 0 && + ctx->servername[servername_len - 1] == '.') + ctx->servername[servername_len - 1] = '\0'; + } + + if ((ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { + tls_set_errorx(ctx, "ssl context failure"); + goto err; + } + + if (tls_configure_ssl(ctx, ctx->ssl_ctx) != 0) + goto err; + + if (tls_configure_ssl_keypair(ctx, ctx->ssl_ctx, + ctx->config->keypair, 0) != 0) + goto err; + + if (ctx->config->verify_name) { + if (ctx->servername == NULL) { + tls_set_errorx(ctx, "server name not specified"); + goto err; + } + } + + if (tls_configure_ssl_verify(ctx, ctx->ssl_ctx, SSL_VERIFY_PEER) == -1) + goto err; + + if (ctx->config->ecdhecurves != NULL) { + if (SSL_CTX_set1_groups(ctx->ssl_ctx, ctx->config->ecdhecurves, + ctx->config->ecdhecurves_len) != 1) { + tls_set_errorx(ctx, "failed to set ecdhe curves"); + goto err; + } + } + + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_verify_cb) != 1) { + tls_set_errorx(ctx, "ssl OCSP verification setup failure"); + goto err; + } + + if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { + tls_set_errorx(ctx, "ssl connection failure"); + goto err; + } + + if (SSL_set_app_data(ctx->ssl_conn, ctx) != 1) { + tls_set_errorx(ctx, "ssl application data failure"); + goto err; + } + + if (ctx->config->session_fd != -1) { + SSL_clear_options(ctx->ssl_conn, SSL_OP_NO_TICKET); + if (tls_client_read_session(ctx) == -1) + goto err; + } + + if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) { + tls_set_errorx(ctx, "ssl OCSP extension setup failure"); + goto err; + } + + /* + * RFC 6066 (SNI): Literal IPv4 and IPv6 addresses are not + * permitted in "HostName". + */ + if (ctx->servername != NULL && + inet_pton(AF_INET, ctx->servername, &addrbuf) != 1 && + inet_pton(AF_INET6, ctx->servername, &addrbuf) != 1) { + if (SSL_set_tlsext_host_name(ctx->ssl_conn, + ctx->servername) == 0) { + tls_set_errorx(ctx, "server name indication failure"); + goto err; + } + } + + ctx->state |= TLS_CONNECTED; + rv = 0; + + err: + return (rv); +} + +int +tls_connect_socket(struct tls *ctx, int s, const char *servername) +{ + return tls_connect_fds(ctx, s, s, servername); +} + +int +tls_connect_fds(struct tls *ctx, int fd_read, int fd_write, + const char *servername) +{ + int rv = -1; + + if (fd_read < 0 || fd_write < 0) { + tls_set_errorx(ctx, "invalid file descriptors"); + goto err; + } + + if (tls_connect_common(ctx, servername) != 0) + goto err; + + if (SSL_set_rfd(ctx->ssl_conn, fd_read) != 1 || + SSL_set_wfd(ctx->ssl_conn, fd_write) != 1) { + tls_set_errorx(ctx, "ssl file descriptor failure"); + goto err; + } + + rv = 0; + err: + return (rv); +} + +int +tls_connect_cbs(struct tls *ctx, tls_read_cb read_cb, + tls_write_cb write_cb, void *cb_arg, const char *servername) +{ + int rv = -1; + + if (tls_connect_common(ctx, servername) != 0) + goto err; + + if (tls_set_cbs(ctx, read_cb, write_cb, cb_arg) != 0) + goto err; + + rv = 0; + + err: + return (rv); +} + +int +tls_handshake_client(struct tls *ctx) +{ + X509 *cert = NULL; + int match, ssl_ret; + int rv = -1; + + if ((ctx->flags & TLS_CLIENT) == 0) { + tls_set_errorx(ctx, "not a client context"); + goto err; + } + + if ((ctx->state & TLS_CONNECTED) == 0) { + tls_set_errorx(ctx, "context not connected"); + goto err; + } + + ctx->state |= TLS_SSL_NEEDS_SHUTDOWN; + + ERR_clear_error(); + if ((ssl_ret = SSL_connect(ctx->ssl_conn)) != 1) { + rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "handshake"); + goto err; + } + + if (ctx->config->verify_name) { + cert = SSL_get_peer_certificate(ctx->ssl_conn); + if (cert == NULL) { + tls_set_errorx(ctx, "no server certificate"); + goto err; + } + if (tls_check_name(ctx, cert, ctx->servername, &match) == -1) + goto err; + if (!match) { + tls_set_errorx(ctx, "name `%s' not present in" + " server certificate", ctx->servername); + goto err; + } + } + + ctx->state |= TLS_HANDSHAKE_COMPLETE; + + if (ctx->config->session_fd != -1) { + if (tls_client_write_session(ctx) == -1) + goto err; + } + + rv = 0; + + err: + X509_free(cert); + + return (rv); +} diff --git a/compat/libtls/tls_config.c b/compat/libtls/tls_config.c new file mode 100644 index 0000000..3f4306a --- /dev/null +++ b/compat/libtls/tls_config.c @@ -0,0 +1,915 @@ +/* $OpenBSD: tls_config.c,v 1.66 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <tls.h> + +#include "tls_internal.h" + +const char * +tls_default_ca_cert_file(void) +{ +#ifdef OPENSMTPD_CA_FILE + return OPENSMTPD_CA_FILE; +#else + return X509_get_default_cert_file(); +#endif +} + +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); + + 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; + + refcount = --config->refcount; + + 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 (key_file != NULL && + tls_keypair_set_key_file(keypair, &config->error, key_file) != 0) + goto err; + if (ocsp_file != NULL && + tls_keypair_set_ocsp_staple_file(keypair, &config->error, + ocsp_file) != 0) + goto err; + + tls_config_keypair_add(config, keypair); + + return (0); + + err: + tls_keypair_free(keypair); + return (-1); +} + +static int +tls_config_add_keypair_mem_internal(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + struct tls_keypair *keypair; + + if ((keypair = tls_keypair_new()) == NULL) + return (-1); + if (tls_keypair_set_cert_mem(keypair, &config->error, cert, cert_len) != 0) + goto err; + if (key != NULL && + tls_keypair_set_key_mem(keypair, &config->error, key, key_len) != 0) + goto err; + if (staple != NULL && + tls_keypair_set_ocsp_staple_mem(keypair, &config->error, staple, + staple_len) != 0) + goto err; + + tls_config_keypair_add(config, keypair); + + return (0); + + err: + tls_keypair_free(keypair); + return (-1); +} + +int +tls_config_add_keypair_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len) +{ + return tls_config_add_keypair_mem_internal(config, cert, cert_len, key, + key_len, NULL, 0); +} + +int +tls_config_add_keypair_file(struct tls_config *config, + const char *cert_file, const char *key_file) +{ + return tls_config_add_keypair_file_internal(config, cert_file, + key_file, NULL); +} + +int +tls_config_add_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, const uint8_t *staple, + size_t staple_len) +{ + return tls_config_add_keypair_mem_internal(config, cert, cert_len, key, + key_len, staple, staple_len); +} + +int +tls_config_add_keypair_ocsp_file(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + return tls_config_add_keypair_file_internal(config, cert_file, + key_file, ocsp_file); +} + +int +tls_config_set_ca_file(struct tls_config *config, const char *ca_file) +{ + return tls_config_load_file(&config->error, "CA", ca_file, + &config->ca_mem, &config->ca_len); +} + +int +tls_config_set_ca_path(struct tls_config *config, const char *ca_path) +{ + return tls_set_string(&config->ca_path, ca_path); +} + +int +tls_config_set_ca_mem(struct tls_config *config, const uint8_t *ca, size_t len) +{ + return tls_set_mem(&config->ca_mem, &config->ca_len, ca, len); +} + +int +tls_config_set_cert_file(struct tls_config *config, const char *cert_file) +{ + return tls_keypair_set_cert_file(config->keypair, &config->error, + cert_file); +} + +int +tls_config_set_cert_mem(struct tls_config *config, const uint8_t *cert, + size_t len) +{ + return tls_keypair_set_cert_mem(config->keypair, &config->error, + cert, len); +} + +int +tls_config_set_ciphers(struct tls_config *config, const char *ciphers) +{ + SSL_CTX *ssl_ctx = NULL; + + if (ciphers == NULL || + strcasecmp(ciphers, "default") == 0 || + strcasecmp(ciphers, "secure") == 0) + ciphers = TLS_CIPHERS_DEFAULT; + else if (strcasecmp(ciphers, "compat") == 0) + ciphers = TLS_CIPHERS_COMPAT; + else if (strcasecmp(ciphers, "legacy") == 0) + ciphers = TLS_CIPHERS_LEGACY; + else if (strcasecmp(ciphers, "all") == 0 || + strcasecmp(ciphers, "insecure") == 0) + ciphers = TLS_CIPHERS_ALL; + + if ((ssl_ctx = SSL_CTX_new(SSLv23_method())) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers) != 1) { + tls_config_set_errorx(config, "no ciphers for '%s'", ciphers); + goto err; + } + + SSL_CTX_free(ssl_ctx); + return tls_set_string(&config->ciphers, ciphers); + + err: + SSL_CTX_free(ssl_ctx); + return -1; +} + +int +tls_config_set_crl_file(struct tls_config *config, const char *crl_file) +{ + return tls_config_load_file(&config->error, "CRL", crl_file, + &config->crl_mem, &config->crl_len); +} + +int +tls_config_set_crl_mem(struct tls_config *config, const uint8_t *crl, + size_t len) +{ + return tls_set_mem(&config->crl_mem, &config->crl_len, crl, len); +} + +int +tls_config_set_dheparams(struct tls_config *config, const char *params) +{ + int keylen; + + if (params == NULL || strcasecmp(params, "none") == 0) + keylen = 0; + else if (strcasecmp(params, "auto") == 0) + keylen = -1; + else if (strcasecmp(params, "legacy") == 0) + keylen = 1024; + else { + tls_config_set_errorx(config, "invalid dhe param '%s'", params); + return (-1); + } + + config->dheparams = keylen; + + return (0); +} + +int +tls_config_set_ecdhecurve(struct tls_config *config, const char *curve) +{ + if (curve == NULL || + strcasecmp(curve, "none") == 0 || + strcasecmp(curve, "auto") == 0) { + curve = TLS_ECDHE_CURVES; + } else if (strchr(curve, ',') != NULL || strchr(curve, ':') != NULL) { + tls_config_set_errorx(config, "invalid ecdhe curve '%s'", + curve); + return (-1); + } + + return tls_config_set_ecdhecurves(config, curve); +} + +int +tls_config_set_ecdhecurves(struct tls_config *config, const char *curves) +{ + int *curves_list = NULL, *curves_new; + size_t curves_num = 0; + char *cs = NULL; + char *p, *q; + int rv = -1; + int nid; + + free(config->ecdhecurves); + config->ecdhecurves = NULL; + config->ecdhecurves_len = 0; + + if (curves == NULL || strcasecmp(curves, "default") == 0) + curves = TLS_ECDHE_CURVES; + + if ((cs = strdup(curves)) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + + q = cs; + while ((p = strsep(&q, ",:")) != NULL) { + while (*p == ' ' || *p == '\t') + p++; + + nid = OBJ_sn2nid(p); + if (nid == NID_undef) + nid = OBJ_ln2nid(p); + if (nid == NID_undef) + nid = EC_curve_nist2nid(p); + if (nid == NID_undef) { + tls_config_set_errorx(config, + "invalid ecdhe curve '%s'", p); + goto err; + } + + if ((curves_new = reallocarray(curves_list, curves_num + 1, + sizeof(int))) == NULL) { + tls_config_set_errorx(config, "out of memory"); + goto err; + } + curves_list = curves_new; + curves_list[curves_num] = nid; + curves_num++; + } + + config->ecdhecurves = curves_list; + config->ecdhecurves_len = curves_num; + curves_list = NULL; + + rv = 0; + + err: + free(cs); + free(curves_list); + + return (rv); +} + +int +tls_config_set_key_file(struct tls_config *config, const char *key_file) +{ + return tls_keypair_set_key_file(config->keypair, &config->error, + key_file); +} + +int +tls_config_set_key_mem(struct tls_config *config, const uint8_t *key, + size_t len) +{ + return tls_keypair_set_key_mem(config->keypair, &config->error, + key, len); +} + +static int +tls_config_set_keypair_file_internal(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + if (tls_config_set_cert_file(config, cert_file) != 0) + return (-1); + if (tls_config_set_key_file(config, key_file) != 0) + return (-1); + if (ocsp_file != NULL && + tls_config_set_ocsp_staple_file(config, ocsp_file) != 0) + return (-1); + + return (0); +} + +static int +tls_config_set_keypair_mem_internal(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + if (tls_config_set_cert_mem(config, cert, cert_len) != 0) + return (-1); + if (tls_config_set_key_mem(config, key, key_len) != 0) + return (-1); + if ((staple != NULL) && + (tls_config_set_ocsp_staple_mem(config, staple, staple_len) != 0)) + return (-1); + + return (0); +} + +int +tls_config_set_keypair_file(struct tls_config *config, + const char *cert_file, const char *key_file) +{ + return tls_config_set_keypair_file_internal(config, cert_file, key_file, + NULL); +} + +int +tls_config_set_keypair_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len) +{ + return tls_config_set_keypair_mem_internal(config, cert, cert_len, + key, key_len, NULL, 0); +} + +int +tls_config_set_keypair_ocsp_file(struct tls_config *config, + const char *cert_file, const char *key_file, const char *ocsp_file) +{ + return tls_config_set_keypair_file_internal(config, cert_file, key_file, + ocsp_file); +} + +int +tls_config_set_keypair_ocsp_mem(struct tls_config *config, const uint8_t *cert, + size_t cert_len, const uint8_t *key, size_t key_len, + const uint8_t *staple, size_t staple_len) +{ + return tls_config_set_keypair_mem_internal(config, cert, cert_len, + key, key_len, staple, staple_len); +} + + +int +tls_config_set_protocols(struct tls_config *config, uint32_t protocols) +{ + config->protocols = protocols; + + return (0); +} + +int +tls_config_set_session_fd(struct tls_config *config, int session_fd) +{ + struct stat sb; + mode_t mugo; + + if (session_fd == -1) { + config->session_fd = session_fd; + return (0); + } + + if (fstat(session_fd, &sb) == -1) { + tls_config_set_error(config, "failed to stat session file"); + return (-1); + } + if (!S_ISREG(sb.st_mode)) { + tls_config_set_errorx(config, + "session file is not a regular file"); + return (-1); + } + + if (sb.st_uid != getuid()) { + tls_config_set_errorx(config, "session file has incorrect " + "owner (uid %u != %u)", sb.st_uid, getuid()); + return (-1); + } + mugo = sb.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO); + if (mugo != (S_IRUSR|S_IWUSR)) { + tls_config_set_errorx(config, "session file has incorrect " + "permissions (%o != 600)", mugo); + return (-1); + } + + config->session_fd = session_fd; + + return (0); +} + +int +tls_config_set_verify_depth(struct tls_config *config, int verify_depth) +{ + config->verify_depth = verify_depth; + + return (0); +} + +void +tls_config_prefer_ciphers_client(struct tls_config *config) +{ + config->ciphers_server = 0; +} + +void +tls_config_prefer_ciphers_server(struct tls_config *config) +{ + config->ciphers_server = 1; +} + +void +tls_config_insecure_noverifycert(struct tls_config *config) +{ + config->verify_cert = 0; +} + +void +tls_config_insecure_noverifyname(struct tls_config *config) +{ + config->verify_name = 0; +} + +void +tls_config_insecure_noverifytime(struct tls_config *config) +{ + config->verify_time = 0; +} + +void +tls_config_verify(struct tls_config *config) +{ + config->verify_cert = 1; + config->verify_name = 1; + config->verify_time = 1; +} + +void +tls_config_ocsp_require_stapling(struct tls_config *config) +{ + config->ocsp_require_stapling = 1; +} + +void +tls_config_verify_client(struct tls_config *config) +{ + config->verify_client = 1; +} + +void +tls_config_verify_client_optional(struct tls_config *config) +{ + config->verify_client = 2; +} + +void +tls_config_skip_private_key_check(struct tls_config *config) +{ + config->skip_private_key_check = 1; +} + +void +tls_config_use_fake_private_key(struct tls_config *config) +{ + config->use_fake_private_key = 1; + config->skip_private_key_check = 1; +} + +int +tls_config_set_ocsp_staple_file(struct tls_config *config, const char *staple_file) +{ + return tls_keypair_set_ocsp_staple_file(config->keypair, &config->error, + staple_file); +} + +int +tls_config_set_ocsp_staple_mem(struct tls_config *config, const uint8_t *staple, + size_t len) +{ + return tls_keypair_set_ocsp_staple_mem(config->keypair, &config->error, + staple, len); +} + +int +tls_config_set_session_id(struct tls_config *config, + const unsigned char *session_id, size_t len) +{ + if (len > TLS_MAX_SESSION_ID_LENGTH) { + tls_config_set_errorx(config, "session ID too large"); + return (-1); + } + memset(config->session_id, 0, sizeof(config->session_id)); + memcpy(config->session_id, session_id, len); + return (0); +} + +int +tls_config_set_session_lifetime(struct tls_config *config, int lifetime) +{ + if (lifetime > TLS_MAX_SESSION_TIMEOUT) { + tls_config_set_errorx(config, "session lifetime too large"); + return (-1); + } + if (lifetime != 0 && lifetime < TLS_MIN_SESSION_TIMEOUT) { + tls_config_set_errorx(config, "session lifetime too small"); + return (-1); + } + + config->session_lifetime = lifetime; + return (0); +} + +int +tls_config_add_ticket_key(struct tls_config *config, uint32_t keyrev, + unsigned char *key, size_t keylen) +{ + struct tls_ticket_key newkey; + int i; + + if (TLS_TICKET_KEY_SIZE != keylen || + sizeof(newkey.aes_key) + sizeof(newkey.hmac_key) > keylen) { + tls_config_set_errorx(config, + "wrong amount of ticket key data"); + return (-1); + } + + keyrev = htonl(keyrev); + memset(&newkey, 0, sizeof(newkey)); + memcpy(newkey.key_name, &keyrev, sizeof(keyrev)); + memcpy(newkey.aes_key, key, sizeof(newkey.aes_key)); + memcpy(newkey.hmac_key, key + sizeof(newkey.aes_key), + sizeof(newkey.hmac_key)); + newkey.time = time(NULL); + + for (i = 0; i < TLS_NUM_TICKETS; i++) { + struct tls_ticket_key *tk = &config->ticket_keys[i]; + if (memcmp(newkey.key_name, tk->key_name, + sizeof(tk->key_name)) != 0) + continue; + + /* allow re-entry of most recent key */ + if (i == 0 && memcmp(newkey.aes_key, tk->aes_key, + sizeof(tk->aes_key)) == 0 && memcmp(newkey.hmac_key, + tk->hmac_key, sizeof(tk->hmac_key)) == 0) + return (0); + tls_config_set_errorx(config, "ticket key already present"); + return (-1); + } + + memmove(&config->ticket_keys[1], &config->ticket_keys[0], + sizeof(config->ticket_keys) - sizeof(config->ticket_keys[0])); + config->ticket_keys[0] = newkey; + + config->ticket_autorekey = 0; + + return (0); +} + +int +tls_config_ticket_autorekey(struct tls_config *config) +{ + unsigned char key[TLS_TICKET_KEY_SIZE]; + int rv; + + arc4random_buf(key, sizeof(key)); + rv = tls_config_add_ticket_key(config, config->ticket_keyrev++, key, + sizeof(key)); + config->ticket_autorekey = 1; + return (rv); +} diff --git a/compat/libtls/tls_conninfo.c b/compat/libtls/tls_conninfo.c new file mode 100644 index 0000000..ac6df2f --- /dev/null +++ b/compat/libtls/tls_conninfo.c @@ -0,0 +1,346 @@ +/* $OpenBSD: tls_conninfo.c,v 1.23 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2015 Joel Sing <jsing@openbsd.org> + * Copyright (c) 2015 Bob Beck <beck@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> +#include <string.h> + +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +int ASN1_time_tm_clamp_notafter(struct tm *tm); + +int +tls_hex_string(const unsigned char *in, size_t inlen, char **out, + size_t *outlen) +{ + static const char hex[] = "0123456789abcdef"; + size_t i, len; + char *p; + + if (outlen != NULL) + *outlen = 0; + + if (inlen >= SIZE_MAX) + return (-1); + if ((*out = reallocarray(NULL, inlen + 1, 2)) == NULL) + return (-1); + + p = *out; + len = 0; + for (i = 0; i < inlen; i++) { + p[len++] = hex[(in[i] >> 4) & 0x0f]; + p[len++] = hex[in[i] & 0x0f]; + } + p[len++] = 0; + + if (outlen != NULL) + *outlen = len; + + return (0); +} + +static int +tls_get_peer_cert_hash(struct tls *ctx, char **hash) +{ + *hash = NULL; + if (ctx->ssl_peer_cert == NULL) + return (0); + + if (tls_cert_hash(ctx->ssl_peer_cert, hash) == -1) { + tls_set_errorx(ctx, "unable to compute peer certificate hash - out of memory"); + *hash = NULL; + return -1; + } + return 0; +} + +static int +tls_get_peer_cert_issuer(struct tls *ctx, char **issuer) +{ + X509_NAME *name = NULL; + + *issuer = NULL; + if (ctx->ssl_peer_cert == NULL) + return (-1); + if ((name = X509_get_issuer_name(ctx->ssl_peer_cert)) == NULL) + return (-1); + *issuer = X509_NAME_oneline(name, 0, 0); + if (*issuer == NULL) + return (-1); + return (0); +} + +static int +tls_get_peer_cert_subject(struct tls *ctx, char **subject) +{ + X509_NAME *name = NULL; + + *subject = NULL; + if (ctx->ssl_peer_cert == NULL) + return (-1); + if ((name = X509_get_subject_name(ctx->ssl_peer_cert)) == NULL) + return (-1); + *subject = X509_NAME_oneline(name, 0, 0); + if (*subject == NULL) + return (-1); + return (0); +} + +static int +tls_get_peer_cert_times(struct tls *ctx, time_t *notbefore, + time_t *notafter) +{ + struct tm before_tm, after_tm; + ASN1_TIME *before, *after; + + if (ctx->ssl_peer_cert == NULL) + return (-1); + + if ((before = X509_get_notBefore(ctx->ssl_peer_cert)) == NULL) + goto err; + if ((after = X509_get_notAfter(ctx->ssl_peer_cert)) == NULL) + goto err; + if (ASN1_time_parse(before->data, before->length, &before_tm, 0) == -1) + goto err; + if (ASN1_time_parse(after->data, after->length, &after_tm, 0) == -1) + goto err; + if (!ASN1_time_tm_clamp_notafter(&after_tm)) + goto err; + if ((*notbefore = timegm(&before_tm)) == -1) + goto err; + if ((*notafter = timegm(&after_tm)) == -1) + goto err; + + return (0); + + err: + return (-1); +} + +static int +tls_get_peer_cert_info(struct tls *ctx) +{ + if (ctx->ssl_peer_cert == NULL) + return (0); + + if (tls_get_peer_cert_hash(ctx, &ctx->conninfo->hash) == -1) + goto err; + if (tls_get_peer_cert_subject(ctx, &ctx->conninfo->subject) == -1) + goto err; + if (tls_get_peer_cert_issuer(ctx, &ctx->conninfo->issuer) == -1) + goto err; + if (tls_get_peer_cert_times(ctx, &ctx->conninfo->notbefore, + &ctx->conninfo->notafter) == -1) + goto err; + + return (0); + + err: + return (-1); +} + +static int +tls_conninfo_alpn_proto(struct tls *ctx) +{ + const unsigned char *p; + unsigned int len; + + free(ctx->conninfo->alpn); + ctx->conninfo->alpn = NULL; + + SSL_get0_alpn_selected(ctx->ssl_conn, &p, &len); + if (len > 0) { + if ((ctx->conninfo->alpn = malloc(len + 1)) == NULL) + return (-1); + memcpy(ctx->conninfo->alpn, p, len); + ctx->conninfo->alpn[len] = '\0'; + } + + return (0); +} + +static int +tls_conninfo_cert_pem(struct tls *ctx) +{ + int i, rv = -1; + BIO *membio = NULL; + BUF_MEM *bptr = NULL; + + if (ctx->ssl_peer_cert == NULL) + return 0; + if ((membio = BIO_new(BIO_s_mem()))== NULL) + goto err; + + /* + * We have to write the peer cert out separately, because + * the certificate chain may or may not contain it. + */ + if (!PEM_write_bio_X509(membio, ctx->ssl_peer_cert)) + goto err; + for (i = 0; i < sk_X509_num(ctx->ssl_peer_chain); i++) { + X509 *chaincert = sk_X509_value(ctx->ssl_peer_chain, i); + if (chaincert != ctx->ssl_peer_cert && + !PEM_write_bio_X509(membio, chaincert)) + goto err; + } + + BIO_get_mem_ptr(membio, &bptr); + free(ctx->conninfo->peer_cert); + ctx->conninfo->peer_cert_len = 0; + if ((ctx->conninfo->peer_cert = malloc(bptr->length)) == NULL) + goto err; + ctx->conninfo->peer_cert_len = bptr->length; + memcpy(ctx->conninfo->peer_cert, bptr->data, + ctx->conninfo->peer_cert_len); + + /* BIO_free() will kill BUF_MEM - because we have not set BIO_NOCLOSE */ + rv = 0; + err: + BIO_free(membio); + return rv; +} + +static int +tls_conninfo_session(struct tls *ctx) +{ + ctx->conninfo->session_resumed = SSL_session_reused(ctx->ssl_conn); + + return 0; +} + +int +tls_conninfo_populate(struct tls *ctx) +{ + const char *tmp; + + tls_conninfo_free(ctx->conninfo); + + if ((ctx->conninfo = calloc(1, sizeof(struct tls_conninfo))) == NULL) { + tls_set_errorx(ctx, "out of memory"); + goto err; + } + + if (tls_conninfo_alpn_proto(ctx) == -1) + goto err; + + if ((tmp = SSL_get_cipher(ctx->ssl_conn)) == NULL) + goto err; + if ((ctx->conninfo->cipher = strdup(tmp)) == NULL) + goto err; + ctx->conninfo->cipher_strength = SSL_get_cipher_bits(ctx->ssl_conn, NULL); + + if (ctx->servername != NULL) { + if ((ctx->conninfo->servername = + strdup(ctx->servername)) == NULL) + goto err; + } + + if ((tmp = SSL_get_version(ctx->ssl_conn)) == NULL) + goto err; + if ((ctx->conninfo->version = strdup(tmp)) == NULL) + goto err; + + if (tls_get_peer_cert_info(ctx) == -1) + goto err; + + if (tls_conninfo_cert_pem(ctx) == -1) + goto err; + + if (tls_conninfo_session(ctx) == -1) + goto err; + + return (0); + + err: + tls_conninfo_free(ctx->conninfo); + ctx->conninfo = NULL; + + return (-1); +} + +void +tls_conninfo_free(struct tls_conninfo *conninfo) +{ + if (conninfo == NULL) + return; + + free(conninfo->alpn); + free(conninfo->cipher); + free(conninfo->servername); + free(conninfo->version); + + free(conninfo->hash); + free(conninfo->issuer); + free(conninfo->subject); + + free(conninfo->peer_cert); + + free(conninfo); +} + +const char * +tls_conn_alpn_selected(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->alpn); +} + +const char * +tls_conn_cipher(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->cipher); +} + +int +tls_conn_cipher_strength(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (0); + return (ctx->conninfo->cipher_strength); +} + +const char * +tls_conn_servername(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->servername); +} + +int +tls_conn_session_resumed(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (0); + return (ctx->conninfo->session_resumed); +} + +const char * +tls_conn_version(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->version); +} diff --git a/compat/libtls/tls_internal.h b/compat/libtls/tls_internal.h new file mode 100644 index 0000000..4846a88 --- /dev/null +++ b/compat/libtls/tls_internal.h @@ -0,0 +1,300 @@ +/* $OpenBSD: tls_internal.h,v 1.82 2023/06/18 11:43:03 op 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 <arpa/inet.h> +#include <netinet/in.h> + +#include <openssl/ssl.h> + +#define TLS_CIPHERS_DEFAULT TLS_CIPHERS_COMPAT +#define TLS_CIPHERS_COMPAT "HIGH:!aNULL" +#define TLS_CIPHERS_LEGACY "HIGH:MEDIUM:!aNULL" +#define TLS_CIPHERS_ALL "ALL:!aNULL:!eNULL" + +#define TLS_ECDHE_CURVES "X25519,P-256,P-384" + +union tls_addr { + struct in_addr ip4; + struct in6_addr ip6; +}; + +struct tls_error { + char *msg; + int num; + int tls; +}; + +struct tls_keypair { + struct tls_keypair *next; + + char *cert_mem; + size_t cert_len; + char *key_mem; + size_t key_len; + char *ocsp_staple; + size_t ocsp_staple_len; + char *pubkey_hash; +}; + +#define TLS_MIN_SESSION_TIMEOUT (4) +#define TLS_MAX_SESSION_TIMEOUT (24 * 60 * 60) + +#define TLS_NUM_TICKETS 4 +#define TLS_TICKET_NAME_SIZE 16 +#define TLS_TICKET_AES_SIZE 32 +#define TLS_TICKET_HMAC_SIZE 16 + +struct tls_ticket_key { + /* The key_name must be 16 bytes according to -lssl */ + unsigned char key_name[TLS_TICKET_NAME_SIZE]; + unsigned char aes_key[TLS_TICKET_AES_SIZE]; + unsigned char hmac_key[TLS_TICKET_HMAC_SIZE]; + time_t time; +}; + +struct tls_config { + struct tls_error error; + + int refcount; + + char *alpn; + size_t alpn_len; + const char *ca_path; + char *ca_mem; + size_t ca_len; + const char *ciphers; + int ciphers_server; + char *crl_mem; + size_t crl_len; + int dheparams; + int *ecdhecurves; + size_t ecdhecurves_len; + struct tls_keypair *keypair; + int ocsp_require_stapling; + uint32_t protocols; + unsigned char session_id[TLS_MAX_SESSION_ID_LENGTH]; + int session_fd; + int session_lifetime; + struct tls_ticket_key ticket_keys[TLS_NUM_TICKETS]; + uint32_t ticket_keyrev; + int ticket_autorekey; + int verify_cert; + int verify_client; + int verify_depth; + int verify_name; + int verify_time; + int skip_private_key_check; + int use_fake_private_key; +}; + +struct tls_conninfo { + char *alpn; + char *cipher; + int cipher_strength; + char *servername; + int session_resumed; + char *version; + + char *hash; + char *issuer; + char *subject; + + uint8_t *peer_cert; + size_t peer_cert_len; + + time_t notbefore; + time_t notafter; +}; + +#define TLS_CLIENT (1 << 0) +#define TLS_SERVER (1 << 1) +#define TLS_SERVER_CONN (1 << 2) + +#define TLS_EOF_NO_CLOSE_NOTIFY (1 << 0) +#define TLS_CONNECTED (1 << 1) +#define TLS_HANDSHAKE_COMPLETE (1 << 2) +#define TLS_SSL_NEEDS_SHUTDOWN (1 << 3) + +struct tls_ocsp_result { + const char *result_msg; + int response_status; + int cert_status; + int crl_reason; + time_t this_update; + time_t next_update; + time_t revocation_time; +}; + +struct tls_ocsp { + /* responder location */ + char *ocsp_url; + + /* cert data, this struct does not own these */ + X509 *main_cert; + STACK_OF(X509) *extra_certs; + + struct tls_ocsp_result *ocsp_result; +}; + +struct tls_sni_ctx { + struct tls_sni_ctx *next; + + struct tls_keypair *keypair; + + SSL_CTX *ssl_ctx; + X509 *ssl_cert; +}; + +struct tls { + struct tls_config *config; + struct tls_keypair *keypair; + + struct tls_error error; + + uint32_t flags; + uint32_t state; + + char *servername; + int socket; + + SSL *ssl_conn; + SSL_CTX *ssl_ctx; + + struct tls_sni_ctx *sni_ctx; + + X509 *ssl_peer_cert; + STACK_OF(X509) *ssl_peer_chain; + + struct tls_conninfo *conninfo; + + struct tls_ocsp *ocsp; + + tls_read_cb read_cb; + tls_write_cb write_cb; + void *cb_arg; +}; + +int tls_set_mem(char **_dest, size_t *_destlen, const void *_src, + size_t _srclen); +int tls_set_string(const char **_dest, const char *_src); + +struct tls_keypair *tls_keypair_new(void); +void tls_keypair_clear_key(struct tls_keypair *_keypair); +void tls_keypair_free(struct tls_keypair *_keypair); +int tls_keypair_set_cert_file(struct tls_keypair *_keypair, + struct tls_error *_error, const char *_cert_file); +int tls_keypair_set_cert_mem(struct tls_keypair *_keypair, + struct tls_error *_error, const uint8_t *_cert, size_t _len); +int tls_keypair_set_key_file(struct tls_keypair *_keypair, + struct tls_error *_error, const char *_key_file); +int tls_keypair_set_key_mem(struct tls_keypair *_keypair, + struct tls_error *_error, const uint8_t *_key, size_t _len); +int tls_keypair_set_ocsp_staple_file(struct tls_keypair *_keypair, + struct tls_error *_error, const char *_ocsp_file); +int tls_keypair_set_ocsp_staple_mem(struct tls_keypair *_keypair, + struct tls_error *_error, const uint8_t *_staple, size_t _len); +int tls_keypair_load_cert(struct tls_keypair *_keypair, + struct tls_error *_error, X509 **_cert); + +struct tls_sni_ctx *tls_sni_ctx_new(void); +void tls_sni_ctx_free(struct tls_sni_ctx *sni_ctx); + +struct tls_config *tls_config_new_internal(void); + +struct tls *tls_new(void); +struct tls *tls_server_conn(struct tls *ctx); + +int tls_check_name(struct tls *ctx, X509 *cert, const char *servername, + int *match); +int tls_configure_server(struct tls *ctx); + +int tls_configure_ssl(struct tls *ctx, SSL_CTX *ssl_ctx); +int tls_configure_ssl_keypair(struct tls *ctx, SSL_CTX *ssl_ctx, + struct tls_keypair *keypair, int required); +int tls_configure_ssl_verify(struct tls *ctx, SSL_CTX *ssl_ctx, int verify); + +int tls_handshake_client(struct tls *ctx); +int tls_handshake_server(struct tls *ctx); + +int tls_config_load_file(struct tls_error *error, const char *filetype, + const char *filename, char **buf, size_t *len); +int tls_config_ticket_autorekey(struct tls_config *config); +int tls_host_port(const char *hostport, char **host, char **port); + +int tls_set_cbs(struct tls *ctx, + tls_read_cb read_cb, tls_write_cb write_cb, void *cb_arg); + +void tls_error_clear(struct tls_error *error); +int tls_error_set(struct tls_error *error, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_error_setx(struct tls_error *error, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_config_set_error(struct tls_config *cfg, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_config_set_errorx(struct tls_config *cfg, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_set_error(struct tls *ctx, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_set_errorx(struct tls *ctx, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); +int tls_set_ssl_errorx(struct tls *ctx, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))) + __attribute__((__nonnull__ (2))); + +int tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret, + const char *prefix); + +int tls_conninfo_populate(struct tls *ctx); +void tls_conninfo_free(struct tls_conninfo *conninfo); + +int tls_ocsp_verify_cb(SSL *ssl, void *arg); +int tls_ocsp_stapling_cb(SSL *ssl, void *arg); +void tls_ocsp_free(struct tls_ocsp *ctx); +struct tls_ocsp *tls_ocsp_setup_from_peer(struct tls *ctx); +int tls_hex_string(const unsigned char *_in, size_t _inlen, char **_out, + size_t *_outlen); +int tls_cert_hash(X509 *_cert, char **_hash); +int tls_cert_pubkey_hash(X509 *_cert, char **_hash); + +int tls_password_cb(char *_buf, int _size, int _rwflag, void *_u); + +/* XXX this function is not fully hidden so relayd can use it */ +void tls_config_skip_private_key_check(struct tls_config *config); +void tls_config_use_fake_private_key(struct tls_config *config); + +/* XXX prototypes brought for OpenSMTPD libtls wrapper to OpenSSL */ +int ASN1_time_parse(const char *bytes, size_t len, struct tm *tm, int mode); + +#ifndef HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM +int SSL_CTX_use_certificate_chain_mem(SSL_CTX *, void *, int); +#endif + +#ifndef HAVE_SSL_CTX_LOAD_VERIFY_MEM +int SSL_CTX_load_verify_mem(SSL_CTX *, void *, int); +#endif + +#endif /* HEADER_TLS_INTERNAL_H */ diff --git a/compat/libtls/tls_keypair.c b/compat/libtls/tls_keypair.c new file mode 100644 index 0000000..8f62f53 --- /dev/null +++ b/compat/libtls/tls_keypair.c @@ -0,0 +1,171 @@ +/* $OpenBSD: tls_keypair.c,v 1.8 2021/01/05 17:37:12 jsing Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/pem.h> + +#include <tls.h> + +#include "tls_internal.h" + +struct tls_keypair * +tls_keypair_new(void) +{ + return calloc(1, sizeof(struct tls_keypair)); +} + +static int +tls_keypair_pubkey_hash(struct tls_keypair *keypair, struct tls_error *error) +{ + X509 *cert = NULL; + int rv = -1; + + free(keypair->pubkey_hash); + keypair->pubkey_hash = NULL; + + if (keypair->cert_mem == NULL) { + rv = 0; + goto done; + } + + if (tls_keypair_load_cert(keypair, error, &cert) == -1) + goto err; + if (tls_cert_pubkey_hash(cert, &keypair->pubkey_hash) == -1) + goto err; + + rv = 0; + + err: + X509_free(cert); + done: + return (rv); +} + +void +tls_keypair_clear_key(struct tls_keypair *keypair) +{ + freezero(keypair->key_mem, keypair->key_len); + keypair->key_mem = NULL; + keypair->key_len = 0; +} + +int +tls_keypair_set_cert_file(struct tls_keypair *keypair, struct tls_error *error, + const char *cert_file) +{ + if (tls_config_load_file(error, "certificate", cert_file, + &keypair->cert_mem, &keypair->cert_len) == -1) + return -1; + return tls_keypair_pubkey_hash(keypair, error); +} + +int +tls_keypair_set_cert_mem(struct tls_keypair *keypair, struct tls_error *error, + const uint8_t *cert, size_t len) +{ + if (tls_set_mem(&keypair->cert_mem, &keypair->cert_len, cert, len) == -1) + return -1; + return tls_keypair_pubkey_hash(keypair, error); +} + +int +tls_keypair_set_key_file(struct tls_keypair *keypair, struct tls_error *error, + const char *key_file) +{ + tls_keypair_clear_key(keypair); + return tls_config_load_file(error, "key", key_file, + &keypair->key_mem, &keypair->key_len); +} + +int +tls_keypair_set_key_mem(struct tls_keypair *keypair, struct tls_error *error, + const uint8_t *key, size_t len) +{ + tls_keypair_clear_key(keypair); + return tls_set_mem(&keypair->key_mem, &keypair->key_len, key, len); +} + +int +tls_keypair_set_ocsp_staple_file(struct tls_keypair *keypair, + struct tls_error *error, const char *ocsp_file) +{ + return tls_config_load_file(error, "ocsp", ocsp_file, + &keypair->ocsp_staple, &keypair->ocsp_staple_len); +} + +int +tls_keypair_set_ocsp_staple_mem(struct tls_keypair *keypair, + struct tls_error *error, const uint8_t *staple, size_t len) +{ + return tls_set_mem(&keypair->ocsp_staple, &keypair->ocsp_staple_len, + staple, len); +} + +void +tls_keypair_free(struct tls_keypair *keypair) +{ + if (keypair == NULL) + return; + + tls_keypair_clear_key(keypair); + + free(keypair->cert_mem); + free(keypair->ocsp_staple); + free(keypair->pubkey_hash); + + free(keypair); +} + +int +tls_keypair_load_cert(struct tls_keypair *keypair, struct tls_error *error, + X509 **cert) +{ + char *errstr = "unknown"; + BIO *cert_bio = NULL; + unsigned long ssl_err; + int rv = -1; + + X509_free(*cert); + *cert = NULL; + + if (keypair->cert_mem == NULL) { + tls_error_set(error, "keypair has no certificate"); + goto err; + } + if ((cert_bio = BIO_new_mem_buf(keypair->cert_mem, + keypair->cert_len)) == NULL) { + tls_error_set(error, "failed to create certificate bio"); + goto err; + } + if ((*cert = PEM_read_bio_X509(cert_bio, NULL, tls_password_cb, + NULL)) == NULL) { + if ((ssl_err = ERR_peek_error()) != 0) + errstr = ERR_error_string(ssl_err, NULL); + tls_error_set(error, "failed to load certificate: %s", errstr); + goto err; + } + + rv = 0; + + err: + BIO_free(cert_bio); + + return (rv); +} diff --git a/compat/libtls/tls_ocsp.c b/compat/libtls/tls_ocsp.c new file mode 100644 index 0000000..ada3362 --- /dev/null +++ b/compat/libtls/tls_ocsp.c @@ -0,0 +1,465 @@ +/* $OpenBSD: tls_ocsp.c,v 1.23 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2015 Marko Kreen <markokr@gmail.com> + * Copyright (c) 2016 Bob Beck <beck@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/types.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <string.h> + +#include <openssl/err.h> +#include <openssl/ocsp.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +#define MAXAGE_SEC (14*24*60*60) +#define JITTER_SEC (60) + +/* + * State for request. + */ + +static struct tls_ocsp * +tls_ocsp_new(void) +{ + return (calloc(1, sizeof(struct tls_ocsp))); +} + +void +tls_ocsp_free(struct tls_ocsp *ocsp) +{ + if (ocsp == NULL) + return; + + X509_free(ocsp->main_cert); + free(ocsp->ocsp_result); + free(ocsp->ocsp_url); + + free(ocsp); +} + +static int +tls_ocsp_asn1_parse_time(struct tls *ctx, ASN1_GENERALIZEDTIME *gt, time_t *gt_time) +{ + struct tm tm; + + if (gt == NULL) + return -1; + /* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */ + if (ASN1_time_parse(gt->data, gt->length, &tm, + V_ASN1_GENERALIZEDTIME) == -1) + return -1; + if ((*gt_time = timegm(&tm)) == -1) + return -1; + return 0; +} + +static int +tls_ocsp_fill_info(struct tls *ctx, int response_status, int cert_status, + int crl_reason, ASN1_GENERALIZEDTIME *revtime, + ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd) +{ + struct tls_ocsp_result *info = NULL; + + free(ctx->ocsp->ocsp_result); + ctx->ocsp->ocsp_result = NULL; + + if ((info = calloc(1, sizeof (struct tls_ocsp_result))) == NULL) { + tls_set_error(ctx, "calloc"); + return -1; + } + info->response_status = response_status; + info->cert_status = cert_status; + info->crl_reason = crl_reason; + if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + info->result_msg = + OCSP_response_status_str(info->response_status); + } else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) { + info->result_msg = OCSP_cert_status_str(info->cert_status); + } else { + info->result_msg = OCSP_crl_reason_str(info->crl_reason); + } + info->revocation_time = info->this_update = info->next_update = -1; + if (revtime != NULL && + tls_ocsp_asn1_parse_time(ctx, revtime, &info->revocation_time) != 0) { + tls_set_error(ctx, + "unable to parse revocation time in OCSP reply"); + goto err; + } + if (thisupd != NULL && + tls_ocsp_asn1_parse_time(ctx, thisupd, &info->this_update) != 0) { + tls_set_error(ctx, + "unable to parse this update time in OCSP reply"); + goto err; + } + if (nextupd != NULL && + tls_ocsp_asn1_parse_time(ctx, nextupd, &info->next_update) != 0) { + tls_set_error(ctx, + "unable to parse next update time in OCSP reply"); + goto err; + } + ctx->ocsp->ocsp_result = info; + return 0; + + err: + free(info); + return -1; +} + +static OCSP_CERTID * +tls_ocsp_get_certid(X509 *main_cert, STACK_OF(X509) *extra_certs, + SSL_CTX *ssl_ctx) +{ + X509_NAME *issuer_name; + X509 *issuer; + X509_STORE_CTX *storectx = NULL; + X509_OBJECT *obj = NULL; + OCSP_CERTID *cid = NULL; + X509_STORE *store; + + if ((issuer_name = X509_get_issuer_name(main_cert)) == NULL) + goto out; + + if (extra_certs != NULL) { + issuer = X509_find_by_subject(extra_certs, issuer_name); + if (issuer != NULL) { + cid = OCSP_cert_to_id(NULL, main_cert, issuer); + goto out; + } + } + + if ((store = SSL_CTX_get_cert_store(ssl_ctx)) == NULL) + goto out; + if ((storectx = X509_STORE_CTX_new()) == NULL) + goto out; + if (X509_STORE_CTX_init(storectx, store, main_cert, extra_certs) != 1) + goto out; + if ((obj = X509_STORE_CTX_get_obj_by_subject(storectx, X509_LU_X509, + issuer_name)) == NULL) + goto out; + + cid = OCSP_cert_to_id(NULL, main_cert, X509_OBJECT_get0_X509(obj)); + + out: + X509_STORE_CTX_free(storectx); + X509_OBJECT_free(obj); + + return cid; +} + +struct tls_ocsp * +tls_ocsp_setup_from_peer(struct tls *ctx) +{ + struct tls_ocsp *ocsp = NULL; + STACK_OF(OPENSSL_STRING) *ocsp_urls = NULL; + + if ((ocsp = tls_ocsp_new()) == NULL) + goto err; + + /* steal state from ctx struct */ + ocsp->main_cert = SSL_get_peer_certificate(ctx->ssl_conn); + ocsp->extra_certs = SSL_get_peer_cert_chain(ctx->ssl_conn); + if (ocsp->main_cert == NULL) { + tls_set_errorx(ctx, "no peer certificate for OCSP"); + goto err; + } + + ocsp_urls = X509_get1_ocsp(ocsp->main_cert); + if (ocsp_urls == NULL) { + tls_set_errorx(ctx, "no OCSP URLs in peer certificate"); + goto err; + } + + ocsp->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0)); + if (ocsp->ocsp_url == NULL) { + tls_set_errorx(ctx, "out of memory"); + goto err; + } + + X509_email_free(ocsp_urls); + return ocsp; + + err: + tls_ocsp_free(ocsp); + X509_email_free(ocsp_urls); + return NULL; +} + +static int +tls_ocsp_verify_response(struct tls *ctx, OCSP_RESPONSE *resp) +{ + OCSP_BASICRESP *br = NULL; + ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; + OCSP_CERTID *cid = NULL; + STACK_OF(X509) *combined = NULL; + int response_status=0, cert_status=0, crl_reason=0; + int ret = -1; + unsigned long flags; + + if ((br = OCSP_response_get1_basic(resp)) == NULL) { + tls_set_errorx(ctx, "cannot load ocsp reply"); + goto err; + } + + /* + * Skip validation of 'extra_certs' as this should be done + * already as part of main handshake. + */ + flags = OCSP_TRUSTOTHER; + + /* now verify */ + if (OCSP_basic_verify(br, ctx->ocsp->extra_certs, + SSL_CTX_get_cert_store(ctx->ssl_ctx), flags) != 1) { + tls_set_errorx(ctx, "ocsp verify failed"); + goto err; + } + + /* signature OK, look inside */ + response_status = OCSP_response_status(resp); + if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + tls_set_errorx(ctx, "ocsp verify failed: response - %s", + OCSP_response_status_str(response_status)); + goto err; + } + + cid = tls_ocsp_get_certid(ctx->ocsp->main_cert, + ctx->ocsp->extra_certs, ctx->ssl_ctx); + if (cid == NULL) { + tls_set_errorx(ctx, "ocsp verify failed: no issuer cert"); + goto err; + } + + if (OCSP_resp_find_status(br, cid, &cert_status, &crl_reason, + &revtime, &thisupd, &nextupd) != 1) { + tls_set_errorx(ctx, "ocsp verify failed: no result for cert"); + goto err; + } + + if (OCSP_check_validity(thisupd, nextupd, JITTER_SEC, + MAXAGE_SEC) != 1) { + tls_set_errorx(ctx, + "ocsp verify failed: ocsp response not current"); + goto err; + } + + if (tls_ocsp_fill_info(ctx, response_status, cert_status, + crl_reason, revtime, thisupd, nextupd) != 0) + goto err; + + /* finally can look at status */ + if (cert_status != V_OCSP_CERTSTATUS_GOOD && cert_status != + V_OCSP_CERTSTATUS_UNKNOWN) { + tls_set_errorx(ctx, "ocsp verify failed: revoked cert - %s", + OCSP_crl_reason_str(crl_reason)); + goto err; + } + ret = 0; + + err: + sk_X509_free(combined); + OCSP_CERTID_free(cid); + OCSP_BASICRESP_free(br); + return ret; +} + +/* + * Process a raw OCSP response from an OCSP server request. + * OCSP details can then be retrieved with tls_peer_ocsp_* functions. + * returns 0 if certificate ok, -1 otherwise. + */ +static int +tls_ocsp_process_response_internal(struct tls *ctx, const unsigned char *response, + size_t size) +{ + int ret; + OCSP_RESPONSE *resp; + + resp = d2i_OCSP_RESPONSE(NULL, &response, size); + if (resp == NULL) { + tls_ocsp_free(ctx->ocsp); + ctx->ocsp = NULL; + tls_set_error(ctx, "unable to parse OCSP response"); + return -1; + } + ret = tls_ocsp_verify_response(ctx, resp); + OCSP_RESPONSE_free(resp); + return ret; +} + +/* TLS handshake verification callback for stapled requests */ +int +tls_ocsp_verify_cb(SSL *ssl, void *arg) +{ + const unsigned char *raw = NULL; + int size, res = -1; + struct tls *ctx; + + if ((ctx = SSL_get_app_data(ssl)) == NULL) + return -1; + + size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw); + if (size <= 0) { + if (ctx->config->ocsp_require_stapling) { + tls_set_errorx(ctx, "no stapled OCSP response provided"); + return 0; + } + return 1; + } + + tls_ocsp_free(ctx->ocsp); + if ((ctx->ocsp = tls_ocsp_setup_from_peer(ctx)) == NULL) + return 0; + + if (ctx->config->verify_cert == 0 || ctx->config->verify_time == 0) + return 1; + + res = tls_ocsp_process_response_internal(ctx, raw, size); + + return (res == 0) ? 1 : 0; +} + + +/* Staple the OCSP information in ctx->ocsp to the server handshake. */ +int +tls_ocsp_stapling_cb(SSL *ssl, void *arg) +{ + int ret = SSL_TLSEXT_ERR_ALERT_FATAL; + unsigned char *ocsp_staple = NULL; + struct tls *ctx; + + if ((ctx = SSL_get_app_data(ssl)) == NULL) + goto err; + + if (ctx->keypair == NULL || ctx->keypair->ocsp_staple == NULL || + ctx->keypair->ocsp_staple_len == 0) + return SSL_TLSEXT_ERR_NOACK; + + if ((ocsp_staple = malloc(ctx->keypair->ocsp_staple_len)) == NULL) + goto err; + + memcpy(ocsp_staple, ctx->keypair->ocsp_staple, + ctx->keypair->ocsp_staple_len); + + if (SSL_set_tlsext_status_ocsp_resp(ctx->ssl_conn, ocsp_staple, + ctx->keypair->ocsp_staple_len) != 1) + goto err; + + ret = SSL_TLSEXT_ERR_OK; + err: + if (ret != SSL_TLSEXT_ERR_OK) + free(ocsp_staple); + + return ret; +} + +/* + * Public API + */ + +/* Retrieve OCSP URL from peer certificate, if present. */ +const char * +tls_peer_ocsp_url(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return NULL; + return ctx->ocsp->ocsp_url; +} + +const char * +tls_peer_ocsp_result(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return NULL; + if (ctx->ocsp->ocsp_result == NULL) + return NULL; + return ctx->ocsp->ocsp_result->result_msg; +} + +int +tls_peer_ocsp_response_status(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->response_status; +} + +int +tls_peer_ocsp_cert_status(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->cert_status; +} + +int +tls_peer_ocsp_crl_reason(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->crl_reason; +} + +time_t +tls_peer_ocsp_this_update(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->this_update; +} + +time_t +tls_peer_ocsp_next_update(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->next_update; +} + +time_t +tls_peer_ocsp_revocation_time(struct tls *ctx) +{ + if (ctx->ocsp == NULL) + return -1; + if (ctx->ocsp->ocsp_result == NULL) + return -1; + return ctx->ocsp->ocsp_result->revocation_time; +} + +int +tls_ocsp_process_response(struct tls *ctx, const unsigned char *response, + size_t size) +{ + if ((ctx->state & TLS_HANDSHAKE_COMPLETE) == 0) + return -1; + return tls_ocsp_process_response_internal(ctx, response, size); +} diff --git a/compat/libtls/tls_peer.c b/compat/libtls/tls_peer.c new file mode 100644 index 0000000..d5c550a --- /dev/null +++ b/compat/libtls/tls_peer.c @@ -0,0 +1,101 @@ +/* $OpenBSD: tls_peer.c,v 1.8 2017/04/10 17:11:13 jsing Exp $ */ +/* + * Copyright (c) 2015 Joel Sing <jsing@openbsd.org> + * Copyright (c) 2015 Bob Beck <beck@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> + +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +const char * +tls_peer_cert_hash(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->hash); +} +const char * +tls_peer_cert_issuer(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->issuer); +} + +const char * +tls_peer_cert_subject(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->subject); +} + +int +tls_peer_cert_provided(struct tls *ctx) +{ + return (ctx->ssl_peer_cert != NULL); +} + +int +tls_peer_cert_contains_name(struct tls *ctx, const char *name) +{ + int match; + + if (ctx->ssl_peer_cert == NULL) + return (0); + + if (tls_check_name(ctx, ctx->ssl_peer_cert, name, &match) == -1) + return (0); + + return (match); +} + +time_t +tls_peer_cert_notbefore(struct tls *ctx) +{ + if (ctx->ssl_peer_cert == NULL) + return (-1); + if (ctx->conninfo == NULL) + return (-1); + return (ctx->conninfo->notbefore); +} + +time_t +tls_peer_cert_notafter(struct tls *ctx) +{ + if (ctx->ssl_peer_cert == NULL) + return (-1); + if (ctx->conninfo == NULL) + return (-1); + return (ctx->conninfo->notafter); +} + +const uint8_t * +tls_peer_cert_chain_pem(struct tls *ctx, size_t *size) +{ + if (ctx->ssl_peer_cert == NULL) + return (NULL); + if (ctx->conninfo == NULL) + return (NULL); + *size = ctx->conninfo->peer_cert_len; + return (ctx->conninfo->peer_cert); +} + diff --git a/compat/libtls/tls_server.c b/compat/libtls/tls_server.c new file mode 100644 index 0000000..b374012 --- /dev/null +++ b/compat/libtls/tls_server.c @@ -0,0 +1,471 @@ +/* $OpenBSD: tls_server.c,v 1.49 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/socket.h> + +#include <arpa/inet.h> + +#include <string.h> + +#include <openssl/asn1.h> +#include <openssl/ec.h> +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include <tls.h> +#include "tls_internal.h" + +struct tls * +tls_server(void) +{ + struct tls *ctx; + + if (tls_init() == -1) + return (NULL); + + if ((ctx = tls_new()) == NULL) + return (NULL); + + ctx->flags |= TLS_SERVER; + + return (ctx); +} + +struct tls * +tls_server_conn(struct tls *ctx) +{ + struct tls *conn_ctx; + + if ((conn_ctx = tls_new()) == NULL) + return (NULL); + + conn_ctx->flags |= TLS_SERVER_CONN; + + ctx->config->refcount++; + + conn_ctx->config = ctx->config; + conn_ctx->keypair = ctx->config->keypair; + + return (conn_ctx); +} + +static int +tls_server_alpn_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + struct tls *ctx = arg; + + if (SSL_select_next_proto((unsigned char**)out, outlen, + ctx->config->alpn, ctx->config->alpn_len, in, inlen) == + OPENSSL_NPN_NEGOTIATED) + return (SSL_TLSEXT_ERR_OK); + + return (SSL_TLSEXT_ERR_NOACK); +} + +static int +tls_servername_cb(SSL *ssl, int *al, void *arg) +{ + struct tls *ctx = (struct tls *)arg; + struct tls_sni_ctx *sni_ctx; + union tls_addr addrbuf; + struct tls *conn_ctx; + const char *name; + int match; + + if ((conn_ctx = SSL_get_app_data(ssl)) == NULL) + goto err; + + if ((name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) == + NULL) { + /* + * The servername callback gets called even when there is no + * TLS servername extension provided by the client. Sigh! + */ + return (SSL_TLSEXT_ERR_NOACK); + } + + /* + * Per RFC 6066 section 3: ensure that name is not an IP literal. + * + * While we should treat this as an error, a number of clients + * (Python, Ruby and Safari) are not RFC compliant. To avoid handshake + * failures, pretend that we did not receive the extension. + */ + if (inet_pton(AF_INET, name, &addrbuf) == 1 || + inet_pton(AF_INET6, name, &addrbuf) == 1) + return (SSL_TLSEXT_ERR_NOACK); + + free(conn_ctx->servername); + if ((conn_ctx->servername = strdup(name)) == NULL) + goto err; + + /* Find appropriate SSL context for requested servername. */ + for (sni_ctx = ctx->sni_ctx; sni_ctx != NULL; sni_ctx = sni_ctx->next) { + if (tls_check_name(ctx, sni_ctx->ssl_cert, name, + &match) == -1) + goto err; + if (match) { + conn_ctx->keypair = sni_ctx->keypair; + SSL_set_SSL_CTX(conn_ctx->ssl_conn, sni_ctx->ssl_ctx); + return (SSL_TLSEXT_ERR_OK); + } + } + + /* No match, use the existing context/certificate. */ + return (SSL_TLSEXT_ERR_OK); + + err: + /* + * There is no way to tell libssl that an internal failure occurred. + * The only option we have is to return a fatal alert. + */ + *al = SSL_AD_INTERNAL_ERROR; + return (SSL_TLSEXT_ERR_ALERT_FATAL); +} + +static struct tls_ticket_key * +tls_server_ticket_key(struct tls_config *config, unsigned char *keyname) +{ + struct tls_ticket_key *key = NULL; + time_t now; + int i; + + now = time(NULL); + if (config->ticket_autorekey == 1) { + if (now - 3 * (config->session_lifetime / 4) > + config->ticket_keys[0].time) { + if (tls_config_ticket_autorekey(config) == -1) + return (NULL); + } + } + for (i = 0; i < TLS_NUM_TICKETS; i++) { + struct tls_ticket_key *tk = &config->ticket_keys[i]; + if (now - config->session_lifetime > tk->time) + continue; + if (keyname == NULL || timingsafe_memcmp(keyname, + tk->key_name, sizeof(tk->key_name)) == 0) { + key = tk; + break; + } + } + return (key); +} + +static int +tls_server_ticket_cb(SSL *ssl, unsigned char *keyname, unsigned char *iv, + EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int mode) +{ + struct tls_ticket_key *key; + struct tls *tls_ctx; + + if ((tls_ctx = SSL_get_app_data(ssl)) == NULL) + return (-1); + + if (mode == 1) { + /* create new session */ + key = tls_server_ticket_key(tls_ctx->config, NULL); + if (key == NULL) { + tls_set_errorx(tls_ctx, "no valid ticket key found"); + return (-1); + } + + memcpy(keyname, key->key_name, sizeof(key->key_name)); + arc4random_buf(iv, EVP_MAX_IV_LENGTH); + if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, + key->aes_key, iv)) { + tls_set_errorx(tls_ctx, "failed to init encrypt"); + return (-1); + } + if (!HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + EVP_sha256(), NULL)) { + tls_set_errorx(tls_ctx, "failed to init hmac"); + return (-1); + } + return (0); + } else { + /* get key by name */ + key = tls_server_ticket_key(tls_ctx->config, keyname); + if (key == NULL) + return (0); + + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, + key->aes_key, iv)) { + tls_set_errorx(tls_ctx, "failed to init decrypt"); + return (-1); + } + if (!HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + EVP_sha256(), NULL)) { + tls_set_errorx(tls_ctx, "failed to init hmac"); + return (-1); + } + + /* time to renew the ticket? is it the primary key? */ + if (key != &tls_ctx->config->ticket_keys[0]) + return (2); + return (1); + } +} + +static int +tls_configure_server_ssl(struct tls *ctx, SSL_CTX **ssl_ctx, + struct tls_keypair *keypair) +{ + SSL_CTX_free(*ssl_ctx); + + if ((*ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { + tls_set_errorx(ctx, "ssl context failure"); + goto err; + } + +#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION + SSL_CTX_set_options(*ssl_ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); +#endif + + if (SSL_CTX_set_tlsext_servername_callback(*ssl_ctx, + tls_servername_cb) != 1) { + tls_set_error(ctx, "failed to set servername callback"); + goto err; + } + if (SSL_CTX_set_tlsext_servername_arg(*ssl_ctx, ctx) != 1) { + tls_set_error(ctx, "failed to set servername callback arg"); + goto err; + } + + if (tls_configure_ssl(ctx, *ssl_ctx) != 0) + goto err; + if (tls_configure_ssl_keypair(ctx, *ssl_ctx, keypair, 1) != 0) + goto err; + if (ctx->config->verify_client != 0) { + int verify = SSL_VERIFY_PEER; + if (ctx->config->verify_client == 1) + verify |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + if (tls_configure_ssl_verify(ctx, *ssl_ctx, verify) == -1) + goto err; + } + + if (ctx->config->alpn != NULL) + SSL_CTX_set_alpn_select_cb(*ssl_ctx, tls_server_alpn_cb, + ctx); + + if (ctx->config->dheparams == -1) + SSL_CTX_set_dh_auto(*ssl_ctx, 1); + else if (ctx->config->dheparams == 1024) + SSL_CTX_set_dh_auto(*ssl_ctx, 2); + + if (ctx->config->ecdhecurves != NULL) { + SSL_CTX_set_ecdh_auto(*ssl_ctx, 1); + if (SSL_CTX_set1_groups(*ssl_ctx, ctx->config->ecdhecurves, + ctx->config->ecdhecurves_len) != 1) { + tls_set_errorx(ctx, "failed to set ecdhe curves"); + goto err; + } + } + + if (ctx->config->ciphers_server == 1) + SSL_CTX_set_options(*ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (SSL_CTX_set_tlsext_status_cb(*ssl_ctx, tls_ocsp_stapling_cb) != 1) { + tls_set_errorx(ctx, "failed to add OCSP stapling callback"); + goto err; + } + + if (ctx->config->session_lifetime > 0) { + /* set the session lifetime and enable tickets */ + SSL_CTX_set_timeout(*ssl_ctx, ctx->config->session_lifetime); + SSL_CTX_clear_options(*ssl_ctx, SSL_OP_NO_TICKET); + if (!SSL_CTX_set_tlsext_ticket_key_cb(*ssl_ctx, + tls_server_ticket_cb)) { + tls_set_error(ctx, + "failed to set the TLS ticket callback"); + goto err; + } + } + + if (SSL_CTX_set_session_id_context(*ssl_ctx, ctx->config->session_id, + sizeof(ctx->config->session_id)) != 1) { + tls_set_error(ctx, "failed to set session id context"); + goto err; + } + + return (0); + + err: + SSL_CTX_free(*ssl_ctx); + *ssl_ctx = NULL; + + return (-1); +} + +static int +tls_configure_server_sni(struct tls *ctx) +{ + struct tls_sni_ctx **sni_ctx; + struct tls_keypair *kp; + + if (ctx->config->keypair->next == NULL) + return (0); + + /* Set up additional SSL contexts for SNI. */ + sni_ctx = &ctx->sni_ctx; + for (kp = ctx->config->keypair->next; kp != NULL; kp = kp->next) { + if ((*sni_ctx = tls_sni_ctx_new()) == NULL) { + tls_set_errorx(ctx, "out of memory"); + goto err; + } + (*sni_ctx)->keypair = kp; + if (tls_configure_server_ssl(ctx, &(*sni_ctx)->ssl_ctx, kp) == -1) + goto err; + if (tls_keypair_load_cert(kp, &ctx->error, + &(*sni_ctx)->ssl_cert) == -1) + goto err; + sni_ctx = &(*sni_ctx)->next; + } + + return (0); + + err: + return (-1); +} + +int +tls_configure_server(struct tls *ctx) +{ + if (tls_configure_server_ssl(ctx, &ctx->ssl_ctx, + ctx->config->keypair) == -1) + goto err; + if (tls_configure_server_sni(ctx) == -1) + goto err; + + return (0); + + err: + return (-1); +} + +static struct tls * +tls_accept_common(struct tls *ctx) +{ + struct tls *conn_ctx = NULL; + + if ((ctx->flags & TLS_SERVER) == 0) { + tls_set_errorx(ctx, "not a server context"); + goto err; + } + + if ((conn_ctx = tls_server_conn(ctx)) == NULL) { + tls_set_errorx(ctx, "connection context failure"); + goto err; + } + + if ((conn_ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { + tls_set_errorx(ctx, "ssl failure"); + goto err; + } + + if (SSL_set_app_data(conn_ctx->ssl_conn, conn_ctx) != 1) { + tls_set_errorx(ctx, "ssl application data failure"); + goto err; + } + + return conn_ctx; + + err: + tls_free(conn_ctx); + + return (NULL); +} + +int +tls_accept_socket(struct tls *ctx, struct tls **cctx, int s) +{ + return (tls_accept_fds(ctx, cctx, s, s)); +} + +int +tls_accept_fds(struct tls *ctx, struct tls **cctx, int fd_read, int fd_write) +{ + struct tls *conn_ctx; + + if ((conn_ctx = tls_accept_common(ctx)) == NULL) + goto err; + + if (SSL_set_rfd(conn_ctx->ssl_conn, fd_read) != 1 || + SSL_set_wfd(conn_ctx->ssl_conn, fd_write) != 1) { + tls_set_errorx(ctx, "ssl file descriptor failure"); + goto err; + } + + *cctx = conn_ctx; + + return (0); + err: + tls_free(conn_ctx); + *cctx = NULL; + + return (-1); +} + +int +tls_accept_cbs(struct tls *ctx, struct tls **cctx, + tls_read_cb read_cb, tls_write_cb write_cb, void *cb_arg) +{ + struct tls *conn_ctx; + + if ((conn_ctx = tls_accept_common(ctx)) == NULL) + goto err; + + if (tls_set_cbs(conn_ctx, read_cb, write_cb, cb_arg) != 0) + goto err; + + *cctx = conn_ctx; + + return (0); + err: + tls_free(conn_ctx); + *cctx = NULL; + + return (-1); +} + +int +tls_handshake_server(struct tls *ctx) +{ + int ssl_ret; + int rv = -1; + + if ((ctx->flags & TLS_SERVER_CONN) == 0) { + tls_set_errorx(ctx, "not a server connection context"); + goto err; + } + + ctx->state |= TLS_SSL_NEEDS_SHUTDOWN; + + ERR_clear_error(); + if ((ssl_ret = SSL_accept(ctx->ssl_conn)) != 1) { + rv = tls_ssl_error(ctx, ctx->ssl_conn, ssl_ret, "handshake"); + goto err; + } + + ctx->state |= TLS_HANDSHAKE_COMPLETE; + rv = 0; + + err: + return (rv); +} diff --git a/compat/libtls/tls_util.c b/compat/libtls/tls_util.c new file mode 100644 index 0000000..dc54af8 --- /dev/null +++ b/compat/libtls/tls_util.c @@ -0,0 +1,228 @@ +/* $OpenBSD: tls_util.c,v 1.16 2023/05/14 07:26:25 op Exp $ */ +/* + * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> + * Copyright (c) 2014 Ted Unangst <tedu@openbsd.org> + * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include "tls.h" +#include "tls_internal.h" + +static void * +memdup(const void *in, size_t len) +{ + void *out; + + if ((out = malloc(len)) == NULL) + return NULL; + memcpy(out, in, len); + return out; +} + +int +tls_set_mem(char **dest, size_t *destlen, const void *src, size_t srclen) +{ + free(*dest); + *dest = NULL; + *destlen = 0; + if (src != NULL) { + if ((*dest = memdup(src, srclen)) == NULL) + return -1; + *destlen = srclen; + } + return 0; +} + +int +tls_set_string(const char **dest, const char *src) +{ + free((char *)*dest); + *dest = NULL; + if (src != NULL) + if ((*dest = strdup(src)) == NULL) + return -1; + return 0; +} + +/* + * Extract the host and port from a colon separated value. For a literal IPv6 + * address the address must be contained with square braces. If a host and + * port are successfully extracted, the function will return 0 and the + * caller is responsible for freeing the host and port. If no port is found + * then the function will return 1, with both host and port being NULL. + * On memory allocation failure -1 will be returned. + */ +int +tls_host_port(const char *hostport, char **host, char **port) +{ + char *h, *p, *s; + int rv = 1; + + *host = NULL; + *port = NULL; + + if ((s = strdup(hostport)) == NULL) + goto err; + + h = p = s; + + /* See if this is an IPv6 literal with square braces. */ + if (p[0] == '[') { + h++; + if ((p = strchr(s, ']')) == NULL) + goto done; + *p++ = '\0'; + } + + /* Find the port separator. */ + if ((p = strchr(p, ':')) == NULL) + goto done; + + /* If there is another separator then we have issues. */ + if (strchr(p + 1, ':') != NULL) + goto done; + + *p++ = '\0'; + + if (asprintf(host, "%s", h) == -1) { + *host = NULL; + goto err; + } + if (asprintf(port, "%s", p) == -1) { + *port = NULL; + goto err; + } + + rv = 0; + goto done; + + err: + free(*host); + *host = NULL; + free(*port); + *port = NULL; + rv = -1; + + done: + free(s); + + return (rv); +} + +int +tls_password_cb(char *buf, int size, int rwflag, void *u) +{ + size_t len; + + if (size < 0) + return (0); + + if (u == NULL) { + memset(buf, 0, size); + return (0); + } + + if ((len = strlcpy(buf, u, size)) >= (size_t)size) + return (0); + + return (len); +} + +uint8_t * +tls_load_file(const char *name, size_t *len, char *password) +{ + FILE *fp; + EVP_PKEY *key = NULL; + BIO *bio = NULL; + char *data; + uint8_t *buf = NULL; + struct stat st; + size_t size = 0; + int fd = -1; + ssize_t n; + + *len = 0; + + if ((fd = open(name, O_RDONLY)) == -1) + return (NULL); + + /* Just load the file into memory without decryption */ + if (password == NULL) { + if (fstat(fd, &st) != 0) + goto err; + if (st.st_size < 0) + goto err; + size = (size_t)st.st_size; + if ((buf = malloc(size)) == NULL) + goto err; + n = read(fd, buf, size); + if (n < 0 || (size_t)n != size) + goto err; + close(fd); + goto done; + } + + /* Or read the (possibly) encrypted key from file */ + if ((fp = fdopen(fd, "r")) == NULL) + goto err; + fd = -1; + + key = PEM_read_PrivateKey(fp, NULL, tls_password_cb, password); + fclose(fp); + if (key == NULL) + goto err; + + /* Write unencrypted key to memory buffer */ + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto err; + if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) + goto err; + if ((size = BIO_get_mem_data(bio, &data)) <= 0) + goto err; + if ((buf = malloc(size)) == NULL) + goto err; + memcpy(buf, data, size); + + BIO_free_all(bio); + EVP_PKEY_free(key); + + done: + *len = size; + return (buf); + + err: + if (fd != -1) + close(fd); + freezero(buf, size); + BIO_free_all(bio); + EVP_PKEY_free(key); + + return (NULL); +} + +void +tls_unload_file(uint8_t *buf, size_t len) +{ + freezero(buf, len); +} diff --git a/compat/libtls/tls_verify.c b/compat/libtls/tls_verify.c new file mode 100644 index 0000000..053dc2f --- /dev/null +++ b/compat/libtls/tls_verify.c @@ -0,0 +1,286 @@ +/* $OpenBSD: tls_verify.c,v 1.23 2023/05/11 07:35:27 tb Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include <sys/socket.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <string.h> + +#include <openssl/x509v3.h> + +#include <tls.h> +#include "tls_internal.h" + +static int +tls_match_name(const char *cert_name, const char *name) +{ + const char *cert_domain, *domain, *next_dot; + + if (strcasecmp(cert_name, name) == 0) + return 0; + + /* Wildcard match? */ + if (cert_name[0] == '*') { + /* + * Valid wildcards: + * - "*.domain.tld" + * - "*.sub.domain.tld" + * - etc. + * Reject "*.tld". + * No attempt to prevent the use of eg. "*.co.uk". + */ + cert_domain = &cert_name[1]; + /* Disallow "*" */ + if (cert_domain[0] == '\0') + return -1; + /* Disallow "*foo" */ + if (cert_domain[0] != '.') + return -1; + /* Disallow "*.." */ + if (cert_domain[1] == '.') + return -1; + next_dot = strchr(&cert_domain[1], '.'); + /* Disallow "*.bar" */ + if (next_dot == NULL) + return -1; + /* Disallow "*.bar.." */ + if (next_dot[1] == '.') + return -1; + + domain = strchr(name, '.'); + + /* No wildcard match against a name with no host part. */ + if (name[0] == '.') + return -1; + /* No wildcard match against a name with no domain part. */ + if (domain == NULL || strlen(domain) == 1) + return -1; + + if (strcasecmp(cert_domain, domain) == 0) + return 0; + } + + return -1; +} + +/* + * See RFC 5280 section 4.2.1.6 for SubjectAltName details. + * alt_match is set to 1 if a matching alternate name is found. + * alt_exists is set to 1 if any known alternate name exists in the certificate. + */ +static int +tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, + int *alt_match, int *alt_exists) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union tls_addr addrbuf; + int addrlen, type; + int count, i; + int rv = 0; + + *alt_match = 0; + *alt_exists = 0; + + altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, + NULL, NULL); + if (altname_stack == NULL) + return 0; + + if (inet_pton(AF_INET, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 4; + } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 16; + } else { + type = GEN_DNS; + addrlen = 0; + } + + count = sk_GENERAL_NAME_num(altname_stack); + for (i = 0; i < count; i++) { + GENERAL_NAME *altname; + + altname = sk_GENERAL_NAME_value(altname_stack, i); + + if (altname->type == GEN_DNS || altname->type == GEN_IPADD) + *alt_exists = 1; + + if (altname->type != type) + continue; + + if (type == GEN_DNS) { + const unsigned char *data; + int format, len; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_get0_data(altname->d.dNSName); + len = ASN1_STRING_length(altname->d.dNSName); + + if (len < 0 || (size_t)len != strlen(data)) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "NUL byte in subjectAltName, " + "probably a malicious certificate", + name); + rv = -1; + 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_errorx(ctx, + "error verifying name '%s': " + "a dNSName of \" \" must not be " + "used", name); + rv = -1; + break; + } + + if (tls_match_name(data, name) == 0) { + *alt_match = 1; + break; + } + } else { +#ifdef DEBUG + fprintf(stdout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); +#endif + } + + } else if (type == GEN_IPADD) { + const unsigned char *data; + int datalen; + + datalen = ASN1_STRING_length(altname->d.iPAddress); + data = ASN1_STRING_get0_data(altname->d.iPAddress); + + if (datalen < 0) { + tls_set_errorx(ctx, + "Unexpected negative length for an " + "IP address: %d", datalen); + rv = -1; + 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) { + *alt_match = 1; + 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, + int *cn_match) +{ + X509_NAME *subject_name; + char *common_name = NULL; + union tls_addr addrbuf; + int common_name_len; + int rv = -1; + + *cn_match = 0; + + subject_name = X509_get_subject_name(cert); + if (subject_name == NULL) + goto done; + + common_name_len = X509_NAME_get_text_by_NID(subject_name, + NID_commonName, NULL, 0); + if (common_name_len < 0) + goto done; + + common_name = calloc(common_name_len + 1, 1); + if (common_name == NULL) { + tls_set_error(ctx, "out of memory"); + goto err; + } + + X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len < 0 || + (size_t)common_name_len != strlen(common_name)) { + tls_set_errorx(ctx, "error verifying name '%s': " + "NUL byte in Common Name field, " + "probably a malicious certificate", name); + goto err; + } + + /* + * We don't want to attempt wildcard matching against IP addresses, + * so perform a simple comparison here. + */ + if (inet_pton(AF_INET, name, &addrbuf) == 1 || + inet_pton(AF_INET6, name, &addrbuf) == 1) { + if (strcmp(common_name, name) == 0) + *cn_match = 1; + goto done; + } + + if (tls_match_name(common_name, name) == 0) + *cn_match = 1; + + done: + rv = 0; + + err: + free(common_name); + return rv; +} + +int +tls_check_name(struct tls *ctx, X509 *cert, const char *name, int *match) +{ + int alt_exists; + + *match = 0; + + if (tls_check_subject_altname(ctx, cert, name, match, + &alt_exists) == -1) + return -1; + + /* + * As per RFC 6125 section 6.4.4, if any known alternate name existed + * in the certificate, we do not attempt to match on the CN. + */ + if (*match || alt_exists) + return 0; + + return tls_check_common_name(ctx, cert, name, match); +} diff --git a/compat/timingsafe_memcmp.c b/compat/timingsafe_memcmp.c new file mode 100644 index 0000000..927208b --- /dev/null +++ b/compat/timingsafe_memcmp.c @@ -0,0 +1,48 @@ +/* $OpenBSD: timingsafe_memcmp.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ +/* + * Copyright (c) 2014 Google Inc. + * + * 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 "../config.h" + +#include <limits.h> +#include <string.h> + +int +timingsafe_memcmp(const void *b1, const void *b2, size_t len) +{ + const unsigned char *p1 = b1, *p2 = b2; + size_t i; + int res = 0, done = 0; + + for (i = 0; i < len; i++) { + /* lt is -1 if p1[i] < p2[i]; else 0. */ + int lt = (p1[i] - p2[i]) >> CHAR_BIT; + + /* gt is -1 if p1[i] > p2[i]; else 0. */ + int gt = (p2[i] - p1[i]) >> CHAR_BIT; + + /* cmp is 1 if p1[i] > p2[i]; -1 if p1[i] < p2[i]; else 0. */ + int cmp = lt - gt; + + /* set res = cmp if !done. */ + res |= cmp & ~done; + + /* set done if p1[i] != p2[i]. */ + done |= lt | gt; + } + + return (res); +} @@ -56,7 +56,12 @@ CDIAGFLAGS="${CDIAGFLAGS} -W -Wall -Wextra -Wpointer-arith -Wuninitialized" CDIAGFLAGS="${CDIAGFLAGS} -Wstrict-prototypes -Wmissing-prototypes -Wunused" CDIAGFLAGS="${CDIAGFLAGS} -Wsign-compare -Wno-unused-parameter" # -Wshadow CDIAGFLAGS="${CDIAGFLAGS} -Wno-missing-field-initializers" -CDIAGFLAGS="${CDIAGFLAGS} -Wpointer-sign" +CDIAGFLAGS="${CDIAGFLAGS} -Wno-pointer-sign" + +LIBTLS=bundled # or system +if [ "$(uname || true)" = OpenBSD ]; then + LIBTLS=system +fi while [ $# -gt 0 ]; do key="${1%%=*}" @@ -95,9 +100,17 @@ while [ $# -gt 0 ]; do --bindir) key=BINDIR ;; --mandir) key=MANDIR ;; --prefix) key=PREFIX ;; + --with-libtls) key=LIBTLS ;; esac case "$key" in + LIBTLS) + case "$val" in + bundled) LIBTLS=bundled ;; + system) LIBTLS=system ;; + *) usage ;; + esac + ;; BINDIR) BINDIR="$val" ;; CC) CC="$val" ;; CFLAGS) CFLAGS="$val" ;; @@ -267,15 +280,17 @@ if [ ${HAVE_ENDIAN_H} -eq 0 -a \ exit 1 fi +runtest arc4random ARC4RANDOM || true +runtest arc4random_buf ARC4RANDOM_BUF || true runtest err ERR || true runtest explicit_bzero EXPLICIT_BZERO || true runtest freezero FREEZERO || true runtest getdtablecount GETDTABLECOUNT || true runtest getdtablesize GETDTABLESIZE || true +runtest getentropy GETENTROPY || true runtest getprogname GETPROGNAME || true runtest imsg IMSG "" -lutil libimsg || true runtest libevent LIBEVENT "" -levent libevent_core|| true -runtest libtls LIBTLS "" -ltls libtls || true runtest memmem MEMMEM -D_GNU_SOURCE || true runtest openssl OPENSSL "" '-lcrypto -lssl' 'libcrypto libssl' || true runtest pr_set_name PR_SET_NAME || true @@ -289,15 +304,51 @@ runtest setresuid SETRESUID -D_GNU_SOURCE || true runtest strlcat STRLCAT || true runtest strlcpy STRLCPY || true runtest strtonum STRTONUM -D_OPENBSD_SOURCE || true +runtest timingsafe_memcmp TIMINGSAFE_MEMCMP || true runtest tree_h TREE_H || true runtest vasprintf VASPRINTF -D_GNU_SOURCE || true runtest vis VIS -DLIBBSD_OPENBSD_VIS || true +if [ ${HAVE_ARC4RANDOM} -eq 1 -a ${HAVE_ARC4RANDOM_BUF} -eq 0 ]; then + COMPATS="compat/arc4random.c ${COMPATS}" +fi + +if [ ${HAVE_ARC4RANDOM} -eq 0 -a ${HAVE_GETENTROPY} -eq 1 ]; then + COMPATS="compat/getentropy.c ${COMPATS}" +fi + +if [ "${LIBTLS}" = system ]; then + runtest libtls LIBTLS "" -ltls libtls || true + + # not actually needed + HAVE_ASN1_TIME_TM_CMP=1 + HAVE_ASN1_TIME_TM_CLAMP_NOTAFTER=1 + HAVE_ASN1_TIME_PARSE=1 + HAVE_SSL_CTX_UCCM=1 + HAVE_SSL_CTX_LVM=1 + HAVE_X509_LOOKUP_MEM=1 +else + # use bundled one + HAVE_LIBTLS=1 + for f in compat/libtls/*.c; do + COMPATS="$f ${COMPATS}" + done + + CFLAGS="-Icompat/libtls ${CFLAGS}" + + deptest ASN1_time_tm_cmp ASN1_TIME_TM_CMP || true + deptest ASN1_time_tm_clamp_notafter ASN1_TIME_TM_CLAMP_NOTAFTER || true + deptest ASN1_time_parse ASN1_TIME_PARSE || true + deptest SSL_CTX_use_certificate_chain_mem SSL_CTX_UCCM || true + deptest SSL_CTX_load_verify_mem SSL_CTX_LVM || true + deptest X509_LOOKUP_mem X509_LOOKUP_MEM || true +fi + deptest libevent2 LIBEVENT2 || true if [ ${HAVE_LIBTLS} -eq 0 ]; then - echo "FATAL: libtls not found" 1>&2 - echo "FATAL: libtls not found" 1>&3 + echo "FATAL: openssl not found" 1>&2 + echo "FATAL: openssl not found" 1>&3 exit 1 fi @@ -340,7 +391,7 @@ if [ $NEED_LIBBSD_OPENBSD_VIS = 1 ]; then CFLAGS="$CFLAGS -DLIBBSD_OPENBSD_VIS" fi -CFLAGS="${CFLAGS} ${CDIAGFLAGS}" +CFLAGS="-I. ${CFLAGS} ${CDIAGFLAGS}" exec > config.h echo "config.h: writing.." >&2 @@ -394,17 +445,62 @@ elif [ ${HAVE_MACHINE_ENDIAN} -eq 1 ]; then __HEREDOC__ fi -[ ${HAVE_EXPLICIT_BZERO} -eq 0 -o \ +[ ${HAVE_ARC4RANDOM_BUF} -eq 0 -o \ + ${HAVE_ASN1_TIME_PARSE} -eq 0 -o \ + ${HAVE_EXPLICIT_BZERO} -eq 0 -o \ ${HAVE_FREEZERO} -eq 0 -o \ + ${HAVE_GETENTROPY} -eq 0 -o \ ${HAVE_REALLOCARRAY} -eq 0 -o \ ${HAVE_RECALLOCARRAY} -eq 0 -o \ ${HAVE_STRLCAT} -eq 0 -o \ ${HAVE_STRLCPY} -eq 0 -o \ - ${HAVE_STRTONUM} -eq 0 ] && echo "#include <stddef.h>" + ${HAVE_STRTONUM} -eq 0 -o \ + ${HAVE_TIMINGSAFE_MEMCMP} -eq 0 ] && echo "#include <stddef.h>" + +[ ${HAVE_ARC4RANDOM} -eq 0 ] && echo "#include <stdint.h>" [ ${HAVE_SETRESGID} -eq 0 -o \ ${HAVE_SETRESUID} -eq 0 ] && echo "#include <unistd.h>" +if [ ${HAVE_GETENTROPY} -eq 1 ]; then + echo "#define HAVE_GETENTROPY 1" +else + echo "#define WITH_OPENSSL 1" + echo "#define OPENSSL_PRNG_ONLY 1" +fi + +if [ ${HAVE_ARC4RANDOM} -eq 0 ]; then + echo "extern uint32_t arc4random(void);" +else + echo "#define HAVE_ARC4RANDOM 1" +fi +if [ ${HAVE_ARC4RANDOM_BUF} -eq 0 ]; then + echo "extern void arc4random_buf(void *, size_t);" +else + echo "#define HAVE_ARC4RANDOM_BUF 1" +fi + +if [ ${HAVE_ASN1_TIME_TM_CMP} -eq 0 ]; then + echo "struct tm;" + echo "extern int ASN1_time_tm_cmp(struct tm *, struct tm *);" +else + echo "#define HAVE_ASN1_TIME_TM_CMP 1" +fi + +if [ ${HAVE_ASN1_TIME_TM_CLAMP_NOTAFTER} -eq 0 ]; then + echo "struct tm;" + echo "extern int ASN1_time_tm_clamp_notafter(struct tm *);" +else + echo "#define HAVE_ASN1_TIME_TM_CLAMP_NOTAFTER 1" +fi + +if [ ${HAVE_ASN1_TIME_PARSE} -eq 0 ]; then + echo "struct tm;" + echo "extern int ASN1_time_parse(const char *, size_t, struct tm *, int);" +else + echo "#define HAVE_ASN1_TIME_PARSE 1" +fi + if [ ${HAVE_ERR} -eq 0 ]; then echo "extern void err(int, const char*, ...);" echo "extern void errx(int, const char*, ...);" @@ -425,6 +521,9 @@ fi if [ ${HAVE_GETDTABLESIZE} -eq 0 ]; then echo "extern int getdtablesize(void);" fi +if [ ${HAVE_GETENTROPY} -eq 0 ]; then + echo "extern int getentropy(void *, size_t)"; +fi if [ ${HAVE_GETPROGNAME} -eq 0 ]; then echo "extern const char *getprogname(void);" fi @@ -455,10 +554,44 @@ fi if [ ${HAVE_STRTONUM} -eq 0 ]; then echo "extern long long strtonum(const char*, long long, long long, const char**);" fi +if [ ${HAVE_TIMINGSAFE_MEMCMP} -eq 0 ]; then + echo "extern int timingsafe_memcmp(const void *, const void *, size_t);" +fi if [ ${HAVE_VASPRINTF} -eq 0 ]; then echo "extern int vasprintf(char**, const char*, va_list);" fi +if [ ${HAVE_ASN1_TIME_TM_CMP} -eq 0 ]; then + echo "#include <openssl/asn1.h>" + echo "struct tm;" + echo "int ASN1_time_tm_cmp(struct tm *, struct tm *);" +else + echo "#define HAVE_ASN1_TIME_TM_CMP 1" +fi + +if [ ${HAVE_SSL_CTX_UCCM} -eq 0 -o ${HAVE_SSL_CTX_LVM} -eq 0 ]; then + echo "#include <openssl/ssl.h>" +fi + +if [ ${HAVE_SSL_CTX_UCCM} -eq 0 ]; then + echo "int SSL_CTX_use_certificate_chain_mem(SSL_CTX *, void *, int);" +else + echo "#define HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM 1" +fi + +if [ ${HAVE_SSL_CTX_LVM} -eq 0 ]; then + echo "int SSL_CTX_load_verify_mem(SSL_CTX *, void *, int);" +else + echo "#define HAVE_SSL_CTX_LOAD_VERIFY_MEM 1" +fi + +if [ ${HAVE_X509_LOOKUP_MEM} -eq 0 ]; then + echo "#include <openssl/x509_vfy.h>" + echo "X509_LOOKUP_METHOD *X509_LOOKUP_mem(void);" +else + echo "#define HAVE_X509_LOOKUP_MEM 1" +fi + cat <<__HEREDOC__ #ifndef __dead diff --git a/have/ASN1_time_parse.c b/have/ASN1_time_parse.c new file mode 100644 index 0000000..abc7d5b --- /dev/null +++ b/have/ASN1_time_parse.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdlib.h> + +#include <openssl/asn1.h> + +int +main(void) +{ + return ASN1_time_parse("", 0, NULL, 0); +} + diff --git a/have/ASN1_time_tm_cmp.c b/have/ASN1_time_tm_cmp.c new file mode 100644 index 0000000..807c97d --- /dev/null +++ b/have/ASN1_time_tm_cmp.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdlib.h> + +#include <openssl/asn1.h> + +int +main(void) +{ + return ASN1_time_tm_cmp(NULL, NULL); +} + diff --git a/have/Makefile b/have/Makefile index 21e75c4..d2a1dfa 100644 --- a/have/Makefile +++ b/have/Makefile @@ -1,10 +1,18 @@ -DISTFILES = Makefile \ +DISTFILES = ASN1_time_parse.c \ + ASN1_time_tm_clamp_notafter.c \ + ASN1_time_tm_cmp.c \ + Makefile \ + SSL_CTX_load_verify_mem.c \ + SSL_CTX_use_certificate_chain_mem.c \ + X509_LOOKUP_mem.c \ + arc4random.c \ endian_h.c \ err.c \ explicit_bzero.c \ freezero.c \ getdtablecount.c \ getdtablesize.c \ + getentropy.c \ getprogname.c \ imsg.c \ libevent.c \ @@ -24,6 +32,7 @@ DISTFILES = Makefile \ strlcat.c \ strlcpy.c \ strtonum.c \ + timingsafe_memcmp.c \ tree_h.c \ vasprintf.c \ vis.c \ diff --git a/have/SSL_CTX_load_verify_mem.c b/have/SSL_CTX_load_verify_mem.c new file mode 100644 index 0000000..32ef15b --- /dev/null +++ b/have/SSL_CTX_load_verify_mem.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdio.h> +#include <openssl/ssl.h> + +int +main(void) +{ + return SSL_CTX_load_verify_mem(NULL, NULL, 0); +} diff --git a/have/SSL_CTX_use_certificate_chain_mem.c b/have/SSL_CTX_use_certificate_chain_mem.c new file mode 100644 index 0000000..0e45c31 --- /dev/null +++ b/have/SSL_CTX_use_certificate_chain_mem.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdio.h> +#include <openssl/ssl.h> + +int +main(void) +{ + return SSL_CTX_use_certificate_chain_mem(NULL, NULL, 0); +} diff --git a/have/X509_LOOKUP_mem.c b/have/X509_LOOKUP_mem.c new file mode 100644 index 0000000..3df485e --- /dev/null +++ b/have/X509_LOOKUP_mem.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdio.h> +#include <openssl/x509_vfy.h> + +int +main(void) +{ + return X509_LOOKUP_mem() != NULL; +} diff --git a/have/arc4random.c b/have/arc4random.c new file mode 100644 index 0000000..4d3d48b --- /dev/null +++ b/have/arc4random.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdlib.h> + +int +main(void) +{ + return arc4random(); +} diff --git a/have/arc4random_buf.c b/have/arc4random_buf.c new file mode 100644 index 0000000..450ab9c --- /dev/null +++ b/have/arc4random_buf.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdlib.h> + +int +main(void) +{ + char buf[128]; + + arc4random_buf(buf, sizeof(buf)); + return 0; +} diff --git a/have/getentropy.c b/have/getentropy.c new file mode 100644 index 0000000..46cd74d --- /dev/null +++ b/have/getentropy.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <stdio.h> +#include <unistd.h> + +int +main(void) +{ + char buf[1024]; + + return getentropy(buf, sizeof(buf)); +} diff --git a/have/timingsafe_memcmp.c b/have/timingsafe_memcmp.c new file mode 100644 index 0000000..0922d4b --- /dev/null +++ b/have/timingsafe_memcmp.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Omar Polo <op@omarpolo.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 <string.h> + +int +main(void) +{ + const char *a = "foo"; + const char *b = "bar"; + + return timingsafe_memcmp(a, b, 3); +} diff --git a/regress/Makefile b/regress/Makefile index ed79b78..89b87ef 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -31,7 +31,7 @@ REG_COMPATS = ${COBJS:%=../%} PUNY_SRCS = puny-test.c ../puny.c ../utf8.c ../utils.c ../log.c PUNY_OBJS = ${PUNY_SRCS:.c=.o} ${REG_COMPATS} -IRI_SRCS = iri_test.c ../iri.c ../utf8.c +IRI_SRCS = iri_test.c ../iri.c ../utf8.c ../log.c IRI_OBJS = ${IRI_SRCS:.c=.o} ${REG_COMPATS} .PHONY: all data clean dist @@ -51,7 +51,7 @@ fill-file: fill-file.o ${CC} fill-file.o -o $@ ${LIBS} ${LDFLAGS} fcgi-test: fcgi-test.o - ${CC} fcgi-test.o ${REG_COMPATS} -o fcgi-test ${LIBS} ${LDFLAGS} + ${CC} fcgi-test.o ../log.o ${REG_COMPATS} -o fcgi-test ${LIBS} ${LDFLAGS} key.pem: cert.pem |