/*
- Bacula® - The Network Backup Solution
-
- Copyright (C) 2005-2007 Free Software Foundation Europe e.V.
-
- The main author of Bacula is Kern Sibbald, with contributions from
- many others, a complete list can be found in the file AUTHORS.
- This program is Free Software; you can redistribute it and/or
- modify it under the terms of version two of the GNU General Public
- License as published by the Free Software Foundation and included
- in the file LICENSE.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA.
-
- Bacula® is a registered trademark of John Walker.
- The licensor of Bacula is the Free Software Foundation Europe
- (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
- Switzerland, email:ftf@fsfeurope.org.
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2017 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
*/
/*
* tls.c TLS support functions
*
* Author: Landon Fuller <landonf@threerings.net>
*
- * Version $Id$
- *
* This file was contributed to the Bacula project by Landon Fuller
* and Three Rings Design, Inc.
*
#include "bacula.h"
#include <assert.h>
-extern time_t watchdog_time;
#ifdef HAVE_TLS /* Is TLS enabled? */
#ifdef HAVE_OPENSSL /* How about OpenSSL? */
+#include "openssl-compat.h"
+
/* No anonymous ciphers, no <128 bit ciphers, no export ciphers, no MD5 ciphers */
#define TLS_DEFAULT_CIPHERS "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"
struct TLS_Connection {
SSL *openssl;
+ pthread_mutex_t wlock; /* make openssl_bsock_readwrite() atomic when writing */
+ pthread_mutex_t rwlock; /* only one SSL_read() or SSL_write() at a time */
};
/*
X509_NAME_oneline(X509_get_issuer_name(cert), issuer, 256);
X509_NAME_oneline(X509_get_subject_name(cert), subject, 256);
- Jmsg5(get_jcr_from_tid(), M_ERROR, 0, _("Error with certificate at depth: %d, issuer = %s,"
- " subject = %s, ERR=%d:%s\n"), depth, issuer,
- subject, err, X509_verify_cert_error_string(err));
+ Jmsg5(NULL, M_ERROR, 0, _("Error with certificate at depth: %d, issuer = %s,"
+ " subject = %s, ERR=%d:%s\n"), depth, issuer,
+ subject, err, X509_verify_cert_error_string(err));
}
ctx = (TLS_CONTEXT *)malloc(sizeof(TLS_CONTEXT));
- /* Allocate our OpenSSL TLSv1 Context */
- ctx->openssl = SSL_CTX_new(TLSv1_method());
+ /* Allocate our OpenSSL TLS Context */
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ /* Allows SSLv3, TLSv1, TLSv1.1 and TLSv1.2 protocols */
+ ctx->openssl = SSL_CTX_new(TLS_method());
+
+#else
+ /* Allows most all protocols */
+ ctx->openssl = SSL_CTX_new(SSLv23_method());
+
+#endif
+
+ /* Use SSL_OP_ALL to turn on all "rather harmless" workarounds that
+ * OpenSSL offers
+ */
+ SSL_CTX_set_options(ctx->openssl, SSL_OP_ALL);
+
+ /* Now disable old broken SSLv3 and SSLv2 protocols */
+ SSL_CTX_set_options(ctx->openssl, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
if (!ctx->openssl) {
- openssl_post_errors(M_ERROR, _("Error initializing SSL context"));
+ openssl_post_errors(M_FATAL, _("Error initializing SSL context"));
goto err;
}
*/
if (ca_certfile || ca_certdir) {
if (!SSL_CTX_load_verify_locations(ctx->openssl, ca_certfile, ca_certdir)) {
- openssl_post_errors(M_ERROR, _("Error loading certificate verification stores"));
+ openssl_post_errors(M_FATAL, _("Error loading certificate verification stores"));
goto err;
}
} else if (verify_peer) {
/* At least one CA is required for peer verification */
- Jmsg0(get_jcr_from_tid(), M_ERROR, 0, _("Either a certificate file or a directory must be"
+ Jmsg0(NULL, M_ERROR, 0, _("Either a certificate file or a directory must be"
" specified as a verification store\n"));
goto err;
}
*/
if (certfile) {
if (!SSL_CTX_use_certificate_chain_file(ctx->openssl, certfile)) {
- openssl_post_errors(M_ERROR, _("Error loading certificate file"));
+ openssl_post_errors(M_FATAL, _("Error loading certificate file"));
goto err;
}
}
/* Load our private key. */
if (keyfile) {
if (!SSL_CTX_use_PrivateKey_file(ctx->openssl, keyfile, SSL_FILETYPE_PEM)) {
- openssl_post_errors(M_ERROR, _("Error loading private key"));
+ openssl_post_errors(M_FATAL, _("Error loading private key"));
goto err;
}
}
/* Load Diffie-Hellman Parameters. */
if (dhfile) {
if (!(bio = BIO_new_file(dhfile, "r"))) {
- openssl_post_errors(M_ERROR, _("Unable to open DH parameters file"));
+ openssl_post_errors(M_FATAL, _("Unable to open DH parameters file"));
goto err;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if (!dh) {
- openssl_post_errors(M_ERROR, _("Unable to load DH parameters from specified file"));
+ openssl_post_errors(M_FATAL, _("Unable to load DH parameters from specified file"));
goto err;
}
if (!SSL_CTX_set_tmp_dh(ctx->openssl, dh)) {
- openssl_post_errors(M_ERROR, _("Failed to set TLS Diffie-Hellman parameters"));
+ openssl_post_errors(M_FATAL, _("Failed to set TLS Diffie-Hellman parameters"));
DH_free(dh);
goto err;
}
}
if (SSL_CTX_set_cipher_list(ctx->openssl, TLS_DEFAULT_CIPHERS) != 1) {
- Jmsg0(get_jcr_from_tid(), M_ERROR, 0,
+ Jmsg0(NULL, M_ERROR, 0,
_("Error setting cipher list, no valid ciphers available\n"));
goto err;
}
free(ctx);
}
-bool get_tls_require(TLS_CONTEXT *ctx)
+bool get_tls_require(TLS_CONTEXT *ctx)
{
return ctx->tls_require;
}
-bool get_tls_enable(TLS_CONTEXT *ctx)
+bool get_tls_enable(TLS_CONTEXT *ctx)
{
return ctx->tls_enable;
}
* Returns: true on success
* false on failure
*/
-bool tls_postconnect_verify_cn(TLS_CONNECTION *tls, alist *verify_list)
+bool tls_postconnect_verify_cn(JCR *jcr, TLS_CONNECTION *tls, alist *verify_list)
{
SSL *ssl = tls->openssl;
X509 *cert;
/* Check if peer provided a certificate */
if (!(cert = SSL_get_peer_certificate(ssl))) {
- Jmsg0(get_jcr_from_tid(), M_ERROR, 0, _("Peer failed to present a TLS certificate\n"));
+ Qmsg0(jcr, M_ERROR, 0, _("Peer failed to present a TLS certificate\n"));
return false;
}
* Returns: true on success
* false on failure
*/
-bool tls_postconnect_verify_host(TLS_CONNECTION *tls, const char *host)
+bool tls_postconnect_verify_host(JCR *jcr, TLS_CONNECTION *tls, const char *host)
{
SSL *ssl = tls->openssl;
X509 *cert;
X509_NAME *subject;
bool auth_success = false;
int extensions;
- char data[256];
int i, j;
+ const char *pval, *phost;
+ int cnLastPos = -1;
+ X509_NAME_ENTRY *neCN;
+ ASN1_STRING *asn1CN;
/* Check if peer provided a certificate */
if (!(cert = SSL_get_peer_certificate(ssl))) {
- Jmsg1(get_jcr_from_tid(), M_ERROR, 0,
+ Qmsg1(jcr, M_ERROR, 0,
_("Peer %s failed to present a TLS certificate\n"), host);
+ Dmsg1(250, _("Peer %s failed to present a TLS certificate\n"), host);
return false;
}
extname = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
if (strcmp(extname, "subjectAltName") == 0) {
+#ifdef HAVE_OPENSSLv1
+ const X509V3_EXT_METHOD *method;
+#else
X509V3_EXT_METHOD *method;
+#endif
STACK_OF(CONF_VALUE) *val;
CONF_VALUE *nval;
void *extstr = NULL;
-#if (OPENSSL_VERSION_NUMBER >= 0x0090800FL)
const unsigned char *ext_value_data;
-#else
- unsigned char *ext_value_data;
-#endif
/* Get x509 extension method structure */
if (!(method = X509V3_EXT_get(ext))) {
ext_value_data = ext->value->data;
-#if (OPENSSL_VERSION_NUMBER > 0x00907000L)
if (method->it) {
/* New style ASN1 */
extstr = method->d2i(NULL, &ext_value_data, ext->value->length);
}
-#else
- extstr = method->d2i(NULL, &ext_value_data, ext->value->length);
-#endif
-
/* Iterate through to find the dNSName field(s) */
val = method->i2v(method, extstr, NULL);
/* dNSName shortname is "DNS" */
+ Dmsg0(250, "Check DNS name\n");
for (j = 0; j < sk_CONF_VALUE_num(val); j++) {
nval = sk_CONF_VALUE_value(val, j);
if (strcmp(nval->name, "DNS") == 0) {
- if (strcasecmp(nval->value, host) == 0) {
+ if (strncasecmp(nval->value, "*.", 2) == 0) {
+ Dmsg0(250, "Wildcard Certificate\n");
+ pval = strstr(nval->value, ".");
+ phost = strstr(host, ".");
+ if (pval && phost && (strcasecmp(pval, phost) == 0)) {
+ auth_success = true;
+ goto success;
+ }
+ } else if (strcasecmp(nval->value, host) == 0) {
auth_success = true;
goto success;
}
+ Dmsg2(250, "No DNS name match. Host=%s cert=%s\n", host, nval->value);
}
}
}
/* Try verifying against the subject name */
if (!auth_success) {
+ Dmsg0(250, "Check subject name name\n");
if ((subject = X509_get_subject_name(cert)) != NULL) {
- if (X509_NAME_get_text_by_NID(subject, NID_commonName, data, sizeof(data)) > 0) {
- /* NULL terminate data */
- data[255] = 0;
- if (strcasecmp(data, host) == 0) {
+ /* Loop through all CNs */
+ for (;;) {
+ cnLastPos = X509_NAME_get_index_by_NID(subject, NID_commonName, cnLastPos);
+ if (cnLastPos == -1) {
+ break;
+ }
+ neCN = X509_NAME_get_entry(subject, cnLastPos);
+ asn1CN = X509_NAME_ENTRY_get_data(neCN);
+ if (strncasecmp((const char*)asn1CN->data, "*.", 2) == 0) {
+ /* wildcard certificate */
+ Dmsg0(250, "Wildcard Certificate\n");
+ pval = strstr((const char*)asn1CN->data, ".");
+ phost = strstr(host, ".");
+ if (pval && phost && (strcasecmp(pval, phost) == 0)) {
+ auth_success = true;
+ goto success;
+ }
+ } else if (strcasecmp((const char*)asn1CN->data, host) == 0) {
auth_success = true;
+ break;
}
+ Dmsg2(250, "No subject name match. Host=%s cert=%s\n", host, (const char*)asn1CN->data);
}
}
}
success:
X509_free(cert);
-
return auth_success;
}
bio = BIO_new(BIO_s_socket());
if (!bio) {
/* Not likely, but never say never */
- openssl_post_errors(M_ERROR, _("Error creating file descriptor-based BIO"));
+ openssl_post_errors(M_FATAL, _("Error creating file descriptor-based BIO"));
return NULL; /* Nothing allocated, nothing to clean up */
}
BIO_set_fd(bio, fd, BIO_NOCLOSE);
/* Create the SSL object and attach the socket BIO */
if ((tls->openssl = SSL_new(ctx->openssl)) == NULL) {
/* Not likely, but never say never */
- openssl_post_errors(M_ERROR, _("Error creating new SSL object"));
+ openssl_post_errors(M_FATAL, _("Error creating new SSL object"));
goto err;
}
/* Non-blocking partial writes */
SSL_set_mode(tls->openssl, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ pthread_mutex_init(&tls->wlock, NULL);
+ pthread_mutex_init(&tls->rwlock, NULL);
+
return tls;
err:
*/
void free_tls_connection(TLS_CONNECTION *tls)
{
+ pthread_mutex_destroy(&tls->rwlock);
+ pthread_mutex_destroy(&tls->wlock);
SSL_free(tls->openssl);
free(tls);
}
{
TLS_CONNECTION *tls = bsock->tls;
int err;
- int fdmax, flags;
+ int flags;
int stat = true;
- fd_set fdset;
- struct timeval tv;
-
- /* Zero the fdset, we'll set our fd prior to each invocation of select() */
- FD_ZERO(&fdset);
- fdmax = bsock->m_fd + 1;
/* Ensure that socket is non-blocking */
flags = bsock->set_nonblocking();
/* start timer */
bsock->timer_start = watchdog_time;
bsock->clear_timed_out();
+ bsock->set_killable(false);
- for (;;) {
+ for (;;) {
if (server) {
err = SSL_accept(tls->openssl);
} else {
goto cleanup;
case SSL_ERROR_ZERO_RETURN:
/* TLS connection was cleanly shut down */
- openssl_post_errors(M_ERROR, _("Connect failure"));
+ openssl_post_errors(bsock->get_jcr(), M_FATAL, _("Connect failure"));
stat = false;
goto cleanup;
case SSL_ERROR_WANT_READ:
- /* If we timeout of a select, this will be unset */
- FD_SET((unsigned) bsock->m_fd, &fdset);
- /* Set our timeout */
- tv.tv_sec = 10;
- tv.tv_usec = 0;
/* Block until we can read */
- select(fdmax, &fdset, NULL, &fdset, &tv);
+ fd_wait_data(bsock->m_fd, WAIT_READ, 10, 0);
break;
case SSL_ERROR_WANT_WRITE:
- /* If we timeout of a select, this will be unset */
- FD_SET((unsigned) bsock->m_fd, &fdset);
- /* Set our timeout */
- tv.tv_sec = 10;
- tv.tv_usec = 0;
/* Block until we can write */
- select(fdmax, NULL, &fdset, &fdset, &tv);
+ fd_wait_data(bsock->m_fd, WAIT_WRITE, 10, 0);
break;
default:
- /* Socket Error Occured */
- openssl_post_errors(M_ERROR, _("Connect failure"));
+ /* Socket Error Occurred */
+ openssl_post_errors(bsock->get_jcr(), M_FATAL, _("Connect failure"));
stat = false;
goto cleanup;
}
bsock->restore_blocking(flags);
/* Clear timer */
bsock->timer_start = 0;
+ bsock->set_killable(true);
return stat;
}
*/
int err;
+ btimer_t *tid;
+
/* Set socket blocking for shutdown */
bsock->set_blocking();
+ tid = start_bsock_timer(bsock, 60 * 2);
err = SSL_shutdown(bsock->tls->openssl);
+ stop_bsock_timer(tid);
if (err == 0) {
/* Complete shutdown */
+ tid = start_bsock_timer(bsock, 60 * 2);
err = SSL_shutdown(bsock->tls->openssl);
+ stop_bsock_timer(tid);
}
+
switch (SSL_get_error(bsock->tls->openssl, err)) {
case SSL_ERROR_NONE:
break;
case SSL_ERROR_ZERO_RETURN:
/* TLS connection was shut down on us via a TLS protocol-level closure */
- openssl_post_errors(M_ERROR, _("TLS shutdown failure."));
+ openssl_post_errors(bsock->get_jcr(), M_ERROR, _("TLS shutdown failure."));
break;
default:
/* Socket Error Occurred */
- openssl_post_errors(M_ERROR, _("TLS shutdown failure."));
+ openssl_post_errors(bsock->get_jcr(), M_ERROR, _("TLS shutdown failure."));
break;
}
}
static inline int openssl_bsock_readwrite(BSOCK *bsock, char *ptr, int nbytes, bool write)
{
TLS_CONNECTION *tls = bsock->tls;
- int fdmax, flags;
- fd_set fdset;
- struct timeval tv;
+ int flags;
int nleft = 0;
int nwritten = 0;
- /* Zero the fdset, we'll set our fd prior to each invocation of select() */
- FD_ZERO(&fdset);
- fdmax = bsock->m_fd + 1;
-
/* Ensure that socket is non-blocking */
flags = bsock->set_nonblocking();
/* start timer */
bsock->timer_start = watchdog_time;
bsock->clear_timed_out();
+ bsock->set_killable(false);
nleft = nbytes;
- while (nleft > 0) {
+ if (write) {
+ pthread_mutex_lock(&tls->wlock);
+ }
+ while (nleft > 0) {
+
+ pthread_mutex_lock(&tls->rwlock);
if (write) {
nwritten = SSL_write(tls->openssl, ptr, nleft);
} else {
nwritten = SSL_read(tls->openssl, ptr, nleft);
}
+ pthread_mutex_unlock(&tls->rwlock);
/* Handle errors */
switch (SSL_get_error(tls->openssl, nwritten)) {
}
break;
+ case SSL_ERROR_SYSCALL:
+ if (nwritten == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN) {
+ bmicrosleep(0, 20000); /* try again in 20 ms */
+ continue;
+ }
+ }
+ openssl_post_errors(bsock->get_jcr(), M_FATAL, _("TLS read/write failure."));
+ goto cleanup;
+
case SSL_ERROR_WANT_READ:
+ /* Block until we can read */
+ fd_wait_data(bsock->m_fd, WAIT_READ, 10, 0);
+ break;
+
case SSL_ERROR_WANT_WRITE:
- /* If we timeout on a select, this will be unset */
- FD_SET((unsigned)bsock->m_fd, &fdset);
- tv.tv_sec = 10;
- tv.tv_usec = 0;
- /* Block until we can read or write */
- select(fdmax, NULL, &fdset, &fdset, &tv);
+ /* Block until we can read */
+ fd_wait_data(bsock->m_fd, WAIT_WRITE, 10, 0);
break;
case SSL_ERROR_ZERO_RETURN:
/* Fall through wanted */
default:
/* Socket Error Occured */
- openssl_post_errors(M_ERROR, _("TLS read/write failure."));
+ openssl_post_errors(bsock->get_jcr(), M_FATAL, _("TLS read/write failure."));
goto cleanup;
}
}
cleanup:
+ if (write) {
+ pthread_mutex_unlock(&tls->wlock);
+ }
/* Restore saved flags */
bsock->restore_blocking(flags);
/* Clear timer */
bsock->timer_start = 0;
+ bsock->set_killable(true);
return nbytes - nleft;
}
-int tls_bsock_writen(BSOCK *bsock, char *ptr, int32_t nbytes)
+int tls_bsock_writen(BSOCK *bsock, char *ptr, int32_t nbytes)
{
/* SSL_write(bsock->tls->openssl, ptr, nbytes) */
return openssl_bsock_readwrite(bsock, ptr, nbytes, true);
}
-int tls_bsock_readn(BSOCK *bsock, char *ptr, int32_t nbytes)
+int tls_bsock_readn(BSOCK *bsock, char *ptr, int32_t nbytes)
{
/* SSL_read(bsock->tls->openssl, ptr, nbytes) */
return openssl_bsock_readwrite(bsock, ptr, nbytes, false);
}
+/* test if 4 bytes can be read without "blocking" */
+bool tls_bsock_probe(BSOCK *bsock)
+{
+ int32_t pktsiz;
+ return SSL_peek(bsock->tls->openssl, &pktsiz, sizeof(pktsiz))==sizeof(pktsiz);
+}
+
+
#else /* HAVE_OPENSSL */
# error No TLS implementation available.
#endif /* !HAVE_OPENSSL */
void free_tls_connection(TLS_CONNECTION *tls) { }
-bool get_tls_require(TLS_CONTEXT *ctx)
+bool get_tls_require(TLS_CONTEXT *ctx)
{
return false;
}
-bool get_tls_enable(TLS_CONTEXT *ctx)
+bool get_tls_enable(TLS_CONTEXT *ctx)
{
return false;
}