blob: 2dcd24a7c95548c3c478d9a381993905ed8f13e3 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apr_lib.h"
#include "apu.h"
#include "apr_private.h"
#include "apr_crypto.h"
#include "apr_crypto_internal.h"
#include "apr_strings.h"
#if APU_HAVE_CRYPTO
#if APU_HAVE_OPENSSL
#include "apr_thread_mutex.h"
#include <openssl/crypto.h>
#include <openssl/engine.h>
#include <openssl/conf.h>
#include <openssl/comp.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#if APR_HAS_THREADS && APR_USE_OPENSSL_PRE_1_1_API
static apr_status_t ossl_thread_setup(apr_pool_t *pool);
#else
static APR_INLINE apr_status_t ossl_thread_setup(apr_pool_t *pool)
{
return APR_SUCCESS;
}
#endif
const char *apr__crypto_openssl_version(void)
{
return OPENSSL_VERSION_TEXT;
}
apr_status_t apr__crypto_openssl_init(const char *params,
const apu_err_t **result,
apr_pool_t *pool)
{
/* Both undefined (or no-op) with LibreSSL */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
CRYPTO_malloc_init();
#elif !defined(LIBRESSL_VERSION_NUMBER)
OPENSSL_malloc_init();
#endif
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
ENGINE_load_builtin_engines();
ENGINE_register_all_complete();
SSL_load_error_strings();
SSL_library_init();
return ossl_thread_setup(pool);
}
apr_status_t apr__crypto_openssl_term(void)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#ifdef OPENSSL_FIPS
FIPS_mode_set(0);
#endif
CONF_modules_unload(1);
OBJ_cleanup();
EVP_cleanup();
#if !defined(LIBRESSL_VERSION_NUMBER)
RAND_cleanup();
#endif
ENGINE_cleanup();
#ifndef OPENSSL_NO_COMP
COMP_zlib_cleanup();
#endif
#if OPENSSL_VERSION_NUMBER >= 0x1000000fL
ERR_remove_thread_state(NULL);
#else
ERR_remove_state(0);
#endif
ERR_free_strings();
CRYPTO_cleanup_all_ex_data();
#else /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
OPENSSL_cleanup();
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */
return APR_SUCCESS;
}
/*
* To ensure thread-safetyness in OpenSSL - work in progress
* Taken from httpd's mod_ssl code.
*/
#if APR_HAS_THREADS && APR_USE_OPENSSL_PRE_1_1_API
static apr_thread_mutex_t **ossl_locks;
static int ossl_num_locks;
static void ossl_thread_locking(int mode, int type, const char *file, int line)
{
if (type < ossl_num_locks) {
if (mode & CRYPTO_LOCK) {
(void)apr_thread_mutex_lock(ossl_locks[type]);
}
else {
(void)apr_thread_mutex_unlock(ossl_locks[type]);
}
}
}
/* Dynamic lock structure */
struct CRYPTO_dynlock_value {
apr_pool_t *pool;
apr_thread_mutex_t *mutex;
const char* file;
int line;
};
/* Global reference to the pool passed into ossl_thread_setup() */
static apr_pool_t *ossl_dynlock_pool = NULL;
/*
* Dynamic lock creation callback
*/
static
struct CRYPTO_dynlock_value *ossl_dynlock_create(const char *file, int line)
{
struct CRYPTO_dynlock_value *value;
apr_status_t rv;
apr_pool_t *p;
/*
* We need a pool to allocate our mutex. Since we can't clear
* allocated memory from a pool, create a subpool that we can blow
* away in the destruction callback.
*/
rv = apr_pool_create(&p, ossl_dynlock_pool);
if (rv != APR_SUCCESS) {
return NULL;
}
value = apr_palloc(p, sizeof(*value));
rv = apr_thread_mutex_create(&value->mutex, APR_THREAD_MUTEX_DEFAULT, p);
if (rv != APR_SUCCESS) {
apr_pool_destroy(p);
return NULL;
}
/* Keep our own copy of the place from which we were created,
using our own pool. */
value->file = apr_pstrdup(p, file);
value->line = line;
value->pool = p;
return value;
}
/*
* Dynamic locking and unlocking function
*/
static void ossl_dynlock_locking(int mode, struct CRYPTO_dynlock_value *l,
const char *file, int line)
{
if (mode & CRYPTO_LOCK) {
(void)apr_thread_mutex_lock(l->mutex);
}
else {
(void)apr_thread_mutex_unlock(l->mutex);
}
}
/*
* Dynamic lock destruction callback
*/
static void ossl_dynlock_destroy(struct CRYPTO_dynlock_value *l,
const char *file, int line)
{
/* Trust that whomever owned the CRYPTO_dynlock_value we were
* passed has no future use for it...
*/
apr_pool_destroy(l->pool);
}
/* Windows and BeOS can use the default THREADID callback shipped with OpenSSL
* 1.0.x, as can any platform with a thread-safe errno.
*/
#define OSSL_DEFAULT_THREADID_IS_SAFE (OPENSSL_VERSION_NUMBER >= 0x10000000L \
&& (defined(_REENTRANT) \
|| __BEOS__ \
|| _WIN32 \
))
#if OSSL_DEFAULT_THREADID_IS_SAFE
/* We don't need to set up a threadid callback on this platform. */
static APR_INLINE apr_status_t ossl_thread_id_setup(apr_pool_t *pool)
{
return APR_SUCCESS;
}
static APR_INLINE apr_status_t ossl_thread_id_cleanup(void)
{
return APR_SUCCESS;
}
#else
/**
* Used by both versions of ossl_thread_id(). Returns an unsigned long that
* should be unique to the currently executing thread.
*/
static unsigned long ossl_thread_id_internal(void)
{
/* OpenSSL needs this to return an unsigned long. On OS/390, the pthread
* id is a structure twice that big. Use the TCB pointer instead as a
* unique unsigned long.
*/
#ifdef __MVS__
struct PSA {
char unmapped[540]; /* PSATOLD is at offset 540 in the PSA */
unsigned long PSATOLD;
} *psaptr = 0; /* PSA is at address 0 */
return psaptr->PSATOLD;
#else
return (unsigned long) apr_os_thread_current();
#endif
}
#ifndef OPENSSL_NO_DEPRECATED
static unsigned long ossl_thread_id(void)
{
return ossl_thread_id_internal();
}
#else
static void ossl_thread_id(CRYPTO_THREADID *id)
{
/* XXX Ideally we would be using the _set_pointer() callback on platforms
* that have a pointer-based thread "identity". But this entire API is
* fraught with problems (see PR60947) and has been removed completely in
* OpenSSL 1.1.0, so I'm not too invested in fixing it right now. */
CRYPTO_THREADID_set_numeric(id, ossl_thread_id_internal());
}
#endif /* OPENSSL_NO_DEPRECATED */
static apr_status_t ossl_thread_id_cleanup(void)
{
#ifndef OPENSSL_NO_DEPRECATED
CRYPTO_set_id_callback(NULL);
#else
/* XXX This does nothing. The new-style THREADID callback is write-once. */
CRYPTO_THREADID_set_callback(NULL);
#endif
return APR_SUCCESS;
}
static apr_status_t ossl_thread_id_setup(apr_pool_t *pool)
{
#ifndef OPENSSL_NO_DEPRECATED
/* This API is deprecated, but we prefer it to its replacement since it
* allows us to unset the callback when this module is being unloaded. */
CRYPTO_set_id_callback(ossl_thread_id);
#else
/* This is a last resort. We can only set this once, which means that we'd
* better not get reloaded into a different address. See PR60947.
*/
CRYPTO_THREADID_set_callback(ossl_thread_id);
if (CRYPTO_THREADID_get_callback() != ossl_thread_id) {
return APR_EGENERAL;
}
#endif
return APR_SUCCESS;
}
#endif /* OSSL_DEFAULT_THREADID_IS_SAFE */
static apr_status_t ossl_thread_cleanup(void *data)
{
CRYPTO_set_dynlock_create_callback(NULL);
CRYPTO_set_dynlock_lock_callback(NULL);
CRYPTO_set_dynlock_destroy_callback(NULL);
ossl_dynlock_pool = NULL;
CRYPTO_set_locking_callback(NULL);
ossl_thread_id_cleanup();
return APR_SUCCESS;
}
static apr_status_t ossl_thread_setup(apr_pool_t *pool)
{
apr_status_t rv;
int i, num_locks;
rv = ossl_thread_id_setup(pool);
if (rv != APR_SUCCESS) {
return rv;
}
num_locks = CRYPTO_num_locks();
ossl_locks = apr_palloc(pool, num_locks * sizeof(*ossl_locks));
if (!ossl_locks) {
return APR_ENOMEM;
}
for (i = 0; i < num_locks; i++) {
rv = apr_thread_mutex_create(&ossl_locks[i],
APR_THREAD_MUTEX_DEFAULT, pool);
if (rv != APR_SUCCESS) {
return rv;
}
}
ossl_num_locks = num_locks;
CRYPTO_set_locking_callback(ossl_thread_locking);
/* Set up dynamic locking scaffolding for OpenSSL to use at its
* convenience.
*/
ossl_dynlock_pool = pool;
CRYPTO_set_dynlock_create_callback(ossl_dynlock_create);
CRYPTO_set_dynlock_lock_callback(ossl_dynlock_locking);
CRYPTO_set_dynlock_destroy_callback(ossl_dynlock_destroy);
apr_pool_cleanup_register(pool, NULL, ossl_thread_cleanup,
apr_pool_cleanup_null);
return APR_SUCCESS;
}
#endif /* #if APR_HAS_THREADS && APR_USE_OPENSSL_PRE_1_1_API */
#endif /* APU_HAVE_OPENSSL */
#endif /* APU_HAVE_CRYPTO */