summaryrefslogtreecommitdiffstats
path: root/usr.sbin/ldapd/ssl.c
diff options
context:
space:
mode:
authormartinh <martinh@openbsd.org>2010-05-31 17:36:31 +0000
committermartinh <martinh@openbsd.org>2010-05-31 17:36:31 +0000
commit5d46595254f0cbe873cb84406beec56890693a15 (patch)
treeea0f82e4eb53aa797c0fc5cb7f63b33c120e4fa3 /usr.sbin/ldapd/ssl.c
parentOpenBSD/moko development has stopped, it effectively stopped long ago when (diff)
downloadwireguard-openbsd-5d46595254f0cbe873cb84406beec56890693a15.tar.xz
wireguard-openbsd-5d46595254f0cbe873cb84406beec56890693a15.zip
Initial import of ldapd, a small LDAP daemon. Work in progress.
ok deraadt@ jacekm@ gilles@ back@ henning@ blambert@
Diffstat (limited to 'usr.sbin/ldapd/ssl.c')
-rw-r--r--usr.sbin/ldapd/ssl.c710
1 files changed, 710 insertions, 0 deletions
diff --git a/usr.sbin/ldapd/ssl.c b/usr.sbin/ldapd/ssl.c
new file mode 100644
index 00000000000..7d320bf6b93
--- /dev/null
+++ b/usr.sbin/ldapd/ssl.c
@@ -0,0 +1,710 @@
+/* $OpenBSD: ssl.c,v 1.1 2010/05/31 17:36:31 martinh Exp $ */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+
+#include "ldapd.h"
+
+#define SSL_CIPHERS "HIGH"
+
+void ssl_error(const char *);
+char *ssl_load_file(const char *, off_t *);
+SSL_CTX *ssl_ctx_create(void);
+void ssl_session_accept(int, short, void *);
+void ssl_read(int, short, void *);
+void ssl_write(int, short, void *);
+int ssl_bufferevent_add(struct event *, int);
+void ssl_connect(int, short, void *);
+
+SSL *ssl_client_init(int, char *, size_t, char *, size_t);
+
+int ssl_buf_read(SSL *, struct ibuf_read *);
+int ssl_buf_write(SSL *, struct msgbuf *);
+
+DH *get_dh512(void);
+void ssl_set_ephemeral_key_exchange(SSL_CTX *);
+
+extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+ size_t, void *);
+
+/* From OpenSSL's documentation:
+ *
+ * If "strong" primes were used to generate the DH parameters, it is
+ * not strictly necessary to generate a new key for each handshake
+ * but it does improve forward secrecy.
+ *
+ * These are the parameters used by both sendmail and openssl's
+ * s_server.
+ *
+ * -- gilles@
+ */
+
+unsigned char dh512_p[] = {
+ 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
+ 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
+ 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
+ 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
+ 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
+ 0x47,0x74,0xE8,0x33,
+};
+
+unsigned char dh512_g[] = {
+ 0x02,
+};
+
+#if 0
+void
+ssl_connect(int fd, short event, void *p)
+{
+ struct conn *s = p;
+ int ret;
+ int retry_flag;
+ int ssl_err;
+
+ if (event == EV_TIMEOUT) {
+ log_debug("ssl_connect: session timed out");
+ conn_close(s);
+ return;
+ }
+
+ ret = SSL_connect(s->s_ssl);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ retry_flag = EV_READ;
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ retry_flag = EV_WRITE;
+ goto retry;
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ log_debug("session destroy in MTA #1");
+ conn_close(s);
+ return;
+ }
+ /* FALLTHROUGH */
+ default:
+ ssl_error("ssl_session_connect");
+ conn_close(s);
+ return;
+ }
+ }
+
+ event_set(&s->bev->ev_read, s->fd, EV_READ, ssl_read, s->bev);
+ event_set(&s->bev->ev_write, s->fd, EV_WRITE, ssl_write, s->bev);
+
+ log_info("ssl_connect: connected to remote ssl server");
+ bufferevent_enable(s->bev, EV_READ|EV_WRITE);
+ s->s_flags |= F_SECURE;
+
+ if (s->s_flags & F_PEERHASTLS) {
+ session_respond(s, "EHLO %s", s->s_env->sc_hostname);
+ }
+
+ return;
+retry:
+ event_add(&s->s_ev, &s->s_tv);
+}
+#endif
+
+void
+ssl_read(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct conn *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+ size_t len;
+ char rbuf[IBUF_READ_SIZE];
+ int howmuch = IBUF_READ_SIZE;
+
+ what = EVBUFFER_READ;
+ ret = ssl_err = 0;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (bufev->wm_read.high != 0)
+ howmuch = MIN(sizeof(rbuf), bufev->wm_read.high);
+
+ ret = SSL_read(s->s_ssl, rbuf, howmuch);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_read");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+
+ if (evbuffer_add(bufev->input, rbuf, ret) == -1) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+ len = EVBUFFER_LENGTH(bufev->input);
+ if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+ return;
+ if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+ struct evbuffer *buf = bufev->input;
+ event_del(&bufev->ev_read);
+ evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+ return;
+ }
+
+ if (bufev->readcb != NULL)
+ (*bufev->readcb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+ return;
+
+err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+
+void
+ssl_write(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct conn *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+
+ ret = 0;
+ what = EVBUFFER_WRITE;
+
+ if (event == EV_TIMEOUT) {
+ what |= EV_TIMEOUT;
+ goto err;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output)) {
+ if (s->s_buf == NULL) {
+ s->s_buflen = EVBUFFER_LENGTH(bufev->output);
+ if ((s->s_buf = malloc(s->s_buflen)) == NULL) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ memcpy(s->s_buf, EVBUFFER_DATA(bufev->output),
+ s->s_buflen);
+ }
+
+ ret = SSL_write(s->s_ssl, s->s_buf, s->s_buflen);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_write");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+ evbuffer_drain(bufev->output, ret);
+ }
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ if (EVBUFFER_LENGTH(bufev->output) != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+ if (bufev->writecb != NULL &&
+ EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+ (*bufev->writecb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ if (s->s_buflen != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+ return;
+
+err:
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+int
+ssl_bufferevent_add(struct event *ev, int timeout)
+{
+ struct timeval tv;
+ struct timeval *ptv = NULL;
+
+ if (timeout) {
+ timerclear(&tv);
+ tv.tv_sec = timeout;
+ ptv = &tv;
+ }
+
+ return (event_add(ev, ptv));
+}
+
+int
+ssl_cmp(struct ssl *s1, struct ssl *s2)
+{
+ return (strcmp(s1->ssl_name, s2->ssl_name));
+}
+
+SPLAY_GENERATE(ssltree, ssl, ssl_nodes, ssl_cmp);
+
+char *
+ssl_load_file(const char *name, off_t *len)
+{
+ struct stat st;
+ off_t size;
+ char *buf = NULL;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY)) == -1)
+ return (NULL);
+ if (fstat(fd, &st) != 0)
+ goto fail;
+ size = st.st_size;
+ if ((buf = calloc(1, size + 1)) == NULL)
+ goto fail;
+ if (read(fd, buf, size) != size)
+ goto fail;
+ close(fd);
+
+ *len = size + 1;
+ return (buf);
+
+fail:
+ if (buf != NULL)
+ free(buf);
+ close(fd);
+ return (NULL);
+}
+
+SSL_CTX *
+ssl_ctx_create(void)
+{
+ SSL_CTX *ctx;
+
+ ctx = SSL_CTX_new(SSLv23_method());
+ if (ctx == NULL) {
+ ssl_error("ssl_ctx_create");
+ fatal("ssl_ctx_create: could not create SSL context");
+ }
+
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+ SSL_CTX_set_timeout(ctx, LDAPD_SESSION_TIMEOUT);
+ SSL_CTX_set_options(ctx, SSL_OP_ALL);
+ SSL_CTX_set_options(ctx,
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ if (!SSL_CTX_set_cipher_list(ctx, SSL_CIPHERS)) {
+ ssl_error("ssl_ctx_create");
+ fatal("ssl_ctx_create: could not set cipher list");
+ }
+ return (ctx);
+}
+
+int
+ssl_load_certfile(struct ldapd_config *env, const char *name, u_int8_t flags)
+{
+ struct ssl *s;
+ struct ssl key;
+ char certfile[PATH_MAX];
+
+ if (strlcpy(key.ssl_name, name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name)) {
+ log_warn("ssl_load_certfile: certificate name truncated");
+ return -1;
+ }
+
+ s = SPLAY_FIND(ssltree, env->sc_ssl, &key);
+ if (s != NULL) {
+ s->flags |= flags;
+ return 0;
+ }
+
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+
+ s->flags = flags;
+ (void)strlcpy(s->ssl_name, key.ssl_name, sizeof(s->ssl_name));
+
+ if ((name[0] == '/' &&
+ !bsnprintf(certfile, sizeof(certfile), "%s.crt", name)) ||
+ !bsnprintf(certfile, sizeof(certfile), "/etc/ldap/certs/%s.crt",
+ name)) {
+ log_warn("ssl_load_certfile: path truncated");
+ free(s);
+ return -1;
+ }
+
+ log_debug("loading certificate file %s", certfile);
+ if ((s->ssl_cert = ssl_load_file(certfile, &s->ssl_cert_len)) == NULL) {
+ free(s);
+ return (-1);
+ }
+
+ if ((name[0] == '/' &&
+ !bsnprintf(certfile, sizeof(certfile), "%s.key", name)) ||
+ !bsnprintf(certfile, sizeof(certfile), "/etc/ldap/certs/%s.key",
+ name)) {
+ log_warn("ssl_load_certfile: path truncated");
+ free(s->ssl_cert);
+ free(s);
+ return -1;
+ }
+
+ log_debug("loading key file %s", certfile);
+ if ((s->ssl_key = ssl_load_file(certfile, &s->ssl_key_len)) == NULL) {
+ free(s->ssl_cert);
+ free(s);
+ return (-1);
+ }
+
+ SPLAY_INSERT(ssltree, env->sc_ssl, s);
+
+ return (0);
+}
+
+void
+ssl_init(void)
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ OpenSSL_add_all_algorithms();
+
+ /* Init hardware crypto engines. */
+ ENGINE_load_builtin_engines();
+ ENGINE_register_all_complete();
+}
+
+void
+ssl_setup(struct ldapd_config *env, struct listener *l)
+{
+ struct ssl key;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ if (strlcpy(key.ssl_name, l->ssl_cert_name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name))
+ fatal("ssl_setup: certificate name truncated");
+
+ if ((l->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &key)) == NULL)
+ fatal("ssl_setup: certificate tree corrupted");
+
+ l->ssl_ctx = ssl_ctx_create();
+
+ if (!ssl_ctx_use_certificate_chain(l->ssl_ctx,
+ l->ssl->ssl_cert, l->ssl->ssl_cert_len))
+ goto err;
+ if (!ssl_ctx_use_private_key(l->ssl_ctx,
+ l->ssl->ssl_key, l->ssl->ssl_key_len))
+ goto err;
+
+ if (!SSL_CTX_check_private_key(l->ssl_ctx))
+ goto err;
+ if (!SSL_CTX_set_session_id_context(l->ssl_ctx,
+ (const unsigned char *)l->ssl_cert_name, strlen(l->ssl_cert_name) + 1))
+ goto err;
+
+ ssl_set_ephemeral_key_exchange(l->ssl_ctx);
+
+ log_debug("ssl_setup: ssl setup finished for listener: %p", l);
+ return;
+
+err:
+ if (l->ssl_ctx != NULL)
+ SSL_CTX_free(l->ssl_ctx);
+ ssl_error("ssl_setup");
+ fatal("ssl_setup: cannot set SSL up");
+ return;
+}
+
+void
+ssl_error(const char *where)
+{
+ unsigned long code;
+ char errbuf[128];
+ extern int debug;
+
+ if (!debug)
+ return;
+ for (; (code = ERR_get_error()) != 0 ;) {
+ ERR_error_string_n(code, errbuf, sizeof(errbuf));
+ log_debug("SSL library error: %s: %s", where, errbuf);
+ }
+}
+
+void
+ssl_session_accept(int fd, short event, void *p)
+{
+ struct conn *s = p;
+ int ret;
+ int retry_flag;
+ int ssl_err;
+
+ if (event == EV_TIMEOUT) {
+ log_debug("ssl_session_accept: session timed out");
+ conn_close(s);
+ return;
+ }
+
+ retry_flag = ssl_err = 0;
+
+ log_debug("ssl_session_accept: accepting client");
+ ret = SSL_accept(s->s_ssl);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ retry_flag = EV_READ;
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ retry_flag = EV_WRITE;
+ goto retry;
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ conn_close(s);
+ return;
+ }
+ /* FALLTHROUGH */
+ default:
+ ssl_error("ssl_session_accept");
+ conn_close(s);
+ return;
+ }
+ }
+
+ log_debug("ssl_session_accept: accepted ssl client");
+ s->s_flags |= F_SECURE;
+
+ s->bev = bufferevent_new(s->fd, conn_read, conn_write, conn_err, s);
+ if (s->bev == NULL) {
+ log_warn("ssl_session_accept: bufferevent_new");
+ conn_close(s);
+ return;
+ }
+ bufferevent_settimeout(s->bev, 0, 60);
+
+ event_set(&s->bev->ev_read, s->fd, EV_READ, ssl_read, s->bev);
+ event_set(&s->bev->ev_write, s->fd, EV_WRITE, ssl_write, s->bev);
+ bufferevent_enable(s->bev, EV_READ);
+
+ return;
+retry:
+ event_add(&s->s_ev, &s->s_tv);
+}
+
+void
+ssl_session_init(struct conn *s)
+{
+ struct listener *l;
+ SSL *ssl;
+
+ l = s->s_l;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ log_debug("ssl_session_init: switching to SSL");
+ ssl = SSL_new(l->ssl_ctx);
+ if (ssl == NULL)
+ goto err;
+
+ if (!SSL_set_ssl_method(ssl, SSLv23_server_method()))
+ goto err;
+ if (!SSL_set_fd(ssl, s->fd))
+ goto err;
+ SSL_set_accept_state(ssl);
+
+ s->s_ssl = ssl;
+
+ s->s_tv.tv_sec = LDAPD_SESSION_TIMEOUT;
+ s->s_tv.tv_usec = 0;
+ event_set(&s->s_ev, s->fd, EV_READ|EV_TIMEOUT, ssl_session_accept, s);
+ event_add(&s->s_ev, &s->s_tv);
+ return;
+
+ err:
+ if (ssl != NULL)
+ SSL_free(ssl);
+ ssl_error("ssl_session_init");
+}
+
+SSL *
+ssl_client_init(int fd, char *cert, size_t certsz, char *key, size_t keysz)
+{
+ SSL_CTX *ctx;
+ SSL *ssl = NULL;
+ int rv = -1;
+
+ ctx = ssl_ctx_create();
+
+ if (cert && key) {
+ if (!ssl_ctx_use_certificate_chain(ctx, cert, certsz))
+ goto done;
+ else if (!ssl_ctx_use_private_key(ctx, key, keysz))
+ goto done;
+ else if (!SSL_CTX_check_private_key(ctx))
+ goto done;
+ }
+
+ if ((ssl = SSL_new(ctx)) == NULL)
+ goto done;
+
+ if (!SSL_set_ssl_method(ssl, SSLv23_client_method()))
+ goto done;
+ if (!SSL_set_fd(ssl, fd))
+ goto done;
+ SSL_set_connect_state(ssl);
+
+ rv = 0;
+done:
+ if (rv) {
+ if (ssl)
+ SSL_free(ssl);
+ else if (ctx)
+ SSL_CTX_free(ctx);
+ ssl = NULL;
+ }
+ return (ssl);
+}
+
+void
+ssl_session_destroy(struct conn *s)
+{
+ SSL_free(s->s_ssl);
+}
+
+int
+ssl_buf_read(SSL *s, struct ibuf_read *r)
+{
+ char *buf = r->buf + r->wpos;
+ ssize_t bufsz = sizeof(r->buf) - r->wpos;
+ int ret;
+
+ if (bufsz == 0) {
+ errno = EMSGSIZE;
+ return (SSL_ERROR_SYSCALL);
+ }
+
+ if ((ret = SSL_read(s, buf, bufsz)) > 0)
+ r->wpos += ret;
+
+ return SSL_get_error(s, ret);
+}
+
+int
+ssl_buf_write(SSL *s, struct msgbuf *msgbuf)
+{
+ struct ibuf *buf;
+ int ret;
+
+ buf = TAILQ_FIRST(&msgbuf->bufs);
+ if (buf == NULL)
+ return (SSL_ERROR_NONE);
+
+ ret = SSL_write(s, buf->buf + buf->rpos, buf->wpos - buf->rpos);
+
+ if (ret > 0)
+ msgbuf_drain(msgbuf, ret);
+
+ return SSL_get_error(s, ret);
+}
+
+DH *
+get_dh512(void)
+{
+ DH *dh;
+
+ if ((dh = DH_new()) == NULL)
+ return NULL;
+
+ dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
+ dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
+ if (dh->p == NULL || dh->g == NULL)
+ return NULL;
+
+ return dh;
+}
+
+
+void
+ssl_set_ephemeral_key_exchange(SSL_CTX *ctx)
+{
+ DH *dh;
+
+ dh = get_dh512();
+ if (dh != NULL)
+ SSL_CTX_set_tmp_dh(ctx, dh);
+}