diff --git a/.github/workflows/simple.yml b/.github/workflows/simple.yml index c7eadde0..e0bc700b 100644 --- a/.github/workflows/simple.yml +++ b/.github/workflows/simple.yml @@ -30,7 +30,10 @@ jobs: 'openssl-3.2.5', 'openssl-3.1.8', 'openssl-3.0.17'] - debug: ['', 'WOLFPROV_DEBUG=1'] + debug: ['', '--debug'] + replace_default: [ + '', + '--replace-default --enable-replace-default-testing'] steps: - name: Checkout wolfProvider @@ -40,7 +43,7 @@ jobs: - name: Build and test wolfProvider run: | - OPENSSL_TAG=${{ matrix.openssl_ref }} WOLFSSL_TAG=${{ matrix.wolfssl_ref }} ${{ matrix.debug }} ./scripts/build-wolfprovider.sh + OPENSSL_TAG=${{ matrix.openssl_ref }} WOLFSSL_TAG=${{ matrix.wolfssl_ref }} ./scripts/build-wolfprovider.sh ${{ matrix.debug }} ${{ matrix.replace_default }} - name: Print errors if: ${{ failure() }} diff --git a/README.md b/README.md index 8fcce246..965efe6d 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,71 @@ Then use the following command to build wolfProvider with FIPS enabled. ./scripts/build-wolfprovider.sh --fips-bundle="path/to/fips-bundle" --fips-check=ready --distclean ``` +## Building with Replace Default + +wolfProvider can be configured to replace OpenSSL's default provider, making wolfProvider the default cryptographic provider for all OpenSSL operations. This is useful for applications that want to use wolfSSL's cryptographic implementations without modifying their code. + +### Replace Default vs. Standard Provider Mode + +Replace default mode is fundamentally different from the standard provider approach: + +**Standard Provider Mode:** When wolfProvider is loaded as a standard provider alongside OpenSSL's default provider, applications can still access OpenSSL's native crypto implementations in several ways: +- When an application explicitly requests a specific provider (e.g., "default") for an algorithm +- When wolfProvider doesn't implement a particular algorithm, OpenSSL falls back to its built-in implementations +- If the execution environment does not pick up the specified configuration file enabling +use of wolfProvider + +**Replace Default Mode:** This mode patches OpenSSL to disable many of these fallback paths. +When replace default is enabled: +- wolfProvider becomes the primary cryptographic provider +- Requests for the "default" provider are redirected to wolfProvider +- Requests for the "fips" provider are redirected to wolfProvider +- Requests for the "wolfProvider" provider are redirected to wolfProvider +- This ensures maximum use of wolfSSL's cryptographic implementations for testing and validation + +This makes replace default mode particularly useful for comprehensive testing scenarios where you want to ensure that wolfSSL's implementations are being used throughout the entire system. + +### Basic Replace Default + +To build wolfProvider as a replacement for OpenSSL's default provider: + +```bash +./scripts/build-wolfprovider.sh --replace-default +``` + +This patches OpenSSL so that wolfProvider becomes the default provider. + +### Replace Default with Testing Support + +For unit testing with replace-default enabled, you need additional support to load the real OpenSSL default provider alongside wolfProvider. This requires both flags: + +```bash +./scripts/build-wolfprovider.sh --replace-default --enable-replace-default-testing +``` + +### Important Notes + +**For `--replace-default`:** +- Can be used standalone in production or testing environments +- Makes wolfProvider the default cryptographic provider + +**For `--enable-replace-default-testing`:** +**Warning:** This option patches OpenSSL to export internal symbols that are not part of the public API. This configuration: +- Should only be used for development and testing +- Is not suitable for production deployments + +### Examples + +Build with replace-default only: +```bash +./scripts/build-wolfprovider.sh --replace-default +``` + +Build with replace-default and unit testing support: +```bash +./scripts/build-wolfprovider.sh --replace-default --enable-replace-default-testing +``` + ## Testing ### Unit Tests diff --git a/scripts/build-wolfprovider.sh b/scripts/build-wolfprovider.sh index c8de5014..ffe7b8f8 100755 --- a/scripts/build-wolfprovider.sh +++ b/scripts/build-wolfprovider.sh @@ -22,6 +22,9 @@ show_help() { echo " --debian --enable-fips Build a Debian package with FIPS support" echo " --quicktest Disable some tests for a faster testing suite" echo " --replace-default Patch OpenSSL and build it so that wolfProvider is the default provider" + echo " --enable-replace-default-testing" + echo " Enable direct provider loading in unit tests. This option patches openssl to export additional symbols." + echo " Note: Requires --replace-default. Only for test builds, not for production." echo " --leave-silent Enable leave silent mode to suppress logging of return 0 in probing functions where expected failures may occur." echo " Note: This only affects logging; the calling function is still responsible for handling all return values appropriately." echo "" @@ -37,6 +40,8 @@ show_help() { echo " WOLFPROV_LOG_FILE Path to log file for wolfProvider debug output (alternative to stderr)" echo " WOLFPROV_QUICKTEST If set to 1, disables some tests in the test suite to increase test speed" echo " WOLFPROV_DISABLE_ERR_TRACE If set to 1, wolfSSL will not be configured with --enable-debug-trace-errcodes=backtrace" + echo " WOLFPROV_REPLACE_DEFAULT If set to 1, patches OpenSSL so wolfProvider is the default provider" + echo " WOLFPROV_REPLACE_DEFAULT_TESTING If set to 1, enables direct provider loading in unit tests (requires WOLFPROV_REPLACE_DEFAULT=1)" echo " WOLFPROV_LEAVE_SILENT If set to 1, suppress logging of return 0 in functions where return 0 is expected behavior sometimes." echo "" } @@ -118,6 +123,9 @@ for arg in "$@"; do --replace-default) WOLFPROV_REPLACE_DEFAULT=1 ;; + --enable-replace-default-testing) + WOLFPROV_REPLACE_DEFAULT_TESTING=1 + ;; --leave-silent) WOLFPROV_LEAVE_SILENT=1 ;; @@ -145,6 +153,12 @@ if [ -n "$WOLFPROV_LOG_FILE" ] && [ -z "$WOLFPROV_DEBUG" ]; then exit 1 fi +# Check for consistency between replace-default options +if [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" = "1" ] && [ "$WOLFPROV_REPLACE_DEFAULT" != "1" ]; then + echo "Error: --enable-replace-default-testing requires --replace-default to also be set." + exit 1 +fi + if [ -n "$build_debian" ]; then set -e diff --git a/scripts/patch-libcrypto-exports.sh b/scripts/patch-libcrypto-exports.sh new file mode 100755 index 00000000..d21199f4 --- /dev/null +++ b/scripts/patch-libcrypto-exports.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# +# Copyright (C) 2006-2024 wolfSSL Inc. +# +# This file is part of wolfProvider. +# +# wolfProvider is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfProvider 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 wolfProvider. If not, see . +# + +# +# Patch libcrypto.num to export internal provider functions +# +# This script appends 6 internal provider symbols to OpenSSL's libcrypto.num +# file, making them available for direct provider loading in wolfprovider unit tests. +# + +set -e + +# OPENSSL_SOURCE_DIR should be set by the caller (utils-openssl.sh) +if [ -z "$OPENSSL_SOURCE_DIR" ]; then + echo "ERROR: OPENSSL_SOURCE_DIR not set" + exit 1 +fi + +# Source utils-openssl.sh to use is_libcrypto_num_patched function +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/utils-openssl.sh" + +LIBCRYPTO_NUM="${OPENSSL_SOURCE_DIR}/util/libcrypto.num" + +# Check if file exists +if [ ! -f "$LIBCRYPTO_NUM" ]; then + echo "ERROR: libcrypto.num not found at: $LIBCRYPTO_NUM" + exit 1 +fi + +# Check if already patched using shared function +if is_libcrypto_num_patched; then + echo "libcrypto.num already patched (provider symbols present)" + exit 0 +fi + +# Get the last symbol number +LAST_NUM=$(awk '{print $2}' "$LIBCRYPTO_NUM" | grep -E '^[0-9]+$' | sort -n | tail -1) + +if [ -z "$LAST_NUM" ]; then + echo "ERROR: Could not determine last symbol number from libcrypto.num" + exit 1 +fi + +# Get the version tag from the last line (column 3) +LAST_VERSION=$(tail -1 "$LIBCRYPTO_NUM" | awk '{print $3}') + +if [ -z "$LAST_VERSION" ]; then + echo "ERROR: Could not determine version tag from libcrypto.num" + exit 1 +fi + +# Calculate new symbol numbers +NUM1=$((LAST_NUM + 1)) +NUM2=$((LAST_NUM + 2)) +NUM3=$((LAST_NUM + 3)) +NUM4=$((LAST_NUM + 4)) +NUM5=$((LAST_NUM + 5)) +NUM6=$((LAST_NUM + 6)) + +# Append the 6 provider symbols +# Format matches existing entries: name (40 chars) TAB number TAB version TAB specification +# Use printf to ensure proper tab characters +{ + printf "ossl_provider_new %s\t%s\tEXIST::FUNCTION:\n" "${NUM1}" "${LAST_VERSION}" + printf "ossl_provider_activate %s\t%s\tEXIST::FUNCTION:\n" "${NUM2}" "${LAST_VERSION}" + printf "ossl_provider_deactivate %s\t%s\tEXIST::FUNCTION:\n" "${NUM3}" "${LAST_VERSION}" + printf "ossl_provider_add_to_store %s\t%s\tEXIST::FUNCTION:\n" "${NUM4}" "${LAST_VERSION}" + printf "ossl_provider_free %s\t%s\tEXIST::FUNCTION:\n" "${NUM5}" "${LAST_VERSION}" + printf "ossl_default_provider_init %s\t%s\tEXIST::FUNCTION:\n" "${NUM6}" "${LAST_VERSION}" +} >> "$LIBCRYPTO_NUM" + +echo "Successfully patched libcrypto.num: Added symbols ${NUM1}-${NUM6} with version ${LAST_VERSION}" +exit 0 + diff --git a/scripts/utils-openssl.sh b/scripts/utils-openssl.sh index 9a878079..67423dbd 100755 --- a/scripts/utils-openssl.sh +++ b/scripts/utils-openssl.sh @@ -117,6 +117,23 @@ is_openssl_patched() { return 1 } +is_libcrypto_num_patched() { + # Return 0 if patched with provider symbols, 1 if not + local dir="${OPENSSL_SOURCE_DIR:?OPENSSL_SOURCE_DIR not set}" + local file="${dir%/}/util/libcrypto.num" + + # File must exist to be patched + [[ -f "$file" ]] || return 1 + + # Check for our provider symbol exports + if grep -q '^ossl_provider_new' -- "$file"; then + return 0 + fi + + # Not patched + return 1 +} + patch_openssl_version() { # Patch the OpenSSL version (wolfProvider/openssl-source/VERSION.dat) # with our BUILD_METADATA, depending on the FIPS flag. Either "wolfProvider" or "wolfProvider-fips". @@ -171,6 +188,41 @@ patch_openssl() { printf "Done.\n" popd &> /dev/null fi + + # Patch libcrypto.num for replace-default-testing mode + if [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" = "1" ] && [ "$WOLFPROV_REPLACE_DEFAULT" = "1" ]; then + if [ -d "${OPENSSL_INSTALL_DIR}" ]; then + # OpenSSL already installed, skip patching + return 0 + fi + + printf "\tPatching libcrypto.num for provider symbol exports ... " + export OPENSSL_SOURCE_DIR + ${SCRIPT_DIR}/patch-libcrypto-exports.sh >>$LOG_FILE 2>&1 + if [ $? != 0 ]; then + printf "ERROR.\n" + printf "\n\nLibcrypto.num patch failed. Last 40 lines of log:\n" + tail -n 40 $LOG_FILE + do_cleanup + exit 1 + fi + printf "Done.\n" + + printf "\n" + printf " ╔════════════════════════════════════════════════════════════════════╗\n" + printf " ║ *** WARNING *** ║\n" + printf " ╠════════════════════════════════════════════════════════════════════╣\n" + printf " ║ OpenSSL has been PATCHED to export internal provider symbols ║\n" + printf " ║ for unit testing purposes. ║\n" + printf " ║ ║\n" + printf " ║ >> DO NOT USE THIS BUILD IN PRODUCTION ║\n" + printf " ║ >> This build is for TESTING ONLY ║\n" + printf " ║ ║\n" + printf " ║ Internal symbols exported: ossl_provider_new, ossl_provider_* ║\n" + printf " ║ ossl_default_provider_init ║\n" + printf " ╚════════════════════════════════════════════════════════════════════╝\n" + printf "\n" + fi } check_openssl_replace_default_mismatch() { @@ -200,11 +252,40 @@ check_openssl_replace_default_mismatch() { fi } +check_replace_default_testing_mismatch() { + local libcrypto_is_patched=0 + + # Check if libcrypto.num was patched for --enable-replace-default-testing + if is_libcrypto_num_patched; then + libcrypto_is_patched=1 + printf "INFO: OpenSSL libcrypto.num patched with internal provider symbol exports (testing build).\n" + fi + + # Check for mismatch + if [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" = "1" ] && [ "$libcrypto_is_patched" = "0" ]; then + printf "ERROR: --enable-replace-default-testing build mode mismatch!\n" + printf "Existing OpenSSL was built WITHOUT libcrypto.num patch\n" + printf "Current request: --enable-replace-default-testing build\n\n" + printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" + printf "Then rebuild with desired configuration.\n" + exit 1 + elif [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" != "1" ] && [ "$libcrypto_is_patched" = "1" ]; then + printf "ERROR: Standard build mode mismatch!\n" + printf "Existing OpenSSL was built WITH libcrypto.num patch (testing mode)\n" + printf "Current request: standard build\n\n" + printf "This OpenSSL build exports internal provider symbols and should NOT be used.\n" + printf "Fix: ./scripts/build-wolfprovider.sh --distclean\n" + printf "Then rebuild with desired configuration.\n" + exit 1 + fi +} + install_openssl() { printf "\nInstalling OpenSSL ${OPENSSL_TAG} ...\n" clone_openssl patch_openssl check_openssl_replace_default_mismatch + check_replace_default_testing_mismatch pushd ${OPENSSL_SOURCE_DIR} &> /dev/null diff --git a/scripts/utils-wolfprovider.sh b/scripts/utils-wolfprovider.sh index 98d19247..26d2a171 100644 --- a/scripts/utils-wolfprovider.sh +++ b/scripts/utils-wolfprovider.sh @@ -49,6 +49,10 @@ if [ "${WOLFPROV_QUICKTEST}" = "1" ]; then WOLFPROV_CONFIG_CFLAGS="${WOLFPROV_CONFIG_CFLAGS} -DWOLFPROV_QUICKTEST" fi +if [ "${WOLFPROV_REPLACE_DEFAULT_TESTING}" = "1" ]; then + WOLFPROV_CONFIG_CFLAGS="${WOLFPROV_CONFIG_CFLAGS} -DWOLFPROV_REPLACE_DEFAULT_UNIT_TEST" +fi + if [ "$WOLFSSL_ISFIPS" -eq "1" ] || [ -n "$WOLFSSL_FIPS_BUNDLE" ]; then WOLFPROV_CONFIG=${WOLFPROV_CONFIG:-"$WOLFPROV_SOURCE_DIR/provider-fips.conf"} else @@ -143,8 +147,8 @@ install_wolfprov() { # Build the replacement default library after wolfprov to avoid linker errors # but before testing so that the library is present if needed - if [ "$WOLFPROV_REPLACE_DEFAULT" = "1" ]; then - printf "\tWARNING: Skipping tests in replace mode...\n" + if [ "$WOLFPROV_REPLACE_DEFAULT" = "1" ] && [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" != "1" ]; then + printf "\tWARNING: Skipping tests in replace mode (use --enable-replace-default-testing to enable)...\n" else # Setup the environment to ensure we use the local builds of wolfprov, wolfssl, and openssl. if ! source ${SCRIPT_DIR}/env-setup >/dev/null 2>&1; then @@ -166,6 +170,23 @@ install_wolfprov() { printf "Done.\n" fi + # Final warning for replace-default-testing builds + if [ "$WOLFPROV_REPLACE_DEFAULT_TESTING" = "1" ]; then + printf "\n" + printf "╔══════════════════════════════════════════════════════════════════════════╗\n" + printf "║ *** TESTING BUILD COMPLETE *** ║\n" + printf "╠══════════════════════════════════════════════════════════════════════════╣\n" + printf "║ This OpenSSL build has been patched with INTERNAL SYMBOL EXPORTS ║\n" + printf "║ for unit testing with --enable-replace-default-testing ║\n" + printf "║ ║\n" + printf "║ >> DO NOT DEPLOY TO PRODUCTION ║\n" + printf "║ >> FOR DEVELOPMENT AND TESTING USE ONLY ║\n" + printf "║ ║\n" + printf "║ To build a production version, rebuild WITHOUT this flag. ║\n" + printf "╚══════════════════════════════════════════════════════════════════════════╝\n" + printf "\n" + fi + popd &> /dev/null } diff --git a/test/unit.c b/test/unit.c index 854831be..a55a5bcf 100644 --- a/test/unit.c +++ b/test/unit.c @@ -49,6 +49,123 @@ void print_buffer(const char *desc, const unsigned char *buffer, size_t len) } #endif +#ifdef WOLFPROV_REPLACE_DEFAULT_UNIT_TEST + +#include + +/* Forward declarations for OpenSSL internal DSO functions. */ +typedef struct dso_st DSO; +DSO *DSO_dsobyaddr(void *addr, int flags); +void *DSO_bind_func(DSO *dso, const char *symname); +int DSO_free(DSO *dso); + +/* Forward declarations for OpenSSL internal provider functions. + * These are not part of the public API but are needed to manually + * construct and register a provider with a specific init function. */ +OSSL_PROVIDER *ossl_provider_new(OSSL_LIB_CTX *libctx, const char *name, + OSSL_provider_init_fn *init_fn, + int no_config); +int ossl_provider_activate(OSSL_PROVIDER *prov, int retain_fallbacks, + int upcalls); +int ossl_provider_deactivate(OSSL_PROVIDER *prov, int removechildren); +int ossl_provider_add_to_store(OSSL_PROVIDER *prov, OSSL_PROVIDER **actualprov, + int retain_fallbacks); +void ossl_provider_free(OSSL_PROVIDER *prov); + +/* + * Get the ossl_default_provider_init function pointer from OpenSSL's + * libcrypto. This allows us to directly initialize the real OpenSSL default + * provider, bypassing the name-based lookup that would trigger replace-default + * behavior in patched OpenSSL builds. + * + * @return Function pointer to ossl_default_provider_init on success. + * @return NULL on failure. + */ +static OSSL_provider_init_fn* wp_get_default_provider_init_sym(void) +{ + DSO *dso = NULL; + OSSL_provider_init_fn* init_fn = NULL; + + /* Get a DSO handle for the library containing OPENSSL_init_crypto.*/ + dso = DSO_dsobyaddr((void *)&OPENSSL_init_crypto, 0); + if (dso == NULL) { + PRINT_ERR_MSG("DSO_dsobyaddr() failed to get handle to libcrypto"); + return NULL; + } + + /* Directly get the init function of the default provider */ + init_fn = (OSSL_provider_init_fn*)DSO_bind_func(dso, "ossl_default_provider_init"); + if (init_fn == NULL) { + PRINT_ERR_MSG("Failed to find ossl_default_provider_init symbol via DSO API"); + DSO_free(dso); + return NULL; + } + + /* Don't free the DSO - we need the symbol to remain valid */ + return init_fn; +} + +/* + * Load the real OpenSSL default provider directly, bypassing the name-based + * lookup that would trigger replace-default behavior in patched OpenSSL builds. + * + * This function replicates the logic from OpenSSL's OSSL_PROVIDER_try_load_ex(), + * but instead of loading a provider by name, it directly uses the + * ossl_default_provider_init function obtained via wp_get_default_provider_init_sym(). + * + * @param [in] libctx Library context to load the provider into. + * @return Provider handle on success. + * @return NULL on failure. + */ +static OSSL_PROVIDER* wp_load_default_provider_direct(OSSL_LIB_CTX* libctx) +{ + OSSL_provider_init_fn* init_fn = NULL; + OSSL_PROVIDER* prov = NULL; + OSSL_PROVIDER* actual = NULL; + + /* Get the real default provider init function */ + init_fn = wp_get_default_provider_init_sym(); + if (init_fn == NULL) { + PRINT_ERR_MSG("Failed to get default provider init function"); + return NULL; + } + + /* Create a new provider structure with the name "real-default" */ + prov = ossl_provider_new(libctx, "real-default", init_fn, 0); + if (prov == NULL) { + PRINT_ERR_MSG("ossl_provider_new() failed"); + return NULL; + } + + /* Activate the provider */ + if (!ossl_provider_activate(prov, 1, 0)) { + PRINT_ERR_MSG("ossl_provider_activate() failed"); + ossl_provider_free(prov); + return NULL; + } + + /* Add provider to the store */ + actual = prov; + if (!ossl_provider_add_to_store(prov, &actual, 0)) { + PRINT_ERR_MSG("ossl_provider_add_to_store() failed"); + ossl_provider_deactivate(prov, 1); + ossl_provider_free(prov); + return NULL; + } + + if (actual != prov) { + if (!ossl_provider_activate(actual, 1, 0)) { + PRINT_ERR_MSG("ossl_provider_activate() failed"); + ossl_provider_free(actual); + return NULL; + } + } + + return actual; +} + +#endif /* ifdef WOLFPROV_REPLACE_DEFAULT_UNIT_TEST */ + static int debug = 1; static unsigned long flags = 0; @@ -645,19 +762,28 @@ int main(int argc, char* argv[]) OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL); wpLibCtx = OSSL_LIB_CTX_new(); + OSSL_PROVIDER_set_default_search_path(wpLibCtx, dir); wpProv = OSSL_PROVIDER_load(wpLibCtx, name); if (wpProv == NULL) { PRINT_ERR_MSG("Failed to find wolf provider!\n"); - ERR_print_errors_fp(stderr); err = 1; } osslLibCtx = OSSL_LIB_CTX_new(); +#ifdef WOLFPROV_REPLACE_DEFAULT_UNIT_TEST + /* If enabled, directly load the default provider for unit testing + * with default replace. */ + osslProv = wp_load_default_provider_direct(osslLibCtx); + if (osslProv == NULL) { + PRINT_ERR_MSG("Failed to load default provider directly!\n"); + err = 1; + } +#else osslProv = OSSL_PROVIDER_load(osslLibCtx, "default"); +#endif if (osslProv == NULL) { PRINT_ERR_MSG("Failed to find default provider!\n"); - ERR_print_errors_fp(stderr); err = 1; } }