/* * libmowgli: A collection of useful routines for programming. * vio_openssl.c: OpenSSL routines built on VIO * * Copyright (c) 2012 Elizabeth J. Myers. All rights reserved. * * Permission to use, copy, modify, and/or 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "mowgli.h" /* Note these routines are just defaults for clients -- if you have more * specific needs, you should write your own implementation (that is the * whole point of vio) */ #ifdef HAVE_OPENSSL typedef struct { SSL *ssl_handle; SSL_CTX *ssl_context; mowgli_vio_ssl_settings_t settings; } mowgli_ssl_connection_t; static int mowgli_vio_openssl_connect(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr); static int mowgli_vio_openssl_listen(mowgli_vio_t *vio, int backlog); static int mowgli_vio_openssl_accept(mowgli_vio_t *vio, mowgli_vio_t *newvio); static int mowgli_vio_openssl_client_handshake(mowgli_vio_t *vio, mowgli_ssl_connection_t *connection); static int mowgli_vio_openssl_read(mowgli_vio_t *vio, void *buffer, size_t len); static int mowgli_vio_openssl_write(mowgli_vio_t *vio, const void *buffer, size_t len); static int mowgli_openssl_read_or_write(bool read, mowgli_vio_t *vio, void *readbuf, const void *writebuf, size_t len); static int mowgli_vio_openssl_close(mowgli_vio_t *vio); static mowgli_heap_t *ssl_heap = NULL; static bool openssl_init = false; int mowgli_vio_openssl_setssl(mowgli_vio_t *vio, mowgli_vio_ssl_settings_t *settings) { mowgli_ssl_connection_t *connection; if (!ssl_heap) ssl_heap = mowgli_heap_create(sizeof(mowgli_ssl_connection_t), 64, BH_NOW); connection = mowgli_heap_alloc(ssl_heap); vio->privdata = connection; if (settings) memcpy(&connection->settings, settings, sizeof(mowgli_vio_ssl_settings_t)); else /* Greatest compat without being terribly insecure */ connection->settings.ssl_version = MOWGLI_VIO_SSLFLAGS_SSLV3; /* Change ops */ mowgli_vio_set_op(vio, connect, mowgli_vio_openssl_connect); mowgli_vio_set_op(vio, read, mowgli_vio_openssl_read); mowgli_vio_set_op(vio, write, mowgli_vio_openssl_write); mowgli_vio_set_op(vio, close, mowgli_vio_openssl_close); mowgli_vio_set_op(vio, accept, mowgli_vio_openssl_accept); mowgli_vio_set_op(vio, listen, mowgli_vio_openssl_listen); /* SSL setup */ if (!openssl_init) { openssl_init = true; SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); } return 0; } /* Returns void so they can be stubs */ void * mowgli_vio_openssl_getsslhandle(mowgli_vio_t *vio) { mowgli_ssl_connection_t *connection = vio->privdata; return connection->ssl_handle; } void * mowgli_vio_openssl_getsslcontext(mowgli_vio_t *vio) { mowgli_ssl_connection_t *connection = vio->privdata; return connection->ssl_context; } static int mowgli_vio_openssl_connect(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr) { vio->error.op = MOWGLI_VIO_ERR_OP_CONNECT; mowgli_ssl_connection_t *connection = vio->privdata; if (connect(vio->fd, (struct sockaddr *)&addr->addr, addr->addrlen) < 0) { if (!mowgli_eventloop_ignore_errno(errno)) return mowgli_vio_err_errcode(vio, strerror, errno); else { mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCONNECTING, true); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISSSLCONNECTING, true); vio->error.op = MOWGLI_VIO_ERR_OP_NONE; return 0; } } memcpy(&vio->addr.addr, &addr->addr, addr->addrlen); vio->addr.addrlen = addr->addrlen; mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCLIENT, true); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISSERVER, false); /* Non-blocking socket, begin handshake */ mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCONNECTING, false); return mowgli_vio_openssl_client_handshake(vio, connection); } static int mowgli_vio_openssl_listen(mowgli_vio_t *vio, int backlog) { vio->error.op = MOWGLI_VIO_ERR_OP_LISTEN; mowgli_ssl_connection_t *connection = vio->privdata; const SSL_METHOD *method; switch (connection->settings.ssl_version) { case MOWGLI_VIO_SSLFLAGS_SSLV2: method = SSLv23_server_method(); break; case MOWGLI_VIO_SSLFLAGS_SSLV3: method = SSLv3_server_method(); break; case MOWGLI_VIO_SSLFLAGS_TLSV10: case MOWGLI_VIO_SSLFLAGS_TLSV11: case MOWGLI_VIO_SSLFLAGS_TLSV12: method = TLSv1_server_method(); break; default: /* Compat method */ method = SSLv23_server_method(); } connection->ssl_context = SSL_CTX_new((SSL_METHOD *)method); if (connection->ssl_context == NULL) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); connection->ssl_handle = SSL_new(connection->ssl_context); if (connection->ssl_handle == NULL) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); SSL_set_accept_state(connection->ssl_handle); SSL_CTX_set_options(connection->ssl_context, SSL_OP_SINGLE_DH_USE); if (connection->settings.password_func) { SSL_CTX_set_default_passwd_cb(connection->ssl_context, connection->settings.password_func); SSL_CTX_set_default_passwd_cb_userdata(connection->ssl_context, vio->userdata); } if (SSL_CTX_use_certificate_file(connection->ssl_context, connection->settings.cert_path, SSL_FILETYPE_PEM) != 1) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); if (SSL_CTX_use_PrivateKey_file(connection->ssl_context, connection->settings.privatekey_path, SSL_FILETYPE_PEM) != 1) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); if (listen(vio->fd, backlog) != 0) return mowgli_vio_err_errcode(vio, strerror, errno); if (!SSL_set_fd(connection->ssl_handle, vio->fd)) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISSERVER, true); vio->error.op = MOWGLI_VIO_ERR_OP_NONE; return 0; } static int mowgli_vio_openssl_accept(mowgli_vio_t *vio, mowgli_vio_t *newvio) { int fd; int ret; mowgli_ssl_connection_t *connection = vio->privdata; mowgli_ssl_connection_t *newconnection; vio->error.op = MOWGLI_VIO_ERR_OP_ACCEPT; if (!newvio) { const char errstr[] = "accept not called with valid new VIO object"; vio->error.type = MOWGLI_VIO_ERR_API; mowgli_strlcpy(vio->error.string, errstr, sizeof(errstr)); return mowgli_vio_error(vio); } if ((fd = accept(vio->fd, (struct sockaddr *)&newvio->addr.addr, &(newvio->addr.addrlen))) < 0) { if (!mowgli_eventloop_ignore_errno(errno)) return mowgli_vio_err_errcode(vio, strerror, errno); else return 0; } newvio->fd = fd; mowgli_vio_openssl_setssl(newvio, &connection->settings); newconnection = newvio->privdata; newconnection->ssl_context = connection->ssl_context; newconnection->ssl_handle = SSL_new(newconnection->ssl_context); if (!SSL_set_fd(newconnection->ssl_handle, fd)) return mowgli_vio_err_sslerrcode(newvio, ERR_get_error()); if ((ret = SSL_accept(newconnection->ssl_handle)) != 1) { unsigned long err; switch (SSL_get_error(newconnection->ssl_handle, ret)) { case SSL_ERROR_WANT_READ: mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDREAD, true); return 0; case SSL_ERROR_WANT_WRITE: mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDWRITE, true); return 0; case SSL_ERROR_ZERO_RETURN: return 0; case SSL_ERROR_SYSCALL: return mowgli_vio_err_errcode(newvio, strerror, errno); default: err = ERR_get_error(); break; } if(err > 0) { errno = EIO; return mowgli_vio_err_errcode(vio, strerror, errno); } return -1; } mowgli_vio_setflag(newvio, MOWGLI_VIO_FLAGS_ISCLIENT, true); mowgli_vio_setflag(newvio, MOWGLI_VIO_FLAGS_ISSERVER, false); vio->error.op = MOWGLI_VIO_ERR_OP_NONE; return 0; } static int mowgli_vio_openssl_client_handshake(mowgli_vio_t *vio, mowgli_ssl_connection_t *connection) { int ret; const SSL_METHOD *method; vio->error.op = MOWGLI_VIO_ERR_OP_CONNECT; switch (connection->settings.ssl_version) { case MOWGLI_VIO_SSLFLAGS_SSLV2: method = SSLv23_client_method(); break; case MOWGLI_VIO_SSLFLAGS_SSLV3: method = SSLv3_client_method(); break; case MOWGLI_VIO_SSLFLAGS_TLSV10: case MOWGLI_VIO_SSLFLAGS_TLSV11: case MOWGLI_VIO_SSLFLAGS_TLSV12: method = TLSv1_client_method(); break; default: /* Compat method */ method = SSLv23_client_method(); } /* Cast is to eliminate an excessively bogus warning on old OpenSSL --Elizacat */ connection->ssl_context = SSL_CTX_new((SSL_METHOD *)method); if (connection->ssl_context == NULL) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); connection->ssl_handle = SSL_new(connection->ssl_context); if (connection->ssl_handle == NULL) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); SSL_set_connect_state(connection->ssl_handle); if (!SSL_set_fd(connection->ssl_handle, vio->fd)) return mowgli_vio_err_sslerrcode(vio, ERR_get_error()); /* XXX not what we want for blocking sockets if they're in use! */ SSL_CTX_set_mode(connection->ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE); if ((ret = SSL_connect(connection->ssl_handle)) != 1) { int err = SSL_get_error(connection->ssl_handle, ret); if (err == SSL_ERROR_WANT_READ) mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDREAD, true); else if (err == SSL_ERROR_WANT_WRITE) mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDWRITE, true); else if (err == SSL_ERROR_WANT_CONNECT) { mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCONNECTING, true); return 0; } else return mowgli_vio_err_sslerrcode(vio, err); } /* Connected */ mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISSSLCONNECTING, false); vio->error.op = MOWGLI_VIO_ERR_OP_NONE; return 0; } #define MOWGLI_VIO_SSL_DOREAD true #define MOWGLI_VIO_SSL_DOWRITE false static int mowgli_vio_openssl_read(mowgli_vio_t *vio, void *buffer, size_t len) { vio->error.op = MOWGLI_VIO_ERR_OP_READ; return mowgli_openssl_read_or_write(MOWGLI_VIO_SSL_DOREAD, vio, buffer, NULL, len); } static int mowgli_vio_openssl_write(mowgli_vio_t *vio, const void *buffer, size_t len) { vio->error.op = MOWGLI_VIO_ERR_OP_WRITE; return mowgli_openssl_read_or_write(MOWGLI_VIO_SSL_DOWRITE, vio, NULL, buffer, len); } static int mowgli_openssl_read_or_write(bool read, mowgli_vio_t *vio, void *readbuf, const void *writebuf, size_t len) { mowgli_ssl_connection_t *connection = vio->privdata; int ret; unsigned long err; /* We are connected */ mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCONNECTING, false); if (mowgli_vio_hasflag(vio, MOWGLI_VIO_FLAGS_ISSSLCONNECTING)) return mowgli_vio_openssl_client_handshake(vio, connection); return_val_if_fail(connection->ssl_handle != NULL, -1); if(read) ret = (int)SSL_read(connection->ssl_handle, readbuf, len); else ret = (int)SSL_write(connection->ssl_handle, writebuf, len); if (ret < 0) { switch (SSL_get_error(connection->ssl_handle, ret)) { case SSL_ERROR_WANT_READ: mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDREAD, true); return 0; case SSL_ERROR_WANT_WRITE: mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDWRITE, true); return 0; case SSL_ERROR_ZERO_RETURN: return 0; case SSL_ERROR_SYSCALL: if((err = ERR_get_error()) == 0) { vio->error.type = MOWGLI_VIO_ERR_REMOTE_HANGUP; mowgli_strlcpy(vio->error.string, "Remote host closed the socket", sizeof(vio->error.string)); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCONNECTING, false); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_ISCLOSED, true); return mowgli_vio_error(vio); } break; default: err = ERR_get_error(); break; } if(err > 0) { errno = EIO; return mowgli_vio_err_errcode(vio, strerror, errno); } /* idk lol */ return -1; } mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDREAD, false); mowgli_vio_setflag(vio, MOWGLI_VIO_FLAGS_NEEDWRITE, false); vio->error.op = MOWGLI_VIO_ERR_OP_NONE; return ret; } static int mowgli_vio_openssl_close(mowgli_vio_t *vio) { mowgli_ssl_connection_t *connection = vio->privdata; return_val_if_fail(connection->ssl_handle != NULL, -1); SSL_shutdown(connection->ssl_handle); SSL_free(connection->ssl_handle); SSL_CTX_free(connection->ssl_context); mowgli_heap_free(ssl_heap, connection); MOWGLI_VIO_SET_CLOSED(vio); close(vio->fd); return 0; } #else int mowgli_vio_openssl_setssl(mowgli_vio_t *vio, mowgli_vio_ssl_settings_t *settings) { mowgli_log("OpenSSL requested on a VIO object, but mowgli was built without OpenSSL support..."); return -1; } void * mowgli_vio_openssl_getsslhandle(mowgli_vio_t *vio) { mowgli_log("Cannot get VIO SSL handle as libmowgli was built without OpenSSL support"); return NULL; } void * mowgli_vio_openssl_getsslcontext(mowgli_vio_t *vio) { mowgli_log("Cannot get VIO SSL context as libmowgli was built without OpenSSL support"); return NULL; } #endif