From c21c3b0befeb46a51b6bf3758ffa30813bea0ff0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 9 Mar 2024 14:19:22 +0100 Subject: Adding upstream version 1.44.3. Signed-off-by: Daniel Baumann --- .../h2o/libh2o/deps/neverbleed/.clang-format | 7 + web/server/h2o/libh2o/deps/neverbleed/.gitignore | 32 + web/server/h2o/libh2o/deps/neverbleed/LICENSE | 22 + web/server/h2o/libh2o/deps/neverbleed/README.md | 70 + web/server/h2o/libh2o/deps/neverbleed/neverbleed.c | 1521 ++++++++++++++++++++ web/server/h2o/libh2o/deps/neverbleed/neverbleed.h | 61 + web/server/h2o/libh2o/deps/neverbleed/test.c | 149 ++ 7 files changed, 1862 insertions(+) create mode 100644 web/server/h2o/libh2o/deps/neverbleed/.clang-format create mode 100644 web/server/h2o/libh2o/deps/neverbleed/.gitignore create mode 100644 web/server/h2o/libh2o/deps/neverbleed/LICENSE create mode 100644 web/server/h2o/libh2o/deps/neverbleed/README.md create mode 100644 web/server/h2o/libh2o/deps/neverbleed/neverbleed.c create mode 100644 web/server/h2o/libh2o/deps/neverbleed/neverbleed.h create mode 100644 web/server/h2o/libh2o/deps/neverbleed/test.c (limited to 'web/server/h2o/libh2o/deps/neverbleed') diff --git a/web/server/h2o/libh2o/deps/neverbleed/.clang-format b/web/server/h2o/libh2o/deps/neverbleed/.clang-format new file mode 100644 index 000000000..9640123c7 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/.clang-format @@ -0,0 +1,7 @@ +# requires clang-format >= 3.6 +BasedOnStyle: "LLVM" +IndentWidth: 4 +ColumnLimit: 132 +BreakBeforeBraces: Linux +AllowShortFunctionsOnASingleLine: None +SortIncludes: false diff --git a/web/server/h2o/libh2o/deps/neverbleed/.gitignore b/web/server/h2o/libh2o/deps/neverbleed/.gitignore new file mode 100644 index 000000000..bbf313b25 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/.gitignore @@ -0,0 +1,32 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ diff --git a/web/server/h2o/libh2o/deps/neverbleed/LICENSE b/web/server/h2o/libh2o/deps/neverbleed/LICENSE new file mode 100644 index 000000000..2bd3423e2 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Kazuho Oku, DeNA Co., Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/web/server/h2o/libh2o/deps/neverbleed/README.md b/web/server/h2o/libh2o/deps/neverbleed/README.md new file mode 100644 index 000000000..8357055ca --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/README.md @@ -0,0 +1,70 @@ +Neverbleed +=============== + +Neverbleed is an [OpenSSL engine](https://www.openssl.org/docs/manmaster/crypto/engine.html) that runs RSA private key operations in an isolated process, thereby minimizing the risk of private key leak in case of vulnerability such as [Heartbleed](http://heartbleed.com/). + +The engine is known to work together with existing versions of OpenSSL or LibreSSL, with minimal changes to the server source code. + +FAQ +--- + +### Q. How much is the overhead? + +Virtually none. + +Generally speaking, private key operations are much more heavier than the overhead of inter-process communication. +On my Linux VM running on Core i7 @ 2.4GHz (MacBook Pro 15" Late 2013), OpenSSL 1.0.2 without privilege separation processes 319.56 full TLS handshakes per second, whereas OpenSSL with privilege separation processes 316.72 handshakes per second (note: RSA key length: 2,048 bits, selected cipher-suite: ECDHE-RSA-AES128-GCM-SHA256). + +### Q. Why does the library only protect the private keys? + +Because private keys are the only _long-term_ secret being used for encrypting and/or digitally-signing the communication. + +Depending on how OpenSSL is used, it might be beneficial to separate symmetric cipher operations or TLS operations as a whole. +But even in such case, it would still be a good idea to isolate private key operations from them considering the impact of private key leaks. +In other words, separating private key operations only to an isolated process in always a good thing to do. + +### Q. Is there any HTTP server that uses Neverbleed? + +Neverbleed is used by [H2O](https://h2o.examp1e.net/) HTTP2 server since version [1.5.0-beta4](https://github.com/h2o/h2o/releases/tag/v1.5.0-beta4). + +How-to +------ + +The library exposes two functions: `neverbleed_init` and `neverbleed_load_private_key_file`. + +The first function spawns an external process dedicated to private key operations, and the second function assigns a RSA private key stored in the specified file to an existing SSL context (`SSL_CTX`). + +By + +1. adding call to `neverbleed_init` +2. replacing call to `SSL_CTX_use_PrivateKey_file` with `neverbleed_load_private_key_file` + +the privilege separation engine will be used for all the incoming TLS connections. + +``` + neverbleed_t nb; + char errbuf[NEVERBLEED_ERRBUF_SIZE]; + + /* initialize the OpenSSL library and the neverbleed engine */ + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + if (neverbleed_init(&nb, errbuf) != 0) { + fprintf(stderr, "neverbleed_init failed: %s\n", errbuf); + ... + } + + ... + + /* load certificate chain and private key */ + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, certchain_fn) != 1) { + fprintf(stderr, "failed to load certificate chain file:%s\n", certchain_fn); + ... + } + if (neverbleed_load_private_key_file(&nb, ctx, privkey_fn, errbuf) != 1) { + fprintf(stderr, "failed to load private key from file:%s:%s\n", privkey_fn, errbuf); + ... + } +``` + +Also, `neverbleed_setuidgid` function can be used to drop the privileges of the daemon process once it completes loading all the private keys. diff --git a/web/server/h2o/libh2o/deps/neverbleed/neverbleed.c b/web/server/h2o/libh2o/deps/neverbleed/neverbleed.c new file mode 100644 index 000000000..7b36d6cb6 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/neverbleed.c @@ -0,0 +1,1521 @@ +/* + * Copyright (c) 2015 Kazuho Oku, DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include "neverbleed.h" + +#if (!defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x1010000fL) +#define OPENSSL_1_1_API 1 +#else +#define OPENSSL_1_1_API 0 +#endif + +enum neverbleed_type { NEVERBLEED_TYPE_ERROR, NEVERBLEED_TYPE_RSA, NEVERBLEED_TYPE_ECDSA }; + +struct expbuf_t { + char *buf; + char *start; + char *end; + size_t capacity; +}; + +struct st_neverbleed_rsa_exdata_t { + neverbleed_t *nb; + size_t key_index; +}; + +struct st_neverbleed_thread_data_t { + pid_t self_pid; + int fd; +}; + +static void warnvf(const char *fmt, va_list args) +{ + char errbuf[256]; + + if (errno != 0) { + strerror_r(errno, errbuf, sizeof(errbuf)); + } else { + errbuf[0] = '\0'; + } + + fprintf(stderr, "[openssl-privsep] "); + vfprintf(stderr, fmt, args); + if (errbuf[0] != '\0') + fputs(errbuf, stderr); + fputc('\n', stderr); +} + +__attribute__((format(printf, 1, 2))) static void warnf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + warnvf(fmt, args); + va_end(args); +} + +__attribute__((format(printf, 1, 2), noreturn)) static void dief(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + warnvf(fmt, args); + va_end(args); + + abort(); +} + +static char *dirname(const char *path) +{ + const char *last_slash = strrchr(path, '/'); + char *ret; + + if (last_slash == NULL) { + errno = 0; + dief("dirname: no slash in given path:%s", path); + } + if ((ret = malloc(last_slash + 1 - path)) == NULL) + dief("no memory"); + memcpy(ret, path, last_slash - path); + ret[last_slash - path] = '\0'; + return ret; +} + +static void set_cloexec(int fd) +{ + if (fcntl(fd, F_SETFD, O_CLOEXEC) == -1) + dief("failed to set O_CLOEXEC to fd %d", fd); +} + +static int read_nbytes(int fd, void *p, size_t sz) +{ + while (sz != 0) { + ssize_t r; + while ((r = read(fd, p, sz)) == -1 && errno == EINTR) + ; + if (r == -1) { + return -1; + } else if (r == 0) { + errno = 0; + return -1; + } + p = (char *)p + r; + sz -= r; + } + return 0; +} + +static size_t expbuf_size(struct expbuf_t *buf) +{ + return buf->end - buf->start; +} + +static void expbuf_dispose(struct expbuf_t *buf) +{ + if (buf->capacity != 0) + OPENSSL_cleanse(buf->buf, buf->capacity); + free(buf->buf); + memset(buf, 0, sizeof(*buf)); +} + +static void expbuf_reserve(struct expbuf_t *buf, size_t extra) +{ + char *n; + + if (extra <= buf->buf + buf->capacity - buf->end) + return; + + if (buf->capacity == 0) + buf->capacity = 4096; + while (buf->buf + buf->capacity - buf->end < extra) + buf->capacity *= 2; + if ((n = realloc(buf->buf, buf->capacity)) == NULL) + dief("realloc failed"); + buf->start += n - buf->buf; + buf->end += n - buf->buf; + buf->buf = n; +} + +static void expbuf_push_num(struct expbuf_t *buf, size_t v) +{ + expbuf_reserve(buf, sizeof(v)); + memcpy(buf->end, &v, sizeof(v)); + buf->end += sizeof(v); +} + +static void expbuf_push_str(struct expbuf_t *buf, const char *s) +{ + size_t l = strlen(s) + 1; + expbuf_reserve(buf, l); + memcpy(buf->end, s, l); + buf->end += l; +} + +static void expbuf_push_bytes(struct expbuf_t *buf, const void *p, size_t l) +{ + expbuf_push_num(buf, l); + expbuf_reserve(buf, l); + memcpy(buf->end, p, l); + buf->end += l; +} + +static int expbuf_shift_num(struct expbuf_t *buf, size_t *v) +{ + if (expbuf_size(buf) < sizeof(*v)) + return -1; + memcpy(v, buf->start, sizeof(*v)); + buf->start += sizeof(*v); + return 0; +} + +static char *expbuf_shift_str(struct expbuf_t *buf) +{ + char *nul = memchr(buf->start, '\0', expbuf_size(buf)), *ret; + if (nul == NULL) + return NULL; + ret = buf->start; + buf->start = nul + 1; + return ret; +} + +static void *expbuf_shift_bytes(struct expbuf_t *buf, size_t *l) +{ + void *ret; + if (expbuf_shift_num(buf, l) != 0) + return NULL; + if (expbuf_size(buf) < *l) + return NULL; + ret = buf->start; + buf->start += *l; + return ret; +} + +static int expbuf_write(struct expbuf_t *buf, int fd) +{ + struct iovec vecs[2] = {{NULL}}; + size_t bufsz = expbuf_size(buf); + int vecindex; + ssize_t r; + + vecs[0].iov_base = &bufsz; + vecs[0].iov_len = sizeof(bufsz); + vecs[1].iov_base = buf->start; + vecs[1].iov_len = bufsz; + + for (vecindex = 0; vecindex != sizeof(vecs) / sizeof(vecs[0]);) { + while ((r = writev(fd, vecs + vecindex, sizeof(vecs) / sizeof(vecs[0]) - vecindex)) == -1 && errno == EINTR) + ; + if (r == -1) + return -1; + assert(r != 0); + while (r != 0 && r >= vecs[vecindex].iov_len) { + r -= vecs[vecindex].iov_len; + ++vecindex; + } + if (r != 0) { + vecs[vecindex].iov_base = (char *)vecs[vecindex].iov_base + r; + vecs[vecindex].iov_len -= r; + } + } + + return 0; +} + +static int expbuf_read(struct expbuf_t *buf, int fd) +{ + size_t sz; + + if (read_nbytes(fd, &sz, sizeof(sz)) != 0) + return -1; + expbuf_reserve(buf, sz); + if (read_nbytes(fd, buf->end, sz) != 0) + return -1; + buf->end += sz; + return 0; +} + +#if !defined(NAME_MAX) || defined(__linux__) +/* readdir(3) is known to be thread-safe on Linux and should be thread-safe on a platform that does not have a predefined value for + NAME_MAX */ +#define FOREACH_DIRENT(dp, dent) \ + struct dirent *dent; \ + while ((dent = readdir(dp)) != NULL) +#else +#define FOREACH_DIRENT(dp, dent) \ + struct { \ + struct dirent d; \ + char s[NAME_MAX + 1]; \ + } dent_; \ + struct dirent *dentp, *dent = &dent_.d; \ + int ret; \ + while ((ret = readdir_r(dp, dent, &dentp)) == 0 && dentp != NULL) +#endif /* FOREACH_DIRENT */ + +static void unlink_dir(const char *path) +{ + DIR *dp; + char buf[PATH_MAX]; + + if ((dp = opendir(path)) != NULL) { + FOREACH_DIRENT(dp, entp) + { + if (strcmp(entp->d_name, ".") == 0 || strcmp(entp->d_name, "..") == 0) + continue; + snprintf(buf, sizeof(buf), "%s/%s", path, entp->d_name); + unlink_dir(buf); + } + closedir(dp); + } + unlink(path); + rmdir(path); +} + +void dispose_thread_data(void *_thdata) +{ + struct st_neverbleed_thread_data_t *thdata = _thdata; + assert(thdata->fd >= 0); + close(thdata->fd); + thdata->fd = -1; +} + +struct st_neverbleed_thread_data_t *get_thread_data(neverbleed_t *nb) +{ + struct st_neverbleed_thread_data_t *thdata; + pid_t self_pid = getpid(); + ssize_t r; + + if ((thdata = pthread_getspecific(nb->thread_key)) != NULL) { + if (thdata->self_pid == self_pid) + return thdata; + /* we have been forked! */ + close(thdata->fd); + } else { + if ((thdata = malloc(sizeof(*thdata))) == NULL) + dief("malloc failed"); + } + + thdata->self_pid = self_pid; +#ifdef SOCK_CLOEXEC + if ((thdata->fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) + dief("socket(2) failed"); +#else + if ((thdata->fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + dief("socket(2) failed"); + set_cloexec(thdata->fd); +#endif + while (connect(thdata->fd, (void *)&nb->sun_, sizeof(nb->sun_)) != 0) + if (errno != EINTR) + dief("failed to connect to privsep daemon"); + while ((r = write(thdata->fd, nb->auth_token, sizeof(nb->auth_token))) == -1 && errno == EINTR) + ; + if (r != sizeof(nb->auth_token)) + dief("failed to send authentication token"); + pthread_setspecific(nb->thread_key, thdata); + + return thdata; +} + +static void get_privsep_data(const RSA *rsa, struct st_neverbleed_rsa_exdata_t **exdata, + struct st_neverbleed_thread_data_t **thdata) +{ + *exdata = RSA_get_ex_data(rsa, 0); + if (*exdata == NULL) { + errno = 0; + dief("invalid internal ref"); + } + *thdata = get_thread_data((*exdata)->nb); +} + +static const size_t default_reserved_size = 8192; + +struct key_slots { + size_t size; + size_t reserved_size; + /* bit array slots: + * 1-bit slot available + * 0-bit slot unavailable + */ + uint8_t *bita_avail; +}; + +static struct { + struct { + pthread_mutex_t lock; + RSA **keys; + struct key_slots rsa_slots; + EC_KEY **ecdsa_keys; + struct key_slots ecdsa_slots; + } keys; + neverbleed_t *nb; +} daemon_vars = {{PTHREAD_MUTEX_INITIALIZER}}; + +static RSA *daemon_get_rsa(size_t key_index) +{ + RSA *rsa; + + pthread_mutex_lock(&daemon_vars.keys.lock); + rsa = daemon_vars.keys.keys[key_index]; + if (rsa) + RSA_up_ref(rsa); + pthread_mutex_unlock(&daemon_vars.keys.lock); + + return rsa; +} + +/* + * Returns an available slot in bit array B + * or if not found, returns SIZE_MAX + */ +static size_t bita_ffirst(const uint8_t *b, const size_t tot, size_t bits) +{ + if (bits >= tot) + return SIZE_MAX; + + uint64_t w = *((uint64_t *) b); + /* __builtin_ffsll returns one plus the index of the least significant 1-bit, or zero if not found */ + uint32_t r = __builtin_ffsll(w); + if (r) + return bits + r - 1; /* adjust result */ + + return bita_ffirst(&b[8], tot, bits + 64); +} + +/* + * bit operation helpers for the bit-array in key_slots + */ +#define BITMASK(b) (1 << ((b) % CHAR_BIT)) +#define BITBYTE(b) ((b) / CHAR_BIT) +#define BITSET(a, b) ((a)[BITBYTE(b)] |= BITMASK(b)) +#define BITUNSET(a, b) ((a)[BITBYTE(b)] &= ~BITMASK(b)) +#define BITBYTES(nb) ((nb + CHAR_BIT - 1) / CHAR_BIT) +#define BITCHECK(a, b) ((a)[BITBYTE(b)] & BITMASK(b)) + +static void adjust_slots_reserved_size(int type, struct key_slots *slots) +{ +#define ROUND2WORD(n) (n + 64 - 1 - (n + 64 - 1) % 64) + if (!slots->reserved_size || (slots->size >= slots->reserved_size)) { + size_t size = slots->reserved_size ? ROUND2WORD((size_t)(slots->reserved_size * 0.50) + slots->reserved_size) + : default_reserved_size; +#undef ROUND2WORD + + switch (type) { + case NEVERBLEED_TYPE_RSA: + if ((daemon_vars.keys.keys = realloc(daemon_vars.keys.keys, sizeof(*daemon_vars.keys.keys) * size)) == NULL) + dief("no memory"); + break; + case NEVERBLEED_TYPE_ECDSA: + if ((daemon_vars.keys.ecdsa_keys = realloc(daemon_vars.keys.ecdsa_keys, sizeof(*daemon_vars.keys.ecdsa_keys) * size)) == NULL) + dief("no memory"); + break; + default: + dief("invalid type adjusting reserved"); + } + + uint8_t *b; + if ((b = realloc(slots->bita_avail, BITBYTES(size))) == NULL) + dief("no memory"); + + /* set all bits to 1 making all slots available */ + memset(&b[BITBYTES(slots->reserved_size)], 0xff, BITBYTES(size - slots->reserved_size)); + + slots->bita_avail = b; + slots->reserved_size = size; + } +} + +static size_t daemon_set_rsa(RSA *rsa) +{ + pthread_mutex_lock(&daemon_vars.keys.lock); + + adjust_slots_reserved_size(NEVERBLEED_TYPE_RSA, &daemon_vars.keys.rsa_slots); + + size_t index = bita_ffirst(daemon_vars.keys.rsa_slots.bita_avail, daemon_vars.keys.rsa_slots.reserved_size, 0); + + if (index == SIZE_MAX) + dief("no available slot for key"); + + /* set slot as unavailable */ + BITUNSET(daemon_vars.keys.rsa_slots.bita_avail, index); + + daemon_vars.keys.rsa_slots.size++; + daemon_vars.keys.keys[index] = rsa; + RSA_up_ref(rsa); + pthread_mutex_unlock(&daemon_vars.keys.lock); + + return index; +} + +static int priv_encdec_proxy(const char *cmd, int flen, const unsigned char *from, unsigned char *_to, RSA *rsa, int padding) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + struct st_neverbleed_thread_data_t *thdata; + struct expbuf_t buf = {NULL}; + size_t ret; + unsigned char *to; + size_t tolen; + + get_privsep_data(rsa, &exdata, &thdata); + + expbuf_push_str(&buf, cmd); + expbuf_push_bytes(&buf, from, flen); + expbuf_push_num(&buf, exdata->key_index); + expbuf_push_num(&buf, padding); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0 || (to = expbuf_shift_bytes(&buf, &tolen)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + memcpy(_to, to, tolen); + expbuf_dispose(&buf); + + return (int)ret; +} + +static int priv_encdec_stub(const char *name, + int (*func)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding), + struct expbuf_t *buf) +{ + unsigned char *from, to[4096]; + size_t flen; + size_t key_index, padding; + RSA *rsa; + int ret; + + if ((from = expbuf_shift_bytes(buf, &flen)) == NULL || expbuf_shift_num(buf, &key_index) != 0 || + expbuf_shift_num(buf, &padding) != 0) { + errno = 0; + warnf("%s: failed to parse request", name); + return -1; + } + if ((rsa = daemon_get_rsa(key_index)) == NULL) { + errno = 0; + warnf("%s: invalid key index:%zu\n", name, key_index); + return -1; + } + ret = func((int)flen, from, to, rsa, (int)padding); + expbuf_dispose(buf); + RSA_free(rsa); + + expbuf_push_num(buf, ret); + expbuf_push_bytes(buf, to, ret > 0 ? ret : 0); + + return 0; +} + +static int priv_enc_proxy(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) +{ + return priv_encdec_proxy("priv_enc", flen, from, to, rsa, padding); +} + +static int priv_enc_stub(struct expbuf_t *buf) +{ + return priv_encdec_stub(__FUNCTION__, RSA_private_encrypt, buf); +} + +static int priv_dec_proxy(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) +{ + return priv_encdec_proxy("priv_dec", flen, from, to, rsa, padding); +} + +static int priv_dec_stub(struct expbuf_t *buf) +{ + return priv_encdec_stub(__FUNCTION__, RSA_private_decrypt, buf); +} + +static int sign_proxy(int type, const unsigned char *m, unsigned int m_len, unsigned char *_sigret, unsigned *_siglen, + const RSA *rsa) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + struct st_neverbleed_thread_data_t *thdata; + struct expbuf_t buf = {NULL}; + size_t ret, siglen; + unsigned char *sigret; + + get_privsep_data(rsa, &exdata, &thdata); + + expbuf_push_str(&buf, "sign"); + expbuf_push_num(&buf, type); + expbuf_push_bytes(&buf, m, m_len); + expbuf_push_num(&buf, exdata->key_index); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0 || (sigret = expbuf_shift_bytes(&buf, &siglen)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + memcpy(_sigret, sigret, siglen); + *_siglen = (unsigned)siglen; + expbuf_dispose(&buf); + + return (int)ret; +} + +static int sign_stub(struct expbuf_t *buf) +{ + unsigned char *m, sigret[4096]; + size_t type, m_len, key_index; + RSA *rsa; + unsigned siglen = 0; + int ret; + + if (expbuf_shift_num(buf, &type) != 0 || (m = expbuf_shift_bytes(buf, &m_len)) == NULL || + expbuf_shift_num(buf, &key_index) != 0) { + errno = 0; + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + if ((rsa = daemon_get_rsa(key_index)) == NULL) { + errno = 0; + warnf("%s: invalid key index:%zu", __FUNCTION__, key_index); + return -1; + } + ret = RSA_sign((int)type, m, (unsigned)m_len, sigret, &siglen, rsa); + expbuf_dispose(buf); + RSA_free(rsa); + + expbuf_push_num(buf, ret); + expbuf_push_bytes(buf, sigret, ret == 1 ? siglen : 0); + + return 0; +} + +#if !OPENSSL_1_1_API + +static void RSA_get0_key(const RSA *rsa, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + if (n) { + *n = rsa->n; + } + + if (e) { + *e = rsa->e; + } + + if (d) { + *d = rsa->d; + } +} + +static int RSA_set0_key(RSA *rsa, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + if (n == NULL || e == NULL) { + return 0; + } + + BN_free(rsa->n); + BN_free(rsa->e); + BN_free(rsa->d); + rsa->n = n; + rsa->e = e; + rsa->d = d; + + return 1; +} + +static void RSA_set_flags(RSA *r, int flags) +{ + r->flags |= flags; +} +#endif + +static EVP_PKEY *create_pkey(neverbleed_t *nb, size_t key_index, const char *ebuf, const char *nbuf) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + RSA *rsa; + EVP_PKEY *pkey; + BIGNUM *e = NULL, *n = NULL; + + if ((exdata = malloc(sizeof(*exdata))) == NULL) { + fprintf(stderr, "no memory\n"); + abort(); + } + exdata->nb = nb; + exdata->key_index = key_index; + + rsa = RSA_new_method(nb->engine); + RSA_set_ex_data(rsa, 0, exdata); + if (BN_hex2bn(&e, ebuf) == 0) { + fprintf(stderr, "failed to parse e:%s\n", ebuf); + abort(); + } + if (BN_hex2bn(&n, nbuf) == 0) { + fprintf(stderr, "failed to parse n:%s\n", nbuf); + abort(); + } + RSA_set0_key(rsa, n, e, NULL); + RSA_set_flags(rsa, RSA_FLAG_EXT_PKEY); + + pkey = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(pkey, rsa); + RSA_free(rsa); + + return pkey; +} + +#if OPENSSL_1_1_API + +static EC_KEY *daemon_get_ecdsa(size_t key_index) +{ + EC_KEY *ec_key; + + pthread_mutex_lock(&daemon_vars.keys.lock); + ec_key = daemon_vars.keys.ecdsa_keys[key_index]; + if (ec_key) + EC_KEY_up_ref(ec_key); + pthread_mutex_unlock(&daemon_vars.keys.lock); + + return ec_key; +} + +static size_t daemon_set_ecdsa(EC_KEY *ec_key) +{ + pthread_mutex_lock(&daemon_vars.keys.lock); + + adjust_slots_reserved_size(NEVERBLEED_TYPE_ECDSA, &daemon_vars.keys.ecdsa_slots); + + size_t index = bita_ffirst(daemon_vars.keys.ecdsa_slots.bita_avail, daemon_vars.keys.ecdsa_slots.reserved_size, 0); + + if (index == SIZE_MAX) + dief("no available slot for key"); + + /* set slot as unavailable */ + BITUNSET(daemon_vars.keys.ecdsa_slots.bita_avail, index); + + daemon_vars.keys.ecdsa_slots.size++; + daemon_vars.keys.ecdsa_keys[index] = ec_key; + EC_KEY_up_ref(ec_key); + pthread_mutex_unlock(&daemon_vars.keys.lock); + + return index; +} + +static int ecdsa_sign_stub(struct expbuf_t *buf) +{ + unsigned char *m, sigret[4096]; + size_t type, m_len, key_index; + EC_KEY *ec_key; + unsigned siglen = 0; + int ret; + + if (expbuf_shift_num(buf, &type) != 0 || (m = expbuf_shift_bytes(buf, &m_len)) == NULL || + expbuf_shift_num(buf, &key_index) != 0) { + errno = 0; + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + if ((ec_key = daemon_get_ecdsa(key_index)) == NULL) { + errno = 0; + warnf("%s: invalid key index:%zu", __FUNCTION__, key_index); + return -1; + } + + ret = ECDSA_sign((int)type, m, (unsigned)m_len, sigret, &siglen, ec_key); + expbuf_dispose(buf); + + EC_KEY_free(ec_key); + + expbuf_push_num(buf, ret); + expbuf_push_bytes(buf, sigret, ret == 1 ? siglen : 0); + + return 0; +} + +static void ecdsa_get_privsep_data(const EC_KEY *ec_key, struct st_neverbleed_rsa_exdata_t **exdata, + struct st_neverbleed_thread_data_t **thdata) +{ + *exdata = EC_KEY_get_ex_data(ec_key, 0); + if (*exdata == NULL) { + errno = 0; + dief("invalid internal ref"); + } + *thdata = get_thread_data((*exdata)->nb); +} + +static int ecdsa_sign_proxy(int type, const unsigned char *m, int m_len, unsigned char *_sigret, unsigned int *_siglen, + const BIGNUM *kinv, const BIGNUM *rp, EC_KEY *ec_key) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + struct st_neverbleed_thread_data_t *thdata; + struct expbuf_t buf = {}; + size_t ret, siglen; + unsigned char *sigret; + + ecdsa_get_privsep_data(ec_key, &exdata, &thdata); + + /* as far as I've tested so far, kinv and rp are always NULL. + Looks like setup_sign will precompute this, but it is only + called sign_sig, and it seems to be not used in TLS ECDSA */ + if (kinv != NULL || rp != NULL) { + errno = 0; + dief("unexpected non-NULL kinv and rp"); + } + + expbuf_push_str(&buf, "ecdsa_sign"); + expbuf_push_num(&buf, type); + expbuf_push_bytes(&buf, m, m_len); + expbuf_push_num(&buf, exdata->key_index); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0 || (sigret = expbuf_shift_bytes(&buf, &siglen)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + memcpy(_sigret, sigret, siglen); + *_siglen = (unsigned)siglen; + expbuf_dispose(&buf); + + return (int)ret; +} + +static EVP_PKEY *ecdsa_create_pkey(neverbleed_t *nb, size_t key_index, int curve_name, const char *ec_pubkeybuf) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + EC_KEY *ec_key; + EC_GROUP *ec_group; + BIGNUM *ec_pubkeybn = NULL; + EC_POINT *ec_pubkey; + EVP_PKEY *pkey; + + if ((exdata = malloc(sizeof(*exdata))) == NULL) { + fprintf(stderr, "no memory\n"); + abort(); + } + exdata->nb = nb; + exdata->key_index = key_index; + + ec_key = EC_KEY_new_method(nb->engine); + EC_KEY_set_ex_data(ec_key, 0, exdata); + + ec_group = EC_GROUP_new_by_curve_name(curve_name); + if (!ec_group) { + fprintf(stderr, "could not create EC_GROUP\n"); + abort(); + } + + EC_KEY_set_group(ec_key, ec_group); + + if (BN_hex2bn(&ec_pubkeybn, ec_pubkeybuf) == 0) { + fprintf(stderr, "failed to parse ECDSA ephemeral public key:%s\n", ec_pubkeybuf); + abort(); + } + + if ((ec_pubkey = EC_POINT_bn2point(ec_group, ec_pubkeybn, NULL, NULL)) == NULL) { + fprintf(stderr, "failed to get ECDSA ephemeral public key from BIGNUM\n"); + abort(); + } + + EC_KEY_set_public_key(ec_key, ec_pubkey); + + pkey = EVP_PKEY_new(); + EVP_PKEY_set1_EC_KEY(pkey, ec_key); + + EC_POINT_free(ec_pubkey); + BN_free(ec_pubkeybn); + EC_GROUP_free(ec_group); + EC_KEY_free(ec_key); + + return pkey; +} + +static void priv_ecdsa_finish(EC_KEY *key) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + struct st_neverbleed_thread_data_t *thdata; + + ecdsa_get_privsep_data(key, &exdata, &thdata); + + struct expbuf_t buf = {NULL}; + size_t ret; + + expbuf_push_str(&buf, "del_ecdsa_key"); + expbuf_push_num(&buf, exdata->key_index); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0) { + errno = 0; + dief("failed to parse response"); + } + expbuf_dispose(&buf); +} + +static int del_ecdsa_key_stub(struct expbuf_t *buf) +{ + size_t key_index; + int ret = 0; + + if (expbuf_shift_num(buf, &key_index) != 0) { + errno = 0; + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + + if (!daemon_vars.keys.ecdsa_keys || key_index >= daemon_vars.keys.ecdsa_slots.reserved_size) { + errno = 0; + warnf("%s: invalid key index %zu", __FUNCTION__, key_index); + goto respond; + } + + if (BITCHECK(daemon_vars.keys.ecdsa_slots.bita_avail, key_index)) { + warnf("%s: index not in use %zu", __FUNCTION__, key_index); + goto respond; + } + + pthread_mutex_lock(&daemon_vars.keys.lock); + /* set slot as available */ + BITSET(daemon_vars.keys.ecdsa_slots.bita_avail, key_index); + daemon_vars.keys.ecdsa_slots.size--; + EC_KEY_free(daemon_vars.keys.ecdsa_keys[key_index]); + daemon_vars.keys.ecdsa_keys[key_index] = NULL; + pthread_mutex_unlock(&daemon_vars.keys.lock); + + ret = 1; + +respond: + expbuf_dispose(buf); + expbuf_push_num(buf, ret); + return 0; +} + +#endif + +int neverbleed_load_private_key_file(neverbleed_t *nb, SSL_CTX *ctx, const char *fn, char *errbuf) +{ + struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); + struct expbuf_t buf = {NULL}; + int ret = 1; + size_t index, type; + EVP_PKEY *pkey; + + expbuf_push_str(&buf, "load_key"); + expbuf_push_str(&buf, fn); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &type) != 0 || expbuf_shift_num(&buf, &index) != 0) { + errno = 0; + dief("failed to parse response"); + } + + switch (type) { + case NEVERBLEED_TYPE_RSA: { + char *estr, *nstr; + + if ((estr = expbuf_shift_str(&buf)) == NULL || (nstr = expbuf_shift_str(&buf)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + pkey = create_pkey(nb, index, estr, nstr); + break; + } +#if OPENSSL_1_1_API + case NEVERBLEED_TYPE_ECDSA: { + char *ec_pubkeystr; + size_t curve_name; + + if (expbuf_shift_num(&buf, &curve_name) != 0 || (ec_pubkeystr = expbuf_shift_str(&buf)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + pkey = ecdsa_create_pkey(nb, index, curve_name, ec_pubkeystr); + break; + } +#endif + default: { + char *errstr; + + if ((errstr = expbuf_shift_str(&buf)) == NULL) { + errno = 0; + dief("failed to parse response"); + } + + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "%s", errstr); + return -1; + } + } + + expbuf_dispose(&buf); + + /* success */ + if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "SSL_CTX_use_PrivateKey failed"); + ret = 0; + } + + EVP_PKEY_free(pkey); + return ret; +} + +static int load_key_stub(struct expbuf_t *buf) +{ + char *fn; + FILE *fp = NULL; + RSA *rsa = NULL; + size_t key_index = SIZE_MAX; + char *estr = NULL, *nstr = NULL, errbuf[NEVERBLEED_ERRBUF_SIZE] = ""; + size_t type = NEVERBLEED_TYPE_ERROR; + EVP_PKEY *pkey = NULL; + const EC_GROUP *ec_group; + BIGNUM *ec_pubkeybn = NULL; + char *ec_pubkeystr = NULL; + + if ((fn = expbuf_shift_str(buf)) == NULL) { + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + + if ((fp = fopen(fn, "rt")) == NULL) { + strerror_r(errno, errbuf, sizeof(errbuf)); + goto Respond; + } + + if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) { + snprintf(errbuf, sizeof(errbuf), "failed to parse the private key"); + goto Respond; + } + + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: { + const BIGNUM *e, *n; + + rsa = EVP_PKEY_get1_RSA(pkey); + type = NEVERBLEED_TYPE_RSA; + key_index = daemon_set_rsa(rsa); + RSA_get0_key(rsa, &n, &e, NULL); + estr = BN_bn2hex(e); + nstr = BN_bn2hex(n); + break; + } + case EVP_PKEY_EC: { +#if OPENSSL_1_1_API + const EC_POINT *ec_pubkey; + EC_KEY *ec_key; + + ec_key = EVP_PKEY_get0_EC_KEY(pkey); + type = NEVERBLEED_TYPE_ECDSA; + key_index = daemon_set_ecdsa(ec_key); + ec_group = EC_KEY_get0_group(ec_key); + ec_pubkey = EC_KEY_get0_public_key(ec_key); + ec_pubkeybn = BN_new(); + if (!EC_POINT_point2bn(ec_group, ec_pubkey, POINT_CONVERSION_COMPRESSED, ec_pubkeybn, NULL)) { + type = NEVERBLEED_TYPE_ERROR; + snprintf(errbuf, sizeof(errbuf), "failed to convert ECDSA public key to BIGNUM"); + goto Respond; + } + ec_pubkeystr = BN_bn2hex(ec_pubkeybn); + break; +#else + snprintf(errbuf, sizeof(errbuf), "ECDSA support requires OpenSSL >= 1.1.0"); + goto Respond; +#endif + } + default: + snprintf(errbuf, sizeof(errbuf), "unsupported private key: %d", EVP_PKEY_base_id(pkey)); + goto Respond; + } + +Respond: + expbuf_dispose(buf); + expbuf_push_num(buf, type); + expbuf_push_num(buf, key_index); + switch (type) { + case NEVERBLEED_TYPE_RSA: + expbuf_push_str(buf, estr != NULL ? estr : ""); + expbuf_push_str(buf, nstr != NULL ? nstr : ""); + break; + case NEVERBLEED_TYPE_ECDSA: + expbuf_push_num(buf, EC_GROUP_get_curve_name(ec_group)); + expbuf_push_str(buf, ec_pubkeystr); + break; + default: + expbuf_push_str(buf, errbuf); + } + if (rsa != NULL) + RSA_free(rsa); + if (pkey != NULL) + EVP_PKEY_free(pkey); + if (estr != NULL) + OPENSSL_free(estr); + if (nstr != NULL) + OPENSSL_free(nstr); + if (ec_pubkeystr != NULL) + OPENSSL_free(ec_pubkeystr); + if (ec_pubkeybn != NULL) + BN_free(ec_pubkeybn); + if (fp != NULL) + fclose(fp); + + return 0; +} + +int neverbleed_setuidgid(neverbleed_t *nb, const char *user, int change_socket_ownership) +{ + struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); + struct expbuf_t buf = {NULL}; + size_t ret; + + expbuf_push_str(&buf, "setuidgid"); + expbuf_push_str(&buf, user); + expbuf_push_num(&buf, change_socket_ownership); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0) { + errno = 0; + dief("failed to parse response"); + } + expbuf_dispose(&buf); + + return (int)ret; +} + +static int setuidgid_stub(struct expbuf_t *buf) +{ + const char *user; + size_t change_socket_ownership; + struct passwd pwbuf, *pw; + char pwstrbuf[65536]; /* should be large enough */ + int ret = -1; + + if ((user = expbuf_shift_str(buf)) == NULL || expbuf_shift_num(buf, &change_socket_ownership) != 0) { + errno = 0; + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + + errno = 0; + if (getpwnam_r(user, &pwbuf, pwstrbuf, sizeof(pwstrbuf), &pw) != 0) { + warnf("%s: getpwnam_r failed", __FUNCTION__); + goto Respond; + } + if (pw == NULL) { + warnf("%s: failed to obtain information of user:%s", __FUNCTION__, user); + goto Respond; + } + + if (change_socket_ownership) { + char *dir; + if (chown(daemon_vars.nb->sun_.sun_path, pw->pw_uid, pw->pw_gid) != 0) + dief("chown failed for:%s", daemon_vars.nb->sun_.sun_path); + dir = dirname(daemon_vars.nb->sun_.sun_path); + if (chown(dir, pw->pw_uid, pw->pw_gid) != 0) + dief("chown failed for:%s", dir); + free(dir); + } + + /* setuidgid */ + if (setgid(pw->pw_gid) != 0) { + warnf("%s: setgid(%d) failed", __FUNCTION__, (int)pw->pw_gid); + goto Respond; + } + if (initgroups(pw->pw_name, pw->pw_gid) != 0) { + warnf("%s: initgroups(%s, %d) failed", __FUNCTION__, pw->pw_name, (int)pw->pw_gid); + goto Respond; + } + if (setuid(pw->pw_uid) != 0) { + warnf("%s: setuid(%d) failed\n", __FUNCTION__, (int)pw->pw_uid); + goto Respond; + } + ret = 0; + +Respond: + expbuf_dispose(buf); + expbuf_push_num(buf, ret); + return 0; +} + +__attribute__((noreturn)) static void *daemon_close_notify_thread(void *_close_notify_fd) +{ + int close_notify_fd = (int)((char *)_close_notify_fd - (char *)NULL); + char b; + ssize_t r; + +Redo: + r = read(close_notify_fd, &b, 1); + if (r == -1 && errno == EINTR) + goto Redo; + if (r > 0) + goto Redo; + /* close or error */ + + /* unlink the temporary directory and socket file */ + unlink_dir(dirname(daemon_vars.nb->sun_.sun_path)); + + _exit(0); +} + +static int priv_rsa_finish(RSA *rsa) +{ + struct st_neverbleed_rsa_exdata_t *exdata; + struct st_neverbleed_thread_data_t *thdata; + + get_privsep_data(rsa, &exdata, &thdata); + + struct expbuf_t buf = {NULL}; + size_t ret; + + expbuf_push_str(&buf, "del_rsa_key"); + expbuf_push_num(&buf, exdata->key_index); + if (expbuf_write(&buf, thdata->fd) != 0) + dief(errno != 0 ? "write error" : "connection closed by daemon"); + expbuf_dispose(&buf); + + if (expbuf_read(&buf, thdata->fd) != 0) + dief(errno != 0 ? "read error" : "connection closed by daemon"); + if (expbuf_shift_num(&buf, &ret) != 0) { + errno = 0; + dief("failed to parse response"); + } + expbuf_dispose(&buf); + + return (int)ret; +} + +static int del_rsa_key_stub(struct expbuf_t *buf) +{ + size_t key_index; + + int ret = 0; + + if (expbuf_shift_num(buf, &key_index) != 0) { + errno = 0; + warnf("%s: failed to parse request", __FUNCTION__); + return -1; + } + + if (!daemon_vars.keys.keys || key_index >= daemon_vars.keys.rsa_slots.reserved_size) { + errno = 0; + warnf("%s: invalid key index %zu", __FUNCTION__, key_index); + goto respond; + } + + if (BITCHECK(daemon_vars.keys.rsa_slots.bita_avail, key_index)) { + warnf("%s: index not in use %zu", __FUNCTION__, key_index); + goto respond; + } + + pthread_mutex_lock(&daemon_vars.keys.lock); + /* set slot as available */ + BITSET(daemon_vars.keys.rsa_slots.bita_avail, key_index); + daemon_vars.keys.rsa_slots.size--; + RSA_free(daemon_vars.keys.keys[key_index]); + daemon_vars.keys.keys[key_index] = NULL; + pthread_mutex_unlock(&daemon_vars.keys.lock); + + ret = 1; + +respond: + expbuf_dispose(buf); + expbuf_push_num(buf, ret); + return 0; +} + +static void *daemon_conn_thread(void *_sock_fd) +{ + int sock_fd = (int)((char *)_sock_fd - (char *)NULL); + struct expbuf_t buf = {NULL}; + unsigned char auth_token[NEVERBLEED_AUTH_TOKEN_SIZE]; + + /* authenticate */ + if (read_nbytes(sock_fd, &auth_token, sizeof(auth_token)) != 0) { + warnf("failed to receive authencication token from client"); + goto Exit; + } + if (memcmp(auth_token, daemon_vars.nb->auth_token, NEVERBLEED_AUTH_TOKEN_SIZE) != 0) { + warnf("client authentication failed"); + goto Exit; + } + + while (1) { + char *cmd; + if (expbuf_read(&buf, sock_fd) != 0) { + if (errno != 0) + warnf("read error"); + break; + } + if ((cmd = expbuf_shift_str(&buf)) == NULL) { + errno = 0; + warnf("failed to parse request"); + break; + } + if (strcmp(cmd, "priv_enc") == 0) { + if (priv_enc_stub(&buf) != 0) + break; + } else if (strcmp(cmd, "priv_dec") == 0) { + if (priv_dec_stub(&buf) != 0) + break; + } else if (strcmp(cmd, "sign") == 0) { + if (sign_stub(&buf) != 0) + break; +#if OPENSSL_1_1_API + } else if (strcmp(cmd, "ecdsa_sign") == 0) { + if (ecdsa_sign_stub(&buf) != 0) + break; + } else if (strcmp(cmd, "del_ecdsa_key") == 0) { + if (del_ecdsa_key_stub(&buf) != 0) + break; +#endif + } else if (strcmp(cmd, "load_key") == 0) { + if (load_key_stub(&buf) != 0) + break; + } else if (strcmp(cmd, "del_rsa_key") == 0) { + if (del_rsa_key_stub(&buf) != 0) + break; + } else if (strcmp(cmd, "setuidgid") == 0) { + if (setuidgid_stub(&buf) != 0) + break; + } else { + warnf("unknown command:%s", cmd); + break; + } + if (expbuf_write(&buf, sock_fd) != 0) { + warnf(errno != 0 ? "write error" : "connection closed by client"); + break; + } + expbuf_dispose(&buf); + } + +Exit: + expbuf_dispose(&buf); + close(sock_fd); + + return NULL; +} + +__attribute__((noreturn)) static void daemon_main(int listen_fd, int close_notify_fd, const char *tempdir) +{ + pthread_t tid; + pthread_attr_t thattr; + int sock_fd; + + { /* close all descriptors (except STDIN, STDOUT, STRERR, listen_fd, close_notify_fd) */ + int fd = (int)sysconf(_SC_OPEN_MAX) - 1; + for (; fd > 2; --fd) { + if (fd == listen_fd || fd == close_notify_fd) + continue; + close(fd); + } + } + + pthread_attr_init(&thattr); + pthread_attr_setdetachstate(&thattr, 1); + + if (pthread_create(&tid, &thattr, daemon_close_notify_thread, (char *)NULL + close_notify_fd) != 0) + dief("pthread_create failed"); + + while (1) { + while ((sock_fd = accept(listen_fd, NULL, NULL)) == -1) + ; + if (pthread_create(&tid, &thattr, daemon_conn_thread, (char *)NULL + sock_fd) != 0) + dief("pthread_create failed"); + } +} + +#if !OPENSSL_1_1_API + +static RSA_METHOD static_rsa_method = { + "privsep RSA method", /* name */ + NULL, /* rsa_pub_enc */ + NULL, /* rsa_pub_dec */ + priv_enc_proxy, /* rsa_priv_enc */ + priv_dec_proxy, /* rsa_priv_dec */ + NULL, /* rsa_mod_exp */ + NULL, /* bn_mod_exp */ + NULL, /* init */ + priv_rsa_finish, /* finish */ + RSA_FLAG_SIGN_VER, /* flags */ + NULL, /* app data */ + sign_proxy, /* rsa_sign */ + NULL, /* rsa_verify */ + NULL /* rsa_keygen */ +}; + +#endif + +int neverbleed_init(neverbleed_t *nb, char *errbuf) +{ + int pipe_fds[2] = {-1, -1}, listen_fd = -1; + char *tempdir = NULL; +#if OPENSSL_1_1_API + const RSA_METHOD *default_method = RSA_PKCS1_OpenSSL(); + EC_KEY_METHOD *ecdsa_method; + const EC_KEY_METHOD *ecdsa_default_method; + RSA_METHOD *rsa_method = RSA_meth_new("privsep RSA method", 0); + + RSA_meth_set_priv_enc(rsa_method, priv_enc_proxy); + RSA_meth_set_priv_dec(rsa_method, priv_dec_proxy); + RSA_meth_set_sign(rsa_method, sign_proxy); + + RSA_meth_set_pub_enc(rsa_method, RSA_meth_get_pub_enc(default_method)); + RSA_meth_set_pub_dec(rsa_method, RSA_meth_get_pub_dec(default_method)); + RSA_meth_set_verify(rsa_method, RSA_meth_get_verify(default_method)); + + RSA_meth_set_finish(rsa_method, priv_rsa_finish); + + /* setup EC_KEY_METHOD for ECDSA */ + ecdsa_default_method = EC_KEY_get_default_method(); + ecdsa_method = EC_KEY_METHOD_new(ecdsa_default_method); + + EC_KEY_METHOD_set_keygen(ecdsa_method, NULL); + EC_KEY_METHOD_set_compute_key(ecdsa_method, NULL); + /* it seems sign_sig and sign_setup is not used in TLS ECDSA. */ + EC_KEY_METHOD_set_sign(ecdsa_method, ecdsa_sign_proxy, NULL, NULL); + EC_KEY_METHOD_set_init(ecdsa_method, NULL, priv_ecdsa_finish, NULL, NULL, NULL, NULL); +#else + const RSA_METHOD *default_method = RSA_PKCS1_SSLeay(); + RSA_METHOD *rsa_method = &static_rsa_method; + + rsa_method->rsa_pub_enc = default_method->rsa_pub_enc; + rsa_method->rsa_pub_dec = default_method->rsa_pub_dec; + rsa_method->rsa_verify = default_method->rsa_verify; +#endif + + /* setup the daemon */ + if (pipe(pipe_fds) != 0) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "pipe(2) failed:%s", strerror(errno)); + goto Fail; + } + set_cloexec(pipe_fds[1]); + if ((tempdir = strdup("/tmp/openssl-privsep.XXXXXX")) == NULL) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "no memory"); + goto Fail; + } + if (mkdtemp(tempdir) == NULL) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "failed to create temporary directory under /tmp:%s", strerror(errno)); + goto Fail; + } + memset(&nb->sun_, 0, sizeof(nb->sun_)); + nb->sun_.sun_family = AF_UNIX; + snprintf(nb->sun_.sun_path, sizeof(nb->sun_.sun_path), "%s/_", tempdir); + RAND_bytes(nb->auth_token, sizeof(nb->auth_token)); + if ((listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "socket(2) failed:%s", strerror(errno)); + goto Fail; + } + if (bind(listen_fd, (void *)&nb->sun_, sizeof(nb->sun_)) != 0) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "failed to bind to %s:%s", nb->sun_.sun_path, strerror(errno)); + goto Fail; + } + if (listen(listen_fd, SOMAXCONN) != 0) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "listen(2) failed:%s", strerror(errno)); + goto Fail; + } + nb->daemon_pid = fork(); + switch (nb->daemon_pid) { + case -1: + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "fork(2) failed:%s", strerror(errno)); + goto Fail; + case 0: + close(pipe_fds[1]); +#ifdef __linux__ + prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); +#endif + daemon_vars.nb = nb; + daemon_main(listen_fd, pipe_fds[0], tempdir); + break; + default: + break; + } + close(listen_fd); + listen_fd = -1; + close(pipe_fds[0]); + pipe_fds[0] = -1; + + /* setup engine */ + if ((nb->engine = ENGINE_new()) == NULL || !ENGINE_set_id(nb->engine, "neverbleed") || + !ENGINE_set_name(nb->engine, "privilege separation software engine") || !ENGINE_set_RSA(nb->engine, rsa_method) +#if OPENSSL_1_1_API + || !ENGINE_set_EC(nb->engine, ecdsa_method) +#endif + ) { + snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "failed to initialize the OpenSSL engine"); + goto Fail; + } + ENGINE_add(nb->engine); + + /* setup thread key */ + pthread_key_create(&nb->thread_key, dispose_thread_data); + + free(tempdir); + return 0; +Fail: + if (pipe_fds[0] != -1) + close(pipe_fds[0]); + if (pipe_fds[1] != -1) + close(pipe_fds[1]); + if (tempdir != NULL) { + unlink_dir(tempdir); + free(tempdir); + } + if (listen_fd != -1) + close(listen_fd); + if (nb->engine != NULL) { + ENGINE_free(nb->engine); + nb->engine = NULL; + } + return -1; +} diff --git a/web/server/h2o/libh2o/deps/neverbleed/neverbleed.h b/web/server/h2o/libh2o/deps/neverbleed/neverbleed.h new file mode 100644 index 000000000..4abfad479 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/neverbleed.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 Kazuho Oku, DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef NEVERBLEED_H +#define NEVERBLEED_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NEVERBLEED_ERRBUF_SIZE (256) +#define NEVERBLEED_AUTH_TOKEN_SIZE 32 + +typedef struct st_neverbleed_t { + ENGINE *engine; + pid_t daemon_pid; + struct sockaddr_un sun_; + pthread_key_t thread_key; + unsigned char auth_token[NEVERBLEED_AUTH_TOKEN_SIZE]; +} neverbleed_t; + +/** + * initializes the privilege separation engine (returns 0 if successful) + */ +int neverbleed_init(neverbleed_t *nb, char *errbuf); +/** + * loads a private key file (returns 1 if successful) + */ +int neverbleed_load_private_key_file(neverbleed_t *nb, SSL_CTX *ctx, const char *fn, char *errbuf); +/** + * setuidgid (also changes the file permissions so that `user` can connect to the daemon, if change_socket_ownership is non-zero) + */ +int neverbleed_setuidgid(neverbleed_t *nb, const char *user, int change_socket_ownership); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/web/server/h2o/libh2o/deps/neverbleed/test.c b/web/server/h2o/libh2o/deps/neverbleed/test.c new file mode 100644 index 000000000..494fc65f0 --- /dev/null +++ b/web/server/h2o/libh2o/deps/neverbleed/test.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015 Kazuho Oku, DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "neverbleed.h" + +static void setup_ecc_key(SSL_CTX *ssl_ctx) +{ + int nid = NID_X9_62_prime256v1; + EC_KEY *key = EC_KEY_new_by_curve_name(nid); + if (key == NULL) { + fprintf(stderr, "Failed to create curve \"%s\"\n", OBJ_nid2sn(nid)); + return; + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, key); + EC_KEY_free(key); +} + +int dumb_https_server(unsigned short port, SSL_CTX *ctx) +{ + int listen_fd, reuse_flag; + struct sockaddr_in sin = {}; + + if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "failed to create socket:%s\n", strerror(errno)); + return 111; + } + reuse_flag = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, sizeof(reuse_flag)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); + sin.sin_port = htons(8888); + if (bind(listen_fd, (void *)&sin, sizeof(sin)) != 0) { + fprintf(stderr, "bind failed:%s\n", strerror(errno)); + return 111; + } + if (listen(listen_fd, SOMAXCONN) != 0) { + fprintf(stderr, "listen failed:%s\n", strerror(errno)); + return 111; + } + + while (1) { + int conn_fd; + SSL *ssl; + char buf[4096]; + /* accept connection */ + while ((conn_fd = accept(listen_fd, NULL, NULL)) == -1 && errno == EINTR) + ; + if (conn_fd == -1) { + fprintf(stderr, "accept(2) failed:%s\n", strerror(errno)); + return 111; + } + ssl = SSL_new(ctx); + SSL_set_fd(ssl, conn_fd); + if (SSL_accept(ssl) == 1) { + SSL_read(ssl, buf, sizeof(buf)); + const char *resp = + "HTTP/1.0 200 OK\r\nContent-Length: 6\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\nhello\n"; + SSL_write(ssl, resp, strlen(resp)); + SSL_shutdown(ssl); + } else { + fprintf(stderr, "SSL_accept failed\n"); + } + SSL_free(ssl); + close(conn_fd); + } +} + +int main(int argc, char **argv) +{ + unsigned short port; + SSL_CTX *ctx; + neverbleed_t nb; + char errbuf[NEVERBLEED_ERRBUF_SIZE]; + int use_privsep; + + /* initialization */ + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + if (neverbleed_init(&nb, errbuf) != 0) { + fprintf(stderr, "openssl_privsep_init: %s\n", errbuf); + return 111; + } + ctx = SSL_CTX_new(SSLv23_server_method()); + SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); + setup_ecc_key(ctx); + + /* parse args */ + if (argc != 5) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 111; + } + if (strcmp(argv[1], "internal") == 0) { + use_privsep = 0; + } else if (strcmp(argv[1], "privsep") == 0) { + use_privsep = 1; + } else { + fprintf(stderr, "unknown mode:%s\n", argv[1]); + return 111; + } + if (sscanf(argv[2], "%hu", &port) != 1) { + fprintf(stderr, "failed to parse port:%s\n", argv[2]); + return 111; + } + if (SSL_CTX_use_certificate_chain_file(ctx, argv[3]) != 1) { + fprintf(stderr, "failed to load certificate chain file:%s\n", argv[3]); + return 111; + } + if (use_privsep) { + if (neverbleed_load_private_key_file(&nb, ctx, argv[4], errbuf) != 1) { + fprintf(stderr, "failed to load private key from file:%s:%s\n", argv[4], errbuf); + return 111; + } + } else { + if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "failed to load private key from file:%s\n", argv[4]); + return 111; + } + } + + /* start the httpd */ + return dumb_https_server(port, ctx); +} -- cgit v1.2.3