summaryrefslogtreecommitdiff
path: root/openvpn/src/openvpn/ssl_openssl.c
diff options
context:
space:
mode:
Diffstat (limited to 'openvpn/src/openvpn/ssl_openssl.c')
-rw-r--r--openvpn/src/openvpn/ssl_openssl.c1200
1 files changed, 1200 insertions, 0 deletions
diff --git a/openvpn/src/openvpn/ssl_openssl.c b/openvpn/src/openvpn/ssl_openssl.c
new file mode 100644
index 00000000..8f353253
--- /dev/null
+++ b/openvpn/src/openvpn/ssl_openssl.c
@@ -0,0 +1,1200 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ * Copyright (C) 2010 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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 (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/**
+ * @file Control Channel OpenSSL Backend
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#if defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL)
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "misc.h"
+#include "manage.h"
+#include "memdbg.h"
+#include "ssl_backend.h"
+#include "ssl_common.h"
+#include "base64.h"
+
+#ifdef ENABLE_CRYPTOAPI
+#include "cryptoapi.h"
+#endif
+
+#include "ssl_verify_openssl.h"
+
+#include <openssl/err.h>
+#include <openssl/pkcs12.h>
+#include <openssl/x509.h>
+#include <openssl/crypto.h>
+
+/*
+ * Allocate space in SSL objects in which to store a struct tls_session
+ * pointer back to parent.
+ *
+ */
+
+int mydata_index; /* GLOBAL */
+
+void
+tls_init_lib()
+{
+ SSL_library_init();
+#ifndef ENABLE_SMALL
+ SSL_load_error_strings();
+#endif
+ OpenSSL_add_all_algorithms ();
+
+ mydata_index = SSL_get_ex_new_index(0, "struct session *", NULL, NULL, NULL);
+ ASSERT (mydata_index >= 0);
+}
+
+void
+tls_free_lib()
+{
+ EVP_cleanup();
+#ifndef ENABLE_SMALL
+ ERR_free_strings();
+#endif
+}
+
+void
+tls_clear_error()
+{
+ ERR_clear_error ();
+}
+
+/*
+ * OpenSSL callback to get a temporary RSA key, mostly
+ * used for export ciphers.
+ */
+static RSA *
+tmp_rsa_cb (SSL * s, int is_export, int keylength)
+{
+ static RSA *rsa_tmp = NULL;
+ if (rsa_tmp == NULL)
+ {
+ msg (D_HANDSHAKE, "Generating temp (%d bit) RSA key", keylength);
+ rsa_tmp = RSA_generate_key (keylength, RSA_F4, NULL, NULL);
+ }
+ return (rsa_tmp);
+}
+
+void
+tls_ctx_server_new(struct tls_root_ctx *ctx)
+{
+ ASSERT(NULL != ctx);
+
+ ctx->ctx = SSL_CTX_new (TLSv1_server_method ());
+
+ if (ctx->ctx == NULL)
+ msg (M_SSLERR, "SSL_CTX_new TLSv1_server_method");
+
+ SSL_CTX_set_tmp_rsa_callback (ctx->ctx, tmp_rsa_cb);
+}
+
+void
+tls_ctx_client_new(struct tls_root_ctx *ctx)
+{
+ ASSERT(NULL != ctx);
+
+ ctx->ctx = SSL_CTX_new (TLSv1_client_method ());
+
+ if (ctx->ctx == NULL)
+ msg (M_SSLERR, "SSL_CTX_new TLSv1_client_method");
+}
+
+void
+tls_ctx_free(struct tls_root_ctx *ctx)
+{
+ ASSERT(NULL != ctx);
+ if (NULL != ctx->ctx)
+ SSL_CTX_free (ctx->ctx);
+ ctx->ctx = NULL;
+}
+
+bool tls_ctx_initialised(struct tls_root_ctx *ctx)
+{
+ ASSERT(NULL != ctx);
+ return NULL != ctx->ctx;
+}
+
+/*
+ * Print debugging information on SSL/TLS session negotiation.
+ */
+
+#ifndef INFO_CALLBACK_SSL_CONST
+#define INFO_CALLBACK_SSL_CONST const
+#endif
+static void
+info_callback (INFO_CALLBACK_SSL_CONST SSL * s, int where, int ret)
+{
+ if (where & SSL_CB_LOOP)
+ {
+ dmsg (D_HANDSHAKE_VERBOSE, "SSL state (%s): %s",
+ where & SSL_ST_CONNECT ? "connect" :
+ where & SSL_ST_ACCEPT ? "accept" :
+ "undefined", SSL_state_string_long (s));
+ }
+ else if (where & SSL_CB_ALERT)
+ {
+ dmsg (D_HANDSHAKE_VERBOSE, "SSL alert (%s): %s: %s",
+ where & SSL_CB_READ ? "read" : "write",
+ SSL_alert_type_string_long (ret),
+ SSL_alert_desc_string_long (ret));
+ }
+}
+
+void
+tls_ctx_set_options (struct tls_root_ctx *ctx, unsigned int ssl_flags)
+{
+ ASSERT(NULL != ctx);
+
+ SSL_CTX_set_session_cache_mode (ctx->ctx, SSL_SESS_CACHE_OFF);
+ SSL_CTX_set_options (ctx->ctx, SSL_OP_SINGLE_DH_USE);
+ SSL_CTX_set_default_passwd_cb (ctx->ctx, pem_password_callback);
+
+ /* Require peer certificate verification */
+#if P2MP_SERVER
+ if (ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED)
+ {
+ msg (M_WARN, "WARNING: POTENTIALLY DANGEROUS OPTION "
+ "--client-cert-not-required may accept clients which do not present "
+ "a certificate");
+ }
+ else
+#endif
+ SSL_CTX_set_verify (ctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_callback);
+
+ SSL_CTX_set_info_callback (ctx->ctx, info_callback);
+}
+
+void
+tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
+{
+ ASSERT(NULL != ctx);
+
+ if(!SSL_CTX_set_cipher_list(ctx->ctx, ciphers))
+ msg(M_SSLERR, "Failed to set restricted TLS cipher list: %s", ciphers);
+}
+
+void
+tls_ctx_load_dh_params (struct tls_root_ctx *ctx, const char *dh_file
+#if ENABLE_INLINE_FILES
+ , const char *dh_file_inline
+#endif /* ENABLE_INLINE_FILES */
+ )
+{
+ DH *dh;
+ BIO *bio;
+
+ ASSERT(NULL != ctx);
+
+#if ENABLE_INLINE_FILES
+ if (!strcmp (dh_file, INLINE_FILE_TAG) && dh_file_inline)
+ {
+ if (!(bio = BIO_new_mem_buf ((char *)dh_file_inline, -1)))
+ msg (M_SSLERR, "Cannot open memory BIO for inline DH parameters");
+ }
+ else
+#endif /* ENABLE_INLINE_FILES */
+ {
+ /* Get Diffie Hellman Parameters */
+ if (!(bio = BIO_new_file (dh_file, "r")))
+ msg (M_SSLERR, "Cannot open %s for DH parameters", dh_file);
+ }
+
+ dh = PEM_read_bio_DHparams (bio, NULL, NULL, NULL);
+ BIO_free (bio);
+
+ if (!dh)
+ msg (M_SSLERR, "Cannot load DH parameters from %s", dh_file);
+ if (!SSL_CTX_set_tmp_dh (ctx->ctx, dh))
+ msg (M_SSLERR, "SSL_CTX_set_tmp_dh");
+
+ msg (D_TLS_DEBUG_LOW, "Diffie-Hellman initialized with %d bit key",
+ 8 * DH_size (dh));
+
+ DH_free (dh);
+}
+
+int
+tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
+#if ENABLE_INLINE_FILES
+ const char *pkcs12_file_inline,
+#endif /* ENABLE_INLINE_FILES */
+ bool load_ca_file
+ )
+{
+ FILE *fp;
+ EVP_PKEY *pkey;
+ X509 *cert;
+ STACK_OF(X509) *ca = NULL;
+ PKCS12 *p12;
+ int i;
+ char password[256];
+
+ ASSERT(NULL != ctx);
+
+#if ENABLE_INLINE_FILES
+ if (!strcmp (pkcs12_file, INLINE_FILE_TAG) && pkcs12_file_inline)
+ {
+ BIO *b64 = BIO_new(BIO_f_base64());
+ BIO *bio = BIO_new_mem_buf((void *) pkcs12_file_inline,
+ (int) strlen(pkcs12_file_inline));
+ ASSERT(b64 && bio);
+ BIO_push(b64, bio);
+ p12 = d2i_PKCS12_bio(b64, NULL);
+ if (!p12)
+ msg(M_SSLERR, "Error reading inline PKCS#12 file");
+ BIO_free(b64);
+ BIO_free(bio);
+ }
+ else
+#endif
+ {
+ /* Load the PKCS #12 file */
+ if (!(fp = platform_fopen(pkcs12_file, "rb")))
+ msg(M_SSLERR, "Error opening file %s", pkcs12_file);
+ p12 = d2i_PKCS12_fp(fp, NULL);
+ fclose(fp);
+ if (!p12)
+ msg(M_SSLERR, "Error reading PKCS#12 file %s", pkcs12_file);
+ }
+
+ /* Parse the PKCS #12 file */
+ if (!PKCS12_parse(p12, "", &pkey, &cert, &ca))
+ {
+ pem_password_callback (password, sizeof(password) - 1, 0, NULL);
+ /* Reparse the PKCS #12 file with password */
+ ca = NULL;
+ if (!PKCS12_parse(p12, password, &pkey, &cert, &ca))
+ {
+#ifdef ENABLE_MANAGEMENT
+ if (management && (ERR_GET_REASON (ERR_peek_error()) == PKCS12_R_MAC_VERIFY_FAILURE))
+ management_auth_failure (management, UP_TYPE_PRIVATE_KEY, NULL);
+#endif
+ PKCS12_free(p12);
+ return 1;
+ }
+ }
+ PKCS12_free(p12);
+
+ /* Load Certificate */
+ if (!SSL_CTX_use_certificate (ctx->ctx, cert))
+ msg (M_SSLERR, "Cannot use certificate");
+
+ /* Load Private Key */
+ if (!SSL_CTX_use_PrivateKey (ctx->ctx, pkey))
+ msg (M_SSLERR, "Cannot use private key");
+ warn_if_group_others_accessible (pkcs12_file);
+
+ /* Check Private Key */
+ if (!SSL_CTX_check_private_key (ctx->ctx))
+ msg (M_SSLERR, "Private key does not match the certificate");
+
+ /* Set Certificate Verification chain */
+ if (load_ca_file)
+ {
+ if (ca && sk_X509_num(ca))
+ {
+ for (i = 0; i < sk_X509_num(ca); i++)
+ {
+ if (!X509_STORE_add_cert(ctx->ctx->cert_store,sk_X509_value(ca, i)))
+ msg (M_SSLERR, "Cannot add certificate to certificate chain (X509_STORE_add_cert)");
+ if (!SSL_CTX_add_client_CA(ctx->ctx, sk_X509_value(ca, i)))
+ msg (M_SSLERR, "Cannot add certificate to client CA list (SSL_CTX_add_client_CA)");
+ }
+ }
+ }
+ return 0;
+}
+
+#ifdef ENABLE_CRYPTOAPI
+void
+tls_ctx_load_cryptoapi(struct tls_root_ctx *ctx, const char *cryptoapi_cert)
+{
+ ASSERT(NULL != ctx);
+
+ /* Load Certificate and Private Key */
+ if (!SSL_CTX_use_CryptoAPI_certificate (ctx->ctx, cryptoapi_cert))
+ msg (M_SSLERR, "Cannot load certificate \"%s\" from Microsoft Certificate Store",
+ cryptoapi_cert);
+}
+#endif /* WIN32 */
+
+static void
+tls_ctx_add_extra_certs (struct tls_root_ctx *ctx, BIO *bio)
+{
+ X509 *cert;
+ for (;;)
+ {
+ cert = NULL;
+ if (!PEM_read_bio_X509 (bio, &cert, 0, NULL)) /* takes ownership of cert */
+ break;
+ if (!cert)
+ msg (M_SSLERR, "Error reading extra certificate");
+ if (SSL_CTX_add_extra_chain_cert(ctx->ctx, cert) != 1)
+ msg (M_SSLERR, "Error adding extra certificate");
+ }
+}
+
+void
+tls_ctx_load_cert_file (struct tls_root_ctx *ctx, const char *cert_file,
+#if ENABLE_INLINE_FILES
+ const char *cert_file_inline,
+#endif
+ X509 **x509
+ )
+{
+ BIO *in = NULL;
+ X509 *x = NULL;
+ int ret = 0;
+ bool inline_file = false;
+
+ ASSERT (NULL != ctx);
+ if (NULL != x509)
+ ASSERT (NULL == *x509);
+
+#if ENABLE_INLINE_FILES
+ inline_file = (strcmp (cert_file, INLINE_FILE_TAG) == 0);
+
+ if (inline_file && cert_file_inline)
+ in = BIO_new_mem_buf ((char *)cert_file_inline, -1);
+ else
+#endif /* ENABLE_INLINE_FILES */
+ in = BIO_new_file (cert_file, "r");
+
+ if (in == NULL)
+ {
+ SSLerr (SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_SYS_LIB);
+ goto end;
+ }
+
+ x = PEM_read_bio_X509 (in, NULL, ctx->ctx->default_passwd_callback,
+ ctx->ctx->default_passwd_callback_userdata);
+ if (x == NULL)
+ {
+ SSLerr (SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB);
+ goto end;
+ }
+
+ ret = SSL_CTX_use_certificate (ctx->ctx, x);
+ if (ret)
+ tls_ctx_add_extra_certs (ctx, in);
+
+end:
+ if (!ret)
+ {
+ if (inline_file)
+ msg (M_SSLERR, "Cannot load inline certificate file");
+ else
+ msg (M_SSLERR, "Cannot load certificate file %s", cert_file);
+ }
+
+ if (in != NULL)
+ BIO_free(in);
+ if (x509)
+ *x509 = x;
+ else if (x)
+ X509_free (x);
+}
+
+void
+tls_ctx_free_cert_file (X509 *x509)
+{
+ X509_free(x509);
+}
+
+int
+tls_ctx_load_priv_file (struct tls_root_ctx *ctx, const char *priv_key_file
+#if ENABLE_INLINE_FILES
+ , const char *priv_key_file_inline
+#endif
+ )
+{
+ int status;
+ SSL_CTX *ssl_ctx = NULL;
+ BIO *in = NULL;
+ EVP_PKEY *pkey = NULL;
+ int ret = 1;
+
+ ASSERT(NULL != ctx);
+
+ ssl_ctx = ctx->ctx;
+
+#if ENABLE_INLINE_FILES
+ if (!strcmp (priv_key_file, INLINE_FILE_TAG) && priv_key_file_inline)
+ in = BIO_new_mem_buf ((char *)priv_key_file_inline, -1);
+ else
+#endif /* ENABLE_INLINE_FILES */
+ in = BIO_new_file (priv_key_file, "r");
+
+ if (!in)
+ goto end;
+
+ pkey = PEM_read_bio_PrivateKey (in, NULL,
+ ssl_ctx->default_passwd_callback,
+ ssl_ctx->default_passwd_callback_userdata);
+ if (!pkey)
+ goto end;
+
+ if (!SSL_CTX_use_PrivateKey (ssl_ctx, pkey))
+ {
+#ifdef ENABLE_MANAGEMENT
+ if (management && (ERR_GET_REASON (ERR_peek_error()) == EVP_R_BAD_DECRYPT))
+ management_auth_failure (management, UP_TYPE_PRIVATE_KEY, NULL);
+#endif
+ msg (M_WARN|M_SSL, "Cannot load private key file %s", priv_key_file);
+ goto end;
+ }
+ warn_if_group_others_accessible (priv_key_file);
+
+ /* Check Private Key */
+ if (!SSL_CTX_check_private_key (ssl_ctx))
+ msg (M_SSLERR, "Private key does not match the certificate");
+ ret = 0;
+
+end:
+ if (pkey)
+ EVP_PKEY_free (pkey);
+ if (in)
+ BIO_free (in);
+ return ret;
+}
+
+#ifdef MANAGMENT_EXTERNAL_KEY
+
+/* encrypt */
+static int
+rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ ASSERT(0);
+ return -1;
+}
+
+/* verify arbitrary data */
+static int
+rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ ASSERT(0);
+ return -1;
+}
+
+/* decrypt */
+static int
+rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ ASSERT(0);
+ return -1;
+}
+
+/* called at RSA_free */
+static int
+rsa_finish(RSA *rsa)
+{
+ free ((void*)rsa->meth);
+ rsa->meth = NULL;
+ return 1;
+}
+
+/* sign arbitrary data */
+static int
+rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+{
+ /* optional app data in rsa->meth->app_data; */
+ char *in_b64 = NULL;
+ char *out_b64 = NULL;
+ int ret = -1;
+ int len;
+
+ if (padding != RSA_PKCS1_PADDING)
+ {
+ RSAerr (RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+ goto done;
+ }
+
+ /* convert 'from' to base64 */
+ if (openvpn_base64_encode (from, flen, &in_b64) <= 0)
+ goto done;
+
+ /* call MI for signature */
+ if (management)
+ out_b64 = management_query_rsa_sig (management, in_b64);
+ if (!out_b64)
+ goto done;
+
+ /* decode base64 signature to binary */
+ len = RSA_size(rsa);
+ ret = openvpn_base64_decode (out_b64, to, len);
+
+ /* verify length */
+ if (ret != len)
+ ret = -1;
+
+ done:
+ if (in_b64)
+ free (in_b64);
+ if (out_b64)
+ free (out_b64);
+ return ret;
+}
+
+int
+tls_ctx_use_external_private_key (struct tls_root_ctx *ctx, X509 *cert)
+{
+ RSA *rsa = NULL;
+ RSA *pub_rsa;
+ RSA_METHOD *rsa_meth;
+
+ ASSERT (NULL != ctx);
+ ASSERT (NULL != cert);
+
+ /* allocate custom RSA method object */
+ ALLOC_OBJ_CLEAR (rsa_meth, RSA_METHOD);
+ rsa_meth->name = "OpenVPN external private key RSA Method";
+ rsa_meth->rsa_pub_enc = rsa_pub_enc;
+ rsa_meth->rsa_pub_dec = rsa_pub_dec;
+ rsa_meth->rsa_priv_enc = rsa_priv_enc;
+ rsa_meth->rsa_priv_dec = rsa_priv_dec;
+ rsa_meth->init = NULL;
+ rsa_meth->finish = rsa_finish;
+ rsa_meth->flags = RSA_METHOD_FLAG_NO_CHECK;
+ rsa_meth->app_data = NULL;
+
+ /* allocate RSA object */
+ rsa = RSA_new();
+ if (rsa == NULL)
+ {
+ SSLerr(SSL_F_SSL_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
+ /* get the public key */
+ ASSERT(cert->cert_info->key->pkey); /* NULL before SSL_CTX_use_certificate() is called */
+ pub_rsa = cert->cert_info->key->pkey->pkey.rsa;
+
+ /* initialize RSA object */
+ rsa->n = BN_dup(pub_rsa->n);
+ rsa->flags |= RSA_FLAG_EXT_PKEY;
+ if (!RSA_set_method(rsa, rsa_meth))
+ goto err;
+
+ /* bind our custom RSA object to ssl_ctx */
+ if (!SSL_CTX_use_RSAPrivateKey(ctx->ctx, rsa))
+ goto err;
+
+ RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */
+ return 1;
+
+ err:
+ if (rsa)
+ RSA_free(rsa);
+ else
+ {
+ if (rsa_meth)
+ free(rsa_meth);
+ }
+ msg (M_SSLERR, "Cannot enable SSL external private key capability");
+ return 0;
+}
+
+#endif
+
+static int
+sk_x509_name_cmp(const X509_NAME * const *a, const X509_NAME * const *b)
+{
+ return X509_NAME_cmp (*a, *b);
+}
+
+void
+tls_ctx_load_ca (struct tls_root_ctx *ctx, const char *ca_file,
+#if ENABLE_INLINE_FILES
+ const char *ca_file_inline,
+#endif
+ const char *ca_path, bool tls_server
+ )
+{
+ STACK_OF(X509_INFO) *info_stack = NULL;
+ STACK_OF(X509_NAME) *cert_names = NULL;
+ X509_LOOKUP *lookup = NULL;
+ X509_STORE *store = NULL;
+ X509_NAME *xn = NULL;
+ BIO *in = NULL;
+ int i, added = 0;
+
+ ASSERT(NULL != ctx);
+
+ store = SSL_CTX_get_cert_store(ctx->ctx);
+ if (!store)
+ msg(M_SSLERR, "Cannot get certificate store (SSL_CTX_get_cert_store)");
+
+ /* Try to add certificates and CRLs from ca_file */
+ if (ca_file)
+ {
+#if ENABLE_INLINE_FILES
+ if (!strcmp (ca_file, INLINE_FILE_TAG) && ca_file_inline)
+ in = BIO_new_mem_buf ((char *)ca_file_inline, -1);
+ else
+#endif
+ in = BIO_new_file (ca_file, "r");
+
+ if (in)
+ info_stack = PEM_X509_INFO_read_bio (in, NULL, NULL, NULL);
+
+ if (info_stack)
+ {
+ for (i = 0; i < sk_X509_INFO_num (info_stack); i++)
+ {
+ X509_INFO *info = sk_X509_INFO_value (info_stack, i);
+ if (info->crl)
+ X509_STORE_add_crl (store, info->crl);
+
+ if (info->x509)
+ {
+ X509_STORE_add_cert (store, info->x509);
+ added++;
+
+ if (!tls_server)
+ continue;
+
+ /* Use names of CAs as a client CA list */
+ if (cert_names == NULL)
+ {
+ cert_names = sk_X509_NAME_new (sk_x509_name_cmp);
+ if (!cert_names)
+ continue;
+ }
+
+ xn = X509_get_subject_name (info->x509);
+ if (!xn)
+ continue;
+
+ /* Don't add duplicate CA names */
+ if (sk_X509_NAME_find (cert_names, xn) == -1)
+ {
+ xn = X509_NAME_dup (xn);
+ if (!xn)
+ continue;
+ sk_X509_NAME_push (cert_names, xn);
+ }
+ }
+ }
+ sk_X509_INFO_pop_free (info_stack, X509_INFO_free);
+ }
+
+ if (tls_server)
+ SSL_CTX_set_client_CA_list (ctx->ctx, cert_names);
+
+ if (!added || (tls_server && sk_X509_NAME_num (cert_names) != added))
+ msg (M_SSLERR, "Cannot load CA certificate file %s", np(ca_file));
+ if (in)
+ BIO_free (in);
+ }
+
+ /* Set a store for certs (CA & CRL) with a lookup on the "capath" hash directory */
+ if (ca_path)
+ {
+ lookup = X509_STORE_add_lookup (store, X509_LOOKUP_hash_dir ());
+ if (lookup && X509_LOOKUP_add_dir (lookup, ca_path, X509_FILETYPE_PEM))
+ msg(M_WARN, "WARNING: experimental option --capath %s", ca_path);
+ else
+ msg(M_SSLERR, "Cannot add lookup at --capath %s", ca_path);
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+ X509_STORE_set_flags (store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+#else
+ msg(M_WARN, "WARNING: this version of OpenSSL cannot handle CRL files in capath");
+#endif
+ }
+}
+
+void
+tls_ctx_load_extra_certs (struct tls_root_ctx *ctx, const char *extra_certs_file
+#if ENABLE_INLINE_FILES
+ , const char *extra_certs_file_inline
+#endif
+ )
+{
+ BIO *in;
+#if ENABLE_INLINE_FILES
+ if (!strcmp (extra_certs_file, INLINE_FILE_TAG) && extra_certs_file_inline)
+ in = BIO_new_mem_buf ((char *)extra_certs_file_inline, -1);
+ else
+#endif
+ in = BIO_new_file (extra_certs_file, "r");
+
+ if (in == NULL)
+ msg (M_SSLERR, "Cannot load extra-certs file: %s", extra_certs_file);
+ else
+ tls_ctx_add_extra_certs (ctx, in);
+
+ BIO_free (in);
+}
+
+/* **************************************
+ *
+ * Key-state specific functions
+ *
+ ***************************************/
+/*
+ *
+ * BIO functions
+ *
+ */
+
+#ifdef BIO_DEBUG
+
+#warning BIO_DEBUG defined
+
+static FILE *biofp; /* GLOBAL */
+static bool biofp_toggle; /* GLOBAL */
+static time_t biofp_last_open; /* GLOBAL */
+static const int biofp_reopen_interval = 600; /* GLOBAL */
+
+static void
+close_biofp()
+{
+ if (biofp)
+ {
+ ASSERT (!fclose (biofp));
+ biofp = NULL;
+ }
+}
+
+static void
+open_biofp()
+{
+ const time_t current = time (NULL);
+ const pid_t pid = getpid ();
+
+ if (biofp_last_open + biofp_reopen_interval < current)
+ close_biofp();
+ if (!biofp)
+ {
+ char fn[256];
+ openvpn_snprintf(fn, sizeof(fn), "bio/%d-%d.log", pid, biofp_toggle);
+ biofp = fopen (fn, "w");
+ ASSERT (biofp);
+ biofp_last_open = time (NULL);
+ biofp_toggle ^= 1;
+ }
+}
+
+static void
+bio_debug_data (const char *mode, BIO *bio, const uint8_t *buf, int len, const char *desc)
+{
+ struct gc_arena gc = gc_new ();
+ if (len > 0)
+ {
+ open_biofp();
+ fprintf(biofp, "BIO_%s %s time=" time_format " bio=" ptr_format " len=%d data=%s\n",
+ mode, desc, time (NULL), (ptr_type)bio, len, format_hex (buf, len, 0, &gc));
+ fflush (biofp);
+ }
+ gc_free (&gc);
+}
+
+static void
+bio_debug_oc (const char *mode, BIO *bio)
+{
+ open_biofp();
+ fprintf(biofp, "BIO %s time=" time_format " bio=" ptr_format "\n",
+ mode, time (NULL), (ptr_type)bio);
+ fflush (biofp);
+}
+
+#endif
+
+/*
+ * OpenVPN's interface to SSL/TLS authentication,
+ * encryption, and decryption is exclusively
+ * through "memory BIOs".
+ */
+static BIO *
+getbio (BIO_METHOD * type, const char *desc)
+{
+ BIO *ret;
+ ret = BIO_new (type);
+ if (!ret)
+ msg (M_SSLERR, "Error creating %s BIO", desc);
+ return ret;
+}
+
+/*
+ * Write to an OpenSSL BIO in non-blocking mode.
+ */
+static int
+bio_write (BIO *bio, const uint8_t *data, int size, const char *desc)
+{
+ int i;
+ int ret = 0;
+ ASSERT (size >= 0);
+ if (size)
+ {
+ /*
+ * Free the L_TLS lock prior to calling BIO routines
+ * so that foreground thread can still call
+ * tls_pre_decrypt or tls_pre_encrypt,
+ * allowing tunnel packet forwarding to continue.
+ */
+#ifdef BIO_DEBUG
+ bio_debug_data ("write", bio, data, size, desc);
+#endif
+ i = BIO_write (bio, data, size);
+
+ if (i < 0)
+ {
+ if (BIO_should_retry (bio))
+ {
+ ;
+ }
+ else
+ {
+ msg (D_TLS_ERRORS | M_SSL, "TLS ERROR: BIO write %s error",
+ desc);
+ ret = -1;
+ ERR_clear_error ();
+ }
+ }
+ else if (i != size)
+ {
+ msg (D_TLS_ERRORS | M_SSL,
+ "TLS ERROR: BIO write %s incomplete %d/%d", desc, i, size);
+ ret = -1;
+ ERR_clear_error ();
+ }
+ else
+ { /* successful write */
+ dmsg (D_HANDSHAKE_VERBOSE, "BIO write %s %d bytes", desc, i);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Inline functions for reading from and writing
+ * to BIOs.
+ */
+
+static void
+bio_write_post (const int status, struct buffer *buf)
+{
+ if (status == 1) /* success status return from bio_write? */
+ {
+ memset (BPTR (buf), 0, BLEN (buf)); /* erase data just written */
+ buf->len = 0;
+ }
+}
+
+/*
+ * Read from an OpenSSL BIO in non-blocking mode.
+ */
+static int
+bio_read (BIO *bio, struct buffer *buf, int maxlen, const char *desc)
+{
+ int i;
+ int ret = 0;
+ ASSERT (buf->len >= 0);
+ if (buf->len)
+ {
+ ;
+ }
+ else
+ {
+ int len = buf_forward_capacity (buf);
+ if (maxlen < len)
+ len = maxlen;
+
+ /*
+ * BIO_read brackets most of the serious RSA
+ * key negotiation number crunching.
+ */
+ i = BIO_read (bio, BPTR (buf), len);
+
+ VALGRIND_MAKE_READABLE ((void *) &i, sizeof (i));
+
+#ifdef BIO_DEBUG
+ bio_debug_data ("read", bio, BPTR (buf), i, desc);
+#endif
+ if (i < 0)
+ {
+ if (BIO_should_retry (bio))
+ {
+ ;
+ }
+ else
+ {
+ msg (D_TLS_ERRORS | M_SSL, "TLS_ERROR: BIO read %s error",
+ desc);
+ buf->len = 0;
+ ret = -1;
+ ERR_clear_error ();
+ }
+ }
+ else if (!i)
+ {
+ buf->len = 0;
+ }
+ else
+ { /* successful read */
+ dmsg (D_HANDSHAKE_VERBOSE, "BIO read %s %d bytes", desc, i);
+ buf->len = i;
+ ret = 1;
+ VALGRIND_MAKE_READABLE ((void *) BPTR (buf), BLEN (buf));
+ }
+ }
+ return ret;
+}
+
+void
+key_state_ssl_init(struct key_state_ssl *ks_ssl, const struct tls_root_ctx *ssl_ctx, bool is_server, void *session)
+{
+ ASSERT(NULL != ssl_ctx);
+ ASSERT(ks_ssl);
+ CLEAR (*ks_ssl);
+
+ ks_ssl->ssl = SSL_new (ssl_ctx->ctx);
+ if (!ks_ssl->ssl)
+ msg (M_SSLERR, "SSL_new failed");
+
+ /* put session * in ssl object so we can access it
+ from verify callback*/
+ SSL_set_ex_data (ks_ssl->ssl, mydata_index, session);
+
+ ks_ssl->ssl_bio = getbio (BIO_f_ssl (), "ssl_bio");
+ ks_ssl->ct_in = getbio (BIO_s_mem (), "ct_in");
+ ks_ssl->ct_out = getbio (BIO_s_mem (), "ct_out");
+
+#ifdef BIO_DEBUG
+ bio_debug_oc ("open ssl_bio", ks_ssl->ssl_bio);
+ bio_debug_oc ("open ct_in", ks_ssl->ct_in);
+ bio_debug_oc ("open ct_out", ks_ssl->ct_out);
+#endif
+
+ if (is_server)
+ SSL_set_accept_state (ks_ssl->ssl);
+ else
+ SSL_set_connect_state (ks_ssl->ssl);
+
+ SSL_set_bio (ks_ssl->ssl, ks_ssl->ct_in, ks_ssl->ct_out);
+ BIO_set_ssl (ks_ssl->ssl_bio, ks_ssl->ssl, BIO_NOCLOSE);
+}
+
+void key_state_ssl_free(struct key_state_ssl *ks_ssl)
+{
+ if (ks_ssl->ssl) {
+#ifdef BIO_DEBUG
+ bio_debug_oc ("close ssl_bio", ks_ssl->ssl_bio);
+ bio_debug_oc ("close ct_in", ks_ssl->ct_in);
+ bio_debug_oc ("close ct_out", ks_ssl->ct_out);
+#endif
+ BIO_free_all(ks_ssl->ssl_bio);
+ SSL_free (ks_ssl->ssl);
+ }
+}
+
+int
+key_state_write_plaintext (struct key_state_ssl *ks_ssl, struct buffer *buf)
+{
+ int ret = 0;
+ perf_push (PERF_BIO_WRITE_PLAINTEXT);
+
+#ifdef ENABLE_CRYPTO_OPENSSL
+ ASSERT (NULL != ks_ssl);
+
+ ret = bio_write (ks_ssl->ssl_bio, BPTR(buf), BLEN(buf),
+ "tls_write_plaintext");
+ bio_write_post (ret, buf);
+#endif /* ENABLE_CRYPTO_OPENSSL */
+
+ perf_pop ();
+ return ret;
+}
+
+int
+key_state_write_plaintext_const (struct key_state_ssl *ks_ssl, const uint8_t *data, int len)
+{
+ int ret = 0;
+ perf_push (PERF_BIO_WRITE_PLAINTEXT);
+
+ ASSERT (NULL != ks_ssl);
+
+ ret = bio_write (ks_ssl->ssl_bio, data, len, "tls_write_plaintext_const");
+
+ perf_pop ();
+ return ret;
+}
+
+int
+key_state_read_ciphertext (struct key_state_ssl *ks_ssl, struct buffer *buf,
+ int maxlen)
+{
+ int ret = 0;
+ perf_push (PERF_BIO_READ_CIPHERTEXT);
+
+ ASSERT (NULL != ks_ssl);
+
+ ret = bio_read (ks_ssl->ct_out, buf, maxlen, "tls_read_ciphertext");
+
+ perf_pop ();
+ return ret;
+}
+
+int
+key_state_write_ciphertext (struct key_state_ssl *ks_ssl, struct buffer *buf)
+{
+ int ret = 0;
+ perf_push (PERF_BIO_WRITE_CIPHERTEXT);
+
+ ASSERT (NULL != ks_ssl);
+
+ ret = bio_write (ks_ssl->ct_in, BPTR(buf), BLEN(buf), "tls_write_ciphertext");
+ bio_write_post (ret, buf);
+
+ perf_pop ();
+ return ret;
+}
+
+int
+key_state_read_plaintext (struct key_state_ssl *ks_ssl, struct buffer *buf,
+ int maxlen)
+{
+ int ret = 0;
+ perf_push (PERF_BIO_READ_PLAINTEXT);
+
+ ASSERT (NULL != ks_ssl);
+
+ ret = bio_read (ks_ssl->ssl_bio, buf, maxlen, "tls_read_plaintext");
+
+ perf_pop ();
+ return ret;
+}
+
+/* **************************************
+ *
+ * Information functions
+ *
+ * Print information for the end user.
+ *
+ ***************************************/
+void
+print_details (struct key_state_ssl * ks_ssl, const char *prefix)
+{
+ const SSL_CIPHER *ciph;
+ X509 *cert;
+ char s1[256];
+ char s2[256];
+
+ s1[0] = s2[0] = 0;
+ ciph = SSL_get_current_cipher (ks_ssl->ssl);
+ openvpn_snprintf (s1, sizeof (s1), "%s %s, cipher %s %s",
+ prefix,
+ SSL_get_version (ks_ssl->ssl),
+ SSL_CIPHER_get_version (ciph),
+ SSL_CIPHER_get_name (ciph));
+ cert = SSL_get_peer_certificate (ks_ssl->ssl);
+ if (cert != NULL)
+ {
+ EVP_PKEY *pkey = X509_get_pubkey (cert);
+ if (pkey != NULL)
+ {
+ if (pkey->type == EVP_PKEY_RSA && pkey->pkey.rsa != NULL
+ && pkey->pkey.rsa->n != NULL)
+ {
+ openvpn_snprintf (s2, sizeof (s2), ", %d bit RSA",
+ BN_num_bits (pkey->pkey.rsa->n));
+ }
+ else if (pkey->type == EVP_PKEY_DSA && pkey->pkey.dsa != NULL
+ && pkey->pkey.dsa->p != NULL)
+ {
+ openvpn_snprintf (s2, sizeof (s2), ", %d bit DSA",
+ BN_num_bits (pkey->pkey.dsa->p));
+ }
+ EVP_PKEY_free (pkey);
+ }
+ X509_free (cert);
+ }
+ /* The SSL API does not allow us to look at temporary RSA/DH keys,
+ * otherwise we should print their lengths too */
+ msg (D_HANDSHAKE, "%s%s", s1, s2);
+}
+
+void
+show_available_tls_ciphers ()
+{
+ SSL_CTX *ctx;
+ SSL *ssl;
+ const char *cipher_name;
+ int priority = 0;
+
+ ctx = SSL_CTX_new (TLSv1_method ());
+ if (!ctx)
+ msg (M_SSLERR, "Cannot create SSL_CTX object");
+
+ ssl = SSL_new (ctx);
+ if (!ssl)
+ msg (M_SSLERR, "Cannot create SSL object");
+
+ printf ("Available TLS Ciphers,\n");
+ printf ("listed in order of preference:\n\n");
+ while ((cipher_name = SSL_get_cipher_list (ssl, priority++)))
+ printf ("%s\n", cipher_name);
+ printf ("\n");
+
+ SSL_free (ssl);
+ SSL_CTX_free (ctx);
+}
+
+void
+get_highest_preference_tls_cipher (char *buf, int size)
+{
+ SSL_CTX *ctx;
+ SSL *ssl;
+ const char *cipher_name;
+
+ ctx = SSL_CTX_new (TLSv1_method ());
+ if (!ctx)
+ msg (M_SSLERR, "Cannot create SSL_CTX object");
+ ssl = SSL_new (ctx);
+ if (!ssl)
+ msg (M_SSLERR, "Cannot create SSL object");
+
+ cipher_name = SSL_get_cipher_list (ssl, 0);
+ strncpynt (buf, cipher_name, size);
+
+ SSL_free (ssl);
+ SSL_CTX_free (ctx);
+}
+
+#endif /* defined(ENABLE_SSL) && defined(ENABLE_CRYPTO_OPENSSL) */