Repository: h2o/neverbleed Branch: master Commit: 67e21a86d3fa Files: 13 Total size: 90.7 KB Directory structure: gitextract_ad7ysdat/ ├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── neverbleed.c ├── neverbleed.h ├── t/ │ ├── assets/ │ │ ├── test-ecdsa.crt │ │ ├── test-ecdsa.key │ │ ├── test.crt │ │ └── test.key │ └── test_handshake.t └── test.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # requires clang-format >= 3.6 BasedOnStyle: "LLVM" IndentWidth: 4 ColumnLimit: 132 BreakBeforeBraces: Linux AllowShortFunctionsOnASingleLine: None SortIncludes: false ================================================ FILE: .gitignore ================================================ # 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/ ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: Makefile ================================================ CC?= cc CFLAGS+= -Wall -fsanitize=address -fstack-protector -g LIBS+= -lpthread -lssl -lcrypto TARGET= test-neverbleed OBJS= test.o neverbleed.o all: $(TARGET) .c.o: $(CC) $(CFLAGS) -c $< $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS) check: $(TARGET) prove -v t/test_handshake.t clean: rm -fr $(OBJS) $(TARGET) .PHONY: clean check ================================================ FILE: README.md ================================================ Neverbleed =============== Neverbleed is an [OpenSSL engine](https://www.openssl.org/docs/man1.0.2/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. ================================================ FILE: neverbleed.c ================================================ /* * 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 #if defined(__linux__) #include #include #include #elif defined(__APPLE__) #include #elif defined(__FreeBSD__) #include #elif defined(__sun) #include #endif /* to maximize code-reuse between different stacks, we intentionally use API declared by OpenSSL as legacy */ #define OPENSSL_SUPPRESS_DEPRECATED #include #include #if defined(LIBRESSL_VERSION_NUMBER) ? LIBRESSL_VERSION_NUMBER >= 0x3050000fL : OPENSSL_VERSION_NUMBER >= 0x1010000fL /* RSA_METHOD is opaque, so RSA_meth* are used. */ #define NEVERBLEED_OPAQUE_RSA_METHOD #endif #if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_EC) && \ (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2090100fL) /* EC_KEY_METHOD and related APIs are avaliable, so ECDSA is enabled. */ #define NEVERBLEED_ECDSA #endif #include #ifdef NEVERBLEED_ECDSA #include #endif #include #include #include #ifdef __linux #if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL) #define USE_OFFLOAD 1 #endif #if defined(OPENSSL_IS_BORINGSSL) && defined(NEVERBLEED_BORINGSSL_USE_QAT) #include "qat_bssl.h" /* the mapping seems to be missing */ #ifndef ASYNC_WAIT_CTX_get_all_fds extern int bssl_async_wait_ctx_get_all_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *fd, size_t *numfds); #define ASYNC_WAIT_CTX_get_all_fds bssl_async_wait_ctx_get_all_fds #endif #define USE_OFFLOAD 1 #endif #endif #if OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) 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; } #define EVP_PKEY_up_ref(p) CRYPTO_add(&(p)->references, 1, CRYPTO_LOCK_EVP_PKEY) #endif #include "neverbleed.h" enum neverbleed_type { NEVERBLEED_TYPE_ERROR, NEVERBLEED_TYPE_RSA, NEVERBLEED_TYPE_ECDSA }; 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; }; /** * a variant of pthread_once, that does not require you to declare a callback, nor have a global variable */ #define NEVERBLEED_MULTITHREAD_ONCE(block) \ do { \ static volatile int lock = 0; \ int lock_loaded = lock; \ __sync_synchronize(); \ if (!lock_loaded) { \ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; \ pthread_mutex_lock(&mutex); \ if (!lock) { \ do { \ block \ } while (0); \ __sync_synchronize(); \ lock = 1; \ } \ pthread_mutex_unlock(&mutex); \ } \ } while (0) 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; } /** * This function disposes of the memory allocated for `neverbleed_iobuf_t`, but retains the value of `next` and `processing` so that * the buffer can be "cleared" while in use by worker threads. */ static void iobuf_dispose(neverbleed_iobuf_t *buf) { if (buf->capacity != 0) OPENSSL_cleanse(buf->buf, buf->capacity); free(buf->buf); buf->buf = NULL; buf->start = NULL; buf->end = NULL; buf->capacity = 0; } static void iobuf_reserve(neverbleed_iobuf_t *buf, size_t extra) { size_t start_off, end_off; if (extra <= buf->buf - buf->end + buf->capacity) return; if (buf->capacity == 0) buf->capacity = 4096; while (buf->buf - buf->end + buf->capacity < extra) buf->capacity *= 2; if (buf->buf != NULL) { start_off = buf->start - buf->buf; end_off = buf->end - buf->buf; } else { /* C99 forbids us doing `buf->start - buf->buf` when both are NULL (undefined behavior) */ start_off = 0; end_off = 0; } if ((buf->buf = realloc(buf->buf, buf->capacity)) == NULL) dief("realloc failed"); buf->start = buf->buf + start_off; buf->end = buf->buf + end_off; } static void iobuf_push_num(neverbleed_iobuf_t *buf, size_t v) { iobuf_reserve(buf, sizeof(v)); memcpy(buf->end, &v, sizeof(v)); buf->end += sizeof(v); } static void iobuf_push_str(neverbleed_iobuf_t *buf, const char *s) { size_t l = strlen(s) + 1; iobuf_reserve(buf, l); memcpy(buf->end, s, l); buf->end += l; } static void iobuf_push_bytes(neverbleed_iobuf_t *buf, const void *p, size_t l) { iobuf_push_num(buf, l); iobuf_reserve(buf, l); memcpy(buf->end, p, l); buf->end += l; } static int iobuf_shift_num(neverbleed_iobuf_t *buf, size_t *v) { if (neverbleed_iobuf_size(buf) < sizeof(*v)) return -1; memcpy(v, buf->start, sizeof(*v)); buf->start += sizeof(*v); return 0; } static char *iobuf_shift_str(neverbleed_iobuf_t *buf) { char *nul = memchr(buf->start, '\0', neverbleed_iobuf_size(buf)), *ret; if (nul == NULL) return NULL; ret = buf->start; buf->start = nul + 1; return ret; } static void *iobuf_shift_bytes(neverbleed_iobuf_t *buf, size_t *l) { void *ret; if (iobuf_shift_num(buf, l) != 0) return NULL; if (neverbleed_iobuf_size(buf) < *l) return NULL; ret = buf->start; buf->start += *l; return ret; } static int iobuf_write(neverbleed_iobuf_t *buf, int fd) { struct iovec vecs[2] = {{NULL}}; size_t bufsz = neverbleed_iobuf_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 iobuf_read(neverbleed_iobuf_t *buf, int fd) { size_t sz; if (read_nbytes(fd, &sz, sizeof(sz)) != 0) return -1; iobuf_reserve(buf, sz); if (read_nbytes(fd, buf->end, sz) != 0) return -1; buf->end += sz; return 0; } void neverbleed_iobuf_dispose(neverbleed_iobuf_t *buf) { iobuf_dispose(buf); } static void iobuf_transaction_write(neverbleed_iobuf_t *buf, struct st_neverbleed_thread_data_t *thdata) { if (iobuf_write(buf, thdata->fd) == -1) { if (errno != 0) { dief("write error (%d) %s", errno, strerror(errno)); } else { dief("connection closed by daemon"); } } } static void iobuf_transaction_read(neverbleed_iobuf_t *buf, struct st_neverbleed_thread_data_t *thdata) { iobuf_dispose(buf); if (iobuf_read(buf, thdata->fd) == -1) { if (errno != 0) { dief("read error (%d) %s", errno, strerror(errno)); } else { dief("connection closed by daemon"); } } } /** * Only sends a request, does not read a response */ static void iobuf_transaction_no_response(neverbleed_iobuf_t *buf, struct st_neverbleed_thread_data_t *thdata) { if (neverbleed_transaction_cb != NULL) { neverbleed_transaction_cb(buf, 1); } else { iobuf_transaction_write(buf, thdata); iobuf_dispose(buf); } } /** * Sends a request and reads a response. */ static void iobuf_transaction(neverbleed_iobuf_t *buf, struct st_neverbleed_thread_data_t *thdata) { if (neverbleed_transaction_cb != NULL) { neverbleed_transaction_cb(buf, 0); } else { iobuf_transaction_write(buf, thdata); iobuf_transaction_read(buf, thdata); } } #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); } static void dispose_thread_data(void *_thdata) { struct st_neverbleed_thread_data_t *thdata = _thdata; assert(thdata->fd >= 0); close(thdata->fd); thdata->fd = -1; free(thdata); } static 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; } int neverbleed_get_fd(neverbleed_t *nb) { struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); return thdata->fd; } void neverbleed_transaction_read(neverbleed_t *nb, neverbleed_iobuf_t *buf) { struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); iobuf_transaction_read(buf, thdata); } void neverbleed_transaction_write(neverbleed_t *nb, neverbleed_iobuf_t *buf) { struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); iobuf_transaction_write(buf, thdata); } static void do_exdata_free_callback(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) { /* when other engines are used, this callback gets called without neverbleed data */ if (ptr == NULL) return; struct st_neverbleed_rsa_exdata_t *exdata = ptr; struct st_neverbleed_thread_data_t *thdata = get_thread_data(exdata->nb); neverbleed_iobuf_t buf = {NULL}; iobuf_push_str(&buf, "del_pkey"); iobuf_push_num(&buf, exdata->key_index); // "del_pkey" command is fire-and-forget, it cannot fail, so doesn't have a response iobuf_transaction_no_response(&buf, thdata); free(exdata); } static int get_rsa_exdata_idx(void); static void rsa_exdata_free_callback(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) { assert(idx == get_rsa_exdata_idx()); do_exdata_free_callback(parent, ptr, ad, idx, argl, argp); } static int get_rsa_exdata_idx(void) { static volatile int index; NEVERBLEED_MULTITHREAD_ONCE({ index = RSA_get_ex_new_index(0, NULL, NULL, NULL, rsa_exdata_free_callback); }); return index; } 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, get_rsa_exdata_idx()); if (*exdata == NULL) { errno = 0; dief("invalid internal ref"); } *thdata = get_thread_data((*exdata)->nb); } static struct { struct { pthread_mutex_t lock; /** * if the slot is use contains a non-NULL key; if not in use, contains the index of the next empty slot or SIZE_MAX if there * are no more empty slots */ union { EVP_PKEY *pkey; size_t next_empty; } *slots; size_t num_slots; size_t first_empty; } keys; neverbleed_t *nb; } daemon_vars = {{.lock = PTHREAD_MUTEX_INITIALIZER, .first_empty = SIZE_MAX}}; static __thread struct { int sockfd; #ifdef __linux int epollfd; #endif struct { neverbleed_iobuf_t *first, **next; } responses; } conn_ctx; static int use_offload = 0; #if USE_OFFLOAD struct engine_request { neverbleed_iobuf_t *buf; int async_fd; #ifdef OPENSSL_IS_BORINGSSL struct { RSA *rsa; uint8_t output[512]; union { struct { uint8_t padded[512]; } digestsign; }; } data; async_ctx *async_ctx; #else int (*stub)(neverbleed_iobuf_t *); struct { ASYNC_WAIT_CTX *ctx; ASYNC_JOB *job; } async; #endif }; static void offload_free_request(struct engine_request *req) { #ifdef OPENSSL_IS_BORINGSSL bssl_qat_async_finish_job(req->async_ctx); RSA_free(req->data.rsa); #else ASYNC_WAIT_CTX_free(req->async.ctx); #endif OPENSSL_cleanse(req, sizeof(*req)); free(req); } static int do_epoll_ctl(int epollfd, int op, int fd, struct epoll_event *event) { int ret; while ((ret = epoll_ctl(epollfd, op, fd, event) != 0) && errno == EINTR) ; return ret; } static void register_wait_fd(struct engine_request *req) { #ifdef OPENSSL_IS_BORINGSSL ASYNC_WAIT_CTX *ctx = req->async_ctx->currjob->waitctx; #else ASYNC_WAIT_CTX *ctx = req->async.ctx; #endif size_t numfds; if (!ASYNC_WAIT_CTX_get_all_fds(ctx, NULL, &numfds) || numfds != 1) dief("unexpected number of fds (%zu) requested in async mode\n", numfds); if (!ASYNC_WAIT_CTX_get_all_fds(ctx, &req->async_fd, &numfds)) dief("ASYNC_WAIT_CTX_get_all_fds failed\n"); struct epoll_event ev = {.events = EPOLLIN, .data.ptr = req}; if (do_epoll_ctl(conn_ctx.epollfd, EPOLL_CTL_ADD, req->async_fd, &ev) != 0) dief("epoll_ctl failed:%d\n", errno); } #endif static int send_responses(int cleanup) { neverbleed_iobuf_t *buf; int result = 0; /* Send all buffers that have data being filled. The lock is held until everything is being done, as this function can be called * from multiple threads simultaneously. */ while ((buf = conn_ctx.responses.first) != NULL && !buf->processing) { if ((conn_ctx.responses.first = buf->next) == NULL) conn_ctx.responses.next = &conn_ctx.responses.first; if (!cleanup && iobuf_write(buf, conn_ctx.sockfd) != 0) { warnf(errno != 0 ? "write error" : "connection closed by client"); result = -1; } iobuf_dispose(buf); free(buf); if (result != 0) break; } return result; } static RSA *daemon_get_rsa(size_t key_index) { RSA *rsa = NULL; pthread_mutex_lock(&daemon_vars.keys.lock); if (key_index < daemon_vars.keys.num_slots) rsa = EVP_PKEY_get1_RSA(daemon_vars.keys.slots[key_index].pkey); pthread_mutex_unlock(&daemon_vars.keys.lock); return rsa; } size_t allocate_slot(void) { /* expand if all slots are in use */ if (daemon_vars.keys.first_empty == SIZE_MAX) { size_t new_capacity = (daemon_vars.keys.num_slots < 4 ? 4 : daemon_vars.keys.num_slots) * 2; if ((daemon_vars.keys.slots = realloc(daemon_vars.keys.slots, sizeof(daemon_vars.keys.slots[0]) * new_capacity)) == NULL) dief("no memory"); daemon_vars.keys.first_empty = daemon_vars.keys.num_slots; for (size_t i = daemon_vars.keys.num_slots; i < new_capacity - 1; ++i) daemon_vars.keys.slots[i].next_empty = i + 1; daemon_vars.keys.slots[new_capacity - 1].next_empty = SIZE_MAX; daemon_vars.keys.num_slots = new_capacity; } /* detach the first empty slot from the empty list */ size_t slot_index = daemon_vars.keys.first_empty; daemon_vars.keys.first_empty = daemon_vars.keys.slots[slot_index].next_empty; /* set bogus value in the allocated slot to help figure out what happened upon crash */ daemon_vars.keys.slots[slot_index].next_empty = SIZE_MAX - 1; return slot_index; } static size_t daemon_set_pkey(EVP_PKEY *pkey) { assert(pkey != NULL); pthread_mutex_lock(&daemon_vars.keys.lock); size_t index = allocate_slot(); daemon_vars.keys.slots[index].pkey = pkey; EVP_PKEY_up_ref(pkey); 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; neverbleed_iobuf_t buf = {NULL}; size_t ret; unsigned char *to; size_t tolen; get_privsep_data(rsa, &exdata, &thdata); iobuf_push_str(&buf, cmd); iobuf_push_bytes(&buf, from, flen); iobuf_push_num(&buf, exdata->key_index); iobuf_push_num(&buf, padding); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &ret) != 0 || (to = iobuf_shift_bytes(&buf, &tolen)) == NULL) { errno = 0; dief("failed to parse response"); } memcpy(_to, to, tolen); iobuf_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), neverbleed_iobuf_t *buf) { unsigned char *from, to[4096]; size_t flen; size_t key_index, padding; RSA *rsa; int ret; if ((from = iobuf_shift_bytes(buf, &flen)) == NULL || iobuf_shift_num(buf, &key_index) != 0 || iobuf_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); iobuf_dispose(buf); RSA_free(rsa); iobuf_push_num(buf, ret); iobuf_push_bytes(buf, to, ret > 0 ? ret : 0); return 0; } #if !defined(OPENSSL_IS_BORINGSSL) 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(neverbleed_iobuf_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(neverbleed_iobuf_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; neverbleed_iobuf_t buf = {NULL}; size_t ret, siglen; unsigned char *sigret; get_privsep_data(rsa, &exdata, &thdata); iobuf_push_str(&buf, "sign"); iobuf_push_num(&buf, type); iobuf_push_bytes(&buf, m, m_len); iobuf_push_num(&buf, exdata->key_index); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &ret) != 0 || (sigret = iobuf_shift_bytes(&buf, &siglen)) == NULL) { errno = 0; dief("failed to parse response"); } memcpy(_sigret, sigret, siglen); *_siglen = (unsigned)siglen; iobuf_dispose(&buf); return (int)ret; } static int sign_stub(neverbleed_iobuf_t *buf) { unsigned char *m, sigret[4096]; size_t type, m_len, key_index; RSA *rsa; unsigned siglen = 0; int ret; if (iobuf_shift_num(buf, &type) != 0 || (m = iobuf_shift_bytes(buf, &m_len)) == NULL || iobuf_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); iobuf_dispose(buf); RSA_free(rsa); iobuf_push_num(buf, ret); iobuf_push_bytes(buf, sigret, ret == 1 ? siglen : 0); return 0; } #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, get_rsa_exdata_idx(), 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); #if !defined(OPENSSL_IS_BORINGSSL) RSA_set_flags(rsa, RSA_FLAG_EXT_PKEY); #endif pkey = EVP_PKEY_new(); EVP_PKEY_set1_RSA(pkey, rsa); RSA_free(rsa); return pkey; } #ifdef NEVERBLEED_ECDSA static EC_KEY *daemon_get_ecdsa(size_t key_index) { EC_KEY *ec_key = NULL; pthread_mutex_lock(&daemon_vars.keys.lock); if (key_index < daemon_vars.keys.num_slots) ec_key = EVP_PKEY_get1_EC_KEY(daemon_vars.keys.slots[key_index].pkey); pthread_mutex_unlock(&daemon_vars.keys.lock); return ec_key; } static int ecdsa_sign_stub(neverbleed_iobuf_t *buf) { unsigned char *m, sigret[4096]; size_t type, m_len, key_index; EC_KEY *ec_key; unsigned siglen = 0; int ret; if (iobuf_shift_num(buf, &type) != 0 || (m = iobuf_shift_bytes(buf, &m_len)) == NULL || iobuf_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); iobuf_dispose(buf); EC_KEY_free(ec_key); iobuf_push_num(buf, ret); iobuf_push_bytes(buf, sigret, ret == 1 ? siglen : 0); return 0; } static int get_ecdsa_exdata_idx(void); static void ecdsa_exdata_free_callback(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) { assert(idx == get_ecdsa_exdata_idx()); do_exdata_free_callback(parent, ptr, ad, idx, argl, argp); } static int get_ecdsa_exdata_idx(void) { static volatile int index; NEVERBLEED_MULTITHREAD_ONCE({ index = EC_KEY_get_ex_new_index(0, NULL, NULL, NULL, ecdsa_exdata_free_callback); }); return index; } 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, get_ecdsa_exdata_idx()); 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; neverbleed_iobuf_t buf = {NULL}; 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"); } iobuf_push_str(&buf, "ecdsa_sign"); iobuf_push_num(&buf, type); iobuf_push_bytes(&buf, m, m_len); iobuf_push_num(&buf, exdata->key_index); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &ret) != 0 || (sigret = iobuf_shift_bytes(&buf, &siglen)) == NULL) { errno = 0; dief("failed to parse response"); } memcpy(_sigret, sigret, siglen); *_siglen = (unsigned)siglen; iobuf_dispose(&buf); return (int)ret; } static EVP_PKEY *ecdsa_create_pkey(neverbleed_t *nb, size_t key_index, int curve_name, const void *pubkey, size_t pubkey_len) { struct st_neverbleed_rsa_exdata_t *exdata; EC_KEY *ec_key; EC_GROUP *ec_group; 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, get_ecdsa_exdata_idx(), 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); ec_pubkey = EC_POINT_new(ec_group); assert(ec_pubkey != NULL); if (!EC_POINT_oct2point(ec_group, ec_pubkey, pubkey, pubkey_len, 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); EC_GROUP_free(ec_group); EC_KEY_free(ec_key); return pkey; } #endif static EVP_PKEY *daemon_get_pkey(size_t key_index) { EVP_PKEY *pkey = NULL; pthread_mutex_lock(&daemon_vars.keys.lock); if (key_index < daemon_vars.keys.num_slots) { pkey = daemon_vars.keys.slots[key_index].pkey; EVP_PKEY_up_ref(pkey); } pthread_mutex_unlock(&daemon_vars.keys.lock); return pkey; } #if USE_OFFLOAD && defined(OPENSSL_IS_BORINGSSL) static struct engine_request *bssl_offload_create_request(neverbleed_iobuf_t *buf, EVP_PKEY *pkey) { RSA *_rsa = EVP_PKEY_get1_RSA(pkey); struct engine_request *req = malloc(sizeof(*req)); if (req == NULL) dief("no memory\n"); *req = (struct engine_request){.buf = buf, .async_fd = -1, .async_ctx = bssl_qat_async_start_job(), .data.rsa = _rsa}; if (req->async_ctx == NULL) dief("failed to initialize async job\n"); if (RSA_size(req->data.rsa) > sizeof(req->data.output)) dief("RSA key too large\n"); return req; } static void bssl_offload_digestsign(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const EVP_MD *md, const void *signdata, size_t signlen, int rsa_pss) { uint8_t digest[EVP_MAX_MD_SIZE]; unsigned digestlen; { /* generate digest of signdata */ EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); if (mdctx == NULL) dief("no memory\n"); if (!EVP_DigestInit_ex(mdctx, md, NULL) || !EVP_DigestUpdate(mdctx, signdata, signlen) || !EVP_DigestFinal_ex(mdctx, digest, &digestlen)) dief("digest calculation failed\n"); EVP_MD_CTX_free(mdctx); } struct engine_request *req = bssl_offload_create_request(buf, pkey); size_t rsa_size = RSA_size(req->data.rsa), padded_len; int padding; /* generate padded octets to be signed */ if (rsa_pss) { if (!RSA_padding_add_PKCS1_PSS_mgf1(req->data.rsa, req->data.digestsign.padded, digest, md, md, -1)) dief("RSA_paddding_add_PKCS1_PSS_mgf1 failed\n"); padded_len = rsa_size; padding = RSA_NO_PADDING; } else { /* PKCS1 padding */ int hash_nid = EVP_MD_type(md), is_alloced; uint8_t *tbs; if (!RSA_add_pkcs1_prefix(&tbs, &padded_len, &is_alloced, hash_nid, digest, digestlen)) dief("RSA_add_pkcs1_prefix failed\n"); if (padded_len > rsa_size) dief("output of RSA_add_pkcs1_prefix is unexpectedly large\n"); memcpy(req->data.digestsign.padded, tbs, padded_len); if (is_alloced) OPENSSL_free(tbs); padding = RSA_PKCS1_PADDING; } OPENSSL_cleanse(digest, sizeof(digest)); /* dispatch RSA calculation */ RSA_METHOD *meth = bssl_engine_get_rsa_method(); if (meth == NULL) dief("failed to obtain QAT RSA method table\n"); size_t siglen; if (!meth->sign_raw(req->data.rsa, &siglen, req->data.output, rsa_size, req->data.digestsign.padded, padded_len, padding)) dief("sign_raw failure\n"); if (siglen != 0) dief("sign_raw completed synchronously unexpectedly\n"); buf->processing = 1; register_wait_fd(req); } static int bssl_offload_decrypt(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const void *src, size_t len) { struct engine_request *req = bssl_offload_create_request(buf, pkey); /* dispatch RSA calculation */ RSA_METHOD *meth = bssl_engine_get_rsa_method(); if (meth == NULL) dief("failed to obtain QAT RSA method table\n"); size_t outlen; if (!meth->decrypt(req->data.rsa, &outlen, req->data.output, sizeof(req->data.output), src, len, RSA_NO_PADDING)) { warnf("RSA decrypt failure\n"); goto Exit; } if (outlen != 0) dief("RSA decrypt completed synchronously unexpectedly\n"); buf->processing = 1; register_wait_fd(req); return 1; Exit: offload_free_request(req); return 0; } #endif static int digestsign_stub(neverbleed_iobuf_t *buf) { size_t key_index, md_nid, signlen; void *signdata; size_t rsa_pss; EVP_PKEY *pkey; const EVP_MD *md; /* parse input */ if (iobuf_shift_num(buf, &key_index) != 0 || iobuf_shift_num(buf, &md_nid) != 0 || (signdata = iobuf_shift_bytes(buf, &signlen)) == NULL || iobuf_shift_num(buf, &rsa_pss) != 0) { errno = 0; warnf("%s: failed to parse request", __FUNCTION__); return -1; } if ((pkey = daemon_get_pkey(key_index)) == NULL) { errno = 0; warnf("%s: invalid key index:%zu", __FUNCTION__, key_index); return -1; } if (md_nid != SIZE_MAX) { if ((md = EVP_get_digestbynid((int)md_nid)) == NULL) { errno = 0; warnf("%s: invalid EVP_MD nid", __FUNCTION__); return -1; } } else { md = NULL; } #if USE_OFFLOAD && defined(OPENSSL_IS_BORINGSSL) if (use_offload && EVP_PKEY_id(pkey) == EVP_PKEY_RSA) { bssl_offload_digestsign(buf, pkey, md, signdata, signlen, rsa_pss); goto Exit; } #endif /* generate signature */ EVP_MD_CTX *mdctx = NULL; EVP_PKEY_CTX *pkey_ctx = NULL; unsigned char digestbuf[4096]; size_t digestlen; if ((mdctx = EVP_MD_CTX_create()) == NULL) goto Softfail; if (EVP_DigestSignInit(mdctx, &pkey_ctx, md, NULL, pkey) != 1) goto Softfail; if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA && rsa_pss) { if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1 || EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) goto Softfail; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, md) != 1) goto Softfail; } /* ED25519 keys can never be loaded, so use the Update -> Final call chain without worrying about backward compatibility */ if (EVP_DigestSignUpdate(mdctx, signdata, signlen) != 1) goto Softfail; if (EVP_DigestSignFinal(mdctx, NULL, &digestlen) != 1) goto Softfail; if (sizeof(digestbuf) < digestlen) { warnf("%s: digest unexpectedly long as %zu bytes", __FUNCTION__, digestlen); goto Softfail; } if (EVP_DigestSignFinal(mdctx, digestbuf, &digestlen) != 1) goto Softfail; Respond: /* build response */ iobuf_dispose(buf); iobuf_push_bytes(buf, digestbuf, digestlen); if (mdctx != NULL) EVP_MD_CTX_destroy(mdctx); Exit: __attribute__((unused)) if (pkey != NULL) EVP_PKEY_free(pkey); return 0; Softfail: digestlen = 0; goto Respond; } void neverbleed_start_digestsign(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const EVP_MD *md, const void *input, size_t len, int rsa_pss) { struct st_neverbleed_rsa_exdata_t *exdata; struct st_neverbleed_thread_data_t *thdata; const char *cmd = "digestsign"; /* obtain reference */ switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: { RSA *rsa = EVP_PKEY_get1_RSA(pkey); /* get0 is available not available in OpenSSL 1.0.2 */ get_privsep_data(rsa, &exdata, &thdata); RSA_free(rsa); cmd = "digestsign-rsa"; } break; #ifdef NEVERBLEED_ECDSA case EVP_PKEY_EC: ecdsa_get_privsep_data(EVP_PKEY_get0_EC_KEY(pkey), &exdata, &thdata); break; #endif default: dief("unexpected private key"); break; } *buf = (neverbleed_iobuf_t){NULL}; iobuf_push_str(buf, cmd); iobuf_push_num(buf, exdata->key_index); iobuf_push_num(buf, md != NULL ? (size_t)EVP_MD_nid(md) : SIZE_MAX); iobuf_push_bytes(buf, input, len); iobuf_push_num(buf, rsa_pss); } void neverbleed_finish_digestsign(neverbleed_iobuf_t *buf, void **digest, size_t *digest_len) { const void *src; if ((src = iobuf_shift_bytes(buf, digest_len)) == NULL) { errno = 0; dief("failed to parse response"); } if ((*digest = malloc(*digest_len)) == NULL) dief("no memory"); memcpy(*digest, src, *digest_len); iobuf_dispose(buf); } static int decrypt_stub(neverbleed_iobuf_t *buf) { size_t key_index, srclen; void *src; EVP_PKEY *pkey; RSA *rsa; uint8_t decryptbuf[1024]; int decryptlen; /* parse input */ if (iobuf_shift_num(buf, &key_index) != 0 || (src = iobuf_shift_bytes(buf, &srclen)) == NULL) { errno = 0; warnf("%s: failed to parse request", __FUNCTION__); return -1; } if ((pkey = daemon_get_pkey(key_index)) == NULL) { errno = 0; warnf("%s: invalid key index:%zu", __FUNCTION__, key_index); return -1; } rsa = EVP_PKEY_get1_RSA(pkey); /* get0 is available not available in OpenSSL 1.0.2 */ assert(rsa != NULL); assert(sizeof(decryptbuf) >= RSA_size(rsa)); #if USE_OFFLOAD && defined(OPENSSL_IS_BORINGSSL) if (use_offload) { if (!bssl_offload_decrypt(buf, pkey, src, srclen)) goto Softfail; goto Exit; } #endif if ((decryptlen = RSA_private_decrypt(srclen, src, decryptbuf, rsa, RSA_NO_PADDING)) == -1) { errno = 0; warnf("RSA decryption error"); goto Softfail; } Respond: iobuf_dispose(buf); iobuf_push_bytes(buf, decryptbuf, decryptlen); Exit: __attribute__((unused)) RSA_free(rsa); EVP_PKEY_free(pkey); return 0; Softfail: decryptlen = 0; goto Respond; } void neverbleed_start_decrypt(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const void *input, size_t len) { struct st_neverbleed_rsa_exdata_t *exdata; struct st_neverbleed_thread_data_t *thdata; { RSA *rsa = EVP_PKEY_get1_RSA(pkey); /* get0 is available not available in OpenSSL 1.0.2 */ assert(rsa != NULL); get_privsep_data(rsa, &exdata, &thdata); RSA_free(rsa); } *buf = (neverbleed_iobuf_t){NULL}; iobuf_push_str(buf, "decrypt"); iobuf_push_num(buf, exdata->key_index); iobuf_push_bytes(buf, input, len); } void neverbleed_finish_decrypt(neverbleed_iobuf_t *buf, void **digest, size_t *digest_len) { neverbleed_finish_digestsign(buf, digest, digest_len); } 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); neverbleed_iobuf_t buf = {NULL}; int ret = 1; size_t index, type; EVP_PKEY *pkey; iobuf_push_str(&buf, "load_key"); iobuf_push_str(&buf, fn); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &type) != 0 || iobuf_shift_num(&buf, &index) != 0) { errno = 0; dief("failed to parse response"); } switch (type) { case NEVERBLEED_TYPE_RSA: { char *estr, *nstr; if ((estr = iobuf_shift_str(&buf)) == NULL || (nstr = iobuf_shift_str(&buf)) == NULL) { errno = 0; dief("failed to parse response"); } pkey = create_pkey(nb, index, estr, nstr); break; } #ifdef NEVERBLEED_ECDSA case NEVERBLEED_TYPE_ECDSA: { size_t curve_name, pubkey_len; void *pubkey_bytes; if (iobuf_shift_num(&buf, &curve_name) != 0 || (pubkey_bytes = iobuf_shift_bytes(&buf, &pubkey_len)) == NULL) { errno = 0; dief("failed to parse response"); } pkey = ecdsa_create_pkey(nb, index, (int)curve_name, pubkey_bytes, pubkey_len); break; } #endif default: { char *errstr; if ((errstr = iobuf_shift_str(&buf)) == NULL) { errno = 0; dief("failed to parse response"); } snprintf(errbuf, NEVERBLEED_ERRBUF_SIZE, "%s", errstr); return -1; } } iobuf_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(neverbleed_iobuf_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; #ifdef NEVERBLEED_ECDSA const EC_GROUP *ec_group; void *ec_pubkeybytes = NULL; size_t ec_pubkeylen; #endif if ((fn = iobuf_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; RSA_get0_key(rsa, &n, &e, NULL); estr = BN_bn2hex(e); nstr = BN_bn2hex(n); break; } case EVP_PKEY_EC: { #ifdef NEVERBLEED_ECDSA const EC_POINT *ec_pubkey; EC_KEY *ec_key; ec_key = (EC_KEY *)EVP_PKEY_get0_EC_KEY(pkey); type = NEVERBLEED_TYPE_ECDSA; ec_group = EC_KEY_get0_group(ec_key); ec_pubkey = EC_KEY_get0_public_key(ec_key); ec_pubkeylen = EC_POINT_point2oct(ec_group, ec_pubkey, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); if (!(ec_pubkeylen > 0 && (ec_pubkeybytes = malloc(ec_pubkeylen)) != NULL && EC_POINT_point2oct(ec_group, ec_pubkey, POINT_CONVERSION_UNCOMPRESSED, ec_pubkeybytes, ec_pubkeylen, NULL) == ec_pubkeylen)) dief("failed to serialize EC public key"); break; #else snprintf(errbuf, sizeof(errbuf), "ECDSA support requires OpenSSL >= 1.1.0, LibreSSL >= 2.9.1, or BoringSSL"); goto Respond; #endif } default: snprintf(errbuf, sizeof(errbuf), "unsupported private key: %d", EVP_PKEY_base_id(pkey)); goto Respond; } /* store the key */ key_index = daemon_set_pkey(pkey); Respond: iobuf_dispose(buf); iobuf_push_num(buf, type); iobuf_push_num(buf, key_index); switch (type) { case NEVERBLEED_TYPE_RSA: iobuf_push_str(buf, estr != NULL ? estr : ""); iobuf_push_str(buf, nstr != NULL ? nstr : ""); break; #ifdef NEVERBLEED_ECDSA case NEVERBLEED_TYPE_ECDSA: iobuf_push_num(buf, EC_GROUP_get_curve_name(ec_group)); iobuf_push_bytes(buf, ec_pubkeybytes, ec_pubkeylen); break; #endif default: iobuf_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); #ifdef NEVERBLEED_ECDSA if (ec_pubkeybytes != NULL) free(ec_pubkeybytes); #endif 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); neverbleed_iobuf_t buf = {NULL}; size_t ret; iobuf_push_str(&buf, "setuidgid"); iobuf_push_str(&buf, user); iobuf_push_num(&buf, change_socket_ownership); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &ret) != 0) { errno = 0; dief("failed to parse response"); } iobuf_dispose(&buf); return (int)ret; } static int setuidgid_stub(neverbleed_iobuf_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 = iobuf_shift_str(buf)) == NULL || iobuf_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: iobuf_dispose(buf); iobuf_push_num(buf, ret); return 0; } #if NEVERBLEED_HAS_PTHREAD_SETAFFINITY_NP int neverbleed_setaffinity(neverbleed_t *nb, NEVERBLEED_CPU_SET_T *cpuset) { struct st_neverbleed_thread_data_t *thdata = get_thread_data(nb); neverbleed_iobuf_t buf = {NULL}; size_t ret; iobuf_push_str(&buf, "setaffinity"); iobuf_push_bytes(&buf, cpuset, sizeof(*cpuset)); iobuf_transaction(&buf, thdata); if (iobuf_shift_num(&buf, &ret) != 0) { errno = 0; dief("failed to parse response"); } iobuf_dispose(&buf); return (int)ret; } static int setaffinity_stub(neverbleed_iobuf_t *buf) { char *cpuset_bytes; size_t cpuset_len; NEVERBLEED_CPU_SET_T cpuset; int ret = 1; if ((cpuset_bytes = iobuf_shift_bytes(buf, &cpuset_len)) == NULL) { errno = 0; warnf("%s: failed to parse request", __FUNCTION__); return -1; } assert(cpuset_len == sizeof(NEVERBLEED_CPU_SET_T)); memcpy(&cpuset, cpuset_bytes, cpuset_len); #ifdef __NetBSD__ ret = pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset); #else ret = pthread_setaffinity_np(pthread_self(), sizeof(NEVERBLEED_CPU_SET_T), &cpuset); #endif if (ret != 0) { ret = 1; goto Respond; } ret = 0; Respond: iobuf_dispose(buf); iobuf_push_num(buf, ret); return 0; } #endif __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 del_pkey_stub(neverbleed_iobuf_t *buf) { size_t key_index; if (iobuf_shift_num(buf, &key_index) != 0) { errno = 0; warnf("%s: failed to parse request", __FUNCTION__); return -1; } pthread_mutex_lock(&daemon_vars.keys.lock); /* set slot as available */ if (key_index < daemon_vars.keys.num_slots) { EVP_PKEY_free(daemon_vars.keys.slots[key_index].pkey); daemon_vars.keys.slots[key_index].next_empty = daemon_vars.keys.first_empty; daemon_vars.keys.first_empty = key_index; } else { warnf("%s: invalid key index %zu", __FUNCTION__, key_index); } pthread_mutex_unlock(&daemon_vars.keys.lock); return 0; } #define offload_start(stub, buf) ((stub)(buf)) #if USE_OFFLOAD #ifdef OPENSSL_IS_BORINGSSL static int offload_resume(struct engine_request *req) { size_t outlen; if (do_epoll_ctl(conn_ctx.epollfd, EPOLL_CTL_DEL, req->async_fd, NULL) != 0) dief("epoll_ctl failed:%d\n", errno); /* get result */ if (bssl_qat_async_ctx_copy_result(req->async_ctx, req->data.output, &outlen, sizeof(req->data.output)) != 0) dief("failed to obtain offload result\n"); if (outlen > sizeof(req->data.output)) dief("RSA output is unexpectedly large\n"); /* save the result */ iobuf_dispose(req->buf); iobuf_push_bytes(req->buf, req->data.output, outlen); req->buf->processing = 0; offload_free_request(req); return 0; } #else static int offload_jobfunc(void *_req) { struct engine_request *req = *(void **)_req; return req->stub(req->buf); } #undef offload_start static int offload_start(int (*stub)(neverbleed_iobuf_t *), neverbleed_iobuf_t *buf) { /* if engine is not used, run the stub synchronously */ if (!use_offload) return stub(buf); buf->processing = 1; struct engine_request *req = malloc(sizeof(*req)); if (req == NULL) dief("no memory"); *req = (struct engine_request){.buf = buf, .async_fd = -1, .stub = stub}; if ((req->async.ctx = ASYNC_WAIT_CTX_new()) == NULL) dief("failed to create ASYNC_WAIT_CTX\n"); int ret; switch (ASYNC_start_job(&req->async.job, req->async.ctx, &ret, offload_jobfunc, &req, sizeof(req))) { case ASYNC_PAUSE: /* operation running async; register fd and bail out */ register_wait_fd(req); return 0; case ASYNC_FINISH: /* completed synchronously */ buf->processing = 0; break; default: dief("ASYNC_start_job errored\n"); break; } offload_free_request(req); return ret; } static int offload_resume(struct engine_request *req) { int ret; switch (ASYNC_start_job(&req->async.job, req->async.ctx, &ret, offload_jobfunc, &req, sizeof(req))) { case ASYNC_PAUSE: /* assume that wait fd is unchanged */ return 0; case ASYNC_FINISH: if (do_epoll_ctl(conn_ctx.epollfd, EPOLL_CTL_DEL, req->async_fd, NULL) != 0) dief("epoll_ctl failed:%d\n", errno); break; default: dief("ASYNC_start_job failed\n"); break; } /* job done */ req->buf->processing = 0; offload_free_request(req); return ret; } #endif #endif /** * This function waits for the provided socket to become readable, then calls `nanosleep(1)` before returning. * The intention behind sleep is to provide the application to complete its event loop before the neverbleed process starts * spending CPU cycles on the time-consuming RSA operation. * In addition, when QAT is used, this function processes completion notifications from QAT and sends the responses. */ static int wait_for_data(int cleanup) { #if USE_OFFLOAD struct epoll_event events[20]; int has_read = 0, num_events; do { while ((num_events = epoll_wait(conn_ctx.epollfd, events, sizeof(events) / sizeof(events[0]), -1)) == -1 && (errno == EAGAIN || errno == EINTR)) ; if (num_events == -1) dief("epoll_wait(2):%d\n", errno); for (int i = 0; i < num_events; ++i) { if (events[i].data.ptr == NULL) { has_read = 1; } else { struct engine_request *req = events[i].data.ptr; int ret; if ((ret = offload_resume(req)) != 0) return ret; if ((ret = send_responses(0)) != 0) return ret; } } } while (!has_read); #else fd_set rfds; int ret; FD_ZERO(&rfds); if (!cleanup) FD_SET(conn_ctx.sockfd, &rfds); while ((ret = select(conn_ctx.sockfd + 1, &rfds, NULL, NULL, NULL)) == -1 && (errno == EAGAIN || errno == EINTR)) ; if (ret == -1) dief("select(2):%d\n", errno); #endif // yield when data is available struct timespec tv = {.tv_nsec = 1}; (void)nanosleep(&tv, NULL); return 0; } static void *daemon_conn_thread(void *_sock_fd) { conn_ctx.sockfd = (int)((char *)_sock_fd - (char *)NULL); conn_ctx.responses.next = &conn_ctx.responses.first; neverbleed_iobuf_t *buf = NULL; #if USE_OFFLOAD if ((conn_ctx.epollfd = epoll_create1(EPOLL_CLOEXEC)) == -1) dief("epoll_create1 failed:%d\n", errno); { struct epoll_event ev = {.events = EPOLLIN}; if (do_epoll_ctl(conn_ctx.epollfd, EPOLL_CTL_ADD, conn_ctx.sockfd, &ev) != 0) dief("epoll_ctl failed:%d\n", errno); } #endif { /* authenticate */ unsigned char auth_token[NEVERBLEED_AUTH_TOKEN_SIZE]; if (read_nbytes(conn_ctx.sockfd, &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) { if (wait_for_data(0) != 0) break; free(buf); buf = malloc(sizeof(*buf)); if (buf == NULL) dief("no memory"); *buf = (neverbleed_iobuf_t){}; char *cmd; if (iobuf_read(buf, conn_ctx.sockfd) != 0) { if (errno != 0) warnf("read error"); break; } if ((cmd = iobuf_shift_str(buf)) == NULL) { errno = 0; warnf("failed to parse request"); break; } #if !defined(OPENSSL_IS_BORINGSSL) if (strcmp(cmd, "priv_enc") == 0) { if (offload_start(priv_enc_stub, buf) != 0) break; } else if (strcmp(cmd, "priv_dec") == 0) { if (offload_start(priv_dec_stub, buf) != 0) break; } else if (strcmp(cmd, "sign") == 0) { if (offload_start(sign_stub, buf) != 0) break; #ifdef NEVERBLEED_ECDSA } else if (strcmp(cmd, "ecdsa_sign") == 0) { if (ecdsa_sign_stub(buf) != 0) break; #endif } else #endif if (strcmp(cmd, "digestsign") == 0) { if (digestsign_stub(buf) != 0) break; } else if (strcmp(cmd, "digestsign-rsa") == 0) { if (offload_start(digestsign_stub, buf) != 0) break; } else if (strcmp(cmd, "decrypt") == 0) { if (offload_start(decrypt_stub, buf) != 0) break; } else if (strcmp(cmd, "load_key") == 0) { if (load_key_stub(buf) != 0) break; } else if (strcmp(cmd, "del_pkey") == 0) { if (del_pkey_stub(buf) != 0) break; iobuf_dispose(buf); // "del_pkey" command is fire-and-forget, it cannot fail, so doesn't have a response continue; } else if (strcmp(cmd, "setuidgid") == 0) { if (setuidgid_stub(buf) != 0) break; #if NEVERBLEED_HAS_PTHREAD_SETAFFINITY_NP } else if (strcmp(cmd, "setaffinity") == 0) { if (setaffinity_stub(buf) != 0) break; #endif } else { warnf("unknown command:%s", cmd); break; } /* add response to chain */ *conn_ctx.responses.next = buf; conn_ctx.responses.next = &buf->next; buf = NULL; /* do not free */ /* send responses if possible */ if (send_responses(0) != 0) break; } Exit: free(buf); /* run the loop while async ops are running */ while (conn_ctx.responses.first != NULL) wait_for_data(1); close(conn_ctx.sockfd); #ifdef __linux close(conn_ctx.epollfd); #endif return NULL; } #if !(defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)) #define closefrom my_closefrom static void my_closefrom(int lowfd) { /* On linux, try close_range (2), then fall back to the slow loop if it fails. */ #if defined(__linux__) && defined(__NR_close_range) if (syscall(__NR_close_range, lowfd, ~0, 0) == 0) return; #endif for (int fd = (int)sysconf(_SC_OPEN_MAX) - 1; fd >= lowfd; --fd) (void)close(fd); } #endif static void cleanup_fds(int listen_fd, int close_notify_fd) { int maxfd, k; maxfd = 0; if (listen_fd > maxfd) { maxfd = listen_fd; } if (close_notify_fd > maxfd) { maxfd = close_notify_fd; } for (k = 0; k < maxfd; k++) { if (k == listen_fd || k == close_notify_fd) continue; switch (k) { case STDOUT_FILENO: case STDERR_FILENO: case STDIN_FILENO: break; default: (void)close(k); } } closefrom(maxfd + 1); } __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; cleanup_fds(listen_fd, close_notify_fd); pthread_attr_init(&thattr); pthread_attr_setdetachstate(&thattr, 1); switch (neverbleed_offload) { case NEVERBLEED_OFFLOAD_QAT_ON: case NEVERBLEED_OFFLOAD_QAT_AUTO: { #if USE_OFFLOAD && defined(OPENSSL_IS_BORINGSSL) ENGINE_load_qat(); bssl_qat_set_default_string("RSA"); use_offload = ENGINE_QAT_PTR_GET() != NULL; #elif USE_OFFLOAD && !defined(OPENSSL_IS_BORINGSSL) ENGINE *qat = ENGINE_by_id("qatengine"); if (qat != NULL && ENGINE_init(qat)) { if (!ENGINE_set_default_RSA(qat)) dief("failed to assign RSA operations to QAT\n"); use_offload = 1; } #endif if (!use_offload && neverbleed_offload == NEVERBLEED_OFFLOAD_QAT_ON) dief("use of QAT is forced but unavailable\n"); } break; default: break; } 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"); } } static void set_signal_handler(int signo, void (*cb)(int signo)) { struct sigaction action; memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); action.sa_handler = cb; sigaction(signo, &action, NULL); } #ifndef NEVERBLEED_OPAQUE_RSA_METHOD 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 */ NULL, /* 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; /* 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]); #if defined(__linux__) prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); prctl(PR_SET_PDEATHSIG, SIGTERM); #elif defined(__FreeBSD__) int dumpable = PROC_TRACE_CTL_DISABLE; procctl(P_PID, 0, PROC_TRACE_CTL, &dumpable); #elif defined(__sun) setpflags(__PROC_PROTECT, 1); #elif defined(__APPLE__) ptrace(PT_DENY_ATTACH, 0, 0, 0); #endif set_signal_handler(SIGTERM, SIG_IGN); if (neverbleed_post_fork_cb != NULL) neverbleed_post_fork_cb(); 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; #if defined(OPENSSL_IS_BORINGSSL) nb->engine = NULL; #else { /* setup engine */ const RSA_METHOD *rsa_default_method; RSA_METHOD *rsa_method; #ifdef NEVERBLEED_ECDSA const EC_KEY_METHOD *ecdsa_default_method; EC_KEY_METHOD *ecdsa_method; #endif #ifdef NEVERBLEED_OPAQUE_RSA_METHOD rsa_default_method = RSA_PKCS1_OpenSSL(); rsa_method = RSA_meth_dup(rsa_default_method); RSA_meth_set1_name(rsa_method, "privsep RSA method"); 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); #else rsa_default_method = RSA_PKCS1_SSLeay(); rsa_method = &static_rsa_method; rsa_method->rsa_pub_enc = rsa_default_method->rsa_pub_enc; rsa_method->rsa_pub_dec = rsa_default_method->rsa_pub_dec; rsa_method->rsa_verify = rsa_default_method->rsa_verify; rsa_method->bn_mod_exp = rsa_default_method->bn_mod_exp; #endif #ifdef NEVERBLEED_ECDSA ecdsa_default_method = EC_KEY_get_default_method(); ecdsa_method = EC_KEY_METHOD_new(ecdsa_default_method); /* 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); #endif 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) #ifdef NEVERBLEED_ECDSA || !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); } #endif /* 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; } void (*neverbleed_post_fork_cb)(void) = NULL; void (*neverbleed_transaction_cb)(neverbleed_iobuf_t *, int) = NULL; enum neverbleed_offload_type neverbleed_offload = NEVERBLEED_OFFLOAD_OFF; ================================================ FILE: neverbleed.h ================================================ /* * 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 __FreeBSD__ #include #endif #ifdef __cplusplus extern "C" { #endif #if (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__) || defined(__NetBSD__) #define NEVERBLEED_HAS_PTHREAD_SETAFFINITY_NP 1 #if defined(__linux__) #define NEVERBLEED_CPU_SET_T cpu_set_t #else #define NEVERBLEED_CPU_SET_T cpuset_t #endif #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; typedef struct st_neverbleed_iobuf_t { char *buf; char *start; char *end; size_t capacity; struct st_neverbleed_iobuf_t *next; unsigned processing : 1; } neverbleed_iobuf_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); /** * builds a digestsign request */ void neverbleed_start_digestsign(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const EVP_MD *md, const void *input, size_t len, int rsa_pss); /** * parses a digestsign response */ void neverbleed_finish_digestsign(neverbleed_iobuf_t *buf, void **digest, size_t *digest_len); /** * builds a RSA decrypt request */ void neverbleed_start_decrypt(neverbleed_iobuf_t *buf, EVP_PKEY *pkey, const void *input, size_t len); /** * parses a decrypt response */ void neverbleed_finish_decrypt(neverbleed_iobuf_t *buf, void **digest, size_t *digest_len); #if NEVERBLEED_HAS_PTHREAD_SETAFFINITY_NP /** * set the cpu affinity for the neverbleed thread (returns 0 if successful) */ int neverbleed_setaffinity(neverbleed_t *nb, NEVERBLEED_CPU_SET_T *cpuset); #endif /** * an optional callback that can be registered by the application for doing stuff immediately after the neverbleed process is being * spawned */ extern void (*neverbleed_post_fork_cb)(void); /** * An optional callback used for replacing `iobuf_transaction`; i.e., the logic that sends the request and receives the response. * * If `responseless` equals `1`, the ownership of stack-allocated `req` is given to the callback. In this case, `req` must be free'd using `neverbleed_iobuf_dispose` */ extern void (*neverbleed_transaction_cb)(neverbleed_iobuf_t *req, int responseless); typedef void (*neverbleed_cb)(int); int neverbleed_get_fd(neverbleed_t *nb); static size_t neverbleed_iobuf_size(neverbleed_iobuf_t *buf); void neverbleed_iobuf_dispose(neverbleed_iobuf_t *buf); void neverbleed_transaction_read(neverbleed_t *nb, neverbleed_iobuf_t *buf); void neverbleed_transaction_write(neverbleed_t *nb, neverbleed_iobuf_t *buf); /** * if set to a non-zero value, RSA operations are offloaded */ extern enum neverbleed_offload_type { NEVERBLEED_OFFLOAD_OFF = 0, NEVERBLEED_OFFLOAD_QAT_ON, NEVERBLEED_OFFLOAD_QAT_AUTO, } neverbleed_offload; /* inline function definitions */ inline size_t neverbleed_iobuf_size(neverbleed_iobuf_t *buf) { return buf->end - buf->start; } #ifdef __cplusplus } #endif #endif ================================================ FILE: t/assets/test-ecdsa.crt ================================================ -----BEGIN CERTIFICATE----- MIIBfTCCASOgAwIBAgIUWGYyQm67cza/h5J4I+76bYqFnLAwCgYIKoZIzj0EAwIw FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwNDIzMTMwM1oXDTM2MDMwMTIz MTMwM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEhN/grDfl7Qi4fZEUklFFJ6lcGpOjWrwGPa5WPyUPS2UVx6qXLg5Tgt78 b67RZO58CQI7amC7iogLtkhSzONs9KNTMFEwHQYDVR0OBBYEFOERv7PPj3kgMch+ gv2c9l7Yx60HMB8GA1UdIwQYMBaAFOERv7PPj3kgMch+gv2c9l7Yx60HMA8GA1Ud EwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAOUITjVTvw/wcdqyQPlzSPU/ 249h69Z7eh8XUB2dxvxkAiAaJXL6e4GYo+Kz9dE0lcnvlJtCe7Cts+IPTSci0umh Lw== -----END CERTIFICATE----- ================================================ FILE: t/assets/test-ecdsa.key ================================================ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIEqdJE0WtAI2i+7ugUk7qfj+3SDmA4HmSzYSgBz0ChtwoAoGCCqGSM49 AwEHoUQDQgAEhN/grDfl7Qi4fZEUklFFJ6lcGpOjWrwGPa5WPyUPS2UVx6qXLg5T gt78b67RZO58CQI7amC7iogLtkhSzONs9A== -----END EC PRIVATE KEY----- ================================================ FILE: t/assets/test.crt ================================================ -----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIUMBYfyg+s4uddFNCoLEyBascfr9EwDQYJKoZIhvcNAQEL BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwMTAzNTI0NVoXDTM2MDIy NzAzNTI0NVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAvthlR+U55PNRcbA4LfSIJCU0L/pNk5Mq4wReXDzUdirO d0adQ8D2FOSWMBuxbhMyRLmbjZRPL5qD52vbgiVeKUEJ1AOHCQznRi8qgmdKN5cF zFUYqdCJs8HrE7XVPzN9/vi0zNStyvRdJ8zT6BrFelIt/c/do1XOmU+dxujzbgdm WsMrodQzS/6Km3XVXfQ77I5blPg+zkkGo2Tqms5e0T4YvrpdgB7nW58cmcTVuhuK nox4BkFWcwyJ1EN4VnnnPpX6iyhJrhkfYCy2NMloSprHv2/KFMH42mOTn7TfHXhj SBlRAiAy5BiE6DFS+TjbdST9/cQ17V5FHen6TkS8hwIDAQABo1MwUTAdBgNVHQ4E FgQUNeZr1Y7Sd2B1sCsHpOjbOA2qzjAwHwYDVR0jBBgwFoAUNeZr1Y7Sd2B1sCsH pOjbOA2qzjAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArQRu zl7Zn/FqMwECSaRPXXHOqCWcGvQUy1cfZkfn4BnSBOPr7AqUbf3kqTuNyj0IjvLH YyA15/Lcjz1qtKBl26coGOQ7WqSflDAdW0TptcjF3YgOMe+oxdiCw6sHJu1jN4td Wp/DW55AOBAoB0yGhkPd1d80AgeYOaflZBNoRkU713MJVNoGRFHeSaRYm6UAu5DB 0NYK1OSIEkGOBRHbKOur8vuXThj8wD6V+NIf1fa++4oLD5gohD+L71iW2TvOcbic eMepCbZ9xTYSmXt2tyI6Bn0rpZNHDzmKsIybO4uJtPE0pBjuVidu/nNUgOIwFeC6 cqDol1kEF3l1JusTbw== -----END CERTIFICATE----- ================================================ FILE: t/assets/test.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+2GVH5Tnk81Fx sDgt9IgkJTQv+k2TkyrjBF5cPNR2Ks53Rp1DwPYU5JYwG7FuEzJEuZuNlE8vmoPn a9uCJV4pQQnUA4cJDOdGLyqCZ0o3lwXMVRip0ImzwesTtdU/M33++LTM1K3K9F0n zNPoGsV6Ui39z92jVc6ZT53G6PNuB2Zawyuh1DNL/oqbddVd9DvsjluU+D7OSQaj ZOqazl7RPhi+ul2AHudbnxyZxNW6G4qejHgGQVZzDInUQ3hWeec+lfqLKEmuGR9g LLY0yWhKmse/b8oUwfjaY5OftN8deGNIGVECIDLkGIToMVL5ONt1JP39xDXtXkUd 6fpORLyHAgMBAAECggEAIeZ7mzlPPumv4nOMjzE8S7tmGU2roRbHy/q3LkhJ71Gs Ski7X9EzhUOToCkTK/vx9n5H8O7S4CBg5OdmXyh3IPniHoyf3I4zuOZg9TgW2WgU yhalomieRVWhhedLYYYqj/Oq3iW7V21v7MV4MOcshA18CPV3J+/ymo4ndzFjKHyO ZbigrfnOkzNn5DPNXKUGDRQ3JCk7gxqYrBk9pPHwyc9MLm9zwcStTVQr6jAcisrB wVtztqjmDpQZaZHaZoGCMHX0aqxkYzAXW5klu1w5VMnvdZBPDa+USH7g5G0pxRfl yDB5FbSKYIZPOXcg5XypDoN9hMFMyajHJEqbnBqo3QKBgQDoDsdp9PG/obgvUVuh zl4PKi91ziGnF2yULiPPtpfF9HlxYgClrUbl6l1OehprrSPMtUWNcV0YxKHsrHvI /J/ta17kMp65E3UQ6dC/1rHujZtXumuAt6tN3+bmskDCNXm9Hd7xbqEU0kgCEE3D ltKraFJPRZYjCsQfJYmwp3+9hQKBgQDSiRgJykwBDTLWk16n4TTDuGoKs31UiZ0b 5mKiiNSrUVY6zVAVXY9b8X54laSdYMyVuLImg4F6Tx47FgGKxfJerFgbwK01tJcB Fowixtq78JTHeZp3ejY2sTCXb7NrLM9GE5KJkt34hQzvWS8fLSX/P/MKXp4UlE3j 7o1N0y8ZmwKBgQDT4qqyVLVoBIHosqC4XXYE4r/zEQQpTXoW0wpf3pk2ZsN8g7+T h2P1CsmnnlYBe1X01I9tVtVqiCBRuixMmF5uqls6gf3rf5ikmNnCUIanCyWMNOtz 3EDOGmL6wkffDHTb+SpXyGvMVzTorXpT3KL/X4HIYAF2fZ4V0nCmnEpHAQKBgGPz 5k+vlUnihEJPEN9PEgfho6aU9GmQM+CtDiLwJ1d2dCPSmbSrCIa0LkD9enulGzvx xdJ3GJ+CtG2E0xKZS6oa1HHIlfMrW42OsNVJ50rWuyvA1c7nXJm2ocUjqOC3E2jH nghmi6+TK0Lu6mo4uxNlvvMrXI2Uoy4VcUyDeJcFAoGBAKiOMiqe1Koh28nIxKxk G+BmsSWZSQzrUALd5BCPajMHOpSStbMNobuNj3yaTO5Nqmda9CmNQVmpZ3rTZXb8 UW12qOSqsUjtqUkn+coIM9ZJANSeqA8twdAzRCXkviUT9bnvdhaghBRPox9jXvo2 gCtoHKhngAQedpkREPmGcqPC -----END PRIVATE KEY----- ================================================ FILE: t/test_handshake.t ================================================ #!/usr/bin/env perl use strict; use warnings; use Net::EmptyPort qw(check_port empty_port); use POSIX ":sys_wait_h"; use Scope::Guard qw(scope_guard); use Test::More; use Time::HiRes qw(sleep); my $server = "./test-neverbleed"; sub spawn_server { my ($port, $crt, $key) = @_; my $pid = fork; die "fork failed:$!" unless defined $pid; if ($pid == 0) { exec $server, "privsep", $port, $crt, $key; die "failed to exec $server:$!"; } while (!check_port($port)) { sleep 0.1; } return scope_guard(sub { kill 9, $pid; while (waitpid($pid, 0) != $pid) {} }); } sub doit { my ($port, $crt, $args) = @_; open my $fh, "-|", "printf 'GET / HTTP/1.0\\r\\n\\r\\n' | openssl s_client $args -connect 127.0.0.1:$port -CAfile $crt -verify_return_error -ign_eof 2>&1" or die "failed to start s_client:$!"; my $content = do { local $/; <$fh> }; close $fh; like($content, qr/Verification: OK/, "TLS verification passed"); like($content, qr/HTTP\/1\.0 200 OK/, "HTTP 200 response received"); like($content, qr/hello/, "Response contains expected content"); }; subtest "RSA" => sub { my $port = empty_port(); my $crt = "./t/assets/test.crt"; my $key = "./t/assets/test.key"; my $guard = spawn_server($port, $crt, $key); subtest "sign" => sub { doit($port, $crt, ""); }; subtest "decrypt" => sub { doit($port, $crt, "-no_tls1_3 -cipher AES128-SHA"); }; }; subtest "ECDSA" => sub { my $port = empty_port(); my $crt = "./t/assets/test-ecdsa.crt"; my $key = "./t/assets/test-ecdsa.key"; my $guard = spawn_server($port, $crt, $key); subtest "sign" => sub { doit($port, $crt, ""); }; }; done_testing; ================================================ FILE: test.c ================================================ /* * 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 #define OPENSSL_SUPPRESS_DEPRECATED #include #include #if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_EC) && \ (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2090100fL) #define NEVERBLEED_TEST_ECDSA #endif #ifdef NEVERBLEED_TEST_ECDSA #include #endif #include #include #include "neverbleed.h" static neverbleed_t nb; #ifdef OPENSSL_IS_BORINGSSL static void setup_boringssl_key_method(SSL_CTX *ctx); static int boringssl_get_pkey_index(void); static void boringssl_free_pkey_callback(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp); #endif #ifdef NEVERBLEED_TEST_ECDSA 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); } #endif #ifdef OPENSSL_IS_BORINGSSL static void boringssl_free_pkey_callback(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) { if (ptr != NULL) EVP_PKEY_free(ptr); } static int boringssl_get_pkey_index(void) { static volatile int index; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); if (!index) { index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, boringssl_free_pkey_callback); } pthread_mutex_unlock(&mutex); return index; } static enum ssl_private_key_result_t boringssl_sign(SSL *ssl, uint8_t *out, size_t *outlen, size_t max_out, uint16_t signature_algorithm, const uint8_t *in, size_t len) { neverbleed_iobuf_t buf = {NULL}; void *digest = NULL; size_t digestlen = 0; SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); EVP_PKEY *pkey = SSL_CTX_get_ex_data(ctx, boringssl_get_pkey_index()); const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm); int rsa_pss = SSL_is_signature_algorithm_rsa_pss(signature_algorithm); neverbleed_start_digestsign(&buf, pkey, md, in, len, rsa_pss); neverbleed_transaction_write(&nb, &buf); neverbleed_transaction_read(&nb, &buf); neverbleed_finish_digestsign(&buf, &digest, &digestlen); assert(digestlen <= max_out); memcpy(out, digest, digestlen); *outlen = digestlen; free(digest); return ssl_private_key_success; } static enum ssl_private_key_result_t boringssl_decrypt(SSL *ssl, uint8_t *out, size_t *outlen, size_t max_out, const uint8_t *in, size_t len) { neverbleed_iobuf_t buf = {NULL}; void *digest = NULL; size_t digestlen = 0; SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); EVP_PKEY *pkey = SSL_CTX_get_ex_data(ctx, boringssl_get_pkey_index()); neverbleed_start_decrypt(&buf, pkey, in, len); neverbleed_transaction_write(&nb, &buf); neverbleed_transaction_read(&nb, &buf); neverbleed_finish_decrypt(&buf, &digest, &digestlen); assert(digestlen <= max_out); memcpy(out, digest, digestlen); *outlen = digestlen; free(digest); return ssl_private_key_success; } static void setup_boringssl_key_method(SSL_CTX *ctx) { EVP_PKEY *pkey = SSL_CTX_get0_privatekey(ctx); EVP_PKEY_up_ref(pkey); SSL_CTX_set_ex_data(ctx, boringssl_get_pkey_index(), pkey); static const SSL_PRIVATE_KEY_METHOD meth = { .sign = boringssl_sign, .decrypt = boringssl_decrypt, }; SSL_CTX_set_private_key_method(ctx, &meth); } #endif 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(port); 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; char errbuf[NEVERBLEED_ERRBUF_SIZE]; int use_privsep; /* initialization */ /* FIXME: These APIs are deprecated in favor of OPENSSL_init_crypto in 1.1.0. */ 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); #ifdef NEVERBLEED_TEST_ECDSA setup_ecc_key(ctx); #endif /* 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; } #ifdef OPENSSL_IS_BORINGSSL setup_boringssl_key_method(ctx); #endif } 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); }