/** * WinPR: Windows Portable Runtime * OpenSSL Library Initialization * * Copyright 2014 Thincast Technologies GmbH * Copyright 2014 Norbert Federa * * Licensed 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 #include #include #include #include #include #ifdef WITH_OPENSSL #include #include #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) #include #endif #include "../log.h" #define TAG WINPR_TAG("utils.ssl") static BOOL g_winpr_openssl_initialized_by_winpr = FALSE; #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) static OSSL_PROVIDER* s_winpr_openssl_provider_fips = NULL; static OSSL_PROVIDER* s_winpr_openssl_provider_legacy = NULL; static OSSL_PROVIDER* s_winpr_openssl_provider_default = NULL; #endif /** * Note from OpenSSL 1.1.0 "CHANGES": * OpenSSL now uses a new threading API. It is no longer necessary to * set locking callbacks to use OpenSSL in a multi-threaded environment. */ #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER) #define WINPR_OPENSSL_LOCKING_REQUIRED 1 static int g_winpr_openssl_num_locks = 0; static HANDLE* g_winpr_openssl_locks = NULL; struct CRYPTO_dynlock_value { HANDLE mutex; }; #if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER) static unsigned long _winpr_openssl_id(void) { return (unsigned long)GetCurrentThreadId(); } #endif static void _winpr_openssl_locking(int mode, int type, const char* file, int line) { if (mode & CRYPTO_LOCK) { WaitForSingleObject(g_winpr_openssl_locks[type], INFINITE); } else { ReleaseMutex(g_winpr_openssl_locks[type]); } } static struct CRYPTO_dynlock_value* _winpr_openssl_dynlock_create(const char* file, int line) { struct CRYPTO_dynlock_value* dynlock; if (!(dynlock = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value)))) return NULL; if (!(dynlock->mutex = CreateMutex(NULL, FALSE, NULL))) { free(dynlock); return NULL; } return dynlock; } static void _winpr_openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value* dynlock, const char* file, int line) { if (mode & CRYPTO_LOCK) { WaitForSingleObject(dynlock->mutex, INFINITE); } else { ReleaseMutex(dynlock->mutex); } } static void _winpr_openssl_dynlock_destroy(struct CRYPTO_dynlock_value* dynlock, const char* file, int line) { CloseHandle(dynlock->mutex); free(dynlock); } static BOOL _winpr_openssl_initialize_locking(void) { int count; /* OpenSSL static locking */ if (CRYPTO_get_locking_callback()) { WLog_WARN(TAG, "OpenSSL static locking callback is already set"); } else { if ((count = CRYPTO_num_locks()) > 0) { HANDLE* locks; if (!(locks = calloc(count, sizeof(HANDLE)))) { WLog_ERR(TAG, "error allocating lock table"); return FALSE; } for (int i = 0; i < count; i++) { if (!(locks[i] = CreateMutex(NULL, FALSE, NULL))) { WLog_ERR(TAG, "error creating lock #%d", i); while (i--) { if (locks[i]) CloseHandle(locks[i]); } free(locks); return FALSE; } } g_winpr_openssl_locks = locks; g_winpr_openssl_num_locks = count; CRYPTO_set_locking_callback(_winpr_openssl_locking); } } /* OpenSSL dynamic locking */ if (CRYPTO_get_dynlock_create_callback() || CRYPTO_get_dynlock_lock_callback() || CRYPTO_get_dynlock_destroy_callback()) { WLog_WARN(TAG, "dynamic locking callbacks are already set"); } else { CRYPTO_set_dynlock_create_callback(_winpr_openssl_dynlock_create); CRYPTO_set_dynlock_lock_callback(_winpr_openssl_dynlock_lock); CRYPTO_set_dynlock_destroy_callback(_winpr_openssl_dynlock_destroy); } /* Use the deprecated CRYPTO_get_id_callback() if building against OpenSSL < 1.0.0 */ #if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER) if (CRYPTO_get_id_callback()) { WLog_WARN(TAG, "OpenSSL id_callback is already set"); } else { CRYPTO_set_id_callback(_winpr_openssl_id); } #endif return TRUE; } static BOOL _winpr_openssl_cleanup_locking(void) { /* undo our static locking modifications */ if (CRYPTO_get_locking_callback() == _winpr_openssl_locking) { CRYPTO_set_locking_callback(NULL); for (int i = 0; i < g_winpr_openssl_num_locks; i++) { CloseHandle(g_winpr_openssl_locks[i]); } g_winpr_openssl_num_locks = 0; free(g_winpr_openssl_locks); g_winpr_openssl_locks = NULL; } /* unset our dynamic locking callbacks */ if (CRYPTO_get_dynlock_create_callback() == _winpr_openssl_dynlock_create) { CRYPTO_set_dynlock_create_callback(NULL); } if (CRYPTO_get_dynlock_lock_callback() == _winpr_openssl_dynlock_lock) { CRYPTO_set_dynlock_lock_callback(NULL); } if (CRYPTO_get_dynlock_destroy_callback() == _winpr_openssl_dynlock_destroy) { CRYPTO_set_dynlock_destroy_callback(NULL); } #if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER) if (CRYPTO_get_id_callback() == _winpr_openssl_id) { CRYPTO_set_id_callback(NULL); } #endif return TRUE; } #endif /* OpenSSL < 1.1.0 */ static BOOL winpr_enable_fips(DWORD flags) { if (flags & WINPR_SSL_INIT_ENABLE_FIPS) { #if (OPENSSL_VERSION_NUMBER < 0x10001000L) || defined(LIBRESSL_VERSION_NUMBER) WLog_ERR(TAG, "Openssl fips mode not available on openssl versions less than 1.0.1!"); return FALSE; #else WLog_DBG(TAG, "Ensuring openssl fips mode is enabled"); #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) s_winpr_openssl_provider_fips = OSSL_PROVIDER_load(NULL, "fips"); if (s_winpr_openssl_provider_fips == NULL) { WLog_WARN(TAG, "OpenSSL FIPS provider failled to load"); } if (!EVP_default_properties_is_fips_enabled(NULL)) #else if (FIPS_mode() != 1) #endif { #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) if (EVP_set_default_properties(NULL, "fips=yes")) #else if (FIPS_mode_set(1)) #endif WLog_INFO(TAG, "Openssl fips mode enabled!"); else { WLog_ERR(TAG, "Openssl fips mode enable failed!"); return FALSE; } } #endif } return TRUE; } static void winpr_openssl_cleanup(void) { winpr_CleanupSSL(WINPR_SSL_INIT_DEFAULT); } static BOOL CALLBACK winpr_openssl_initialize(PINIT_ONCE once, PVOID param, PVOID* context) { DWORD flags = param ? *(PDWORD)param : WINPR_SSL_INIT_DEFAULT; if (flags & WINPR_SSL_INIT_ALREADY_INITIALIZED) { return TRUE; } #ifdef WINPR_OPENSSL_LOCKING_REQUIRED if (flags & WINPR_SSL_INIT_ENABLE_LOCKING) { if (!_winpr_openssl_initialize_locking()) { return FALSE; } } #endif /* SSL_load_error_strings() is void */ #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER) SSL_load_error_strings(); /* SSL_library_init() always returns "1" */ SSL_library_init(); OpenSSL_add_all_digests(); OpenSSL_add_all_ciphers(); #else if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS | OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL) != 1) return FALSE; #endif #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) /* The legacy provider is needed for MD4. */ s_winpr_openssl_provider_legacy = OSSL_PROVIDER_load(NULL, "legacy"); if (s_winpr_openssl_provider_legacy == NULL) { WLog_WARN(TAG, "OpenSSL LEGACY provider failed to load, no md4 support available!"); } s_winpr_openssl_provider_default = OSSL_PROVIDER_load(NULL, "default"); if (s_winpr_openssl_provider_default == NULL) { WLog_WARN(TAG, "OpenSSL DEFAULT provider failed to load"); } #endif atexit(winpr_openssl_cleanup); g_winpr_openssl_initialized_by_winpr = TRUE; return TRUE; } /* exported functions */ BOOL winpr_InitializeSSL(DWORD flags) { static INIT_ONCE once = INIT_ONCE_STATIC_INIT; if (!InitOnceExecuteOnce(&once, winpr_openssl_initialize, &flags, NULL)) return FALSE; return winpr_enable_fips(flags); } #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) static int unload(OSSL_PROVIDER* provider, void* data) { if (!provider) return 1; const char* name = OSSL_PROVIDER_get0_name(provider); if (!name) return 1; OSSL_LIB_CTX* ctx = OSSL_LIB_CTX_get0_global_default(); const int rc = OSSL_PROVIDER_available(ctx, name); if (rc < 1) return 1; OSSL_PROVIDER_unload(provider); return 1; } #endif BOOL winpr_CleanupSSL(DWORD flags) { if (flags & WINPR_SSL_CLEANUP_GLOBAL) { if (!g_winpr_openssl_initialized_by_winpr) { WLog_WARN(TAG, "ssl was not initialized by winpr"); return FALSE; } g_winpr_openssl_initialized_by_winpr = FALSE; #ifdef WINPR_OPENSSL_LOCKING_REQUIRED _winpr_openssl_cleanup_locking(); #endif #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER) CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); EVP_cleanup(); #endif #ifdef WINPR_OPENSSL_LOCKING_REQUIRED flags |= WINPR_SSL_CLEANUP_THREAD; #endif } #ifdef WINPR_OPENSSL_LOCKING_REQUIRED if (flags & WINPR_SSL_CLEANUP_THREAD) { #if (OPENSSL_VERSION_NUMBER < 0x10000000L) || defined(LIBRESSL_VERSION_NUMBER) ERR_remove_state(0); #else ERR_remove_thread_state(NULL); #endif } #endif #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) OSSL_LIB_CTX* ctx = OSSL_LIB_CTX_get0_global_default(); OSSL_PROVIDER_do_all(ctx, unload, NULL); #endif return TRUE; } BOOL winpr_FIPSMode(void) { #if (OPENSSL_VERSION_NUMBER < 0x10001000L) || defined(LIBRESSL_VERSION_NUMBER) return FALSE; #elif defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) return (EVP_default_properties_is_fips_enabled(NULL) == 1); #else return (FIPS_mode() == 1); #endif } #else BOOL winpr_InitializeSSL(DWORD flags) { return TRUE; } BOOL winpr_CleanupSSL(DWORD flags) { return TRUE; } BOOL winpr_FIPSMode(void) { return FALSE; } #endif