From c7039f5e99888f9864403f93ab7b7c5866e4cabb Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Mon, 26 Apr 2021 12:23:08 +0200 Subject: [PATCH] Optionally use OpenSSL or LibreSSL instead of bundled crypto-algorithms crypto-algorithms "have no resistence to side-channel attacks and should not be used in contexts that need cryptographically secure implementations" (see lib/crypto-algorithms/README.md), so using OpenSSL or LibreSSL is preferable. This does solve https://github.com/matrix-org/olm/issues/3 for some platforms, without breaking other platforms without these libraries (like web). Signed-off-by: Lukas Lihotzki --- CMakeLists.txt | 18 +++- cmake/FindLibreSSL.cmake | 227 +++++++++++++++++++++++++++++++++++++++ src/crypto.cpp | 85 +++++++++++++++ 3 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 cmake/FindLibreSSL.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2101186..10b5d6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ project(olm VERSION 3.2.8 LANGUAGES CXX C) option(OLM_TESTS "Build tests" ON) option(BUILD_SHARED_LIBS "Build as a shared library" ON) +option(OLM_USE_OPENSSL "Use OpenSSL instead of bundled crypto-algorithms" OFF) +option(OLM_USE_LIBRESSL "Use LibreSSL instead of bundled crypto-algorithms" OFF) add_definitions(-DOLMLIB_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) add_definitions(-DOLMLIB_VERSION_MINOR=${PROJECT_VERSION_MINOR}) @@ -47,11 +49,23 @@ add_library(olm src/outbound_group_session.c src/pickle_encoding.c - lib/crypto-algorithms/aes.c - lib/crypto-algorithms/sha256.c lib/curve25519-donna/curve25519-donna.c) add_library(Olm::Olm ALIAS olm) +if(OLM_USE_OPENSSL) + find_package(OpenSSL REQUIRED) + target_link_libraries(olm OpenSSL::Crypto) + target_compile_definitions(olm PRIVATE OLM_USE_OPENSSL) +elseif(OLM_USE_LIBRESSL) + find_package(LibreSSL REQUIRED) + target_link_libraries(olm LibreSSL::Crypto) + target_compile_definitions(olm PRIVATE OLM_USE_LIBRESSL) +else() + target_sources(olm PRIVATE + lib/crypto-algorithms/aes.c + lib/crypto-algorithms/sha256.c) +endif() + # restrict the exported symbols include(GenerateExportHeader) generate_export_header(olm diff --git a/cmake/FindLibreSSL.cmake b/cmake/FindLibreSSL.cmake new file mode 100644 index 0000000..6bdc069 --- /dev/null +++ b/cmake/FindLibreSSL.cmake @@ -0,0 +1,227 @@ +#[=======================================================================[ + +Copyright (c) 2019 John Norrbin + +Permission to use, copy, modify, and 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. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +FindLibreSSL +------------ + +Find the LibreSSL encryption library. + +Optional Components +^^^^^^^^^^^^^^^^^^^ + +This module supports two optional components: SSL and TLS. Both +components have associated imported targets, as described below. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module defines the following imported targets: + +LibreSSL::Crypto + The LibreSSL crypto library, if found. + +LibreSSL::SSL + The LibreSSL ssl library, if found. Requires and includes LibreSSL::Crypto automatically. + +LibreSSL::TLS + The LibreSSL tls library, if found. Requires and includes LibreSSL::SSL and LibreSSL::Crypto automatically. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables in your project: + +LIBRESSL_FOUND + System has the LibreSSL library. If no components are requested it only requires the crypto library. +LIBRESSL_INCLUDE_DIR + The LibreSSL include directory. +LIBRESSL_CRYPTO_LIBRARY + The LibreSSL crypto library. +LIBRESSL_SSL_LIBRARY + The LibreSSL SSL library. +LIBRESSL_TLS_LIBRARY + The LibreSSL TLS library. +LIBRESSL_LIBRARIES + All LibreSSL libraries. +LIBRESSL_VERSION + This is set to $major.$minor.$revision (e.g. 2.6.8). + +Hints +^^^^^ + +Set LIBRESSL_ROOT_DIR to the root directory of an LibreSSL installation. + +]=======================================================================] + +INCLUDE(FindPackageHandleStandardArgs) + +# Set Hints +set(_LIBRESSL_ROOT_HINTS + ${LIBRESSL_ROOT_DIR} + ENV LIBRESSL_ROOT_DIR +) + +# Set Paths +if (WIN32) + file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) + set(_LIBRESSL_ROOT_PATHS + "${_programfiles}/LibreSSL" + ) + unset(_programfiles) +else() + set(_LIBRESSL_ROOT_PATHS + "/usr/local/" + ) +endif() + +# Combine +set(_LIBRESSL_ROOT_HINTS_AND_PATHS + HINTS ${_LIBRESSL_ROOT_HINTS} + PATHS ${_LIBRESSL_ROOT_PATHS} +) + +# Find Include Path +find_path(LIBRESSL_INCLUDE_DIR + NAMES + tls.h + ${_LIBRESSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +# Find Crypto Library +find_library(LIBRESSL_CRYPTO_LIBRARY + NAMES + libcrypto + crypto + NAMES_PER_DIR + ${_LIBRESSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +# Find SSL Library +find_library(LIBRESSL_SSL_LIBRARY + NAMES + libssl + ssl + NAMES_PER_DIR + ${_LIBRESSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +# Find TLS Library +find_library(LIBRESSL_TLS_LIBRARY + NAMES + libtls + tls + NAMES_PER_DIR + ${_LIBRESSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +# Set Libraries +set(LIBRESSL_LIBRARIES ${LIBRESSL_CRYPTO_LIBRARY} ${LIBRESSL_SSL_LIBRARY} ${LIBRESSL_TLS_LIBRARY}) + +# Mark Variables As Advanced +mark_as_advanced(LIBRESSL_INCLUDE_DIR LIBRESSL_LIBRARIES LIBRESSL_CRYPTO_LIBRARY LIBRESSL_SSL_LIBRARY LIBRESSL_TLS_LIBRARY) + +# Find Version File +if(LIBRESSL_INCLUDE_DIR AND EXISTS "${LIBRESSL_INCLUDE_DIR}/openssl/opensslv.h") + + # Get Version From File + file(STRINGS "${LIBRESSL_INCLUDE_DIR}/openssl/opensslv.h" OPENSSLV.H REGEX "#define LIBRESSL_VERSION_TEXT[ ]+\".*\"") + + # Match Version String + string(REGEX REPLACE ".*\".*([0-9]+)\\.([0-9]+)\\.([0-9]+)\"" "\\1;\\2;\\3" LIBRESSL_VERSION_LIST "${OPENSSLV.H}") + + # Split Parts + list(GET LIBRESSL_VERSION_LIST 0 LIBRESSL_VERSION_MAJOR) + list(GET LIBRESSL_VERSION_LIST 1 LIBRESSL_VERSION_MINOR) + list(GET LIBRESSL_VERSION_LIST 2 LIBRESSL_VERSION_REVISION) + + # Set Version String + set(LIBRESSL_VERSION "${LIBRESSL_VERSION_MAJOR}.${LIBRESSL_VERSION_MINOR}.${LIBRESSL_VERSION_REVISION}") + +endif() + +# Set Find Package Arguments +find_package_handle_standard_args(LibreSSL + REQUIRED_VARS + LIBRESSL_CRYPTO_LIBRARY + LIBRESSL_INCLUDE_DIR + VERSION_VAR + LIBRESSL_VERSION + HANDLE_COMPONENTS + FAIL_MESSAGE + "Could NOT find LibreSSL, try setting the path to LibreSSL using the LIBRESSL_ROOT_DIR environment variable" +) + +# LibreSSL Found +if(LIBRESSL_FOUND) + + # Set LibreSSL::Crypto + if(NOT TARGET LibreSSL::Crypto AND EXISTS "${LIBRESSL_CRYPTO_LIBRARY}") + + # Add Library + add_library(LibreSSL::Crypto UNKNOWN IMPORTED) + + # Set Properties + set_target_properties( + LibreSSL::Crypto + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBRESSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBRESSL_CRYPTO_LIBRARY}" + ) + + endif() # LibreSSL::Crypto + + # Set LibreSSL::SSL + if(NOT TARGET LibreSSL::SSL AND EXISTS "${LIBRESSL_SSL_LIBRARY}") + + # Add Library + add_library(LibreSSL::SSL UNKNOWN IMPORTED) + + # Set Properties + set_target_properties( + LibreSSL::SSL + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBRESSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBRESSL_SSL_LIBRARY}" + INTERFACE_LINK_LIBRARIES LibreSSL::Crypto + ) + + endif() # LibreSSL::SSL + + # Set LibreSSL::TLS + if(NOT TARGET LibreSSL::TLS AND EXISTS "${LIBRESSL_TLS_LIBRARY}") + add_library(LibreSSL::TLS UNKNOWN IMPORTED) + set_target_properties( + LibreSSL::TLS + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBRESSL_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${LIBRESSL_TLS_LIBRARY}" + INTERFACE_LINK_LIBRARIES LibreSSL::SSL + ) + + endif() # LibreSSL::TLS + +endif(LIBRESSL_FOUND) diff --git a/src/crypto.cpp b/src/crypto.cpp index 5095c79..5e543c7 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -17,12 +17,23 @@ #include +#if defined(OLM_USE_OPENSSL) || defined(OLM_USE_LIBRESSL) +#include +#include +#include +#ifdef OLM_USE_OPENSSL +#include +#else +#include +#endif +#else extern "C" { #include "crypto-algorithms/aes.h" #include "crypto-algorithms/sha256.h" } +#endif #include "ed25519/src/ed25519.h" #include "curve25519-donna.h" @@ -37,6 +48,8 @@ static const std::size_t SHA256_BLOCK_LENGTH = 64; static const std::uint8_t HKDF_DEFAULT_SALT[32] = {}; +#if !defined(OLM_USE_OPENSSL) && !defined(OLM_USE_LIBRESSL) + template inline static void xor_block( std::uint8_t * block, @@ -98,6 +111,20 @@ inline static void hmac_sha256_final( olm::unset(o_pad); } + +#else + +template +static T checked(T val) { + if (!val) { + abort(); + } + return val; +} + +#endif + + } // namespace void _olm_crypto_curve25519_generate_key( @@ -176,6 +203,14 @@ void _olm_crypto_aes_encrypt_cbc( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { +#if defined(OLM_USE_OPENSSL) || defined(OLM_USE_LIBRESSL) + EVP_CIPHER_CTX* ctx = checked(EVP_CIPHER_CTX_new()); + checked(EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key->key, iv->iv)); + int output_length[2]; + checked(EVP_EncryptUpdate(ctx, output, &output_length[0], input, input_length)); + checked(EVP_EncryptFinal_ex(ctx, output + output_length[0], &output_length[1])); + EVP_CIPHER_CTX_free(ctx); +#else std::uint32_t key_schedule[AES_KEY_SCHEDULE_LENGTH]; ::aes_key_setup(key->key, key_schedule, AES_KEY_BITS); std::uint8_t input_block[AES_BLOCK_LENGTH]; @@ -198,6 +233,7 @@ void _olm_crypto_aes_encrypt_cbc( ::aes_encrypt(input_block, output, key_schedule, AES_KEY_BITS); olm::unset(key_schedule); olm::unset(input_block); +#endif } @@ -207,6 +243,15 @@ std::size_t _olm_crypto_aes_decrypt_cbc( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { +#if defined(OLM_USE_OPENSSL) || defined(OLM_USE_LIBRESSL) + EVP_CIPHER_CTX* ctx = checked(EVP_CIPHER_CTX_new()); + checked(EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key->key, iv->iv)); + int output_length[2]; + checked(EVP_DecryptUpdate(ctx, output, &output_length[0], input, input_length)); + checked(EVP_DecryptFinal_ex(ctx, output + output_length[0], &output_length[1])); + EVP_CIPHER_CTX_free(ctx); + return output_length[0] + output_length[1]; +#else std::uint32_t key_schedule[AES_KEY_SCHEDULE_LENGTH]; ::aes_key_setup(key->key, key_schedule, AES_KEY_BITS); std::uint8_t block1[AES_BLOCK_LENGTH]; @@ -223,6 +268,7 @@ std::size_t _olm_crypto_aes_decrypt_cbc( olm::unset(block2); std::size_t padding = output[input_length - 1]; return (padding > input_length) ? std::size_t(-1) : (input_length - padding); +#endif } @@ -230,11 +276,15 @@ void _olm_crypto_sha256( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { +#if defined(OLM_USE_OPENSSL) || defined(OLM_USE_LIBRESSL) + checked(EVP_Digest(input, input_length, output, nullptr, EVP_sha256(), nullptr)); +#else ::SHA256_CTX context; ::sha256_init(&context); ::sha256_update(&context, input, input_length); ::sha256_final(&context, output); olm::unset(context); +#endif } @@ -243,6 +293,9 @@ void _olm_crypto_hmac_sha256( std::uint8_t const * input, std::size_t input_length, std::uint8_t * output ) { +#if defined(OLM_USE_OPENSSL) || defined(OLM_USE_LIBRESSL) + checked(HMAC(EVP_sha256(), key, key_length, input, input_length, output, nullptr)); +#else std::uint8_t hmac_key[SHA256_BLOCK_LENGTH]; ::SHA256_CTX context; hmac_sha256_key(key, key_length, hmac_key); @@ -251,6 +304,7 @@ void _olm_crypto_hmac_sha256( hmac_sha256_final(&context, hmac_key, output); olm::unset(hmac_key); olm::unset(context); +#endif } @@ -260,6 +314,36 @@ void _olm_crypto_hkdf_sha256( std::uint8_t const * info, std::size_t info_length, std::uint8_t * output, std::size_t output_length ) { +#ifdef OLM_USE_OPENSSL + EVP_PKEY_CTX *pctx = checked(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)); + checked(EVP_PKEY_derive_init(pctx)); + checked(EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256())); + + if (input_length) { + checked(EVP_PKEY_CTX_set1_hkdf_key(pctx, input, input_length)); + } else { + /* OpenSSL HKDF doesn't directly support zero-length keys: + * https://github.com/openssl/openssl/issues/8531 + * Do the extract step manually with HMAC and use HKDF only for expand */ + uint8_t intermediate[SHA256_OUTPUT_LENGTH]; + checked(HMAC(EVP_sha256(), nullptr, 0, salt, salt_length, intermediate, nullptr)); + checked(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY)); + checked(EVP_PKEY_CTX_set1_hkdf_key(pctx, intermediate, sizeof(intermediate))); + } + + checked(EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_length)); + checked(EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_length)); + checked(EVP_PKEY_derive(pctx, output, &output_length)); + EVP_PKEY_CTX_free(pctx); +#elif defined(OLM_USE_LIBRESSL) + if (salt_length == 0) { + /* LibreSSL HKDF rejects nullptr salt, even if salt_length is 0. + * salt needs to be set to something else. */ + salt = (const uint8_t*)""; + } + checked(HKDF(output, output_length, EVP_sha256(), input, input_length, + salt, salt_length, info, info_length)); +#else ::SHA256_CTX context; std::uint8_t hmac_key[SHA256_BLOCK_LENGTH]; std::uint8_t step_result[SHA256_OUTPUT_LENGTH]; @@ -296,4 +380,5 @@ void _olm_crypto_hkdf_sha256( olm::unset(context); olm::unset(hmac_key); olm::unset(step_result); +#endif }