diff --git a/.github/workflows/altera-fcs.yml b/.github/workflows/altera-fcs.yml new file mode 100644 index 0000000000..062f4c10f6 --- /dev/null +++ b/.github/workflows/altera-fcs.yml @@ -0,0 +1,105 @@ +name: Altera Agilex 5 FCS port tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +# The stub exposes only the libfcs interface and always reports unavailable +# hardware. This checks port compilation, build guards, linkage, and software +# fallback without treating host output as SDM cryptographic validation. +# Functional offload remains covered by wolfcrypttest on Agilex 5 hardware. + +jobs: + build: + name: ${{ matrix.name }} + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - name: Autotools all algorithms + system: autotools + option: all + extra: --enable-aesctr + - name: Autotools RNG only + system: autotools + option: rng + extra: '' + - name: Autotools without ECC + system: autotools + option: all + extra: --enable-aesctr --disable-ecc + - name: CMake all algorithms + system: cmake + option: all + extra: '' + - name: CMake RNG only + system: cmake + option: rng + extra: '' + + steps: + - uses: actions/checkout@v5 + name: Checkout wolfSSL + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: autoconf automake libtool build-essential cmake + ghcr-debs-tag: ubuntu-24.04-minimal + + - name: Build no-hardware dependency stubs + run: | + stub="$RUNNER_TEMP/altera-fcs-stub" + mkdir -p "$stub/include" "$stub/lib" + cp tests/altera-fcs-stub/libfcs.h \ + tests/altera-fcs-stub/libfcs_osal_types.h "$stub/include/" + cc -I"$stub/include" -c \ + tests/altera-fcs-stub/libfcs_stub.c -o "$stub/libfcs_stub.o" + ar rcs "$stub/lib/libFCS.a" "$stub/libfcs_stub.o" + cc -c tests/altera-fcs-stub/fdt_stub.c -o "$stub/fdt_stub.o" + ar rcs "$stub/lib/libfdt.a" "$stub/fdt_stub.o" + echo "ALTERA_FCS_STUB=$stub" >> "$GITHUB_ENV" + + - name: Build and test with Autotools + if: matrix.system == 'autotools' + run: | + set -o pipefail + ./autogen.sh + CPPFLAGS="-I$ALTERA_FCS_STUB/include" \ + LDFLAGS="-L$ALTERA_FCS_STUB/lib" \ + ./configure --enable-alterafcs=${{ matrix.option }} \ + --disable-shared ${{ matrix.extra }} + make -j"$(nproc)" + ./wolfcrypt/test/testwolfcrypt | tee altera-fcs-test.log + grep -q 'ALTERA-FCS test skipped: hardware unavailable' \ + altera-fcs-test.log + + - name: Build and test with CMake + if: matrix.system == 'cmake' + run: | + set -o pipefail + cmake -S . -B build \ + -DWOLFSSL_ALTERA_FCS=${{ matrix.option }} \ + -DALTERA_FCS_INCLUDE_DIR="$ALTERA_FCS_STUB/include" \ + -DALTERA_FCS_OSAL_INCLUDE_DIR="$ALTERA_FCS_STUB/include" \ + -DALTERA_FCS_LIBRARY="$ALTERA_FCS_STUB/lib/libFCS.a" \ + -DALTERA_FDT_LIBRARY="$ALTERA_FCS_STUB/lib/libfdt.a" + cmake --build build --target wolfcrypttest -j"$(nproc)" + ./build/wolfcrypt/test/testwolfcrypt | tee altera-fcs-test.log + grep -q 'ALTERA-FCS test skipped: hardware unavailable' \ + altera-fcs-test.log diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 6452f64e8e..d857e612db 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -815,6 +815,7 @@ WOLFSSL_ALLOW_NO_CN_IN_SAN WOLFSSL_ALLOW_NO_SUITES WOLFSSL_ALLOW_SERVER_SC_EXT WOLFSSL_ALLOW_TLS_SHA1 +WOLFSSL_ALTERA_FCS_RAW_RNG WOLFSSL_ALTERNATIVE_DOWNGRADE WOLFSSL_ALT_NAMES_NO_REV WOLFSSL_ARM32_BUILD @@ -1034,7 +1035,6 @@ WOLFSSL_SHA256_ALT_CH_MAJ WOLFSSL_SHA3_AVX2 WOLFSSL_SHA3_NO_AVX2 WOLFSSL_SHA3_PPC64_BLOCKS_N -WOLFSSL_SHA512_HASHTYPE WOLFSSL_SHUTDOWNONCE WOLFSSL_SILABS_TRNG WOLFSSL_SLHDSA_FULL_HASH @@ -1112,7 +1112,6 @@ WOLFSSL_XILINX_PATCH WOLFSSL_XIL_MSG_NO_SLEEP WOLFSSL_ZEPHYR WOLF_ALLOW_BUILTIN -WOLF_CRYPTO_CB_CMD WOLF_CRYPTO_CB_ONLY_FALCON WOLF_CRYPTO_DEV WOLF_CRYPT_FIPS_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e44e5d81b..637228f0ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2772,10 +2772,39 @@ endif() # TODO: - Session export +add_option("WOLFSSL_ALTERA_FCS" + "Enable Altera Agilex 5 SDM crypto offload via libfcs (default: disabled)" + "no" "all;rng;yes;no") + add_option("WOLFSSL_CRYPTOCB" "Enable crypto callbacks (default: disabled)" "no" "yes;no") +if(WOLFSSL_ALTERA_FCS) + find_path(ALTERA_FCS_INCLUDE_DIR NAMES libfcs.h) + find_path(ALTERA_FCS_OSAL_INCLUDE_DIR + NAMES libfcs_osal_types.h) + find_library(ALTERA_FCS_LIBRARY NAMES FCS) + find_library(ALTERA_FDT_LIBRARY NAMES fdt) + if(ALTERA_FCS_INCLUDE_DIR AND ALTERA_FCS_OSAL_INCLUDE_DIR AND + ALTERA_FCS_LIBRARY AND ALTERA_FDT_LIBRARY) + list(APPEND WOLFSSL_INCLUDE_DIRS + ${ALTERA_FCS_INCLUDE_DIR} + ${ALTERA_FCS_OSAL_INCLUDE_DIR}) + list(APPEND WOLFSSL_LINK_LIBS + ${ALTERA_FCS_LIBRARY} + ${ALTERA_FDT_LIBRARY}) + list(APPEND PC_LIBS_PRIVATE "-lFCS" "-lfdt") + else() + message(FATAL_ERROR "Altera FCS requires libfcs and libfdt headers " + "and libraries") + endif() +endif() + +if(WOLFSSL_ALTERA_FCS) + override_cache(WOLFSSL_CRYPTOCB "yes") +endif() + add_option("WOLFSSL_CRYPTOCB_NO_SW_TEST" "Disable crypto callback SW testing (default: disabled)" "no" "yes;no") @@ -3999,6 +4028,39 @@ wolfssl_warn_unconsumed_forces() # the library. A series of AM_CONDITIONALs handle this in configure.ac. generate_build_flags() +# FCS depends on algorithm options declared throughout this file. Derive its +# compile definitions only after all options and BUILD_* selections are final. +if(WOLFSSL_ALTERA_FCS) + list(APPEND WOLFSSL_DEFINITIONS + "-DWOLFSSL_ALTERA_FCS" + "-DWOLF_CRYPTO_CB_COPY" + "-DWOLF_CRYPTO_CB_FREE" + "-DWOLF_CRYPTO_CB_SETKEY") + if(WOLFSSL_ALTERA_FCS STREQUAL "rng") + if(BUILD_ALTERA_FCS_RNG) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_ALTERA_FCS_RNG") + endif() + else() + if(BUILD_ALTERA_FCS_RNG) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_ALTERA_FCS_RNG") + endif() + if(BUILD_ALTERA_FCS_HASH) + list(APPEND WOLFSSL_DEFINITIONS + "-DWOLFSSL_ALTERA_FCS_HASH" + "-DWOLFSSL_SHA512_HASHTYPE") + endif() + if(BUILD_ALTERA_FCS_AES) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_ALTERA_FCS_AES") + endif() + if(BUILD_ALTERA_FCS_ECC) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_ALTERA_FCS_ECC") + endif() + if(BUILD_ALTERA_FCS_HMAC) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_ALTERA_FCS_HMAC") + endif() + endif() +endif() + # TODO: - Bit of logic after optimization flags option (above) # - Check for build-type conflicts section diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 1856dcb22c..a4fa718228 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -365,6 +365,31 @@ function(generate_build_flags) if(WOLFSSL_CRYPTOCB OR WOLFSSL_USER_SETTINGS) set(BUILD_CRYPTOCB "yes" PARENT_SCOPE) endif() + if(WOLFSSL_ALTERA_FCS OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS "yes" PARENT_SCOPE) + if(WOLFSSL_ALTERA_FCS STREQUAL "rng") + if(WOLFSSL_RNG OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_RNG "yes" PARENT_SCOPE) + endif() + else() + if(WOLFSSL_RNG OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_RNG "yes" PARENT_SCOPE) + endif() + if(WOLFSSL_SHA256 OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_HASH "yes" PARENT_SCOPE) + endif() + if(WOLFSSL_AES OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_AES "yes" PARENT_SCOPE) + endif() + if((WOLFSSL_ECC AND WOLFSSL_ASN) OR WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_ECC "yes" PARENT_SCOPE) + endif() + if(WOLFSSL_SHA256 OR WOLFSSL_SHA384 OR WOLFSSL_SHA512 OR + WOLFSSL_USER_SETTINGS) + set(BUILD_ALTERA_FCS_HMAC "yes" PARENT_SCOPE) + endif() + endif() + endif() if(WOLFSSL_VAULTIC) set(BUILD_VAULTIC "yes" PARENT_SCOPE) endif() @@ -1288,6 +1313,32 @@ function(generate_lib_src_list LIB_SOURCES) list(APPEND LIB_SOURCES wolfcrypt/src/cryptocb.c) endif() + if(BUILD_ALTERA_FCS) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_glue.c + wolfcrypt/src/port/altera/altera_fcs_cryptocb.c) + if(BUILD_ALTERA_FCS_RNG) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_rng.c) + endif() + if(BUILD_ALTERA_FCS_HASH) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_hash.c) + endif() + if(BUILD_ALTERA_FCS_AES) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_aes.c) + endif() + if(BUILD_ALTERA_FCS_ECC) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_ecc.c) + endif() + if(BUILD_ALTERA_FCS_HMAC) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/altera/altera_fcs_hmac.c) + endif() + endif() + if(BUILD_SHE) list(APPEND LIB_SOURCES wolfcrypt/src/wc_she.c) endif() diff --git a/cmake/options.h.in b/cmake/options.h.in index 431f2545d6..375f88b261 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -261,6 +261,10 @@ extern "C" { #cmakedefine WC_RSA_PSS #undef WOLF_CRYPTO_CB #cmakedefine WOLF_CRYPTO_CB +#undef WOLF_CRYPTO_CB_COPY +#cmakedefine WOLF_CRYPTO_CB_COPY +#undef WOLF_CRYPTO_CB_FREE +#cmakedefine WOLF_CRYPTO_CB_FREE #undef WOLF_CRYPTO_CB_RSA_PAD #cmakedefine WOLF_CRYPTO_CB_RSA_PAD #undef WOLFSSL_AARCH64_BUILD @@ -289,6 +293,20 @@ extern "C" { #cmakedefine WOLFSSL_BASE64_ENCODE #undef WOLFSSL_CAAM #cmakedefine WOLFSSL_CAAM +#undef WOLFSSL_ALTERA_FCS +#cmakedefine WOLFSSL_ALTERA_FCS +#undef WOLFSSL_ALTERA_FCS_RNG +#cmakedefine WOLFSSL_ALTERA_FCS_RNG +#undef WOLFSSL_ALTERA_FCS_HASH +#cmakedefine WOLFSSL_ALTERA_FCS_HASH +#undef WOLFSSL_ALTERA_FCS_AES +#cmakedefine WOLFSSL_ALTERA_FCS_AES +#undef WOLFSSL_ALTERA_FCS_ECC +#cmakedefine WOLFSSL_ALTERA_FCS_ECC +#undef WOLFSSL_ALTERA_FCS_HMAC +#cmakedefine WOLFSSL_ALTERA_FCS_HMAC +#undef WOLFSSL_SHA512_HASHTYPE +#cmakedefine WOLFSSL_SHA512_HASHTYPE #undef WOLFSSL_CERT_EXT #cmakedefine WOLFSSL_CERT_EXT #undef WOLFSSL_CERT_GEN diff --git a/configure.ac b/configure.ac index c446c725cd..4d07bc8d07 100644 --- a/configure.ac +++ b/configure.ac @@ -5012,6 +5012,36 @@ then fi +# Altera Agilex 5 SDM crypto services (via libfcs) +AC_ARG_ENABLE([alterafcs], + [AS_HELP_STRING([--enable-alterafcs],[Enable Altera Agilex 5 SDM crypto offload via libfcs: all | rng (default: disabled)])], + [ ENABLED_ALTERA_FCS=$enableval ], + [ ENABLED_ALTERA_FCS=no ] + ) + +# Only the selection is recorded here. The backends depend on wolfCrypt +# algorithms whose options have not been parsed yet, so the defines are emitted +# later once those are known. +if test "$ENABLED_ALTERA_FCS" = "yes" || test "$ENABLED_ALTERA_FCS" = "all" +then + ENABLED_ALTERA_FCS_RNG=yes + ENABLED_ALTERA_FCS_HASH=yes + ENABLED_ALTERA_FCS_AES=yes + ENABLED_ALTERA_FCS_ECC=yes + ENABLED_ALTERA_FCS_HMAC=yes + ENABLED_ALTERA_FCS=yes +fi +if test "$ENABLED_ALTERA_FCS" = "rng" +then + ENABLED_ALTERA_FCS_RNG=yes + ENABLED_ALTERA_FCS=yes +fi +if test "$ENABLED_ALTERA_FCS" = "yes" +then + # libfdt is a transitive dependency of libFCS + LIBS="$LIBS -lFCS -lfdt" +fi + # Support for Linux dev/crypto calls AC_ARG_ENABLE([devcrypto], [AS_HELP_STRING([--enable-devcrypto],[Enable Linux dev crypto calls: all | aes (all aes support) | hash (all hash algos) | cbc (aes-cbc only) (default: disabled)])], @@ -11508,7 +11538,7 @@ then fi fi -if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" +if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" || test "x$ENABLED_ALTERA_FCS" = "xyes" then ENABLED_CRYPTOCB=yes fi @@ -13188,6 +13218,64 @@ AM_CONDITIONAL([BUILD_INTELASM],[test "x$ENABLED_INTELASM" = "xyes"]) AM_CONDITIONAL([BUILD_X86_ASM],[test "x$ENABLED_X86_ASM" = "xyes"]) AM_CONDITIONAL([BUILD_AFALG],[test "x$ENABLED_AFALG" = "xyes"]) AM_CONDITIONAL([BUILD_KCAPI],[test "x$ENABLED_KCAPI" = "xyes"]) +# The FCS backends are selected before the algorithm options are parsed. With +# build-system settings, drop disabled algorithms and emit their defines. With +# user settings, compile the selected sources and let user_settings.h own all +# preprocessor configuration. +if test "x$ENABLED_ALTERA_FCS" = "xyes" +then + PC_LIBS_PRIVATE="$PC_LIBS_PRIVATE -lFCS -lfdt" + if test "x$ENABLED_USERSETTINGS" != "xyes" + then + if test "x$ENABLED_RNG" = "xno"; then + ENABLED_ALTERA_FCS_RNG=no + fi + if test "x$ENABLED_SHA256" = "xno"; then + ENABLED_ALTERA_FCS_HASH=no + fi + if test "x$ENABLED_AES" = "xno"; then + ENABLED_ALTERA_FCS_AES=no + fi + if test "x$ENABLED_ECC" = "xno" || test "x$ENABLED_ASN" = "xno" + then + ENABLED_ALTERA_FCS_ECC=no + fi + if test "x$ENABLED_HMAC" = "xno" || \ + (test "x$ENABLED_SHA256" = "xno" && \ + test "x$ENABLED_SHA384" = "xno" && \ + test "x$ENABLED_SHA512" = "xno") + then + ENABLED_ALTERA_FCS_HMAC=no + fi + + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS" + AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_COPY -DWOLF_CRYPTO_CB_FREE" + AM_CFLAGS="$AM_CFLAGS -DWOLF_CRYPTO_CB_SETKEY" + if test "x$ENABLED_ALTERA_FCS_RNG" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS_RNG" + fi + if test "x$ENABLED_ALTERA_FCS_HASH" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS_HASH" + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SHA512_HASHTYPE" + fi + if test "x$ENABLED_ALTERA_FCS_AES" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS_AES" + fi + if test "x$ENABLED_ALTERA_FCS_ECC" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS_ECC" + fi + if test "x$ENABLED_ALTERA_FCS_HMAC" = "xyes"; then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALTERA_FCS_HMAC" + fi + fi +fi + +AM_CONDITIONAL([BUILD_ALTERA_FCS],[test "x$ENABLED_ALTERA_FCS" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ALTERA_FCS_RNG],[test "x$ENABLED_ALTERA_FCS_RNG" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ALTERA_FCS_HASH],[test "x$ENABLED_ALTERA_FCS_HASH" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ALTERA_FCS_AES],[test "x$ENABLED_ALTERA_FCS_AES" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ALTERA_FCS_ECC],[test "x$ENABLED_ALTERA_FCS_ECC" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_ALTERA_FCS_HMAC],[test "x$ENABLED_ALTERA_FCS_HMAC" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_DEVCRYPTO],[test "x$ENABLED_DEVCRYPTO" = "xyes"]) AM_CONDITIONAL([BUILD_CAMELLIA],[test "x$ENABLED_CAMELLIA" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_MD2],[test "x$ENABLED_MD2" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) @@ -13942,6 +14030,7 @@ echo " * C89: $ENABLED_C89" echo " * Inline Code: $ENABLED_INLINE" echo " * Linux AF_ALG: $ENABLED_AFALG" echo " * Linux KCAPI: $ENABLED_KCAPI" +echo " * Altera Agilex5 FCS: $ENABLED_ALTERA_FCS" echo " * Linux devcrypto: $ENABLED_DEVCRYPTO" echo " * PK callbacks: $ENABLED_PKCALLBACKS" echo " * Crypto callbacks: $ENABLED_CRYPTOCB" diff --git a/tests/altera-fcs-stub/fdt_stub.c b/tests/altera-fcs-stub/fdt_stub.c new file mode 100644 index 0000000000..e296014bb1 --- /dev/null +++ b/tests/altera-fcs-stub/fdt_stub.c @@ -0,0 +1,26 @@ +/* fdt_stub.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Link-only stand-in for libfcs' libfdt dependency. */ +int wolfssl_altera_fdt_stub(void) +{ + return 0; +} diff --git a/tests/altera-fcs-stub/libfcs.h b/tests/altera-fcs-stub/libfcs.h new file mode 100644 index 0000000000..27927098dc --- /dev/null +++ b/tests/altera-fcs-stub/libfcs.h @@ -0,0 +1,121 @@ +/* libfcs.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Minimal libfcs interface for host build tests. No cryptography is mocked. */ + +#ifndef WOLFSSL_TEST_ALTERA_FCS_LIBFCS_H +#define WOLFSSL_TEST_ALTERA_FCS_LIBFCS_H + +#include "libfcs_osal_types.h" + +#define FCS_AES_BLOCK_MODE_CBC 1 +#define FCS_AES_BLOCK_MODE_CTR 2 +#define FCS_AES_IV_SOURCE_EXTERNAL 0 +#define FCS_AES_ENCRYPT 0 +#define FCS_AES_DECRYPT 1 + +#define FCS_ECC_CURVE_NIST_P256 1 +#define FCS_ECC_CURVE_NIST_P384 2 +#define FCS_ECC_CURVE_BRAINPOOL_P256 3 +#define FCS_ECC_CURVE_BRAINPOOL_P384 4 + +struct fcs_digest_get_req { + FCS_OSAL_U32 sha_op_mode; + FCS_OSAL_U32 sha_digest_sz; + FCS_OSAL_CHAR* src; + FCS_OSAL_U32 src_len; + FCS_OSAL_CHAR* digest; + FCS_OSAL_U32* digest_len; +}; + +struct fcs_aes_req { + FCS_OSAL_U32 crypt_mode; + FCS_OSAL_U32 block_mode; + FCS_OSAL_U32 iv_source; + FCS_OSAL_CHAR* iv; + FCS_OSAL_U32 iv_len; + FCS_OSAL_CHAR* tag; + FCS_OSAL_U32 tag_len; + FCS_OSAL_U32 aad_len; + FCS_OSAL_CHAR* aad; + FCS_OSAL_CHAR* input; + FCS_OSAL_U32 ip_len; + FCS_OSAL_CHAR* output; + FCS_OSAL_U32* op_len; +}; + +struct fcs_ecdsa_req { + FCS_OSAL_U32 ecc_curve; + FCS_OSAL_CHAR* src; + FCS_OSAL_U32 src_len; + FCS_OSAL_CHAR* dst; + FCS_OSAL_U32* dst_len; +}; + +struct fcs_ecdh_req { + FCS_OSAL_U32 ecc_curve; + FCS_OSAL_CHAR* pubkey; + FCS_OSAL_U32 pubkey_len; + FCS_OSAL_CHAR* shared_secret; + FCS_OSAL_U32* shared_secret_len; +}; + +struct fcs_mac_verify_req { + FCS_OSAL_U32 op_mode; + FCS_OSAL_U32 dig_sz; + FCS_OSAL_CHAR* src; + FCS_OSAL_U32 src_sz; + FCS_OSAL_CHAR* dst; + FCS_OSAL_U32* dst_sz; + FCS_OSAL_U32 user_data_sz; +}; + +FCS_OSAL_INT libfcs_init(FCS_OSAL_CHAR* loglevel); +FCS_OSAL_INT fcs_open_service_session(FCS_OSAL_UUID* sessionId); +FCS_OSAL_INT fcs_close_service_session(FCS_OSAL_UUID* sessionId); +FCS_OSAL_INT fcs_random_number_ext(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_CHAR* rng, FCS_OSAL_U32 rngSz); +FCS_OSAL_INT fcs_import_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_CHAR* key, FCS_OSAL_INT keySz, FCS_OSAL_CHAR* status, + FCS_OSAL_UINT* statusSz); +FCS_OSAL_INT fcs_create_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_CHAR* key, FCS_OSAL_INT keySz, FCS_OSAL_CHAR* status, + FCS_OSAL_UINT statusSz); +FCS_OSAL_INT fcs_remove_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 keyId); +FCS_OSAL_INT fcs_get_digest(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_digest_get_req* req); +FCS_OSAL_INT fcs_aes_crypt(FCS_OSAL_UUID* sessionId, FCS_OSAL_U32 keyId, + FCS_OSAL_U32 contextId, struct fcs_aes_req* req); +FCS_OSAL_INT fcs_ecdsa_get_pub_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, FCS_OSAL_U32 curve, + FCS_OSAL_CHAR* publicKey, FCS_OSAL_U32* publicKeySz); +FCS_OSAL_INT fcs_ecdsa_hash_sign(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_ecdsa_req* req); +FCS_OSAL_INT fcs_ecdh_request(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 keyId, FCS_OSAL_U32 contextId, struct fcs_ecdh_req* req); +FCS_OSAL_INT fcs_mac_verify(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_mac_verify_req* req); + +#endif /* WOLFSSL_TEST_ALTERA_FCS_LIBFCS_H */ diff --git a/tests/altera-fcs-stub/libfcs_osal_types.h b/tests/altera-fcs-stub/libfcs_osal_types.h new file mode 100644 index 0000000000..7a5ad635bb --- /dev/null +++ b/tests/altera-fcs-stub/libfcs_osal_types.h @@ -0,0 +1,35 @@ +/* libfcs_osal_types.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFSSL_TEST_ALTERA_FCS_OSAL_TYPES_H +#define WOLFSSL_TEST_ALTERA_FCS_OSAL_TYPES_H + +#include + +#define FCS_OSAL_UUID_SIZE 16U + +typedef uint32_t FCS_OSAL_U32; +typedef char FCS_OSAL_CHAR; +typedef int FCS_OSAL_INT; +typedef unsigned int FCS_OSAL_UINT; +typedef char FCS_OSAL_UUID; + +#endif /* WOLFSSL_TEST_ALTERA_FCS_OSAL_TYPES_H */ diff --git a/tests/altera-fcs-stub/libfcs_stub.c b/tests/altera-fcs-stub/libfcs_stub.c new file mode 100644 index 0000000000..931d3cf045 --- /dev/null +++ b/tests/altera-fcs-stub/libfcs_stub.c @@ -0,0 +1,151 @@ +/* libfcs_stub.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include "libfcs.h" + +#define FCS_STUB_UNUSED(x) (void)(x) + +FCS_OSAL_INT libfcs_init(FCS_OSAL_CHAR* loglevel) +{ + FCS_STUB_UNUSED(loglevel); + return 0; +} + +FCS_OSAL_INT fcs_open_service_session(FCS_OSAL_UUID* sessionId) +{ + FCS_STUB_UNUSED(sessionId); + return -ENXIO; +} + +FCS_OSAL_INT fcs_close_service_session(FCS_OSAL_UUID* sessionId) +{ + FCS_STUB_UNUSED(sessionId); + return 0; +} + +FCS_OSAL_INT fcs_random_number_ext(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_CHAR* rng, FCS_OSAL_U32 rngSz) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(rng); + FCS_STUB_UNUSED(rngSz); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_import_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_CHAR* key, FCS_OSAL_INT keySz, FCS_OSAL_CHAR* status, + FCS_OSAL_UINT* statusSz) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(key); + FCS_STUB_UNUSED(keySz); + FCS_STUB_UNUSED(status); + FCS_STUB_UNUSED(statusSz); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_create_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_CHAR* key, FCS_OSAL_INT keySz, FCS_OSAL_CHAR* status, + FCS_OSAL_UINT statusSz) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(key); + FCS_STUB_UNUSED(keySz); + FCS_STUB_UNUSED(status); + FCS_STUB_UNUSED(statusSz); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_remove_service_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 keyId) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(keyId); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_get_digest(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_digest_get_req* req) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(req); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_aes_crypt(FCS_OSAL_UUID* sessionId, FCS_OSAL_U32 keyId, + FCS_OSAL_U32 contextId, struct fcs_aes_req* req) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(req); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_ecdsa_get_pub_key(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, FCS_OSAL_U32 curve, + FCS_OSAL_CHAR* publicKey, FCS_OSAL_U32* publicKeySz) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(curve); + FCS_STUB_UNUSED(publicKey); + FCS_STUB_UNUSED(publicKeySz); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_ecdsa_hash_sign(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_ecdsa_req* req) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(req); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_ecdh_request(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 keyId, FCS_OSAL_U32 contextId, struct fcs_ecdh_req* req) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(req); + return -ENOTSUP; +} + +FCS_OSAL_INT fcs_mac_verify(FCS_OSAL_UUID* sessionId, + FCS_OSAL_U32 contextId, FCS_OSAL_U32 keyId, + struct fcs_mac_verify_req* req) +{ + FCS_STUB_UNUSED(sessionId); + FCS_STUB_UNUSED(contextId); + FCS_STUB_UNUSED(keyId); + FCS_STUB_UNUSED(req); + return -ENOTSUP; +} diff --git a/tests/include.am b/tests/include.am index 559388fe99..8a652691e8 100644 --- a/tests/include.am +++ b/tests/include.am @@ -105,7 +105,11 @@ EXTRA_DIST += tests/unit.h \ tests/freertos-mem-track-repro/FreeRTOS.h \ tests/freertos-mem-track-repro/semphr.h \ tests/freertos-mem-track-repro/task.h \ - tests/freertos-mem-track-repro/run.sh + tests/freertos-mem-track-repro/run.sh \ + tests/altera-fcs-stub/libfcs_osal_types.h \ + tests/altera-fcs-stub/libfcs.h \ + tests/altera-fcs-stub/libfcs_stub.c \ + tests/altera-fcs-stub/fdt_stub.c DISTCLEANFILES+= tests/.libs/unit.test # MC/DC white-box coverage supplements (tests/unit-mcdc/). diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index cb9c7bc65a..4a18c4e04f 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -435,14 +435,24 @@ void wc_CryptoCb_Init(void) } } -void wc_CryptoCb_Cleanup(void) +static int wc_CryptoCb_UnRegisterDeviceEx(int devId); + +int wc_CryptoCb_Cleanup(void) { int i; - for (i = 0; i < MAX_CRYPTO_DEVID_CALLBACKS; i++) { + int ret; + + /* Tear down in reverse registration order. Stop at the first busy device + * so dependencies registered before it remain available for live objects. */ + for (i = MAX_CRYPTO_DEVID_CALLBACKS - 1; i >= 0; i--) { if(gCryptoDev[i].devId != INVALID_DEVID) { - wc_CryptoCb_UnRegisterDevice(gCryptoDev[i].devId); + ret = wc_CryptoCb_UnRegisterDeviceEx(gCryptoDev[i].devId); + if (ret != 0) { + return ret; + } } } + return 0; } int wc_CryptoCb_GetDevIdAtIndex(int startIdx) @@ -517,18 +527,21 @@ int wc_CryptoCb_RegisterDevice(int devId, CryptoDevCallbackFunc cb, void* ctx) return rc; } -void wc_CryptoCb_UnRegisterDevice(int devId) +static int wc_CryptoCb_UnRegisterDeviceEx(int devId) { CryptoCb* dev = NULL; +#ifdef WOLF_CRYPTO_CB_CMD + int ret = 0; +#endif /* Can't unregister the invalid device */ if (devId == INVALID_DEVID) - return; + return 0; /* Find the matching dev */ dev = wc_CryptoCb_GetDevice(devId); if (dev == NULL) - return; + return 0; #ifdef WOLF_CRYPTO_CB_CMD if (dev->cb != NULL) { @@ -539,11 +552,20 @@ void wc_CryptoCb_UnRegisterDevice(int devId) info.cmd.type = WC_CRYPTOCB_CMD_TYPE_UNREGISTER; info.cmd.ctx = NULL; /* Not used */ - /* Ignore errors here */ - dev->cb(devId, &info, dev->ctx); + ret = dev->cb(devId, &info, dev->ctx); + /* A callback with live device-owned objects must remain registered so + * their free callbacks can release sensitive state and device slots. */ + if (ret == WC_NO_ERR_TRACE(BUSY_E)) + return ret; } #endif wc_CryptoCb_ClearDev(dev); + return 0; +} + +void wc_CryptoCb_UnRegisterDevice(int devId) +{ + (void)wc_CryptoCb_UnRegisterDeviceEx(devId); } #ifndef NO_RSA diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index f0e7ca3c71..28718576ff 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -139,6 +139,14 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/devcrypto/devcrypto_hash.c \ wolfcrypt/src/port/devcrypto/wc_devcrypto.c \ wolfcrypt/src/port/devcrypto/README.md \ + wolfcrypt/src/port/altera/altera_fcs_glue.c \ + wolfcrypt/src/port/altera/altera_fcs_rng.c \ + wolfcrypt/src/port/altera/altera_fcs_hash.c \ + wolfcrypt/src/port/altera/altera_fcs_aes.c \ + wolfcrypt/src/port/altera/altera_fcs_ecc.c \ + wolfcrypt/src/port/altera/altera_fcs_hmac.c \ + wolfcrypt/src/port/altera/altera_fcs_cryptocb.c \ + wolfcrypt/src/port/altera/README.md \ wolfcrypt/src/port/mynewt/mynewt_port.c \ wolfcrypt/src/port/Espressif/esp32_aes.c \ wolfcrypt/src/port/Espressif/esp32_sha.c \ @@ -199,6 +207,26 @@ if BUILD_PKCS11 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/wc_pkcs11.c endif +if BUILD_ALTERA_FCS +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_glue.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_cryptocb.c +if BUILD_ALTERA_FCS_RNG +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_rng.c +endif +if BUILD_ALTERA_FCS_HASH +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_hash.c +endif +if BUILD_ALTERA_FCS_AES +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_aes.c +endif +if BUILD_ALTERA_FCS_ECC +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_ecc.c +endif +if BUILD_ALTERA_FCS_HMAC +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/altera/altera_fcs_hmac.c +endif +endif + if BUILD_DEVCRYPTO src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/devcrypto/devcrypto_ecdsa.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/devcrypto/devcrypto_x25519.c diff --git a/wolfcrypt/src/port/altera/README.md b/wolfcrypt/src/port/altera/README.md new file mode 100644 index 0000000000..c540f0b23f --- /dev/null +++ b/wolfcrypt/src/port/altera/README.md @@ -0,0 +1,255 @@ +# wolfSSL support for the Altera Agilex 5 SDM (FCS) + +This port offloads wolfCrypt operations to the Secure Device Manager (SDM) of +Altera Agilex 5 devices through the FPGA Crypto Services stack: the +`altera_fcs_config` kernel driver and the userspace `libfcs` library +(https://github.com/altera-fpga/libfcs). + +The value of the SDM is key isolation, not throughput. Keys created inside the +device never appear in HPS memory, can only be exported in wrapped form, and +are used by handle. For bulk symmetric work the ARMv8 crypto extensions are +faster than the SDM at every size, so the port keeps small operations in +software and offers the device where it adds protection. + +## Building + +``` +./configure --enable-alterafcs \ + CFLAGS="-I/include -I/toolchain/linux_aarch64/include" \ + LDFLAGS="-L/build/lib" +``` + +`--enable-alterafcs` implies `--enable-cryptocb` and links `-lFCS -lfdt`. +`--enable-alterafcs=rng` builds the RNG support only. + +CMake builds use `-DWOLFSSL_ALTERA_FCS=all` (or `rng`). A user-settings +configuration that defines `WOLFSSL_ALTERA_FCS` must still pass that CMake +option so dependency discovery and linkage are selected explicitly. + +The port registers itself during `wolfCrypt_Init()` on the device id +`WOLFSSL_ALTERA_FCS_DEVID` (default `0x4143`). Operations reach the SDM when a +context is created on that id: + +```c +wc_AesInit(&aes, NULL, WOLFSSL_ALTERA_FCS_DEVID); +wc_InitSha256_ex(&sha, NULL, WOLFSSL_ALTERA_FCS_DEVID); +``` + +Contexts on any other device id are untouched. + +Because one device id covers every algorithm, a deployment that wants only the +operations which gain key isolation can set the automatic mask at build time +and leave hashing and AES on the faster software paths: + +``` +CFLAGS='-DWOLFSSL_ALTERA_FCS_AUTO_MASK=(WC_ALTERA_FCS_ALGO_ECC|WC_ALTERA_FCS_ALGO_RNG)' +``` + +This matters for TLS, which propagates its device id to transcript hashes and +AES contexts as well as to keys. The mask cannot be changed after registration +because doing so could strand state or device keys owned by the port. + +## Algorithm support + +| Algorithm | On the SDM | Notes | +|---|---|---| +| RNG | yes | the TRNG seeds wolfSSL's DRBG; generate requests stay in the DRBG unless built with `WOLFSSL_ALTERA_FCS_RAW_RNG` | +| SHA-256 | yes | messages from `WOLFSSL_ALTERA_FCS_HASH_MIN` (default 4096) through 4 MiB; other sizes complete in software | +| AES-128/256 CBC, CTR | yes | length must be a multiple of 32 bytes and at least `WOLFSSL_ALTERA_FCS_AES_MIN` (default 4096); other requests fall back to software | +| AES-192 | software | the SDM key object has no 192 bit code | +| AES-GCM | software | not offloaded by this port | +| ECDSA sign | yes | device resident keys only, see below; verify always runs in software | +| ECDH | yes | device resident exchange keys only, see below | +| HMAC generation | software | the SDM cannot generate a MAC | +| HMAC verification | yes | explicit API only, see below | +| HKDF | software | by design, see below | +| SHA-384/512 | software | not offloaded by this port | + +Software fallback is automatic and produces identical results; callers do not +need to handle it. The exception is device resident keys, where fallback is +impossible and errors are reported as `WC_HW_E` instead. + +## ECC device keys + +An SDM key object commits to a usage at creation, and signing and key exchange +are mutually exclusive, so device keys are created through explicit calls +rather than `wc_ecc_make_key_ex()` (which always makes an ordinary software +key, even on the FCS devId): + +```c +wc_ecc_init_ex(&key, NULL, WOLFSSL_ALTERA_FCS_DEVID); +wc_AlteraFcsEcc_MakeSigningKey(&key, ECC_SECP256R1); /* or P-384 */ +wc_ecc_sign_hash(hash, hashSz, sig, &sigSz, &rng, &key); +``` + +```c +wc_AlteraFcsEcc_MakeExchangeKey(&key, ECC_SECP256R1); +wc_ecc_shared_secret(&key, &peerKey, secret, &secretSz); +``` + +Properties of a device key: + +* the private scalar never exists in HPS memory; `wc_ecc_export_private_only` + fails, and `wc_AlteraFcsEcc_IsDeviceKey()` returns 1 +* export through the device yields a wrapped blob, not plaintext +* a signing key refuses ECDH and an exchange key refuses signing, enforced by + the device +* if the device is unavailable, creation and signing fail with `WC_HW_E`; they + never degrade to a software key silently + +The device holds roughly 27 key slots. `wc_ecc_free()` releases the slot; a +leaked slot lasts until the service session closes. + +## HMAC verification with vault keys + +The SDM verifies MACs under keys the HPS cannot read, but never produces a +MAC, so this ships as its own API rather than behind `wc_Hmac*()`: + +```c +word32 keyId; +int ok; +wc_AlteraFcs_HmacImportKey(key, 256, &keyId); /* or _HmacMakeKey() */ +wc_AlteraFcs_HmacVerify(keyId, WC_HASH_TYPE_SHA256, data, dataSz, + mac, macSz, &ok); +wc_AlteraFcs_HmacRemoveKey(keyId); +``` + +Key sizes 256, 384 and 512 bits; digests SHA-256/384/512. Tags produced by any +standard HMAC implementation verify correctly. + +## Why HKDF stays in software + +`wc_HKDF()` works normally on this platform and always runs in software. This +is deliberate, not a gap: + +1. The SDM HKDF command derives a key directly into a device key slot and + never returns the derived bytes. `wc_HKDF()` exists to hand the caller + bytes, so the two cannot be connected. +2. The device supports only SHA2-384, while TLS 1.3 and most HKDF callers use + SHA-256. + +This matches the usual division of labor for hardware key stores (TPM, +PKCS#11): long lived identity keys live in the device, session keys are +derived and used in software, because the ciphers that consume them run on the +CPU anyway. A future firmware answer from Altera may enable a derive into +vault helper, which would be a separate API, not `wc_HKDF()`. + +## Verifying on hardware + +Prerequisites, all outside wolfSSL: a booted Agilex 5 Linux (GSRD or +equivalent) whose kernel includes the `altera_fcs_config` driver, a device +provisioned with an owner root key hash (`quartus_pgm` virtual or physical +fuses; see the Altera Security User Guide), and `libFCS.so` from +https://github.com/altera-fpga/libfcs on the target. + +1. Confirm the stack is up: `/sys/kernel/fcs_sysfs` exists and libfcs's + `fcs_client` can open a session and read random data. If this fails the + problem is below wolfSSL. +2. Cross compile wolfSSL as shown above, with + `-DWC_USE_DEVID=0x4143` added to CFLAGS for the test build. When the + `testwolfcrypt` binary will run on a different machine than it was built + on, also add `-DUSE_CERT_BUFFERS_2048 -DUSE_CERT_BUFFERS_256 + -DNO_WRITE_TEMP_FILES`: the test otherwise loads its certificates from + the absolute build tree path baked in at configure time + (wolfcrypt/test/test_paths.h) and fails its RSA test with a misleading + "can't open ./certs/client-key.der". +3. Run `./testwolfcrypt` on the target. Expect every test to pass and the + line `ALTERA-FCS test passed!`, which is the direct exercise of the SDM. + A failure of only ALTERA-FCS with everything else passing points at the + device: check provisioning (status 0x85) and session exhaustion (0x84, + cleared only by a power cycle). +4. Optionally run `./benchmark -aes-cbc -aes-ctr` and confirm the HW rows + differ from the SW rows, which proves requests are reaching the device. + +## Continuous integration coverage + +The `Altera Agilex 5 FCS port tests` workflow builds Autotools and CMake +configurations against the interface-only stubs in `tests/altera-fcs-stub`. +The stubs always report unavailable hardware and never implement cryptography. +This lets host CI check feature combinations, linkage, error handling and +software fallback. It does not replace the hardware procedure above, which is +the authoritative test for SDM offload and device resident keys. + +## Measured performance + +Numbers from a DK-A5E013BM16AEA dev kit (quad Cortex-A55, ARMv8 crypto +extensions), wolfSSL 5.9.2, 1 MB blocks. SW rows are the ARMv8 path, HW rows +are the SDM. The AES-192 HW rows equal the SW rows because 192 bit keys fall +back to software, which doubles as proof the fallback path works. + +``` +wolfCrypt Benchmark (block bytes 1048576, min 1.0 sec each) +AES-128-CBC-enc SW 805 MiB took 1.003 seconds, 802.604 MiB/s +AES-128-CBC-dec SW 1480 MiB took 1.002 seconds, 1476.776 MiB/s +AES-256-CBC-enc SW 620 MiB took 1.003 seconds, 618.076 MiB/s +AES-256-CBC-dec SW 1240 MiB took 1.002 seconds, 1237.991 MiB/s +AES-128-CBC-enc HW 100 MiB took 1.045 seconds, 95.691 MiB/s +AES-128-CBC-dec HW 100 MiB took 1.048 seconds, 95.452 MiB/s +AES-192-CBC-enc HW 700 MiB took 1.003 seconds, 698.170 MiB/s +AES-256-CBC-enc HW 95 MiB took 1.044 seconds, 91.039 MiB/s +AES-256-CBC-dec HW 95 MiB took 1.042 seconds, 91.194 MiB/s +AES-128-CTR SW 1245 MiB took 1.002 seconds, 1242.926 MiB/s +AES-256-CTR SW 1135 MiB took 1.002 seconds, 1132.830 MiB/s +AES-128-CTR HW 105 MiB took 1.045 seconds, 100.463 MiB/s +AES-256-CTR HW 100 MiB took 1.021 seconds, 97.969 MiB/s +``` + +Fixed cost operations, measured with device residency asserted: + +| Operation | SDM | Software (ARMv8) | +|---|---|---| +| ECDSA P-256 sign | 6.99 ms | 4.28 ms | +| ECDSA P-384 sign | 9.29 ms | 10.87 ms | +| ECDH P-256 shared secret | 7.98 ms | about 1 ms | +| ECDH P-384 shared secret | 8.86 ms | about 3 ms | +| HMAC verify (any digest) | about 9.6 ms | microseconds | +| SHA-256 digest (4 KiB-4 MiB) | about 5 ms + transfer | 592 MiB/s | +| RNG (raw TRNG, opt in) | 0.192 MiB/s | 43.1 MiB/s | + +Every operation carries a mailbox round trip of several milliseconds, so the +SDM never beats the CPU except at P-384 signing. The reason to use it is that +the key cannot be read by HPS software, not speed. + +## Test output + +`testwolfcrypt` built with `--enable-alterafcs` and `-DWC_USE_DEVID=0x4143` +on provisioned hardware. The generic RANDOM, SHA-256, AES-CBC and AES-CTR +tests run against the SDM through the crypto callback; ALTERA-FCS exercises +the device resident ECC keys, ECDH, HMAC verification and the offload +thresholds explicitly. All other tests prove the software paths are +unaffected. + +``` +SHA-256 test passed! +RANDOM test passed! +HMAC-SHA256 test passed! +AES test passed! +AES192 test passed! +AES256 test passed! +AES-CBC test passed! +AES-CTR test passed! +AES-GCM test passed! +RSA test passed! +DH test passed! +ECC test passed! +ECC buffer test passed! +ALTERA-FCS test passed! +crypto callback test passed! +Test complete +``` + +48 tests pass, 0 failures, exit code 0. + +## Operational notes + +* The SDM grants a single crypto service session per SoC. The port opens it on + first use, shares it across the process and closes it at exit. A session + leaked by a crashed process can only be recovered by a power cycle. +* Crypto services require the device to be provisioned with an owner root key + hash. An unprovisioned device returns SDM status 0x85. Ordinary RNG, hash and + AES operations fall back to software; device-resident ECC and explicit HMAC + operations report a hardware error. +* `testwolfcrypt` includes an `ALTERA-FCS` test that exercises every offloaded + path against the device, including the explicit ECC and HMAC APIs. Building + the test suite with `-DWC_USE_DEVID=0x4143` additionally routes the generic + tests through the SDM. diff --git a/wolfcrypt/src/port/altera/altera_fcs_aes.c b/wolfcrypt/src/port/altera/altera_fcs_aes.c new file mode 100644 index 0000000000..04b33ba836 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_aes.c @@ -0,0 +1,507 @@ +/* altera_fcs_aes.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* AES-CBC and AES-CTR on the Agilex 5 Secure Device Manager. + * + * Unlike the hash path an AES request arrives whole, so this callback can + * decline any operation it cannot serve exactly and let wolfSSL run it in + * software. Nothing is consumed before the decision is made. + * + * Three device properties shape the code, all measured on hardware rather than + * taken from the documentation: + * + * 1. Input length must be a multiple of 32 bytes. AES blocks are 16, so lengths + * such as 16 and 48 are refused by the driver and stay in software. + * 2. The IV buffer is not updated by the device, so the chaining state in + * aes->reg is maintained here. + * 3. Key slots are few, about 27, and a leaked slot lasts until the session + * closes. Keys are therefore imported lazily on first eligible use and + * removed when the context is freed. + * + * Importing a plaintext key gives SDM enforcement of the usage mask, not key + * secrecy: the key was already in HPS memory when wc_AesSetKey stored it. + */ + +#include + +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLFSSL_ALTERA_FCS_AES) + +#include +#include +#include +#include + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#ifndef WOLF_CRYPTO_CB_FREE + #error "WOLFSSL_ALTERA_FCS_AES requires WOLF_CRYPTO_CB_FREE to release keys" +#endif +#ifndef WOLF_CRYPTO_CB_SETKEY + #error "WOLFSSL_ALTERA_FCS_AES requires WOLF_CRYPTO_CB_SETKEY for re-keying" +#endif + +/* The driver refuses any non-GCM length that is not a multiple of this. */ +#define WC_ALTERA_FCS_AES_ALIGN 32 + +#define FCS_KEY_OBJ_MAGIC 0x43736B4FU +#define FCS_KEY_DATA_MAGIC 0x43736B64U +#define FCS_KEY_OBJ_VER 1 +#define FCS_KEY_TYPE_AES 1 +#define FCS_KEY_SIZE_128 1 +#define FCS_KEY_SIZE_256 2 +#define FCS_KEY_USAGE_ENC_DEC 0x3 +#define FCS_KEY_MAC_SZ 48 +#define FCS_KEY_DATA_OFFSET 56 +#define FCS_KEY_STATUS_SZ 64 + +/* Header, one 32 byte aligned key, then the unused MAC field. */ +#define WC_ALTERA_FCS_KEYOBJ_SZ \ + (FCS_KEY_DATA_OFFSET + WC_ALTERA_FCS_AES_ALIGN + FCS_KEY_MAC_SZ) + +/* The imported key is cached against the material it was built from, because + * wc_AesSetKey may re-key a context without clearing devCtx and a stale id + * would silently encrypt under the previous key. */ +typedef struct { + word32 keyId; + int keyLen; + void* heap; + byte key[AES_256_KEY_SIZE]; +} AlteraAesKey; + +static void wc_AlteraFcs_Put32(byte* out, word32 val) +{ + out[0] = (byte)( val & 0xFF); + out[1] = (byte)((val >> 8) & 0xFF); + out[2] = (byte)((val >> 16) & 0xFF); + out[3] = (byte)((val >> 24) & 0xFF); +} + +/* Encode an unprotected AES key object, the binary layout that fcs_prepare + * produces and fcs_import_service_key expects. */ +static int wc_AlteraFcs_KeyObject(byte* out, word32 keyId, const byte* key, + int keyLen, word32* outSz) +{ + word32 objSz; + word32 padded; + word32 sizeCode; + + if (keyLen == AES_128_KEY_SIZE) { + sizeCode = FCS_KEY_SIZE_128; + } + else if (keyLen == AES_256_KEY_SIZE) { + sizeCode = FCS_KEY_SIZE_256; + } + else { + /* The key object has no code for 192 bit keys. */ + return CRYPTOCB_UNAVAILABLE; + } + + padded = (word32)keyLen; + if ((padded % WC_ALTERA_FCS_AES_ALIGN) != 0) { + padded += WC_ALTERA_FCS_AES_ALIGN - + (padded % WC_ALTERA_FCS_AES_ALIGN); + } + + XMEMSET(out, 0, WC_ALTERA_FCS_KEYOBJ_SZ); + wc_AlteraFcs_Put32(out, FCS_KEY_OBJ_MAGIC); + wc_AlteraFcs_Put32(out + 8, keyId); + wc_AlteraFcs_Put32(out + 20, (sizeCode << 16) | + ((word32)FCS_KEY_TYPE_AES << 24)); + wc_AlteraFcs_Put32(out + 24, FCS_KEY_USAGE_ENC_DEC); + wc_AlteraFcs_Put32(out + 48, FCS_KEY_DATA_MAGIC); + XMEMCPY(out + FCS_KEY_DATA_OFFSET, key, (word32)keyLen); + + /* The declared size covers the object but not the trailing MAC field. */ + objSz = FCS_KEY_DATA_OFFSET + padded; + wc_AlteraFcs_Put32(out + 4, ((word32)FCS_KEY_OBJ_VER << 16) | + (objSz & 0xFFFF)); + + *outSz = objSz + FCS_KEY_MAC_SZ; + return 0; +} + +static int wc_AlteraFcs_KeyRemove(word32 keyId) +{ + return wc_AlteraFcs_RemoveServiceKey(keyId); +} + +/* Resolve the device key id for this context, importing on first use. */ +static int wc_AlteraFcs_AesKeyId(Aes* aes, word32* keyId) +{ + AlteraAesKey* keyCtx; + byte keyObj[WC_ALTERA_FCS_KEYOBJ_SZ]; + byte status[FCS_KEY_STATUS_SZ]; + FCS_OSAL_UINT statusLen = (FCS_OSAL_UINT)sizeof(status); + void* session = NULL; + word32 objSz = 0; + word32 newId = 0; + int resourceReserved = 0; + int ret; + + keyCtx = (AlteraAesKey*)aes->devCtx; + if (keyCtx != NULL) { + void* heap = keyCtx->heap; + + if (keyCtx->keyLen == aes->keylen && + ConstantCompare(keyCtx->key, (const byte*)aes->devKey, + aes->keylen) == 0) { + *keyId = keyCtx->keyId; + return 0; + } + if (wc_AlteraFcs_UnregisterPending()) { + return CRYPTOCB_UNAVAILABLE; + } + /* Keep one resource reserved across replacement. Otherwise a pending + * unregister can remove the callback in the instant between dropping + * the old key and installing the new one. */ + wc_AlteraFcs_ResourceAdd(); + resourceReserved = 1; + ret = wc_AlteraFcs_KeyRemove(keyCtx->keyId); + if (ret != 0) { + wc_AlteraFcs_ResourceRemove(); + return ret; + } + wc_AlteraFcs_ResourceRemove(); + ForceZero(keyCtx, sizeof(*keyCtx)); + XFREE(keyCtx, heap, DYNAMIC_TYPE_TMP_BUFFER); + aes->devCtx = NULL; + } + else if (wc_AlteraFcs_UnregisterPending()) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = wc_AlteraFcs_KeyIdNew(&newId); + if (ret == 0) { + ret = wc_AlteraFcs_KeyObject(keyObj, newId, (const byte*)aes->devKey, + aes->keylen, &objSz); + } + if (ret == 0) { + ret = wc_AlteraFcs_SessionAcquire(&session); + } + if (ret == 0) { + XMEMSET(status, 0, sizeof(status)); + ret = fcs_import_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_CHAR*)keyObj, + (FCS_OSAL_INT)objSz, + (FCS_OSAL_CHAR*)status, &statusLen); + wc_AlteraFcs_SessionRelease(); + if (ret != 0) { + wc_AlteraFcs_DiscardServiceKey(newId); + /* Slots are finite, so exhaustion has to mean software rather + * than a hard failure. */ + WOLFSSL_MSG("Altera FCS AES key import failed"); + ret = CRYPTOCB_UNAVAILABLE; + } + } + + if (ret == 0) { + keyCtx = (AlteraAesKey*)XMALLOC(sizeof(AlteraAesKey), aes->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (keyCtx == NULL) { + wc_AlteraFcs_DiscardServiceKey(newId); + ret = MEMORY_E; + } + else { + XMEMSET(keyCtx, 0, sizeof(*keyCtx)); + keyCtx->keyId = newId; + keyCtx->keyLen = aes->keylen; + keyCtx->heap = aes->heap; + XMEMCPY(keyCtx->key, aes->devKey, (size_t)aes->keylen); + aes->devCtx = keyCtx; + if (!resourceReserved) { + wc_AlteraFcs_ResourceAdd(); + } + *keyId = newId; + } + } + + if (aes->devCtx == NULL && resourceReserved) { + wc_AlteraFcs_ResourceRemove(); + } + + ForceZero(keyObj, sizeof(keyObj)); + return ret; +} + +/* A request is only offloaded when the device can serve it exactly. */ +static int wc_AlteraFcs_AesEligible(const Aes* aes, word32 sz) +{ + if (aes->keylen != AES_128_KEY_SIZE && + aes->keylen != AES_256_KEY_SIZE) { + return 0; + } + if (sz < WC_ALTERA_FCS_AES_ALIGN || sz > WC_ALTERA_FCS_MAX_XFER) { + return 0; + } + if ((sz % WC_ALTERA_FCS_AES_ALIGN) != 0) { + return 0; + } + if (sz < WOLFSSL_ALTERA_FCS_AES_MIN) { + return 0; + } + return 1; +} + +static int wc_AlteraFcs_AesOp(Aes* aes, byte* out, const byte* in, word32 sz, + FCS_OSAL_U32 blockMode, FCS_OSAL_U32 cryptMode) +{ + struct fcs_aes_req req; + byte iv[WC_AES_BLOCK_SIZE]; + byte* tmp = NULL; + void* session = NULL; + FCS_OSAL_U32 outLen = (FCS_OSAL_U32)sz; + word32 keyId = 0; + int ret; + + /* Keep caller input and output untouched until the device confirms a full + * result. This makes in-place requests safe to retry in software after a + * transport, session, or device failure. */ + tmp = (byte*)XMALLOC(sz, aes->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = wc_AlteraFcs_AesKeyId(aes, &keyId); + if (ret != 0) { + ret = CRYPTOCB_UNAVAILABLE; + goto exit; + } + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + ret = CRYPTOCB_UNAVAILABLE; + goto exit; + } + + XMEMCPY(iv, aes->reg, WC_AES_BLOCK_SIZE); + + XMEMSET(&req, 0, sizeof(req)); + req.crypt_mode = cryptMode; + req.block_mode = blockMode; + req.iv_source = FCS_AES_IV_SOURCE_EXTERNAL; + req.iv = (FCS_OSAL_CHAR*)iv; + req.iv_len = WC_AES_BLOCK_SIZE; + req.input = (FCS_OSAL_CHAR*)in; + req.ip_len = (FCS_OSAL_U32)sz; + req.output = (FCS_OSAL_CHAR*)tmp; + req.op_len = &outLen; + + ret = fcs_aes_crypt((FCS_OSAL_UUID*)session, (FCS_OSAL_U32)keyId, + WOLFSSL_ALTERA_FCS_CTX_ID, &req); + if (ret != 0) { + (void)wc_AlteraFcs_MapError(ret); + ret = CRYPTOCB_UNAVAILABLE; + } + else if (outLen != sz) { + /* A stale session has been seen to report success with a short + * result, so the length is checked rather than trusted. */ + WOLFSSL_MSG("Altera FCS AES length mismatch"); + ret = CRYPTOCB_UNAVAILABLE; + } + else { + XMEMCPY(out, tmp, sz); + wc_AlteraFcs_TestHwMark(WC_ALTERA_FCS_TEST_HW_AES); + } + + wc_AlteraFcs_SessionRelease(); +exit: + ForceZero(tmp, sz); + XFREE(tmp, aes->heap, DYNAMIC_TYPE_TMP_BUFFER); + ForceZero(iv, sizeof(iv)); + return ret; +} + +#ifdef HAVE_AES_CBC +static int wc_AlteraFcs_AesCbc(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aescbc.aes; + byte* out = info->cipher.aescbc.out; + const byte* in = info->cipher.aescbc.in; + word32 sz = info->cipher.aescbc.sz; + byte lastIn[WC_AES_BLOCK_SIZE]; + int ret; + + if (aes == NULL || out == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + if (!wc_AlteraFcs_AesEligible(aes, sz)) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Decrypt chains on the last input block, which an in place request would + * overwrite before it could be read back. */ + XMEMSET(lastIn, 0, sizeof(lastIn)); + if (info->cipher.enc == 0) { + XMEMCPY(lastIn, in + sz - WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE); + } + + ret = wc_AlteraFcs_AesOp(aes, out, in, sz, FCS_AES_BLOCK_MODE_CBC, + info->cipher.enc ? FCS_AES_ENCRYPT + : FCS_AES_DECRYPT); + if (ret == 0) { + if (info->cipher.enc) { + XMEMCPY(aes->reg, out + sz - WC_AES_BLOCK_SIZE, + WC_AES_BLOCK_SIZE); + } + else { + XMEMCPY(aes->reg, lastIn, WC_AES_BLOCK_SIZE); + } + } + + ForceZero(lastIn, sizeof(lastIn)); + return ret; +} +#endif /* HAVE_AES_CBC */ + +#ifdef WOLFSSL_AES_COUNTER +/* Advance the big endian counter block by a block count in one bounded pass, + * equivalent to that many single increments including wraparound. */ +static void wc_AlteraFcs_AddCounter(byte* ctr, word32 blocks) +{ + word32 carry = blocks; + int i; + + for (i = WC_AES_BLOCK_SIZE - 1; i >= 0 && carry != 0; i--) { + carry += ctr[i]; + ctr[i] = (byte)(carry & 0xFF); + carry >>= 8; + } +} + +static int wc_AlteraFcs_AesCtr(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesctr.aes; + byte* out = info->cipher.aesctr.out; + const byte* in = info->cipher.aesctr.in; + word32 sz = info->cipher.aesctr.sz; + int ret; + + if (aes == NULL || out == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + /* Keystream left over from an earlier call cannot be expressed in a whole + * operation request, so such calls stay in software. */ + if (aes->left != 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (!wc_AlteraFcs_AesEligible(aes, sz)) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Counter mode is its own inverse, so the device always encrypts. */ + ret = wc_AlteraFcs_AesOp(aes, out, in, sz, FCS_AES_BLOCK_MODE_CTR, + FCS_AES_ENCRYPT); + if (ret == 0) { + wc_AlteraFcs_AddCounter((byte*)aes->reg, sz / WC_AES_BLOCK_SIZE); + } + + return ret; +} +#endif /* WOLFSSL_AES_COUNTER */ + +static void wc_AlteraFcs_AesKeyFree(Aes* aes) +{ + AlteraAesKey* keyCtx; + + if (aes == NULL) { + return; + } + + keyCtx = (AlteraAesKey*)aes->devCtx; + if (keyCtx != NULL) { + void* heap = keyCtx->heap; + + if (wc_AlteraFcs_KeyRemove(keyCtx->keyId) != 0) { + (void)wc_AlteraFcs_OrphanKey(keyCtx->keyId); + } + wc_AlteraFcs_ResourceRemove(); + ForceZero(keyCtx, sizeof(*keyCtx)); + XFREE(keyCtx, heap, DYNAMIC_TYPE_TMP_BUFFER); + aes->devCtx = NULL; + } +} + +/* Release the device key when a context is freed. */ +static int wc_AlteraFcs_AesFreeCtx(wc_CryptoInfo* info) +{ + if (info->free.algo != WC_ALGO_TYPE_CIPHER || + info->free.type != WC_CIPHER_AES) { + return CRYPTOCB_UNAVAILABLE; + } + + wc_AlteraFcs_AesKeyFree((Aes*)info->free.obj); + + /* Decline so wolfSSL still performs its own teardown. */ + return CRYPTOCB_UNAVAILABLE; +} + +/* Retire an imported key before the generic AES setup overwrites devKey. */ +static int wc_AlteraFcs_AesSetKey(wc_CryptoInfo* info) +{ + if (info->setkey.type != WC_SETKEY_AES) { + return CRYPTOCB_UNAVAILABLE; + } + + wc_AlteraFcs_AesKeyFree((Aes*)info->setkey.obj); + return CRYPTOCB_UNAVAILABLE; +} + +int wc_AlteraFcs_Aes(wc_CryptoInfo* info) +{ + int ret = CRYPTOCB_UNAVAILABLE; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + if (info->algo_type == WC_ALGO_TYPE_FREE) { + return wc_AlteraFcs_AesFreeCtx(info); + } + if (info->algo_type == WC_ALGO_TYPE_SETKEY) { + return wc_AlteraFcs_AesSetKey(info); + } + + switch (info->cipher.type) { + #ifdef HAVE_AES_CBC + case WC_CIPHER_AES_CBC: + ret = wc_AlteraFcs_AesCbc(info); + break; + #endif + #ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + ret = wc_AlteraFcs_AesCtr(info); + break; + #endif + default: + break; + } + + return ret; +} + +#endif /* WOLFSSL_ALTERA_FCS && WOLFSSL_ALTERA_FCS_AES */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_cryptocb.c b/wolfcrypt/src/port/altera/altera_fcs_cryptocb.c new file mode 100644 index 0000000000..aeb6a9e712 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_cryptocb.c @@ -0,0 +1,431 @@ +/* altera_fcs_cryptocb.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include + +#ifdef WOLFSSL_ALTERA_FCS + +#include +#include +#include + +#ifndef WOLF_CRYPTO_CB + #error "WOLFSSL_ALTERA_FCS requires WOLF_CRYPTO_CB" +#endif + +/* Set at registration; operations outside the mask are declined so wolfSSL + * uses its software path. */ +static word32 g_algoMask = WC_ALTERA_FCS_ALGO_ALL; +static int g_devId = INVALID_DEVID; +static int g_unregisterPending = 0; +static int g_callbackCount = 0; +static int g_resourceCount = 0; +static wolfSSL_Mutex g_stateLock; +static int g_stateLockInit = 0; +#ifndef SINGLE_THREADED +static int g_stateAtForkLocked = 0; +#endif + +static int wc_AlteraFcs_StateInit(void) +{ + if (g_stateLockInit == 0) { + if (wc_InitMutex(&g_stateLock) != 0) { + return BAD_MUTEX_E; + } + g_stateLockInit = 1; + } + return 0; +} + +static int wc_AlteraFcs_CallbackBegin(word32* algoMask) +{ + int ret = CRYPTOCB_UNAVAILABLE; + + if (g_stateLockInit == 0 || wc_LockMutex(&g_stateLock) != 0) { + return ret; + } + if (g_devId == WOLFSSL_ALTERA_FCS_DEVID && + (!g_unregisterPending || g_resourceCount > 0)) { + g_callbackCount++; + *algoMask = g_algoMask; + ret = 0; + } + wc_UnLockMutex(&g_stateLock); + return ret; +} + +static void wc_AlteraFcs_CallbackEnd(void) +{ + int finish = 0; + + if (g_stateLockInit == 0 || wc_LockMutex(&g_stateLock) != 0) { + return; + } + if (g_callbackCount > 0) { + g_callbackCount--; + } + if (g_callbackCount == 0 && g_resourceCount == 0 && + g_unregisterPending && g_devId != INVALID_DEVID) { + finish = 1; + } + wc_UnLockMutex(&g_stateLock); + if (finish) { + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + } +} + +int wc_AlteraFcs_UnregisterPending(void) +{ + int pending = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + pending = g_unregisterPending; + wc_UnLockMutex(&g_stateLock); + } + return pending; +} + +int wc_AlteraFcs_RegisterActive(void) +{ + int active = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + active = (g_devId == WOLFSSL_ALTERA_FCS_DEVID && + !g_unregisterPending); + wc_UnLockMutex(&g_stateLock); + } + return active; +} + +/* Dispatcher. Returning CRYPTOCB_UNAVAILABLE lets wolfCrypt fall back to + * software, which is the required behaviour whenever the SDM is busy: the one + * chip-wide session means a hard failure here would stall unrelated callers. */ +static int wc_AlteraFcsCryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = CRYPTOCB_UNAVAILABLE; + word32 algoMask; + + (void)devId; + (void)ctx; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLF_CRYPTO_CB_CMD + if (info->algo_type == WC_ALGO_TYPE_NONE) { + if (info->cmd.type == WC_CRYPTOCB_CMD_TYPE_UNREGISTER) { + ret = wc_AlteraFcsCryptoCb_UnRegisterDeviceEx(devId); + return (ret == 0) ? 0 : BUSY_E; + } + return CRYPTOCB_UNAVAILABLE; + } +#endif + + if (wc_AlteraFcs_CallbackBegin(&algoMask) != 0) { + return ret; + } + + switch (info->algo_type) { + case WC_ALGO_TYPE_SEED: + case WC_ALGO_TYPE_RNG: + #ifdef WOLFSSL_ALTERA_FCS_RNG + if (algoMask & WC_ALTERA_FCS_ALGO_RNG) { + ret = wc_AlteraFcs_Rng(info); + } + #endif + break; + case WC_ALGO_TYPE_HASH: + #ifdef WOLFSSL_ALTERA_FCS_HASH + if (algoMask & WC_ALTERA_FCS_ALGO_HASH) { + ret = wc_AlteraFcs_Hash(info); + } + #endif + break; + case WC_ALGO_TYPE_COPY: + #ifdef WOLFSSL_ALTERA_FCS_HASH + if (info->copy.algo == WC_ALGO_TYPE_HASH) { + ret = wc_AlteraFcs_Hash(info); + } + #endif + break; + case WC_ALGO_TYPE_FREE: + #ifdef WOLFSSL_ALTERA_FCS_HASH + if (info->free.algo == WC_ALGO_TYPE_HASH) { + ret = wc_AlteraFcs_Hash(info); + } + #endif + #ifdef WOLFSSL_ALTERA_FCS_AES + if (info->free.algo == WC_ALGO_TYPE_CIPHER) { + ret = wc_AlteraFcs_Aes(info); + } + #endif + #ifdef WC_ALTERA_FCS_HAVE_ECC + if (info->free.algo == WC_ALGO_TYPE_PK) { + ret = wc_AlteraFcs_Ecc(info); + } + #endif + break; + #ifdef WOLFSSL_ALTERA_FCS_AES + case WC_ALGO_TYPE_SETKEY: + ret = wc_AlteraFcs_Aes(info); + break; + #endif + case WC_ALGO_TYPE_CIPHER: + #ifdef WOLFSSL_ALTERA_FCS_AES + if (algoMask & WC_ALTERA_FCS_ALGO_AES) { + ret = wc_AlteraFcs_Aes(info); + } + #endif + break; + case WC_ALGO_TYPE_PK: + #ifdef WC_ALTERA_FCS_HAVE_ECC + if (algoMask & WC_ALTERA_FCS_ALGO_ECC) { + ret = wc_AlteraFcs_Ecc(info); + } + #endif + break; + case WC_ALGO_TYPE_KDF: /* step 6: hkdf */ + case WC_ALGO_TYPE_HMAC: /* step 7: mac_verify */ + default: + break; + } + + wc_AlteraFcs_CallbackEnd(); + return ret; +} + +int wc_AlteraFcsCryptoCb_RegisterDeviceMask(int devId, word32 algoMask) +{ + int ret; + int wasPending; + + if (devId != WOLFSSL_ALTERA_FCS_DEVID || + (algoMask & ~WC_ALTERA_FCS_ALGO_ALL) != 0) { + return BAD_FUNC_ARG; + } + + ret = wc_AlteraFcs_StateInit(); + if (ret != 0) { + return ret; + } + if (wc_LockMutex(&g_stateLock) != 0) { + return BAD_MUTEX_E; + } + wasPending = g_unregisterPending; + if (g_devId != INVALID_DEVID && !wasPending && + algoMask != g_algoMask) { + wc_UnLockMutex(&g_stateLock); + return ALREADY_E; + } + if (wasPending && (g_resourceCount != 0 || g_callbackCount != 0)) { + wc_UnLockMutex(&g_stateLock); + return BUSY_E; + } + wc_UnLockMutex(&g_stateLock); + + ret = wc_AlteraFcs_Init(); + if (ret != 0) { + return ret; + } + + ret = wc_CryptoCb_RegisterDevice(devId, wc_AlteraFcsCryptoDevCb, NULL); + if (wc_LockMutex(&g_stateLock) != 0) { + return BAD_MUTEX_E; + } + if (ret == 0 || (ret == ALREADY_E && + (g_devId == devId || wasPending))) { + g_algoMask = algoMask; + g_devId = devId; + g_unregisterPending = 0; + ret = 0; + } + wc_UnLockMutex(&g_stateLock); + if (ret != 0) { + (void)wc_AlteraFcs_Cleanup(); + } + return ret; +} + +int wc_AlteraFcsCryptoCb_RegisterDevice(int devId) +{ + return wc_AlteraFcsCryptoCb_RegisterDeviceMask(devId, + WC_ALTERA_FCS_ALGO_ALL); +} + +int wc_AlteraFcsCryptoCb_UnRegisterDeviceEx(int devId) +{ + int ret; + + if (g_stateLockInit == 0) { + return 0; + } + if (wc_LockMutex(&g_stateLock) != 0) { + return BAD_MUTEX_E; + } + if (devId != g_devId) { + wc_UnLockMutex(&g_stateLock); + return 0; + } + g_unregisterPending = 1; + if (g_resourceCount != 0 || g_callbackCount != 0) { + WOLFSSL_MSG("Altera FCS unregister deferred by active resource"); + wc_UnLockMutex(&g_stateLock); + return BUSY_E; + } + + /* Pending prevents new admission while cleanup closes the session. The + * generic registry clears its slot only after this callback reports + * success, so a caller that already looked up the callback is also forced + * through the same admission check. */ + ret = wc_AlteraFcs_Cleanup(); + if (ret != 0) { + wc_UnLockMutex(&g_stateLock); + return ret; + } + g_devId = INVALID_DEVID; + g_algoMask = WC_ALTERA_FCS_ALGO_ALL; + wc_UnLockMutex(&g_stateLock); + return 0; +} + +void wc_AlteraFcsCryptoCb_UnRegisterDevice(int devId) +{ + wc_CryptoCb_UnRegisterDevice(devId); +} + +void wc_AlteraFcsCryptoCb_UnRegisterPending(void) +{ + int finish = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + finish = (g_devId != INVALID_DEVID && g_unregisterPending && + g_resourceCount == 0 && g_callbackCount == 0); + wc_UnLockMutex(&g_stateLock); + } + if (finish) { + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + } +} + +int wc_AlteraFcs_AlgoEnabled(word32 algoMask) +{ + int enabled = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + enabled = (g_devId == WOLFSSL_ALTERA_FCS_DEVID && + !g_unregisterPending && + (g_algoMask & algoMask) == algoMask); + wc_UnLockMutex(&g_stateLock); + } + return enabled; +} + +void wc_AlteraFcs_ResourceAdd(void) +{ + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + g_resourceCount++; + wc_UnLockMutex(&g_stateLock); + } +} + +int wc_AlteraFcs_ResourceAcquire(void) +{ + int ret = BUSY_E; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + if (g_devId == WOLFSSL_ALTERA_FCS_DEVID && + !g_unregisterPending) { + g_resourceCount++; + ret = 0; + } + wc_UnLockMutex(&g_stateLock); + } + return ret; +} + +void wc_AlteraFcs_ResourceRemove(void) +{ + int finish = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + if (g_resourceCount > 0) { + g_resourceCount--; + } + if (g_resourceCount == 0 && g_callbackCount == 0 && + g_unregisterPending && g_devId != INVALID_DEVID) { + finish = 1; + } + wc_UnLockMutex(&g_stateLock); + } + if (finish) { + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + } +} + +int wc_AlteraFcs_ResourceActive(void) +{ + int active = 0; + + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + active = (g_resourceCount != 0); + wc_UnLockMutex(&g_stateLock); + } + return active; +} + +void wc_AlteraFcs_StateAtForkPrepare(void) +{ +#ifndef SINGLE_THREADED + if (g_stateLockInit != 0 && wc_LockMutex(&g_stateLock) == 0) { + g_stateAtForkLocked = 1; + } +#endif +} + +void wc_AlteraFcs_StateAtForkParent(void) +{ +#ifndef SINGLE_THREADED + if (g_stateAtForkLocked) { + g_stateAtForkLocked = 0; + wc_UnLockMutex(&g_stateLock); + } +#endif +} + +void wc_AlteraFcs_StateAtForkChild(void) +{ +#ifndef SINGLE_THREADED + if (g_stateAtForkLocked) { + g_stateAtForkLocked = 0; + wc_UnLockMutex(&g_stateLock); + } +#endif +} + +void wc_AlteraFcs_StateForkChildReset(void) +{ + g_callbackCount = 0; + g_resourceCount = 0; + g_unregisterPending = 0; +} + +#endif /* WOLFSSL_ALTERA_FCS */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_ecc.c b/wolfcrypt/src/port/altera/altera_fcs_ecc.c new file mode 100644 index 0000000000..d9da1dd4cb --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_ecc.c @@ -0,0 +1,729 @@ +/* altera_fcs_ecc.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* ECDSA on the Agilex 5 Secure Device Manager. + * + * This is the step where key isolation becomes real. A key made with + * wc_AlteraFcsEcc_MakeSigningKey() or _MakeExchangeKey() is generated inside + * the device; only its public point is returned, and the private scalar never + * appears in HPS memory. Export is possible but yields a wrapped blob, not + * plaintext. wc_ecc_make_key_ex() deliberately stays in software even on the + * FCS devId, because an SDM key object must commit to signing or exchange + * usage at creation and that API cannot express which is wanted. + * + * Only signing is offloaded. Verification uses nothing but the public key, so + * it gains no security from the device and is much faster in software, and the + * device verify command does not work on this firmware: it reports the same + * failure for a valid signature as for a corrupt one. + * + * Sign has an asymmetry with the rest of the port that matters. Everywhere else + * CRYPTOCB_UNAVAILABLE means fall back to software, which is safe. Here the + * private key exists only inside the device, so a failure must be reported as a + * failure. Declining would hand wolfSSL a key with no private scalar. + */ + +#include +#include + +#if defined(WOLFSSL_ALTERA_FCS) && defined(WC_ALTERA_FCS_HAVE_ECC) + +#include +#include +#include + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#ifndef WOLF_CRYPTO_CB_FREE + #error "WOLFSSL_ALTERA_FCS_ECC requires WOLF_CRYPTO_CB_FREE to release keys" +#endif +#define FCS_KEY_OBJ_MAGIC 0x43736B4FU +#define FCS_KEY_DATA_MAGIC 0x43736B64U +#define FCS_KEY_OBJ_VER 1 +#define FCS_KEY_TYPE_ECC_NIST 3 +#define FCS_KEY_TYPE_ECC_BP 4 +#define FCS_KEY_MAC_SZ 48 +#define FCS_KEY_DATA_OFFSET 56 +#define FCS_KEY_ALIGN 32 +#define FCS_KEY_STATUS_SZ 64 + +/* Sign and Verify are exclusive with Exchange for an ECC key object, so an + * ECDSA key cannot also serve ECDH. */ +#define FCS_KEY_USAGE_SIGN_VERIFY 0xC +#define FCS_KEY_USAGE_EXCHANGE 0x10 + +/* Largest object: 56 byte header, 64 byte padded P-384 scalar, 48 byte MAC. */ +#define WC_ALTERA_FCS_ECCOBJ_SZ (FCS_KEY_DATA_OFFSET + 64 + FCS_KEY_MAC_SZ) + +#define WC_ALTERA_FCS_ECC_MAX_SZ 48 + +/* devCtx is a generic void* that any backend may use, so the context carries a + * tag and the devId it was created for. Without them IsDeviceKey() would report + * residency for a foreign backend's context, and a key made on an unregistered + * devId would strand its device slot. */ +#define WC_ALTERA_FCS_ECC_TAG 0x41454343U + +typedef struct { + word32 tag; + word32 keyId; + word32 usage; + int curveId; + int keySz; + int devId; + void* heap; +} AlteraEccKey; + +/* The Agilex 5 kernel driver copies the entire ECDSA response into the address + * supplied for dst_len instead of copying sizeof(FCS_OSAL_U32). Supply a + * response-sized, aligned sink. The first word is the input capacity, but its + * returned contents cannot be interpreted as a length with that driver. */ +typedef union { + FCS_OSAL_U32 capacity; + byte response[2 * WC_ALTERA_FCS_ECC_MAX_SZ]; +} AlteraEccLengthSink; + +static AlteraEccKey* wc_AlteraFcs_EccCtx(const ecc_key* key) +{ + AlteraEccKey* keyCtx; + + if (key == NULL || key->devId != WOLFSSL_ALTERA_FCS_DEVID || + key->devCtx == NULL) { + return NULL; + } + keyCtx = (AlteraEccKey*)key->devCtx; + if (keyCtx->tag != WC_ALTERA_FCS_ECC_TAG || + keyCtx->devId != key->devId) { + return NULL; + } + return keyCtx; +} + +#if (defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)) || \ + defined(HAVE_ECC_DHE) +static void wc_AlteraFcs_Put32(byte* out, word32 val) +{ + out[0] = (byte)( val & 0xFF); + out[1] = (byte)((val >> 8) & 0xFF); + out[2] = (byte)((val >> 16) & 0xFF); + out[3] = (byte)((val >> 24) & 0xFF); +} + +/* Map a wolfSSL curve to the device curve code and scalar size. */ +static int wc_AlteraFcs_EccCurve(int curveId, FCS_OSAL_U32* fcsCurve, + int* keySz, word32* keyType) +{ + int ret = 0; + + switch (curveId) { + case ECC_SECP256R1: + *fcsCurve = FCS_ECC_CURVE_NIST_P256; + *keySz = 32; + *keyType = FCS_KEY_TYPE_ECC_NIST; + break; + case ECC_SECP384R1: + *fcsCurve = FCS_ECC_CURVE_NIST_P384; + *keySz = 48; + *keyType = FCS_KEY_TYPE_ECC_NIST; + break; + #ifdef HAVE_ECC_BRAINPOOL + case ECC_BRAINPOOLP256R1: + *fcsCurve = FCS_ECC_CURVE_BRAINPOOL_P256; + *keySz = 32; + *keyType = FCS_KEY_TYPE_ECC_BP; + break; + case ECC_BRAINPOOLP384R1: + *fcsCurve = FCS_ECC_CURVE_BRAINPOOL_P384; + *keySz = 48; + *keyType = FCS_KEY_TYPE_ECC_BP; + break; + #endif + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + return ret; +} + +/* Build the key object. The data region must be declared even when the device + * generates the key: an object without it is refused with status 0x80. */ +static int wc_AlteraFcs_EccKeyObject(byte* out, word32 keyId, word32 keyType, + int keySz, word32 usage, word32* outSz) +{ + word32 padded; + word32 objSz; + word32 sizeCode; + + if (keySz == 32) { + sizeCode = 2; + } + else if (keySz == 48) { + sizeCode = 3; + } + else { + return CRYPTOCB_UNAVAILABLE; + } + + padded = (word32)keySz; + if ((padded % FCS_KEY_ALIGN) != 0) { + padded += FCS_KEY_ALIGN - (padded % FCS_KEY_ALIGN); + } + + XMEMSET(out, 0, WC_ALTERA_FCS_ECCOBJ_SZ); + wc_AlteraFcs_Put32(out, FCS_KEY_OBJ_MAGIC); + wc_AlteraFcs_Put32(out + 8, keyId); + wc_AlteraFcs_Put32(out + 20, (sizeCode << 16) | (keyType << 24)); + wc_AlteraFcs_Put32(out + 24, usage); + wc_AlteraFcs_Put32(out + 48, FCS_KEY_DATA_MAGIC); + + objSz = FCS_KEY_DATA_OFFSET + padded; + wc_AlteraFcs_Put32(out + 4, ((word32)FCS_KEY_OBJ_VER << 16) | + (objSz & 0xFFFF)); + + *outSz = objSz + FCS_KEY_MAC_SZ; + return 0; +} +#endif + +static int wc_AlteraFcs_EccKeyRemove(word32 keyId) +{ + return wc_AlteraFcs_RemoveServiceKey(keyId); +} + +static void wc_AlteraFcs_EccCtxFree(ecc_key* key) +{ + AlteraEccKey* keyCtx; + void* heap; + + if (key == NULL) { + return; + } + keyCtx = wc_AlteraFcs_EccCtx(key); + if (keyCtx == NULL) { + return; + } + + heap = keyCtx->heap; + if (wc_AlteraFcs_EccKeyRemove(keyCtx->keyId) != 0) { + (void)wc_AlteraFcs_OrphanKey(keyCtx->keyId); + } + wc_AlteraFcs_ResourceRemove(); + ForceZero(keyCtx, sizeof(*keyCtx)); + XFREE(keyCtx, heap, DYNAMIC_TYPE_TMP_BUFFER); + key->devCtx = NULL; +} + +/* Generate the key inside the device and keep only the public point here. */ +#if (defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)) || \ + defined(HAVE_ECC_DHE) +static int wc_AlteraFcs_EccCreate(ecc_key* key, int curveId, int sizeHint, + word32 usage) +{ + AlteraEccKey* keyCtx = NULL; + void* session = NULL; + byte obj[WC_ALTERA_FCS_ECCOBJ_SZ]; + byte status[FCS_KEY_STATUS_SZ]; + byte pub[2 * WC_ALTERA_FCS_ECC_MAX_SZ]; + FCS_OSAL_U32 pubLen = (FCS_OSAL_U32)sizeof(pub); + FCS_OSAL_U32 fcsCurve = 0; + word32 keyType = 0; + word32 objSz = 0; + word32 newId = 0; + int keySz = 0; + int devId; + int ret; + + if (key == NULL) { + return BAD_FUNC_ARG; + } + /* The key must route back to this callback, otherwise sign and ECDH would + * bypass the port and the allocated device slot could never be used or + * released. */ + if (key->devId != WOLFSSL_ALTERA_FCS_DEVID) { + WOLFSSL_MSG("Altera FCS ECC key needs the FCS devId"); + return BAD_FUNC_ARG; + } + if (!wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_ECC)) { + WOLFSSL_MSG("Altera FCS ECC callback is not active"); + return WC_HW_E; + } + /* Overwriting devCtx would strand the previous slot until the session + * closes, and there are only about 27 of them. */ + if (key->devCtx != NULL) { + WOLFSSL_MSG("Altera FCS ECC key already has a device key"); + return BAD_FUNC_ARG; + } + + if (curveId == ECC_CURVE_DEF) { + if (sizeHint == 32) { + curveId = ECC_SECP256R1; + } + else if (sizeHint == 48) { + curveId = ECC_SECP384R1; + } + } + + ret = wc_AlteraFcs_EccCurve(curveId, &fcsCurve, &keySz, &keyType); + if (ret != 0) { + return ret; + } + if (sizeHint != 0 && sizeHint != keySz) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Past this point a failure must be reported as a failure. Returning + * CRYPTOCB_UNAVAILABLE would make wolfSSL quietly generate a software key + * instead, so a caller that asked for a device resident key would receive + * one whose private scalar sits in HPS memory, with nothing to indicate the + * isolation it asked for was not delivered. Only an unsupported curve, + * handled above, may decline. */ + ret = wc_AlteraFcs_ResourceAcquire(); + if (ret != 0) { + WOLFSSL_MSG("Altera FCS ECC keygen blocked by unregister"); + return WC_HW_E; + } + ret = wc_AlteraFcs_KeyIdNew(&newId); + if (ret == 0) { + ret = wc_AlteraFcs_EccKeyObject(obj, newId, keyType, keySz, usage, + &objSz); + } + if (ret == 0) { + ret = wc_AlteraFcs_SessionAcquire(&session); + } + if (ret != 0) { + WOLFSSL_MSG("Altera FCS ECC keygen cannot reach the device"); + wc_AlteraFcs_ResourceRemove(); + return WC_HW_E; + } + + XMEMSET(status, 0, sizeof(status)); + ret = fcs_create_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_CHAR*)obj, (FCS_OSAL_INT)objSz, + (FCS_OSAL_CHAR*)status, + (FCS_OSAL_UINT)sizeof(status)); + if (ret != 0) { + WOLFSSL_MSG("Altera FCS ECC key creation failed"); + wc_AlteraFcs_SessionRelease(); + wc_AlteraFcs_DiscardServiceKey(newId); + wc_AlteraFcs_ResourceRemove(); + return WC_HW_E; + } + + ret = fcs_ecdsa_get_pub_key((FCS_OSAL_UUID*)session, + WOLFSSL_ALTERA_FCS_CTX_ID, + (FCS_OSAL_U32)newId, fcsCurve, + (FCS_OSAL_CHAR*)pub, &pubLen); + wc_AlteraFcs_SessionRelease(); + + if (ret != 0 || pubLen != (FCS_OSAL_U32)(2 * keySz)) { + WOLFSSL_MSG("Altera FCS ECC public key retrieval failed"); + if (wc_AlteraFcs_EccKeyRemove(newId) != 0) { + (void)wc_AlteraFcs_OrphanKey(newId); + } + wc_AlteraFcs_ResourceRemove(); + return WC_HW_E; + } + + /* The device returns the point as raw X||Y with no leading 0x04. Importing + * it resets the device fields, so they are restored afterwards. */ + devId = key->devId; + ret = wc_ecc_import_unsigned(key, pub, pub + keySz, NULL, curveId); + key->devId = devId; + if (ret != 0) { + if (wc_AlteraFcs_EccKeyRemove(newId) != 0) { + (void)wc_AlteraFcs_OrphanKey(newId); + } + wc_AlteraFcs_ResourceRemove(); + return ret; + } + + keyCtx = (AlteraEccKey*)XMALLOC(sizeof(AlteraEccKey), key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (keyCtx == NULL) { + if (wc_AlteraFcs_EccKeyRemove(newId) != 0) { + (void)wc_AlteraFcs_OrphanKey(newId); + } + wc_AlteraFcs_ResourceRemove(); + return MEMORY_E; + } + + keyCtx->tag = WC_ALTERA_FCS_ECC_TAG; + keyCtx->devId = key->devId; + keyCtx->keyId = newId; + keyCtx->usage = usage; + keyCtx->curveId = curveId; + keyCtx->keySz = keySz; + keyCtx->heap = key->heap; + key->devCtx = keyCtx; + + return 0; +} +#endif + +#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) +int wc_AlteraFcsEcc_MakeSigningKey(ecc_key* key, int curveId) +{ + if (key == NULL) { + return BAD_FUNC_ARG; + } + return wc_AlteraFcs_EccCreate(key, curveId, 0, + FCS_KEY_USAGE_SIGN_VERIFY); +} +#endif + +#ifdef HAVE_ECC_DHE +int wc_AlteraFcsEcc_MakeExchangeKey(ecc_key* key, int curveId) +{ + if (key == NULL) { + return BAD_FUNC_ARG; + } + return wc_AlteraFcs_EccCreate(key, curveId, 0, FCS_KEY_USAGE_EXCHANGE); +} +#endif + +#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) +static int wc_AlteraFcs_EccCustomNonce(const ecc_key* key) +{ +#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + if (key->deterministic) { + return 1; + } +#endif +#if defined(WOLFSSL_ECDSA_SET_K) || defined(WOLFSSL_ECDSA_SET_K_ONE_LOOP) + #ifndef WOLFSSL_NO_MALLOC + if (key->sign_k != NULL) { + return 1; + } + #else + if (key->sign_k_set) { + return 1; + } + #endif +#endif + (void)key; + return 0; +} + +static int wc_AlteraFcs_EccSign(wc_CryptoInfo* info) +{ + AlteraEccKey* keyCtx; + struct fcs_ecdsa_req req; + ecc_key* key = info->pk.eccsign.key; + void* session = NULL; + byte sig[2 * WC_ALTERA_FCS_ECC_MAX_SZ]; + byte digest[WC_ALTERA_FCS_ECC_MAX_SZ]; + AlteraEccLengthSink sigLen; + FCS_OSAL_U32 fcsCurve = 0; + word32 keyType = 0; + word32 outCapacity; + int keySz = 0; + int verified = 0; + int ret; + + if (key == NULL || info->pk.eccsign.out == NULL || + info->pk.eccsign.outlen == NULL) { + return BAD_FUNC_ARG; + } + outCapacity = *info->pk.eccsign.outlen; + + /* A key without our device state is an ordinary software key. */ + keyCtx = wc_AlteraFcs_EccCtx(key); + if (keyCtx == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + /* The SDM chooses its own nonce. A device key cannot fall back to software + * to honor deterministic ECDSA or a caller-supplied k value. */ + if (wc_AlteraFcs_EccCustomNonce(key)) { + WOLFSSL_MSG("Altera FCS ECDSA custom nonce unsupported"); + return WC_HW_E; + } + + ret = wc_AlteraFcs_EccCurve(keyCtx->curveId, &fcsCurve, &keySz, &keyType); + if (ret != 0) { + return ret; + } + + if (info->pk.eccsign.in == NULL || info->pk.eccsign.inlen == 0) { + return BAD_FUNC_ARG; + } + +#ifndef WC_ALLOW_ECC_ZERO_HASH + /* Returning from the callback skips the software body of + * wc_ecc_sign_hash_ex(), so its all-zero digest rejection is repeated + * here rather than silently signing what the API would refuse. */ + { + word32 z; + byte acc = 0; + + for (z = 0; z < info->pk.eccsign.inlen; z++) { + acc |= info->pk.eccsign.in[z]; + } + if (acc == 0) { + return ECC_BAD_ARG_E; + } + } +#endif + + /* The device signs the value handed to it and does not hash again, so the + * digest is normalised to the curve size the way wc_ecc_sign_hash does: + * longer digests keep their leftmost bytes, shorter ones are left padded. */ + XMEMSET(digest, 0, sizeof(digest)); + if (info->pk.eccsign.inlen >= (word32)keySz) { + XMEMCPY(digest, info->pk.eccsign.in, (word32)keySz); + } + else { + XMEMCPY(digest + ((word32)keySz - info->pk.eccsign.inlen), + info->pk.eccsign.in, info->pk.eccsign.inlen); + } + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + /* The private key exists only in the device, so this cannot be + * softened into a fallback. */ + ForceZero(digest, sizeof(digest)); + return WC_HW_E; + } + + XMEMSET(&req, 0, sizeof(req)); + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(&sigLen, 0, sizeof(sigLen)); + sigLen.capacity = (FCS_OSAL_U32)sizeof(sig); + req.ecc_curve = fcsCurve; + req.src = (FCS_OSAL_CHAR*)digest; + req.src_len = (FCS_OSAL_U32)keySz; + req.dst = (FCS_OSAL_CHAR*)sig; + req.dst_len = &sigLen.capacity; + + ret = fcs_ecdsa_hash_sign((FCS_OSAL_UUID*)session, + WOLFSSL_ALTERA_FCS_CTX_ID, + (FCS_OSAL_U32)keyCtx->keyId, &req); + wc_AlteraFcs_SessionRelease(); + + ForceZero(digest, sizeof(digest)); + + if (ret != 0) { + WOLFSSL_MSG("Altera FCS ECDSA sign failed"); + ForceZero(&sigLen, sizeof(sigLen)); + return WC_HW_E; + } + /* The device returns raw r||s; wolfSSL callers expect DER. */ + ret = wc_ecc_rs_raw_to_sig(sig, (word32)keySz, sig + keySz, + (word32)keySz, info->pk.eccsign.out, + info->pk.eccsign.outlen); + if (ret == 0) { + /* The Agilex 5 Linux driver does not return a trustworthy response + * length. Verify against the device key's public point before any + * possibly short response is exposed to the caller. ECDSA verify is + * deliberately declined by this callback and completes in software. */ + ret = wc_ecc_verify_hash(info->pk.eccsign.out, + *info->pk.eccsign.outlen, + info->pk.eccsign.in, + info->pk.eccsign.inlen, &verified, key); + if (ret == 0 && verified != 1) { + ret = WC_HW_E; + } + } + if (ret != 0) { + ForceZero(info->pk.eccsign.out, outCapacity); + *info->pk.eccsign.outlen = 0; + } + ForceZero(sig, sizeof(sig)); + ForceZero(&sigLen, sizeof(sigLen)); + return ret; +} +#endif /* HAVE_ECC_SIGN && HAVE_ECC_VERIFY */ + +#ifdef HAVE_ECC_DHE +/* Shared secret from a device resident private key and a peer public point. */ +static int wc_AlteraFcs_Ecdh(wc_CryptoInfo* info) +{ + AlteraEccKey* keyCtx; + struct fcs_ecdh_req req; + ecc_key* priv = info->pk.ecdh.private_key; + ecc_key* pub = info->pk.ecdh.public_key; + void* session = NULL; + byte peer[2 * WC_ALTERA_FCS_ECC_MAX_SZ]; + byte secret[2 * WC_ALTERA_FCS_ECC_MAX_SZ]; + word32 xLen, yLen; + FCS_OSAL_U32 secretLen; + FCS_OSAL_U32 fcsCurve = 0; + word32 keyType = 0; + int keySz = 0; + int ret; + + if (priv == NULL || pub == NULL || info->pk.ecdh.out == NULL || + info->pk.ecdh.outlen == NULL) { + return BAD_FUNC_ARG; + } + + /* Not a device key, so software owns it. */ + keyCtx = wc_AlteraFcs_EccCtx(priv); + if (keyCtx == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + /* A signing key cannot perform key exchange: the two usages are exclusive + * in the key object, and the private scalar is not available here to fall + * back with, so this has to be reported rather than declined. */ + if (keyCtx->usage != FCS_KEY_USAGE_EXCHANGE) { + WOLFSSL_MSG("Altera FCS ECDH needs a key made for exchange usage"); + return WC_HW_E; + } + + ret = wc_AlteraFcs_EccCurve(keyCtx->curveId, &fcsCurve, &keySz, &keyType); + if (ret != 0) { + return WC_HW_E; + } + if (priv->dp == NULL || pub->dp == NULL || + priv->dp->id != keyCtx->curveId || + pub->dp->id != keyCtx->curveId) { + return ECC_BAD_ARG_E; + } + ret = wc_ecc_check_key(pub); + if (ret != 0) { + return ret; + } + + /* The device wants the peer point as raw X||Y with no leading 0x04. */ + xLen = (word32)keySz; + yLen = (word32)keySz; + ret = wc_ecc_export_public_raw(pub, peer, &xLen, peer + keySz, &yLen); + if (ret != 0) { + return ret; + } + if (xLen != (word32)keySz || yLen != (word32)keySz) { + return WC_HW_E; + } + + if (*info->pk.ecdh.outlen < (word32)keySz) { + *info->pk.ecdh.outlen = (word32)keySz; + return BUFFER_E; + } + + /* The device returns the whole shared point, so it writes 2 * keySz even + * though the secret is the X coordinate alone. Writing straight into the + * caller buffer would overrun it, so the result lands here first. */ + XMEMSET(secret, 0, sizeof(secret)); + secretLen = (FCS_OSAL_U32)sizeof(secret); + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + return WC_HW_E; + } + + XMEMSET(&req, 0, sizeof(req)); + req.ecc_curve = fcsCurve; + req.pubkey = (FCS_OSAL_CHAR*)peer; + req.pubkey_len = (FCS_OSAL_U32)(2 * keySz); + req.shared_secret = (FCS_OSAL_CHAR*)secret; + req.shared_secret_len = &secretLen; + + ret = fcs_ecdh_request((FCS_OSAL_UUID*)session, + (FCS_OSAL_U32)keyCtx->keyId, + WOLFSSL_ALTERA_FCS_CTX_ID, &req); + wc_AlteraFcs_SessionRelease(); + + if (ret != 0) { + WOLFSSL_MSG("Altera FCS ECDH failed"); + ForceZero(peer, sizeof(peer)); + ForceZero(secret, sizeof(secret)); + return WC_HW_E; + } + if (secretLen != (FCS_OSAL_U32)(2 * keySz)) { + WOLFSSL_MSG("Altera FCS ECDH secret length unexpected"); + ForceZero(peer, sizeof(peer)); + ForceZero(secret, sizeof(secret)); + return WC_HW_E; + } + + XMEMCPY(info->pk.ecdh.out, secret, (word32)keySz); + *info->pk.ecdh.outlen = (word32)keySz; + ForceZero(peer, sizeof(peer)); + ForceZero(secret, sizeof(secret)); + return 0; +} +#endif /* HAVE_ECC_DHE */ + +/* Lets an application confirm that a key really is device resident rather than + * trusting that it asked for the right devId. */ +int wc_AlteraFcsEcc_IsDeviceKey(const ecc_key* key) +{ + return (wc_AlteraFcs_EccCtx(key) != NULL); +} + +int wc_AlteraFcs_Ecc(wc_CryptoInfo* info) +{ + int ret = CRYPTOCB_UNAVAILABLE; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + if (info->algo_type == WC_ALGO_TYPE_FREE) { + /* The type must be checked, not just the algo: wc_FreeRsaKey also frees + * under WC_ALGO_TYPE_PK and passes an RsaKey, so treating every PK free + * as an ecc_key reads devCtx from the wrong offset and frees garbage. */ + if (info->free.algo == WC_ALGO_TYPE_PK && + info->free.type == WC_PK_TYPE_EC_KEYGEN && + info->free.obj != NULL) { + wc_AlteraFcs_EccCtxFree((ecc_key*)info->free.obj); + } + /* Decline so wolfSSL still runs its own teardown. */ + return CRYPTOCB_UNAVAILABLE; + } + + switch (info->pk.type) { + #ifdef HAVE_ECC_DHE + /* Key generation is deliberately NOT offloaded here. An SDM key object + * must commit to Sign/Verify or Exchange usage at creation and the two + * are mutually exclusive, but wc_ecc_make_key_ex cannot express which + * is wanted, and callers routinely use one key for both ECDSA and ECDH. + * Silently creating a signing key would break the later exchange. Key + * slots are also scarce, so device residency is opt in through + * wc_AlteraFcsEcc_MakeSigningKey and _MakeExchangeKey. */ + case WC_PK_TYPE_ECDH: + ret = wc_AlteraFcs_Ecdh(info); + break; + #endif + #if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) + case WC_PK_TYPE_ECDSA_SIGN: + ret = wc_AlteraFcs_EccSign(info); + break; + #endif + /* Verification needs only the public key, so it is left to software, + * which is faster and avoids the single device session. The device + * verify command is also unusable on this firmware: it reports the + * same error for a valid signature as for a corrupt one. */ + default: + break; + } + + return ret; +} + +#endif /* WOLFSSL_ALTERA_FCS && WC_ALTERA_FCS_HAVE_ECC */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_glue.c b/wolfcrypt/src/port/altera/altera_fcs_glue.c new file mode 100644 index 0000000000..105944a313 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_glue.c @@ -0,0 +1,588 @@ +/* altera_fcs_glue.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Session management for the Agilex 5 Secure Device Manager, via libfcs. + * + * The SDM grants only a handful of crypto sessions and a leaked one cannot be + * reclaimed without a board power cycle, so this file keeps exactly one session + * for the life of the process, reference counted, and closes it from an atexit + * handler as well as the normal path. + * + * A file lock guards the session across processes: the kernel driver serialises + * on a single global context (hal_get_fcs_cmd_ctx) with an uninterruptible + * mutex, so a second process entering at the wrong moment blocks unkillably. + */ + +#include + +#ifdef WOLFSSL_ALTERA_FCS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifndef SINGLE_THREADED + #include +#endif + +#ifndef WOLFSSL_ALTERA_FCS_LOCKFILE + #define WOLFSSL_ALTERA_FCS_LOCKFILE "/sys/kernel/fcs_sysfs" +#endif +#ifndef WOLFSSL_ALTERA_FCS_LOCK_OPEN_FLAGS + #define WOLFSSL_ALTERA_FCS_LOCK_OPEN_FLAGS \ + (O_RDONLY | O_DIRECTORY | O_CLOEXEC) +#endif + +/* FCS_OSAL_UUID is a plain char, so a session id is a 16 byte buffer the + * library writes into, never a scalar. */ +static FCS_OSAL_UUID g_sessionId[FCS_OSAL_UUID_SIZE]; +/* The device grants exactly one session, so it is opened on first use and held + * until cleanup. Releasing the last reference must NOT close it: a re-open + * would be refused with 0x84 and every later request would fall back to + * software. */ +static int g_sessionOpen = 0; +static int g_refCount = 0; +static int g_cleanupPending = 0; +static int g_libReady = 0; +static int g_atexitDone = 0; +static int g_lockFd = -1; +static pid_t g_ownerPid = 0; +static pid_t g_processPid = 0; +static int g_forkChild = 0; +static wolfSSL_Mutex g_lock; +static int g_lockInit = 0; +#ifndef SINGLE_THREADED +static int g_atforkDone = 0; +static int g_atforkLocked = 0; +#endif +static wolfSSL_Atomic_Int g_testHwOperations = 0; +#define WC_ALTERA_FCS_ORPHAN_MAX 32 +static word32 g_orphanKeys[WC_ALTERA_FCS_ORPHAN_MAX]; +static int g_orphanRetrying = 0; +/* Status 0x85 means the device is not provisioned for crypto services, which + * cannot change while the process runs. Remembering it keeps every later + * fallback from paying for a mailbox round trip first. */ +static int g_sessionDenied = 0; +/* Key ids only have to be unique within the session, and the session is + * process wide, so a counter is enough. Zero is rejected by the device. */ +static word32 g_nextKeyId = WOLFSSL_ALTERA_FCS_KEY_ID_BASE; + +static void wc_AlteraFcs_AtExit(void) +{ + (void)wc_AlteraFcs_Cleanup(); +} + +static void wc_AlteraFcs_ResetForkChild(void) +{ + if (g_lockFd >= 0) { + (void)close(g_lockFd); + g_lockFd = -1; + } + XMEMSET(g_sessionId, 0, sizeof(g_sessionId)); + g_sessionOpen = 0; + g_refCount = 0; + g_cleanupPending = 0; + g_ownerPid = 0; + g_processPid = 0; + g_forkChild = 1; + g_libReady = 0; + g_sessionDenied = 0; + XMEMSET(g_orphanKeys, 0, sizeof(g_orphanKeys)); + g_orphanRetrying = 0; + wc_AlteraFcs_StateForkChildReset(); +} + +#ifndef SINGLE_THREADED +static void wc_AlteraFcs_AtForkPrepare(void) +{ + wc_AlteraFcs_StateAtForkPrepare(); + if (g_lockInit && wc_LockMutex(&g_lock) == 0) { + g_atforkLocked = 1; + } +} + +static void wc_AlteraFcs_AtForkParent(void) +{ + if (g_atforkLocked) { + g_atforkLocked = 0; + wc_UnLockMutex(&g_lock); + } + wc_AlteraFcs_StateAtForkParent(); +} + +static void wc_AlteraFcs_AtForkChild(void) +{ + wc_AlteraFcs_ResetForkChild(); + if (g_atforkLocked) { + g_atforkLocked = 0; + wc_UnLockMutex(&g_lock); + } + wc_AlteraFcs_StateAtForkChild(); +} +#endif + +/* Translate a libfcs return into a wolfCrypt error. libfcs returns negative + * errno values for transport problems and positive SDM status codes for device + * refusals; both must stay distinguishable so callers can decide whether to + * fall back to software. */ +int wc_AlteraFcs_MapError(int fcsRet) +{ + int ret; + + if (fcsRet == 0) { + return 0; + } + + switch (fcsRet) { + case 0x84: /* no more sessions may be opened */ + case 0x1FF: /* device busy */ + case -EAGAIN: + case -EBUSY: + ret = CRYPTOCB_UNAVAILABLE; + break; + case 0x85: /* not allowed under current security settings */ + case -EACCES: + case -EPERM: + ret = CRYPTOCB_UNAVAILABLE; + break; + case 0x0F: /* function not supported on this device */ + case -ENOTSUP: + case -ENXIO: + ret = CRYPTOCB_UNAVAILABLE; + break; + case 0x04: /* invalid command parameters */ + case 0x06: + case 0x81: /* cryptographic parameter error */ + case 0x86: /* invalid context id */ + case -EINVAL: + ret = BAD_FUNC_ARG; + break; + case 0x83: /* invalid session id */ + ret = WC_HW_E; + break; + case -ETIMEDOUT: + ret = WC_HW_E; + break; + default: + ret = WC_HW_E; + break; + } + + WOLFSSL_MSG_EX("Altera FCS error %d mapped to %d", fcsRet, ret); + return ret; +} + +int wc_AlteraFcs_Init(void) +{ + int ret = 0; + + if (g_processPid != 0 && g_processPid != getpid()) { + wc_AlteraFcs_ResetForkChild(); + } + /* Session-scoped ECC and HMAC handles cannot be reconstructed safely in + * an inherited process. The child may use software, or exec a fresh image + * to initialize an independent FCS process state. */ + if (g_forkChild) { + return CRYPTOCB_UNAVAILABLE; + } + + if (g_lockInit == 0) { + if (wc_InitMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + g_lockInit = 1; + } + +#ifndef SINGLE_THREADED + if (g_atforkDone == 0) { + if (pthread_atfork(wc_AlteraFcs_AtForkPrepare, + wc_AlteraFcs_AtForkParent, + wc_AlteraFcs_AtForkChild) != 0) { + return WC_INIT_E; + } + g_atforkDone = 1; + } +#endif + + if (wc_LockMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + + if (g_libReady == 0) { + ret = libfcs_init((FCS_OSAL_CHAR*)"error"); + if (ret != 0) { + WOLFSSL_MSG("libfcs_init failed"); + ret = wc_AlteraFcs_MapError(ret); + } + else { + g_libReady = 1; + g_processPid = getpid(); + } + } + + if (ret == 0 && g_atexitDone == 0) { + if (atexit(wc_AlteraFcs_AtExit) == 0) { + g_atexitDone = 1; + } + } + + wc_UnLockMutex(&g_lock); + return ret; +} + +/* Acquire the process wide session, opening it on first use. On success + * *sessionId points at the 16 byte session id and the caller must pair this + * with wc_AlteraFcs_SessionRelease(). */ +int wc_AlteraFcs_SessionAcquire(void** sessionId) +{ + int ret = 0; + + if (sessionId == NULL) { + return BAD_FUNC_ARG; + } + + /* pthread_atfork protects threaded builds from inheriting a locked mutex. + * The PID check also covers SINGLE_THREADED builds. */ + if (g_ownerPid != 0 && g_ownerPid != getpid()) { + wc_AlteraFcs_ResetForkChild(); + } + + ret = wc_AlteraFcs_Init(); + if (ret != 0) { + return ret; + } + + if (wc_LockMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + + if (g_sessionDenied) { + wc_UnLockMutex(&g_lock); + return CRYPTOCB_UNAVAILABLE; + } + + if (g_sessionOpen == 0) { + if (g_lockFd < 0) { + g_lockFd = open(WOLFSSL_ALTERA_FCS_LOCKFILE, + WOLFSSL_ALTERA_FCS_LOCK_OPEN_FLAGS); + } + /* Fail closed, and never block: the driver serialises on one + * uninterruptible global mutex, so entering it unlocked can wedge + * unrelated processes, while waiting here would stall this one behind + * whoever holds the lock. Declining lets wolfSSL use software. */ + if (g_lockFd < 0 || flock(g_lockFd, LOCK_EX | LOCK_NB) != 0) { + WOLFSSL_MSG("Altera FCS cross-process lock unavailable"); + if (g_lockFd >= 0) { + (void)close(g_lockFd); + g_lockFd = -1; + } + wc_UnLockMutex(&g_lock); + return CRYPTOCB_UNAVAILABLE; + } + + ret = fcs_open_service_session(g_sessionId); + if (ret != 0) { + WOLFSSL_MSG("fcs_open_service_session failed"); + if (ret == 0x85) { + g_sessionDenied = 1; + } + if (g_lockFd >= 0) { + (void)flock(g_lockFd, LOCK_UN); + (void)close(g_lockFd); + g_lockFd = -1; + } + XMEMSET(g_sessionId, 0, sizeof(g_sessionId)); + ret = wc_AlteraFcs_MapError(ret); + } + else { + g_sessionOpen = 1; + g_ownerPid = getpid(); + } + } + + if (ret == 0) { + g_refCount++; + *sessionId = (void*)g_sessionId; + } + + wc_UnLockMutex(&g_lock); + return ret; +} + +int wc_AlteraFcs_KeyIdNew(word32* keyId) +{ + int ret; + + if (keyId == NULL) { + return BAD_FUNC_ARG; + } + ret = wc_AlteraFcs_OrphanKey(0); + if (ret != 0) { + return ret; + } + if (g_lockInit == 0) { + return BAD_MUTEX_E; + } + if (wc_LockMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + + *keyId = g_nextKeyId++; + if (g_nextKeyId == 0) { + g_nextKeyId = WOLFSSL_ALTERA_FCS_KEY_ID_BASE; + } + + wc_UnLockMutex(&g_lock); + return 0; +} + +int wc_AlteraFcs_RemoveServiceKey(word32 keyId) +{ + void* session = NULL; + int ret; + + if (keyId == 0) { + return BAD_FUNC_ARG; + } + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + return ret; + } + ret = fcs_remove_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_U32)keyId); + wc_AlteraFcs_SessionRelease(); + if (ret != 0) { + WOLFSSL_MSG("Altera FCS service key removal failed"); + ret = wc_AlteraFcs_MapError(ret); + } + return ret; +} + +/* keyId == 0 retries every orphan before a new key is allocated. A teardown + * failure records the otherwise unreachable handle here; confirmed session + * close reclaims all such slots even if individual deletion keeps failing. */ +int wc_AlteraFcs_OrphanKey(word32 keyId) +{ + word32 retry[WC_ALTERA_FCS_ORPHAN_MAX]; + int count = 0; + int firstErr = 0; + int i; + + if (g_lockInit == 0 || wc_LockMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + if (keyId != 0) { + for (i = 0; i < WC_ALTERA_FCS_ORPHAN_MAX; i++) { + if (g_orphanKeys[i] == keyId) { + wc_UnLockMutex(&g_lock); + return 0; + } + if (g_orphanKeys[i] == 0) { + g_orphanKeys[i] = keyId; + wc_UnLockMutex(&g_lock); + return 0; + } + } + wc_UnLockMutex(&g_lock); + return MEMORY_E; + } + if (g_orphanRetrying) { + wc_UnLockMutex(&g_lock); + return 0; + } + g_orphanRetrying = 1; + for (i = 0; i < WC_ALTERA_FCS_ORPHAN_MAX; i++) { + if (g_orphanKeys[i] != 0) { + retry[count++] = g_orphanKeys[i]; + } + } + wc_UnLockMutex(&g_lock); + + for (i = 0; i < count; i++) { + int ret = wc_AlteraFcs_RemoveServiceKey(retry[i]); + + if (ret == 0) { + int j; + if (wc_LockMutex(&g_lock) == 0) { + for (j = 0; j < WC_ALTERA_FCS_ORPHAN_MAX; j++) { + if (g_orphanKeys[j] == retry[i]) { + g_orphanKeys[j] = 0; + break; + } + } + wc_UnLockMutex(&g_lock); + } + } + else if (firstErr == 0) { + firstErr = ret; + } + } + + if (wc_LockMutex(&g_lock) == 0) { + g_orphanRetrying = 0; + wc_UnLockMutex(&g_lock); + } + return firstErr; +} + +void wc_AlteraFcs_DiscardServiceKey(word32 keyId) +{ + if (wc_AlteraFcs_RemoveServiceKey(keyId) != 0) { + (void)wc_AlteraFcs_OrphanKey(keyId); + } +} + +int wc_AlteraFcs_HardwareAvailable(void) +{ + void* session = NULL; + int ret; + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret == 0) { + wc_AlteraFcs_SessionRelease(); + } + return (ret == 0); +} + +void wc_AlteraFcs_TestHwReset(void) +{ + int operations = wolfSSL_Atomic_Int_FetchAdd(&g_testHwOperations, 0); + + if (operations != 0) + (void)wolfSSL_Atomic_Int_FetchSub(&g_testHwOperations, operations); +} + +word32 wc_AlteraFcs_TestHwGet(void) +{ + return (word32)wolfSSL_Atomic_Int_FetchAdd(&g_testHwOperations, 0); +} + +void wc_AlteraFcs_TestHwMark(word32 operation) +{ + int old; + + do { + old = wolfSSL_Atomic_Int_FetchAdd(&g_testHwOperations, 0); + if ((old & (int)operation) != 0) + return; + } while (!wolfSSL_Atomic_Int_CompareExchange(&g_testHwOperations, &old, + old | (int)operation)); +} + +static int wc_AlteraFcs_CloseLocked(void) +{ + int ret = 0; + + if (g_sessionOpen != 0) { + if (fcs_close_service_session(g_sessionId) != 0) { + WOLFSSL_MSG("fcs_close_service_session failed; retry required"); + g_cleanupPending = 1; + return WC_HW_E; + } + g_sessionOpen = 0; + } + + XMEMSET(g_sessionId, 0, sizeof(g_sessionId)); + XMEMSET(g_orphanKeys, 0, sizeof(g_orphanKeys)); + g_orphanRetrying = 0; + g_cleanupPending = 0; + g_ownerPid = 0; + if (g_lockFd >= 0) { + (void)flock(g_lockFd, LOCK_UN); + (void)close(g_lockFd); + g_lockFd = -1; + } + g_libReady = 0; + g_sessionDenied = 0; + return ret; +} + +void wc_AlteraFcs_SessionRelease(void) +{ + int cleanupDone = 0; + + if (g_lockInit == 0) { + return; + } + if (wc_LockMutex(&g_lock) != 0) { + return; + } + + if (g_refCount > 0) { + g_refCount--; + } + if (g_refCount == 0 && g_cleanupPending) { + cleanupDone = (wc_AlteraFcs_CloseLocked() == 0); + } + + wc_UnLockMutex(&g_lock); + if (cleanupDone) { + wc_AlteraFcsCryptoCb_UnRegisterPending(); + } +} + +int wc_AlteraFcs_Cleanup(void) +{ + int ret; + + if (g_lockInit == 0) { + return 0; + } + if (wc_LockMutex(&g_lock) != 0) { + return BAD_MUTEX_E; + } + + /* A child owns only duplicated descriptors. It must never close the + * parent's device session or explicitly release the shared flock. */ + if (g_ownerPid != 0 && g_ownerPid != getpid()) { + if (g_lockFd >= 0) { + (void)close(g_lockFd); + g_lockFd = -1; + } + g_sessionOpen = 0; + g_refCount = 0; + g_cleanupPending = 0; + g_ownerPid = 0; + g_libReady = 0; + XMEMSET(g_sessionId, 0, sizeof(g_sessionId)); + XMEMSET(g_orphanKeys, 0, sizeof(g_orphanKeys)); + wc_UnLockMutex(&g_lock); + return 0; + } + + if (g_refCount != 0) { + g_cleanupPending = 1; + wc_UnLockMutex(&g_lock); + return BUSY_E; + } + + ret = wc_AlteraFcs_CloseLocked(); + wc_UnLockMutex(&g_lock); + return ret; +} + +#endif /* WOLFSSL_ALTERA_FCS */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_hash.c b/wolfcrypt/src/port/altera/altera_fcs_hash.c new file mode 100644 index 0000000000..902f3d90e3 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_hash.c @@ -0,0 +1,831 @@ +/* altera_fcs_hash.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SHA-2 digests on the Agilex 5 Secure Device Manager. + * + * Why the message is buffered rather than streamed: the device grants a single + * crypto session, and wolfSSL may have several hash contexts open at once + * (a TLS transcript hash alongside the DRBG, for example). Interleaving those + * over one hardware stream is not possible, so each context accumulates its own + * message in the wolfSSL hash devCtx and the digest is produced by one atomic + * fcs_get_digest() at final(). + * + * Because that state lives in devCtx, the copy and free callbacks are + * mandatory: wolfSSL's plain struct copy would leave two contexts sharing one + * buffer, which corrupts the DRBG and shows up as DRBG_CONT_FIPS_E. + * + * A minimum size threshold keeps small hashes in software. An SDM digest costs + * roughly 5 ms regardless of length, so routing short hashes here would slow + * the library dramatically for no benefit. + */ + +#include + +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLFSSL_ALTERA_FCS_HASH) + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#if !defined(WOLF_CRYPTO_CB_COPY) || !defined(WOLF_CRYPTO_CB_FREE) + #error "WOLFSSL_ALTERA_FCS_HASH requires WOLF_CRYPTO_CB_COPY and _FREE" +#endif +#if defined(WOLFSSL_SHA512) && !defined(WOLFSSL_SHA512_HASHTYPE) + /* Without this the truncated SHA-512/224 and /256 variants are + * indistinguishable from full SHA-512 and would be given a 64 byte digest. */ + #error "WOLFSSL_ALTERA_FCS_HASH requires WOLFSSL_SHA512_HASHTYPE" +#endif + +#define FCS_SHA_OP_MODE_SHA 1 +#define FCS_SHA_SZ_256 0 +#define FCS_SHA_SZ_384 1 +#define FCS_SHA_SZ_512 2 + +/* Starting capacity for an accumulated message. */ +#define WC_ALTERA_FCS_KEEP_MIN 256 +#ifndef WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX + #define WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX WC_ALTERA_FCS_MAX_XFER +#endif +#ifndef WOLFSSL_ALTERA_FCS_HASH_TOTAL_MAX + #define WOLFSSL_ALTERA_FCS_HASH_TOTAL_MAX (4 * 1024 * 1024) +#endif +#if WOLFSSL_ALTERA_FCS_HASH_TOTAL_MAX > 0x7fffffff + #error "WOLFSSL_ALTERA_FCS_HASH_TOTAL_MAX exceeds atomic counter range" +#endif + +/* Internal result: retain no more input and continue in the software shadow. */ +#define WC_ALTERA_FCS_KEEP_SOFTWARE 1 +static wolfSSL_Atomic_Int g_alteraHashBuffered = 0; + +/* Public SHA APIs may consult WOLF_CRYPTO_CB_FIND even with INVALID_DEVID. + * Internal software-shadow work sets this thread-local guard so redispatch + * declines instead of recursively entering this callback. */ +#if !defined(SINGLE_THREADED) && \ + (!defined(HAVE_THREAD_LS) || defined(NO_THREAD_LS)) + #error "WOLFSSL_ALTERA_FCS_HASH requires thread-local storage" +#endif +static THREAD_LS_T int g_alteraHashSoftware = 0; + +typedef struct { + byte* msg; + word32 used; + word32 len; + void* heap; + /* Once the message passes what one SDM transaction can carry, the buffer + * is folded into this software state and released: a multi gigabyte hash + * must not be held in memory just to discover at final() that it has to + * run in software anyway. */ +#ifndef NO_SHA256 + wc_Sha256 soft; +#endif + byte softInit; + byte overflowed; + byte active; +} AlteraHashKeep; + +static int wc_AlteraFcs_HashReserve(word32 sz) +{ + int used; + + do { + used = wolfSSL_Atomic_Int_FetchAdd(&g_alteraHashBuffered, 0); + if (used < 0 || sz > (word32)WOLFSSL_ALTERA_FCS_HASH_TOTAL_MAX - + (word32)used) { + return 0; + } + } while (!wolfSSL_Atomic_Int_CompareExchange(&g_alteraHashBuffered, + &used, + used + (int)sz)); + return 1; +} + +static void wc_AlteraFcs_HashRelease(word32 sz) +{ + if (sz > 0) { + (void)wolfSSL_Atomic_Int_FetchSub(&g_alteraHashBuffered, (int)sz); + } +} + +/* Append with geometric growth. _wc_Hash_Grow sizes to exactly used + inSz, so + * a message arriving in many small updates reallocates every time and copies + * the whole prefix again. Reallocating by hand also lets the old buffer be + * zeroized rather than left in the allocator. */ +static int wc_AlteraFcs_KeepAppend(AlteraHashKeep* keep, const byte* in, + word32 inSz) +{ + byte* tmp; + word32 need; + word32 cap; + + if (inSz == 0) { + return 0; + } + + need = keep->used + inSz; + if (need < keep->used) { + return BUFFER_E; + } + + if (need > (word32)WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX) { + return WC_ALTERA_FCS_KEEP_SOFTWARE; + } + + if (need > keep->len) { + cap = (keep->len == 0) ? WC_ALTERA_FCS_KEEP_MIN : keep->len; + while (cap < need) { + word32 next = cap << 1; + + if (next < cap || + next > (word32)WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX) { + next = (word32)WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX; + } + cap = next; + } + + if (!wc_AlteraFcs_HashReserve(cap - keep->len)) { + return WC_ALTERA_FCS_KEEP_SOFTWARE; + } + tmp = (byte*)XMALLOC(cap, keep->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wc_AlteraFcs_HashRelease(cap - keep->len); + return MEMORY_E; + } + if (keep->used > 0) { + XMEMCPY(tmp, keep->msg, keep->used); + } + if (keep->msg != NULL) { + ForceZero(keep->msg, keep->len); + XFREE(keep->msg, keep->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + keep->msg = tmp; + keep->len = cap; + } + + XMEMCPY(keep->msg + keep->used, in, inSz); + keep->used += inSz; + return 0; +} + +static void wc_AlteraFcs_KeepFree(AlteraHashKeep* keep) +{ + if (keep == NULL) { + return; + } +#ifndef NO_SHA256 + if (keep->softInit) { + wc_Sha256Free(&keep->soft); + keep->softInit = 0; + } +#endif + if (keep->msg != NULL) { + ForceZero(keep->msg, keep->len); + XFREE(keep->msg, keep->heap, DYNAMIC_TYPE_TMP_BUFFER); + wc_AlteraFcs_HashRelease(keep->len); + } + if (keep->active) { + wc_AlteraFcs_ResourceRemove(); + } + XFREE(keep, keep->heap, DYNAMIC_TYPE_TMP_BUFFER); +} + +/* Address of the devCtx field inside a hash context of the given type. */ +static void** wc_AlteraFcs_DevCtxOf(void* hashCtx, int type) +{ + void** devCtx = NULL; + + if (hashCtx == NULL) { + return NULL; + } + + switch (type) { +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + devCtx = &((wc_Sha256*)hashCtx)->devCtx; + break; +#endif + /* The SHA-512 family is deliberately not offloaded. Its truncated + * variants (SHA-512/224, /256) are indistinguishable from full SHA-512 + * at update() time - the digest size only appears at final() - so + * buffering them here risks finalising with the wrong length. There is + * nothing to gain either: this CPU has no ARMv8 SHA-512 extension, and + * the SDM is far slower than software regardless. */ + default: + break; + } + + return devCtx; +} + +static int wc_AlteraFcs_HashParams(int type, word32* digestSz, + FCS_OSAL_U32* shaSel) +{ + int ret = 0; + + switch (type) { +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + *digestSz = WC_SHA256_DIGEST_SIZE; + *shaSel = FCS_SHA_SZ_256; + break; +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + *digestSz = WC_SHA384_DIGEST_SIZE; + *shaSel = FCS_SHA_SZ_384; + break; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + *digestSz = WC_SHA512_DIGEST_SIZE; + *shaSel = FCS_SHA_SZ_512; + break; +#endif + /* The SDM offers no truncated SHA-512 modes, so SHA-512/224 and + * SHA-512/256 stay in software. */ + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + return ret; +} + +/* Resolve the hash context pointer carried in info->hash for this type. */ +static void* wc_AlteraFcs_HashObj(wc_CryptoInfo* info) +{ + void* obj = NULL; + + switch (info->hash.type) { +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + obj = info->hash.sha256; + break; +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + obj = info->hash.sha384; + break; +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + case WC_HASH_TYPE_SHA512_224: + case WC_HASH_TYPE_SHA512_256: + obj = info->hash.sha512; + break; +#endif + default: + break; + } + + return obj; +} + +static int wc_AlteraFcs_Digest(const byte* in, word32 inSz, byte* out, + word32 digestSz, FCS_OSAL_U32 shaSel) +{ + struct fcs_digest_get_req req; + void* session = NULL; + FCS_OSAL_U32 outLen = digestSz; + int ret; + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + return ret; + } + + XMEMSET(&req, 0, sizeof(req)); + req.sha_op_mode = FCS_SHA_OP_MODE_SHA; + req.sha_digest_sz = shaSel; + req.src = (FCS_OSAL_CHAR*)in; + req.src_len = (FCS_OSAL_U32)inSz; + req.digest = (FCS_OSAL_CHAR*)out; + req.digest_len = &outLen; + + ret = fcs_get_digest((FCS_OSAL_UUID*)session, WOLFSSL_ALTERA_FCS_CTX_ID, + 0, &req); + if (ret != 0) { + ret = wc_AlteraFcs_MapError(ret); + } + else if (outLen != digestSz) { + /* An invalid session has been seen to report success while returning a + * short or empty digest, so the length is checked rather than trusted. */ + WOLFSSL_MSG("Altera FCS digest length mismatch"); + ret = WC_HW_E; + } + else { + wc_AlteraFcs_TestHwMark(WC_ALTERA_FCS_TEST_HW_HASH); + } + + wc_AlteraFcs_SessionRelease(); + return ret; +} + +/* Digest the accumulated message in software. + * + * Required, not an optimisation: once update() has returned success wolfSSL + * stops maintaining its own hash state, so this callback owns the data. Bailing + * out with CRYPTOCB_UNAVAILABLE at final() would make wolfSSL finalize an empty + * context and silently produce the digest of nothing. Contexts are created with + * INVALID_DEVID so they cannot recurse back into this callback. */ +static int wc_AlteraFcs_SoftDigest(int type, const byte* in, word32 inSz, + byte* out, void* heap) +{ + int ret; + + g_alteraHashSoftware++; + switch (type) { +#ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: { + wc_Sha256 s; + + ret = wc_InitSha256_ex(&s, heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha256Update(&s, in, inSz); + if (ret == 0) { + ret = wc_Sha256Final(&s, out); + } + wc_Sha256Free(&s); + } + break; + } +#endif +#ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: { + wc_Sha384 s; + + ret = wc_InitSha384_ex(&s, heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha384Update(&s, in, inSz); + if (ret == 0) { + ret = wc_Sha384Final(&s, out); + } + wc_Sha384Free(&s); + } + break; + } +#endif +#if defined(WOLFSSL_SHA512) && !defined(WOLFSSL_NOSHA512_224) && \ + !defined(HAVE_SELFTEST) + case WC_HASH_TYPE_SHA512_224: { + wc_Sha512 s; + + ret = wc_InitSha512_224_ex(&s, heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha512_224Update(&s, in, inSz); + if (ret == 0) { + ret = wc_Sha512_224Final(&s, out); + } + wc_Sha512_224Free(&s); + } + break; + } +#endif +#if defined(WOLFSSL_SHA512) && !defined(WOLFSSL_NOSHA512_256) && \ + !defined(HAVE_SELFTEST) + case WC_HASH_TYPE_SHA512_256: { + wc_Sha512 s; + + ret = wc_InitSha512_256_ex(&s, heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha512_256Update(&s, in, inSz); + if (ret == 0) { + ret = wc_Sha512_256Final(&s, out); + } + wc_Sha512_256Free(&s); + } + break; + } +#endif +#ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: { + wc_Sha512 s; + + ret = wc_InitSha512_ex(&s, heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_Sha512Update(&s, in, inSz); + if (ret == 0) { + ret = wc_Sha512Final(&s, out); + } + wc_Sha512Free(&s); + } + break; + } +#endif + default: + ret = CRYPTOCB_UNAVAILABLE; + break; + } + + g_alteraHashSoftware--; + return ret; +} + +#ifndef NO_SHA256 +static int wc_AlteraFcs_Sha256Reset(wc_Sha256* sha) +{ + byte digest[WC_SHA256_DIGEST_SIZE]; + int devId; + int ret; + + devId = sha->devId; + sha->devId = INVALID_DEVID; + g_alteraHashSoftware++; + ret = wc_Sha256Final(sha, digest); + g_alteraHashSoftware--; + sha->devId = devId; + ForceZero(digest, sizeof(digest)); + return ret; +} + +static int wc_AlteraFcs_SoftSha256Update(wc_Sha256* sha, const byte* in, + word32 inSz) +{ + int ret; + + g_alteraHashSoftware++; + ret = wc_Sha256Update(sha, in, inSz); + g_alteraHashSoftware--; + return ret; +} + +static int wc_AlteraFcs_KeepToSoftware(AlteraHashKeep* keep) +{ + int ret; + + ret = wc_InitSha256_ex(&keep->soft, keep->heap, INVALID_DEVID); + if (ret == 0) { + keep->softInit = 1; + if (keep->used > 0) { + ret = wc_AlteraFcs_SoftSha256Update(&keep->soft, keep->msg, + keep->used); + } + } + if (ret != 0) { + return ret; + } + if (keep->msg != NULL) { + ForceZero(keep->msg, keep->len); + XFREE(keep->msg, keep->heap, DYNAMIC_TYPE_TMP_BUFFER); + wc_AlteraFcs_HashRelease(keep->len); + keep->msg = NULL; + } + keep->used = 0; + keep->len = 0; + keep->overflowed = 1; + return 0; +} +#endif + +/* wolfSSL skips its own copy path when this callback succeeds. Delegate the + * ordinary context portion back to wc_Sha256Copy with the FCS state detached, + * then independently duplicate the buffered FCS state. This preserves every + * backend-specific deep-copy hook compiled into the ordinary implementation. */ +static int wc_AlteraFcs_HashCopy(wc_CryptoInfo* info) +{ + wc_Sha256* srcSha; + wc_Sha256* dstSha; + AlteraHashKeep* srcKeep; + AlteraHashKeep* dstKeep = NULL; + void* srcDevCtx; + int srcDevId; + int ret = 0; + + if (info->copy.algo != WC_ALGO_TYPE_HASH || + info->copy.type != WC_HASH_TYPE_SHA256 || + info->copy.src == NULL || info->copy.dst == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + srcSha = (wc_Sha256*)info->copy.src; + dstSha = (wc_Sha256*)info->copy.dst; + if (srcSha == dstSha) { + return BAD_FUNC_ARG; + } + srcKeep = (AlteraHashKeep*)srcSha->devCtx; + if (srcKeep == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Free the destination while its current callback state is still visible. + * The guarded recursive copy then sees an inert destination and cannot + * redispatch into this handler. */ + wc_Sha256Free(dstSha); + XMEMSET(dstSha, 0, sizeof(*dstSha)); + dstSha->devId = INVALID_DEVID; + + srcDevId = srcSha->devId; + srcDevCtx = srcSha->devCtx; + srcSha->devId = INVALID_DEVID; + srcSha->devCtx = NULL; + g_alteraHashSoftware++; + ret = wc_Sha256Copy(srcSha, dstSha); + g_alteraHashSoftware--; + srcSha->devCtx = srcDevCtx; + srcSha->devId = srcDevId; + if (ret != 0) { + goto exit; + } + + dstKeep = (AlteraHashKeep*)XMALLOC(sizeof(AlteraHashKeep), + dstSha->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (dstKeep == NULL) { + ret = MEMORY_E; + goto exit; + } + XMEMSET(dstKeep, 0, sizeof(*dstKeep)); + dstKeep->heap = dstSha->heap; + +#ifndef NO_SHA256 + if (srcKeep->overflowed) { + /* The message is gone; the live software state has to be duplicated + * so the two contexts stay independent. */ + dstKeep->softInit = 1; + ret = wc_Sha256Copy(&srcKeep->soft, &dstKeep->soft); + if (ret != 0) { + goto exit; + } + dstKeep->overflowed = 1; + } + else +#endif + if (srcKeep->used > 0) { + ret = wc_AlteraFcs_KeepAppend(dstKeep, srcKeep->msg, srcKeep->used); + if (ret == WC_ALTERA_FCS_KEEP_SOFTWARE) { + ret = wc_AlteraFcs_KeepToSoftware(dstKeep); + if (ret == 0) { + ret = wc_AlteraFcs_SoftSha256Update(&dstKeep->soft, + srcKeep->msg, + srcKeep->used); + } + } + if (ret != 0) { + goto exit; + } + } + + dstKeep->active = 1; + wc_AlteraFcs_ResourceAdd(); + dstSha->devCtx = dstKeep; + dstSha->devId = srcDevId; + return 0; + +exit: + wc_AlteraFcs_KeepFree(dstKeep); + dstSha->devCtx = NULL; + dstSha->devId = INVALID_DEVID; + wc_Sha256Free(dstSha); + XMEMSET(dstSha, 0, sizeof(*dstSha)); + return ret; +} + +/* Release the buffer when a context is freed without being finalized. */ +static int wc_AlteraFcs_HashFreeCtx(wc_CryptoInfo* info) +{ + void** devCtx; + + if (info->free.algo != WC_ALGO_TYPE_HASH) { + return CRYPTOCB_UNAVAILABLE; + } + + devCtx = wc_AlteraFcs_DevCtxOf(info->free.obj, info->free.type); + if (devCtx == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + wc_AlteraFcs_KeepFree((AlteraHashKeep*)(*devCtx)); + *devCtx = NULL; + + /* Decline so wolfSSL still performs its own teardown. */ + return CRYPTOCB_UNAVAILABLE; +} + +#ifndef NO_SHA256 +static int wc_AlteraFcs_Sha256Started(const wc_Sha256* sha) +{ +#if defined(FREESCALE_LTC_SHA) || \ + (defined(WOLFSSL_SE050) && defined(WOLFSSL_SE050_HASH)) || \ + defined(STM32_HASH_SHA2) || defined(WOLFSSL_SILABS_SE_ACCEL) || \ + defined(WOLFSSL_IMXRT_DCP) || defined(PSOC6_HASH_SHA2) || \ + (defined(WOLFSSL_HAVE_PSA) && !defined(WOLFSSL_PSA_NO_HASH)) + /* An opaque software state cannot prove that no prefix was consumed. */ + (void)sha; + return 1; +#else + return sha->buffLen != 0 || sha->loLen != 0 || sha->hiLen != 0; +#endif +} +#endif + +int wc_AlteraFcs_Hash(wc_CryptoInfo* info) +{ + AlteraHashKeep* keep; + void** devCtxPtr; + void* hashObj; + word32 digestSz = 0; + FCS_OSAL_U32 shaSel = 0; + int hwOk = 0; + int ret = 0; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (g_alteraHashSoftware) { + return CRYPTOCB_UNAVAILABLE; + } + + if (info->algo_type == WC_ALGO_TYPE_COPY) { + return wc_AlteraFcs_HashCopy(info); + } + if (info->algo_type == WC_ALGO_TYPE_FREE) { + return wc_AlteraFcs_HashFreeCtx(info); + } + + /* A failure here only means there is no hardware mode for this variant; the + * message is still buffered and finished in software at final(). */ + hwOk = (wc_AlteraFcs_HashParams(info->hash.type, &digestSz, &shaSel) == 0); + + hashObj = wc_AlteraFcs_HashObj(info); + devCtxPtr = wc_AlteraFcs_DevCtxOf(hashObj, info->hash.type); + if (devCtxPtr == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + keep = (AlteraHashKeep*)(*devCtxPtr); + + /* update(): accumulate into this context's own buffer, unless the message + * has outgrown what one device transaction can carry, in which case it is + * streamed into a software state instead. */ + if (info->hash.in != NULL) { + if (keep == NULL) { + void* heap = ((wc_Sha256*)hashObj)->heap; + + if (wc_AlteraFcs_UnregisterPending()) { + return CRYPTOCB_UNAVAILABLE; + } +#ifndef NO_SHA256 + if (info->hash.type == WC_HASH_TYPE_SHA256 && + wc_AlteraFcs_Sha256Started((wc_Sha256*)hashObj)) { + return CRYPTOCB_UNAVAILABLE; + } +#endif + keep = (AlteraHashKeep*)XMALLOC(sizeof(AlteraHashKeep), heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (keep == NULL) { + return MEMORY_E; + } + XMEMSET(keep, 0, sizeof(*keep)); + keep->heap = heap; + keep->active = 1; + wc_AlteraFcs_ResourceAdd(); + *devCtxPtr = keep; + } + +#ifndef NO_SHA256 + if (!keep->overflowed && info->hash.type == WC_HASH_TYPE_SHA256 && + ((word32)(keep->used + info->hash.inSz) < keep->used || + (word32)(keep->used + info->hash.inSz) > + (word32)WOLFSSL_ALTERA_FCS_HASH_BUFFER_MAX)) { + ret = wc_AlteraFcs_KeepToSoftware(keep); + if (ret != 0) + return ret; + } + + if (keep->overflowed) { + ret = wc_AlteraFcs_SoftSha256Update(&keep->soft, info->hash.in, + info->hash.inSz); + if (ret != 0) { + return ret; + } + } + else +#endif + { + ret = wc_AlteraFcs_KeepAppend(keep, info->hash.in, + info->hash.inSz); + if (ret == WC_ALTERA_FCS_KEEP_SOFTWARE) { +#ifndef NO_SHA256 + if (info->hash.type == WC_HASH_TYPE_SHA256) { + ret = wc_AlteraFcs_KeepToSoftware(keep); + if (ret == 0) { + ret = wc_AlteraFcs_SoftSha256Update( + &keep->soft, info->hash.in, info->hash.inSz); + } + } + else +#endif + { + ret = MEMORY_E; + } + } + if (ret != 0) { + return ret; + } + } + +#ifndef NO_SHA256 + /* The callback owns update processing, but callers such as the TLS CBC + * verifier also inspect the ordinary SHA state through FinalRaw(). + * Advance that state in software while retaining the buffered message + * for the final atomic SDM operation. */ + if (info->hash.type == WC_HASH_TYPE_SHA256) { + wc_Sha256* sha = (wc_Sha256*)hashObj; + int devId = sha->devId; + + sha->devId = INVALID_DEVID; + g_alteraHashSoftware++; + ret = wc_Sha256Update(sha, info->hash.in, info->hash.inSz); + g_alteraHashSoftware--; + sha->devId = devId; + if (ret != 0) { + return ret; + } + } +#endif + } + + /* final(): one atomic SDM digest over everything accumulated. Short + * messages are hashed in software here rather than declined, because this + * callback already consumed the updates. */ + if (ret == 0 && info->hash.digest != NULL) { + const byte* msg = (keep != NULL) ? keep->msg : NULL; + word32 msgLen = (keep != NULL) ? keep->used : 0; + + /* No callback-owned buffer means earlier updates ran in software while + * this device was unregistered. Decline so that state is finalized. */ + if (keep == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + +#ifndef NO_SHA256 + if (keep->overflowed) { + ret = wc_Sha256Final(&keep->soft, info->hash.digest); + if (ret == 0) { + ret = wc_AlteraFcs_Sha256Reset((wc_Sha256*)hashObj); + } + wc_AlteraFcs_KeepFree(keep); + *devCtxPtr = NULL; + return ret; + } +#endif + + /* Oversized messages exceed a single SDM transaction, so they are + * finished in software rather than rejected by the driver. */ + if (hwOk && msgLen >= WOLFSSL_ALTERA_FCS_HASH_MIN && + msgLen <= WC_ALTERA_FCS_MAX_XFER) { + ret = wc_AlteraFcs_Digest(msg, msgLen, info->hash.digest, + digestSz, shaSel); + if (ret != 0) { + /* The callback consumed every update, so the message is only + * available here. Any device failure, not just a busy one, + * must still produce the digest. */ + ret = wc_AlteraFcs_SoftDigest(info->hash.type, msg, msgLen, + info->hash.digest, keep->heap); + } + } + else { + ret = wc_AlteraFcs_SoftDigest(info->hash.type, msg, msgLen, + info->hash.digest, keep->heap); + } + + if (ret == 0 && info->hash.type == WC_HASH_TYPE_SHA256) { + ret = wc_AlteraFcs_Sha256Reset((wc_Sha256*)hashObj); + } + wc_AlteraFcs_KeepFree(keep); + *devCtxPtr = NULL; + } + + return ret; +} + +#endif /* WOLFSSL_ALTERA_FCS && WOLFSSL_ALTERA_FCS_HASH */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_hmac.c b/wolfcrypt/src/port/altera/altera_fcs_hmac.c new file mode 100644 index 0000000000..118a222f63 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_hmac.c @@ -0,0 +1,423 @@ +/* altera_fcs_hmac.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* HMAC verification against a key held inside the Agilex 5 Secure Device + * Manager. + * + * This is deliberately not a crypto callback. The device exposes only + * verification: fcs_mac_verify() returns a four byte verdict and never a tag, + * while wolfSSL's HMAC API is a generator that ends in wc_HmacFinal(). There is + * no verify entry point to hook, so a callback could not be written without + * silently changing what callers get. + * + * What it does provide is authentication under a key that HPS software cannot + * read, which is the isolation property applied to MACs rather than signatures. + * Tags produced by an ordinary software HMAC verify correctly here, so the + * device implementation is interoperable. + */ + +#include + +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLFSSL_ALTERA_FCS_HMAC) + +#include +#include +#include +#include + +#include + +#define WC_ALTERA_FCS_HMAC_TRACK_MAX 32 + +static wolfSSL_Atomic_Uint g_hmacKeys[WC_ALTERA_FCS_HMAC_TRACK_MAX]; + +static int wc_AlteraFcs_HmacTrackAdd(word32 keyId) +{ + int i; + + for (i = 0; i < WC_ALTERA_FCS_HMAC_TRACK_MAX; i++) { + WC_ATOMIC_UINT_ARG empty = 0; + + if (wolfSSL_Atomic_Uint_CompareExchange(&g_hmacKeys[i], &empty, + (WC_ATOMIC_UINT_ARG)keyId)) { + return 0; + } + } + return MEMORY_E; +} + +static int wc_AlteraFcs_HmacTrackTake(word32 keyId) +{ + int i; + + if (keyId == 0) { + return 0; + } + + for (i = 0; i < WC_ALTERA_FCS_HMAC_TRACK_MAX; i++) { + WC_ATOMIC_UINT_ARG expected = (WC_ATOMIC_UINT_ARG)keyId; + + if (wolfSSL_Atomic_Uint_CompareExchange(&g_hmacKeys[i], &expected, + 0)) { + return 1; + } + } + return 0; +} + +static int wc_AlteraFcs_HmacTrackHas(word32 keyId) +{ + int i; + + if (keyId == 0) { + return 0; + } + for (i = 0; i < WC_ALTERA_FCS_HMAC_TRACK_MAX; i++) { + if (wolfSSL_Atomic_Uint_FetchAdd(&g_hmacKeys[i], 0) == + (WC_ATOMIC_UINT_ARG)keyId) { + return 1; + } + } + return 0; +} + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#define FCS_KEY_OBJ_MAGIC 0x43736B4FU +#define FCS_KEY_DATA_MAGIC 0x43736B64U +#define FCS_KEY_OBJ_VER 1 +#define FCS_KEY_TYPE_HMAC 2 +#define FCS_KEY_MAC_SZ 48 +#define FCS_KEY_DATA_OFFSET 56 +#define FCS_KEY_ALIGN 32 +#define FCS_KEY_STATUS_SZ 64 + +/* Sign and Verify only. Setting the Exchange bit as well is refused with 0x80, + * the same exclusivity the ECC key objects enforce. */ +#define FCS_KEY_USAGE_SIGN_VERIFY 0xC + +/* Largest object: header, 64 byte padded 512 bit key, unused MAC field. */ +#define WC_ALTERA_FCS_HMACOBJ_SZ (FCS_KEY_DATA_OFFSET + 64 + FCS_KEY_MAC_SZ) + +/* The device reports the outcome as a 32 bit word rather than a return code. */ +#define FCS_MAC_RESULT_SZ 4 +#define FCS_MAC_RESULT_OK 0x900DU + +static void wc_AlteraFcs_Put32(byte* out, word32 val) +{ + out[0] = (byte)( val & 0xFF); + out[1] = (byte)((val >> 8) & 0xFF); + out[2] = (byte)((val >> 16) & 0xFF); + out[3] = (byte)((val >> 24) & 0xFF); +} + +static word32 wc_AlteraFcs_Get32(const byte* in) +{ + return ((word32)in[0]) | ((word32)in[1] << 8) | + ((word32)in[2] << 16) | ((word32)in[3] << 24); +} + +/* Map a wolfSSL hash type to the device digest selector and tag size. */ +static int wc_AlteraFcs_HmacDigest(int hashType, FCS_OSAL_U32* digSel, + word32* macSz) +{ + int ret = 0; + + switch (hashType) { + #ifndef NO_SHA256 + case WC_HASH_TYPE_SHA256: + *digSel = 0; + *macSz = WC_SHA256_DIGEST_SIZE; + break; + #endif + #ifdef WOLFSSL_SHA384 + case WC_HASH_TYPE_SHA384: + *digSel = 1; + *macSz = WC_SHA384_DIGEST_SIZE; + break; + #endif + #ifdef WOLFSSL_SHA512 + case WC_HASH_TYPE_SHA512: + *digSel = 2; + *macSz = WC_SHA512_DIGEST_SIZE; + break; + #endif + default: + ret = BAD_FUNC_ARG; + break; + } + + return ret; +} + +static int wc_AlteraFcs_HmacKeyObject(byte* out, word32 keyId, int keyBits, + const byte* key, word32* outSz) +{ + word32 keyLen; + word32 padded; + word32 objSz; + word32 sizeCode; + + if (keyBits == 256) { + sizeCode = 2; + } + else if (keyBits == 384) { + sizeCode = 3; + } + else if (keyBits == 512) { + sizeCode = 4; + } + else { + return BAD_FUNC_ARG; + } + + keyLen = (word32)keyBits / 8; + padded = keyLen; + if ((padded % FCS_KEY_ALIGN) != 0) { + padded += FCS_KEY_ALIGN - (padded % FCS_KEY_ALIGN); + } + + XMEMSET(out, 0, WC_ALTERA_FCS_HMACOBJ_SZ); + wc_AlteraFcs_Put32(out, FCS_KEY_OBJ_MAGIC); + wc_AlteraFcs_Put32(out + 8, keyId); + wc_AlteraFcs_Put32(out + 20, (sizeCode << 16) | + ((word32)FCS_KEY_TYPE_HMAC << 24)); + wc_AlteraFcs_Put32(out + 24, FCS_KEY_USAGE_SIGN_VERIFY); + wc_AlteraFcs_Put32(out + 48, FCS_KEY_DATA_MAGIC); + if (key != NULL) { + XMEMCPY(out + FCS_KEY_DATA_OFFSET, key, keyLen); + } + + objSz = FCS_KEY_DATA_OFFSET + padded; + wc_AlteraFcs_Put32(out + 4, ((word32)FCS_KEY_OBJ_VER << 16) | + (objSz & 0xFFFF)); + + *outSz = objSz + FCS_KEY_MAC_SZ; + return 0; +} + +/* Shared by import and generate; key == NULL means the device generates it. */ +static int wc_AlteraFcs_HmacKeyNew(const byte* key, int keyBits, + word32* keyId) +{ + byte obj[WC_ALTERA_FCS_HMACOBJ_SZ]; + byte status[FCS_KEY_STATUS_SZ]; + FCS_OSAL_UINT statusLen = (FCS_OSAL_UINT)sizeof(status); + void* session = NULL; + word32 objSz = 0; + word32 newId = 0; + int createFailed = 0; + int keepResource = 0; + int ret; + + if (keyId == NULL) { + return BAD_FUNC_ARG; + } + ret = wc_AlteraFcs_ResourceAcquire(); + if (ret != 0) { + return WC_HW_E; + } + + ret = wc_AlteraFcs_KeyIdNew(&newId); + if (ret == 0) { + ret = wc_AlteraFcs_HmacKeyObject(obj, newId, keyBits, key, &objSz); + } + if (ret == 0) { + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + ret = WC_HW_E; + } + } + if (ret == 0) { + XMEMSET(status, 0, sizeof(status)); + if (key != NULL) { + ret = fcs_import_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_CHAR*)obj, + (FCS_OSAL_INT)objSz, + (FCS_OSAL_CHAR*)status, &statusLen); + } + else { + ret = fcs_create_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_CHAR*)obj, + (FCS_OSAL_INT)objSz, + (FCS_OSAL_CHAR*)status, + (FCS_OSAL_UINT)sizeof(status)); + } + if (ret != 0) { + WOLFSSL_MSG("Altera FCS HMAC key creation failed"); + createFailed = 1; + ret = WC_HW_E; + } + else { + ret = wc_AlteraFcs_HmacTrackAdd(newId); + if (ret == 0) { + *keyId = newId; + /* Transfer the provisional resource reference to the tracked + * device key. Removal releases it. */ + keepResource = 1; + } + else { + if (fcs_remove_service_key((FCS_OSAL_UUID*)session, + (FCS_OSAL_U32)newId) != 0) { + (void)wc_AlteraFcs_OrphanKey(newId); + } + } + } + wc_AlteraFcs_SessionRelease(); + if (createFailed) { + wc_AlteraFcs_DiscardServiceKey(newId); + } + } + + if (!keepResource) { + wc_AlteraFcs_ResourceRemove(); + } + ForceZero(obj, sizeof(obj)); + return ret; +} + +int wc_AlteraFcs_HmacImportKey(const byte* key, int keyBits, word32* keyId) +{ + if (key == NULL) { + return BAD_FUNC_ARG; + } + return wc_AlteraFcs_HmacKeyNew(key, keyBits, keyId); +} + +int wc_AlteraFcs_HmacMakeKey(int keyBits, word32* keyId) +{ + return wc_AlteraFcs_HmacKeyNew(NULL, keyBits, keyId); +} + +int wc_AlteraFcs_HmacRemoveKey(word32 keyId) +{ + int ret; + + /* Claim the tracker entry before touching the device so two removers cannot + * operate on the same key and release its resource twice. */ + if (!wc_AlteraFcs_HmacTrackTake(keyId)) { + return BAD_FUNC_ARG; + } + + ret = wc_AlteraFcs_RemoveServiceKey(keyId); + if (ret != 0) { + /* The key is no longer caller reachable. Record it for best-effort + * retry; closing the session ultimately reclaims it even if retry + * keeps failing. */ + (void)wc_AlteraFcs_OrphanKey(keyId); + } + wc_AlteraFcs_ResourceRemove(); + return ret; +} + +/* The device wants the message and the tag contiguous, with user_data_sz giving + * the length of the message part. A message only request is refused with 0x4. */ +int wc_AlteraFcs_HmacVerify(word32 keyId, int hashType, const byte* data, + word32 dataSz, const byte* mac, word32 macSz, + int* isValid) +{ + struct fcs_mac_verify_req req; + byte* buf = NULL; + byte result[FCS_MAC_RESULT_SZ]; + void* session = NULL; + FCS_OSAL_U32 resultLen = (FCS_OSAL_U32)sizeof(result); + FCS_OSAL_U32 digSel = 0; + word32 expectSz = 0; + word32 totalSz; + int ret; + + if ((data == NULL && dataSz != 0) || mac == NULL || isValid == NULL) { + return BAD_FUNC_ARG; + } + *isValid = 0; + if (!wc_AlteraFcs_HmacTrackHas(keyId)) { + return BAD_FUNC_ARG; + } + + ret = wc_AlteraFcs_HmacDigest(hashType, &digSel, &expectSz); + if (ret != 0) { + return ret; + } + if (macSz != expectSz) { + return BAD_FUNC_ARG; + } + /* Checked before any arithmetic: dataSz is caller controlled and the sum + * would otherwise wrap, under allocating the buffer that is then filled + * with dataSz bytes. */ + if (dataSz > WC_ALTERA_FCS_MAX_XFER - macSz) { + return BAD_FUNC_ARG; + } + totalSz = dataSz + macSz; + + buf = (byte*)XMALLOC(totalSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + return MEMORY_E; + } + if (dataSz > 0) { + XMEMCPY(buf, data, dataSz); + } + XMEMCPY(buf + dataSz, mac, macSz); + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + ForceZero(buf, totalSz); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WC_HW_E; + } + + XMEMSET(result, 0, sizeof(result)); + XMEMSET(&req, 0, sizeof(req)); + req.op_mode = 0; + req.dig_sz = digSel; + req.src = (FCS_OSAL_CHAR*)buf; + req.src_sz = (FCS_OSAL_U32)totalSz; + req.dst = (FCS_OSAL_CHAR*)result; + req.dst_sz = &resultLen; + req.user_data_sz = (FCS_OSAL_U32)dataSz; + + ret = fcs_mac_verify((FCS_OSAL_UUID*)session, WOLFSSL_ALTERA_FCS_CTX_ID, + (FCS_OSAL_U32)keyId, &req); + wc_AlteraFcs_SessionRelease(); + + ForceZero(buf, totalSz); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + if (ret != 0) { + WOLFSSL_MSG("Altera FCS HMAC verify request failed"); + return WC_HW_E; + } + if (resultLen != FCS_MAC_RESULT_SZ) { + WOLFSSL_MSG("Altera FCS HMAC verify result length unexpected"); + return WC_HW_E; + } + + /* A mismatch is reported in the result word, not as a request failure, so + * the verdict has to be read rather than inferred from ret. */ + *isValid = (wc_AlteraFcs_Get32(result) == FCS_MAC_RESULT_OK) ? 1 : 0; + return 0; +} + +#endif /* WOLFSSL_ALTERA_FCS && WOLFSSL_ALTERA_FCS_HMAC */ diff --git a/wolfcrypt/src/port/altera/altera_fcs_rng.c b/wolfcrypt/src/port/altera/altera_fcs_rng.c new file mode 100644 index 0000000000..3051c89244 --- /dev/null +++ b/wolfcrypt/src/port/altera/altera_fcs_rng.c @@ -0,0 +1,127 @@ +/* altera_fcs_rng.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SDM true random number generator. + * + * Measured cost is roughly 400 us per request regardless of size, so this is + * used as a seed source for wolfCrypt's DRBG rather than as a bulk generator. + */ + +#include + +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLFSSL_ALTERA_FCS_RNG) + +#include +#include +#include + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Arbitrary non-zero identifier tagging our requests within the session. */ +#ifndef WOLFSSL_ALTERA_FCS_CTX_ID + #define WOLFSSL_ALTERA_FCS_CTX_ID 0x574F4C46 +#endif + +/* Largest single random request; the SDM rejects oversized asks, so longer + * outputs are filled in chunks. */ +#define WC_ALTERA_FCS_RNG_CHUNK 256 + +static int wc_AlteraFcs_RngGenerate(byte* out, word32 sz) +{ + void* session = NULL; + word32 done = 0; + int ret; + + if (out == NULL) { + return BAD_FUNC_ARG; + } + if (sz == 0) { + return 0; + } + + ret = wc_AlteraFcs_SessionAcquire(&session); + if (ret != 0) { + return CRYPTOCB_UNAVAILABLE; + } + + while (ret == 0 && done < sz) { + word32 chunk = sz - done; + + if (chunk > WC_ALTERA_FCS_RNG_CHUNK) { + chunk = WC_ALTERA_FCS_RNG_CHUNK; + } + + ret = fcs_random_number_ext((FCS_OSAL_UUID*)session, + WOLFSSL_ALTERA_FCS_CTX_ID, + (FCS_OSAL_CHAR*)(out + done), + (FCS_OSAL_U32)chunk); + if (ret != 0) { + (void)wc_AlteraFcs_MapError(ret); + ret = CRYPTOCB_UNAVAILABLE; + } + else { + done += chunk; + } + } + + wc_AlteraFcs_SessionRelease(); + + if (ret != 0) { + ForceZero(out, sz); + } + else { + wc_AlteraFcs_TestHwMark(WC_ALTERA_FCS_TEST_HW_RNG); + } + return ret; +} + +int wc_AlteraFcs_Rng(wc_CryptoInfo* info) +{ + int ret = CRYPTOCB_UNAVAILABLE; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + /* The TRNG serves seeding, and generation is left to the DRBG it seeded: + * a mailbox round trip per generate request is roughly 225 times slower + * than the DRBG for no entropy benefit. Raw TRNG output for every request + * is available by building with WOLFSSL_ALTERA_FCS_RAW_RNG. */ + if (info->algo_type == WC_ALGO_TYPE_SEED) { + ret = wc_AlteraFcs_RngGenerate(info->seed.seed, info->seed.sz); + } +#ifdef WOLFSSL_ALTERA_FCS_RAW_RNG + else if (info->algo_type == WC_ALGO_TYPE_RNG) { + ret = wc_AlteraFcs_RngGenerate(info->rng.out, info->rng.sz); + } +#endif + + return ret; +} + +#endif /* WOLFSSL_ALTERA_FCS && WOLFSSL_ALTERA_FCS_RNG */ diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 15e9ce614a..b0b414673b 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -197,6 +197,9 @@ Threading/Mutex options: #if defined(WOLFSSL_VERSAL_GEN2_ASU) #include #endif +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLF_CRYPTO_CB) +#include +#endif #ifdef HAVE_INTEL_QA_SYNC #include @@ -409,6 +412,22 @@ int wc_local_InitDownDone(wc_init_state_t *s) return 0; } +#ifdef WOLF_CRYPTO_CB +static int wc_local_InitDownAbort(wc_init_state_t* s) +{ + union wc_init_state_bitfields cur_wc_init_state; + + cur_wc_init_state.u = WOLFSSL_ATOMIC_LOAD(*s); + if (cur_wc_init_state.c.state != WC_INIT_STATE_CLEANING_UP || + cur_wc_init_state.c.count != 1) { + return BAD_STATE_E; + } + cur_wc_init_state.c.state = WC_INIT_STATE_INITED; + WOLFSSL_ATOMIC_STORE(*s, cur_wc_init_state.u); + return 0; +} +#endif /* WOLF_CRYPTO_CB */ + static WC_DECLARE_INIT_STATE(wolfcrypt_init_state); #if defined(__aarch64__) && defined(WOLFSSL_ARMASM_BARRIER_DETECT) @@ -422,6 +441,18 @@ WOLFSSL_ABI int wolfCrypt_Init(void) { int ret; +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLF_CRYPTO_CB) + int alteraFcsRegistered = 0; + #define WOLFCRYPT_INIT_FCS_ROLLBACK() do { \ + if (alteraFcsRegistered) { \ + (void)wc_AlteraFcsCryptoCb_UnRegisterDeviceEx( \ + WOLFSSL_ALTERA_FCS_DEVID); \ + alteraFcsRegistered = 0; \ + } \ + } while (0) +#else + #define WOLFCRYPT_INIT_FCS_ROLLBACK() do { } while (0) +#endif #if defined(HAVE_THREAD_LS) && !defined(NO_THREAD_LS) && defined(__GNUC__) /* If thread-local storage is available, use it to prevent deadlock on * recursion. We only do this when __GNUC__ -- this code is known to cause @@ -432,12 +463,14 @@ int wolfCrypt_Init(void) if (in_init) return DEADLOCK_AVERTED_E; #define WOLFCRYPT_INIT_RAISE_BAD_STATE() do { \ + WOLFCRYPT_INIT_FCS_ROLLBACK(); \ in_init = 0; \ WC_INIT_STATE_RAISE_BAD_STATE(wolfcrypt_init_state); \ return ret; \ } while (0) #else #define WOLFCRYPT_INIT_RAISE_BAD_STATE() do { \ + WOLFCRYPT_INIT_FCS_ROLLBACK(); \ WC_INIT_STATE_RAISE_BAD_STATE(wolfcrypt_init_state); \ return ret; \ } while (0) @@ -582,6 +615,24 @@ int wolfCrypt_Init(void) WOLFCRYPT_INIT_RAISE_BAD_STATE(); } #endif + /* Register the Agilex 5 SDM so wolfCrypt operations created with this + * devId route to the hardware. Registration is per-devId and opt-in by + * design: the device grants one session chip-wide, so routing every + * operation here would serialise the whole library. */ + #if defined(WOLFSSL_ALTERA_FCS) && defined(WOLF_CRYPTO_CB) + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret == 0) { + alteraFcsRegistered = 1; + } + else { + /* FCS is an optional accelerator. Explicit device-key APIs still + * report hardware errors, while ordinary wolfCrypt operations must + * remain available through their software implementations. */ + WOLFSSL_MSG("Altera FCS unavailable; using software fallback"); + ret = 0; + } + #endif #if defined(MAX3266X_RTC) ret = wc_MXC_RTC_Init(); if (ret != 0) { @@ -771,6 +822,7 @@ int wolfCrypt_Init(void) #undef WOLFCRYPT_INIT_RAISE_BAD_STATE +#undef WOLFCRYPT_INIT_FCS_ROLLBACK #if defined(HAVE_THREAD_LS) && !defined(NO_THREAD_LS) && defined(__GNUC__) in_init = 0; @@ -817,6 +869,26 @@ int wolfCrypt_Cleanup(void) else if (ret == WC_INIT_STATE_INITED) return 0; else { +#if defined(WOLFSSL_ALTERA_FCS) && defined(WOLF_CRYPTO_CB) + if (wc_AlteraFcs_ResourceActive()) { + WOLFSSL_MSG("wolfCrypt_Cleanup() called with active Altera FCS objects"); + ret = wc_local_InitDownAbort(&wolfcrypt_init_state); + return (ret == 0) ? BUSY_E : ret; + } + ret = wc_AlteraFcsCryptoCb_UnRegisterDeviceEx( + WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) { + int ret2 = wc_local_InitDownAbort(&wolfcrypt_init_state); + return (ret2 == 0) ? ret : ret2; + } +#endif +#ifdef WOLF_CRYPTO_CB + ret = wc_CryptoCb_Cleanup(); + if (ret != 0) { + int ret2 = wc_local_InitDownAbort(&wolfcrypt_init_state); + return (ret2 == 0) ? ret : ret2; + } +#endif ret = 0; WOLFSSL_ENTER("wolfCrypt_Cleanup"); @@ -904,10 +976,6 @@ int wolfCrypt_Cleanup(void) wc_WinCryptHandleCleanup(); #endif - #ifdef WOLF_CRYPTO_CB - wc_CryptoCb_Cleanup(); - #endif - #if defined(HAVE_HASHDRBG) && !defined(WC_NO_RNG) && \ !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 41c2e71e33..5b137ca1fc 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -466,6 +466,9 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #ifdef WOLFSSL_CAAM #include #endif +#ifdef WOLFSSL_ALTERA_FCS + #include +#endif #ifdef WOLF_CRYPTO_CB #include #ifdef HAVE_INTEL_QA_SYNC @@ -1116,6 +1119,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memcb_test(void); #ifdef WOLFSSL_CAAM_BLOB WOLFSSL_TEST_SUBROUTINE wc_test_ret_t blob_test(void); #endif +#ifdef WOLFSSL_ALTERA_FCS +static wc_test_ret_t altera_fcs_no_hardware_test(void); +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t altera_fcs_test(void); +#endif #ifdef HAVE_ARIA #include "wolfssl/wolfcrypt/port/aria/aria-crypt.h" void printOutput(const char *strName, unsigned char *data, unsigned int dataSz); @@ -3473,6 +3480,20 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("blob test passed!\n"); #endif +#ifdef WOLFSSL_ALTERA_FCS + if (!wc_AlteraFcs_AlgoEnabled(WOLFSSL_ALTERA_FCS_AUTO_MASK) || + !wc_AlteraFcs_HardwareAvailable()) { + if ( (ret = altera_fcs_no_hardware_test()) != 0) + TEST_FAIL("ALTERA-FCS no-hardware test failed!\n", ret); + else + printf("ALTERA-FCS test skipped: hardware unavailable\n"); + } + else if ( (ret = altera_fcs_test()) != 0) + TEST_FAIL("ALTERA-FCS test failed!\n", ret); + else + TEST_PASS("ALTERA-FCS test passed!\n"); +#endif + #if defined(WOLF_CRYPTO_CB) && !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) && \ !(defined(HAVE_INTEL_QAT_SYNC) || defined(HAVE_CAVIUM_OCTEON_SYNC) || \ defined(WOLFSSL_QNX_CAAM) || defined(HAVE_RENESAS_SYNC)) @@ -77855,6 +77876,1197 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t blob_test(void) } #endif /* WOLFSSL_CAAM_BLOB */ +#ifdef WOLFSSL_ALTERA_FCS +/* Exercises every Agilex 5 SDM path on its own devId, so the hardware is + * covered even in builds where WC_USE_DEVID does not route the generic tests + * to it. ECC and HMAC additionally need the port's explicit API: key usage + * must be chosen at creation, and the device only verifies MACs. */ + +static wc_test_ret_t altera_fcs_no_hardware_test(void) +{ + wc_test_ret_t ret = 0; +#if defined(WOLFSSL_ALTERA_FCS_HASH) || defined(WOLFSSL_ALTERA_FCS_AES) + byte* buf = NULL; + int i; +#endif +#if defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256) + wc_Sha256 hwSha; + wc_Sha256 swSha; + byte hwDig[WC_SHA256_DIGEST_SIZE]; + byte swDig[WC_SHA256_DIGEST_SIZE]; + int hwShaInit = 0; + int swShaInit = 0; +#endif +#if defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) + Aes hwAes; + Aes swAes; + byte* hwOut = NULL; + byte* swOut = NULL; + int hwAesInit = 0; + int swAesInit = 0; + WOLFSSL_SMALL_STACK_STATIC const byte aesKey[16] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f + }; + WOLFSSL_SMALL_STACK_STATIC const byte iv[16] = { + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; +#endif + + wc_AlteraFcs_TestHwReset(); + +#if defined(WOLFSSL_ALTERA_FCS_HASH) || defined(WOLFSSL_ALTERA_FCS_AES) + buf = (byte*)XMALLOC(4096, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) + return WC_TEST_RET_ENC_EC(MEMORY_E); + for (i = 0; i < 4096; i++) + buf[i] = (byte)(i & 0xFF); +#endif + +#if defined(WOLFSSL_ALTERA_FCS_RNG) && !defined(WC_NO_RNG) + if (ret == 0) { + WC_RNG rng; + byte out[64]; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) { + ret = wc_RNG_GenerateBlock(&rng, out, (word32)sizeof(out)); + wc_FreeRng(&rng); + } + } +#endif + +#if defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256) + if (ret == 0) { + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, + WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) { + hwShaInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + } + if (ret == 0) { + swShaInit = 1; + ret = wc_Sha256Update(&hwSha, buf, 4096); + } + if (ret == 0) + ret = wc_Sha256Update(&swSha, buf, 4096); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (ret == 0 && XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ret = WC_TEST_RET_ENC_NC; + if (swShaInit) + wc_Sha256Free(&swSha); + if (hwShaInit) + wc_Sha256Free(&hwSha); + } +#endif + +#if defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) + if (ret == 0) { + hwOut = (byte*)XMALLOC(4096, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + swOut = (byte*)XMALLOC(4096, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (hwOut == NULL || swOut == NULL) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + } + } + if (ret == 0) { + ret = wc_AesInit(&hwAes, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) { + hwAesInit = 1; + ret = wc_AesInit(&swAes, HEAP_HINT, INVALID_DEVID); + } + if (ret == 0) { + swAesInit = 1; + ret = wc_AesSetKey(&hwAes, aesKey, (word32)sizeof(aesKey), iv, + AES_ENCRYPTION); + } + if (ret == 0) + ret = wc_AesSetKey(&swAes, aesKey, (word32)sizeof(aesKey), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, hwOut, buf, 4096); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, swOut, buf, 4096); + if (ret == 0 && XMEMCMP(hwOut, swOut, 4096) != 0) + ret = WC_TEST_RET_ENC_NC; + } + if (swAesInit) + wc_AesFree(&swAes); + if (hwAesInit) + wc_AesFree(&hwAes); + if (swOut != NULL) + XFREE(swOut, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (hwOut != NULL) + XFREE(hwOut, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); +#endif + +#if defined(WC_ALTERA_FCS_HAVE_ECC) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) && !defined(NO_ECC256) + if (ret == 0) { + ecc_key key; + + ret = wc_ecc_init_ex(&key, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) { + if (wc_AlteraFcsEcc_MakeSigningKey(&key, ECC_SECP256R1) == 0 || + wc_AlteraFcsEcc_IsDeviceKey(&key) != 0) { + ret = WC_TEST_RET_ENC_NC; + } + wc_ecc_free(&key); + } + } +#endif + +#ifdef WOLFSSL_ALTERA_FCS_HMAC + if (ret == 0) { + word32 keyId = 0; + + if (wc_AlteraFcs_HmacMakeKey(256, &keyId) == 0 || keyId != 0) + ret = WC_TEST_RET_ENC_NC; + } +#endif + + if (ret == 0 && wc_AlteraFcs_TestHwGet() != 0) + ret = WC_TEST_RET_ENC_NC; + +#if defined(WOLFSSL_ALTERA_FCS_HASH) || defined(WOLFSSL_ALTERA_FCS_AES) + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return ret; +} + +#if defined(WOLFSSL_ALTERA_FCS_RNG) && !defined(WC_NO_RNG) +static wc_test_ret_t altera_fcs_rng_test(void) +{ + wc_test_ret_t ret = 0; + WC_RNG rng; + byte block1[64]; + byte block2[64]; + + XMEMSET(block1, 0, sizeof(block1)); + XMEMSET(block2, 0, sizeof(block2)); + wc_AlteraFcs_TestHwReset(); + + ret = wc_InitRng_ex(&rng, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + ret = wc_RNG_GenerateBlock(&rng, block1, (word32)sizeof(block1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_rng); + ret = wc_RNG_GenerateBlock(&rng, block2, (word32)sizeof(block2)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_rng); + + /* identical blocks would mean the device produced nothing */ + if (XMEMCMP(block1, block2, sizeof(block1)) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_rng); + if ((wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_RNG) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_rng); + +exit_fcs_rng: + wc_FreeRng(&rng); + return ret; +} +#endif /* WOLFSSL_ALTERA_FCS_RNG && !WC_NO_RNG */ + +#if defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256) +static wc_test_ret_t altera_fcs_hash_test(const byte* msg, word32 msgSz) +{ + wc_test_ret_t ret = 0; + wc_Sha256 hwSha; + wc_Sha256 swSha; + byte hwDig[WC_SHA256_DIGEST_SIZE]; + byte swDig[WC_SHA256_DIGEST_SIZE]; + int hwInit = 0; + int swInit = 0; + word32 sizes[2]; + word32 sz; + int i; + + wc_AlteraFcs_TestHwReset(); + /* the large message exceeds the offload threshold, the small one takes + * the port's software completion path; both must match plain software */ + sizes[0] = 64; + sizes[1] = msgSz; + for (i = 0; i < 2; i++) { + sz = sizes[i]; + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + hwInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + swInit = 1; + + ret = wc_Sha256Update(&hwSha, msg, sz); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, sz); + if (ret == 0) + ret = wc_Sha256FinalRaw(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256FinalRaw(&swSha, swDig); + if (ret == 0 && XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + + /* Final resets a SHA context for reuse. The callback's software shadow + * must follow the same contract. */ + ret = wc_Sha256Update(&hwSha, msg, 64); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, 64); + if (ret == 0) + ret = wc_Sha256FinalRaw(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256FinalRaw(&swSha, swDig); + if (ret == 0 && XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + + wc_Sha256Free(&hwSha); + wc_Sha256Free(&swSha); + hwInit = 0; + swInit = 0; + } + if (WOLFSSL_ALTERA_FCS_HASH_MIN <= 5000 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_HASH) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + + /* A context updated while the callback is absent remains owned by the + * software state even if the callback is registered before final. */ + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + hwInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + swInit = 1; + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + ret = wc_Sha256Update(&hwSha, msg, 64); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, 64); + if (ret == 0) + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret == 0) + ret = wc_Sha256Update(&hwSha, msg + 64, msgSz - 64); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg + 64, msgSz - 64); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + wc_Sha256Free(&hwSha); + wc_Sha256Free(&swSha); + hwInit = 0; + swInit = 0; + + /* Unregistration must not strand a message already owned by the port. */ + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + hwInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + swInit = 1; + ret = wc_Sha256Update(&hwSha, msg, msgSz); + if (ret == 0) + wc_AlteraFcsCryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, msgSz); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + wc_Sha256Free(&hwSha); + wc_Sha256Free(&swSha); + hwInit = 0; + swInit = 0; + + /* Finalizing the last active context must complete the deferred + * unregistration, and registering again must restore the callback. */ + if (wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_HASH)) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + + /* Copy a partially updated context and finalize both independently: the + * copy callback replaces wolfCrypt's own deep copy, so a shallow copy of + * either side's buffers shows up here. */ + { + wc_Sha256 copySha; + byte copyDig[WC_SHA256_DIGEST_SIZE]; + int copyInit = 0; + + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + hwInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + swInit = 1; + + ret = wc_Sha256Update(&hwSha, msg, 2048); + if (ret == 0) + ret = wc_InitSha256_ex(©Sha, HEAP_HINT, + WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + copyInit = 1; + + /* Copying over a live destination must release its existing state. */ + ret = wc_Sha256Update(©Sha, msg, 64); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + + ret = wc_Sha256Copy(&hwSha, &hwSha); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + ret = 0; + + ret = wc_Sha256Copy(&hwSha, ©Sha); + if (ret == 0) + ret = wc_Sha256Update(&hwSha, msg + 2048, 2048); + if (ret == 0) + ret = wc_Sha256Update(©Sha, msg + 2048, 2048); + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(©Sha, copyDig); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, 4096); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + if (copyInit) + wc_Sha256Free(©Sha); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0 || + XMEMCMP(copyDig, swDig, sizeof(swDig)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + + wc_Sha256Free(&hwSha); + wc_Sha256Free(&swSha); + hwInit = 0; + swInit = 0; + } + + /* Cross the single-request limit, then copy and finalize the streaming + * software state owned by the callback. */ + { + wc_Sha256 copySha; + byte copyDig[WC_SHA256_DIGEST_SIZE]; + word32 chunks = (WC_ALTERA_FCS_MAX_XFER / msgSz) + 1; + word32 j; + int copyInit = 0; + + ret = wc_InitSha256_ex(&hwSha, HEAP_HINT, + WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) { + hwInit = 1; + ret = wc_InitSha256_ex(&swSha, HEAP_HINT, INVALID_DEVID); + } + if (ret == 0) { + swInit = 1; + for (j = 0; j < chunks && ret == 0; j++) { + ret = wc_Sha256Update(&hwSha, msg, msgSz); + if (ret == 0) + ret = wc_Sha256Update(&swSha, msg, msgSz); + } + } + if (ret == 0) { + ret = wc_InitSha256_ex(©Sha, HEAP_HINT, + WOLFSSL_ALTERA_FCS_DEVID); + } + if (ret == 0) { + copyInit = 1; + ret = wc_Sha256Copy(&hwSha, ©Sha); + } + if (ret == 0) + ret = wc_Sha256Final(&hwSha, hwDig); + if (ret == 0) + ret = wc_Sha256Final(©Sha, copyDig); + if (ret == 0) + ret = wc_Sha256Final(&swSha, swDig); + + if (copyInit) + wc_Sha256Free(©Sha); + if (hwInit) { + wc_Sha256Free(&hwSha); + hwInit = 0; + } + if (swInit) { + wc_Sha256Free(&swSha); + swInit = 0; + } + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hash); + if (XMEMCMP(hwDig, swDig, sizeof(swDig)) != 0 || + XMEMCMP(copyDig, swDig, sizeof(swDig)) != 0) { + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hash); + } + } + +exit_fcs_hash: + if (hwInit) + wc_Sha256Free(&hwSha); + if (swInit) + wc_Sha256Free(&swSha); + return ret; +} +#endif /* WOLFSSL_ALTERA_FCS_HASH && !NO_SHA256 */ + +#if defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) +static wc_test_ret_t altera_fcs_aes_test(const byte* pt, byte* out1, + byte* out2) +{ + wc_test_ret_t ret = 0; + Aes hwAes; + Aes swAes; + int hwInit = 0; + int swInit = 0; + + WOLFSSL_SMALL_STACK_STATIC const byte key[32] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f + }; + WOLFSSL_SMALL_STACK_STATIC const byte key2[32] = { + 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, + 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7, + 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf + }; + WOLFSSL_SMALL_STACK_STATIC const byte keyShort[16] = { + 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7, + 0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf + }; + WOLFSSL_SMALL_STACK_STATIC const byte iv[16] = { + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f + }; + + wc_AlteraFcs_TestHwReset(); + ret = wc_AesInit(&hwAes, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + hwInit = 1; + ret = wc_AesInit(&swAes, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + swInit = 1; + + /* CBC encrypt, device against software */ + ret = wc_AesSetKey(&swAes, key, (word32)sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 4096); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 4096) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + + /* AES-128 uses a different SDM key-object size code than AES-256. */ + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKey(&swAes, keyShort, (word32)sizeof(keyShort), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 4096); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, keyShort, (word32)sizeof(keyShort), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 4096) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + + /* IV chaining across calls. Both halves must be large enough to reach the + * device, otherwise this silently tests the software fallback and the + * hardware IV maintenance goes unexercised. */ + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKey(&swAes, key, (word32)sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 8192); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, 4096); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1 + 4096, pt + 4096, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 8192) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + + /* Re-key an already imported context: a stale device key id would encrypt + * under the previous key. */ + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKey(&hwAes, key2, (word32)sizeof(key2), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, 4096); + if (ret == 0) + ret = wc_AesSetKey(&swAes, key2, (word32)sizeof(key2), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 4096) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + + /* Re-keying must retire the imported key immediately, even when the new + * key is used only below the hardware threshold. Successful unregister + * and re-register proves that no old device resource remains. */ + ret = wc_AesSetKey(&hwAes, keyShort, (word32)sizeof(keyShort), iv, + AES_ENCRYPTION); + if (ret == 0) + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + if (ret == 0) + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, WC_AES_BLOCK_SIZE); + if (ret == 0) + ret = wc_AesSetKey(&swAes, keyShort, (word32)sizeof(keyShort), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, WC_AES_BLOCK_SIZE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, WC_AES_BLOCK_SIZE) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + +#ifdef WOLFSSL_AES_DIRECT + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKeyDirect(&hwAes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&hwAes, out1, pt, 4096); + if (ret == 0) + ret = wc_AesSetKeyDirect(&swAes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 4096) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); +#endif + +#ifdef HAVE_AES_DECRYPT + /* Decrypt in place across two hardware calls. This exercises preservation + * of the last ciphertext block as well as the next-call IV. */ + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKey(&swAes, key, (word32)sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, out2, pt, 8192); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, (word32)sizeof(key), iv, + AES_DECRYPTION); + if (ret == 0) + ret = wc_AesCbcDecrypt(&hwAes, out2, out2, 4096); + if (ret == 0) + ret = wc_AesCbcDecrypt(&hwAes, out2 + 4096, out2 + 4096, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out2, pt, 8192) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); +#endif + +#ifdef WOLFSSL_AES_COUNTER + wc_AlteraFcs_TestHwReset(); + ret = wc_AesSetKey(&swAes, key, (word32)sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCtrEncrypt(&swAes, out2, pt, 8192); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, (word32)sizeof(key), iv, + AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCtrEncrypt(&hwAes, out1, pt, 4096); + if (ret == 0) + ret = wc_AesCtrEncrypt(&hwAes, out1 + 4096, pt + 4096, 4096); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + if (XMEMCMP(out1, out2, 8192) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + if (WOLFSSL_ALTERA_FCS_AES_MIN <= 4096 && + (wc_AlteraFcs_TestHwGet() & WC_ALTERA_FCS_TEST_HW_AES) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); +#endif + + /* An active device key must defer unregister until the context is freed. */ + wc_CryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + wc_AesFree(&swAes); + swInit = 0; + wc_AesFree(&hwAes); + hwInit = 0; + if (wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_AES)) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_aes); + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_aes); + +exit_fcs_aes: + if (swInit) + wc_AesFree(&swAes); + if (hwInit) + wc_AesFree(&hwAes); + return ret; +} +#endif /* WOLFSSL_ALTERA_FCS_AES && !NO_AES && HAVE_AES_CBC */ + +#if defined(WC_ALTERA_FCS_HAVE_ECC) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) && \ + !defined(WC_NO_RNG) && (!defined(NO_ECC256) || defined(HAVE_ECC384)) +static wc_test_ret_t altera_fcs_ecc_test(int curveId) +{ + wc_test_ret_t ret = 0; + WC_RNG rng; + ecc_key key; + byte hash[32]; + byte sig[ECC_MAX_SIG_SIZE]; + byte priv[66]; + word32 sigSz = (word32)sizeof(sig); + word32 privSz = (word32)sizeof(priv); + int isValid = 0; + int keyInit = 0; + int i; + + ret = wc_InitRng(&rng); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + for (i = 0; i < (int)sizeof(hash); i++) + hash[i] = (byte)i; + + ret = wc_ecc_init_ex(&key, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecc); + keyInit = 1; + + ret = wc_AlteraFcsEcc_MakeSigningKey(&key, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecc); + + if (wc_AlteraFcsEcc_IsDeviceKey(&key) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecc); + + /* the private scalar must not be exportable from a device key */ + if (wc_ecc_export_private_only(&key, priv, &privSz) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecc); + + ret = wc_ecc_sign_hash(hash, (word32)sizeof(hash), sig, &sigSz, &rng, + &key); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecc); + + /* verify declines to software, using only the public point */ + ret = wc_ecc_verify_hash(sig, sigSz, hash, (word32)sizeof(hash), + &isValid, &key); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecc); + if (isValid != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecc); + + hash[0] ^= 0xFF; + ret = wc_ecc_verify_hash(sig, sigSz, hash, (word32)sizeof(hash), + &isValid, &key); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecc); + if (isValid != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecc); + +exit_fcs_ecc: + if (keyInit) + wc_ecc_free(&key); + wc_FreeRng(&rng); + return ret; +} + +#if defined(HAVE_ECC_DHE) && defined(HAVE_ECC_KEY_EXPORT) +static wc_test_ret_t altera_fcs_ecdh_test(int curveId, int keySz) +{ + wc_test_ret_t ret = 0; + WC_RNG rng; + ecc_key devKey; + ecc_key peerKey; + ecc_key pubKey; + ecc_key signKey; + byte hash[32]; + byte sig[ECC_MAX_SIG_SIZE]; + byte pubX[48]; + byte pubY[48]; + byte secA[48]; + byte secB[48]; + word32 xSz; + word32 ySz; + word32 aSz; + word32 bSz; + word32 sigSz = (word32)sizeof(sig); + int devInit = 0; + int peerInit = 0; + int pubInit = 0; + int signInit = 0; + + xSz = (word32)keySz; + ySz = (word32)keySz; + aSz = (word32)keySz; + bSz = (word32)keySz; + + ret = wc_InitRng(&rng); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + ret = wc_ecc_init_ex(&devKey, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + devInit = 1; + + /* exchange usage has to be chosen at creation, hence the explicit API */ + ret = wc_AlteraFcsEcc_MakeExchangeKey(&devKey, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + XMEMSET(hash, 1, sizeof(hash)); + ret = wc_ecc_sign_hash(hash, (word32)sizeof(hash), sig, &sigSz, &rng, + &devKey); + if (ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecdh); + ret = 0; + + ret = wc_ecc_init_ex(&peerKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + peerInit = 1; + + ret = wc_ecc_make_key_ex(&rng, keySz, &peerKey, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + ret = wc_ecc_init_ex(&signKey, HEAP_HINT, WOLFSSL_ALTERA_FCS_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + signInit = 1; + ret = wc_AlteraFcsEcc_MakeSigningKey(&signKey, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + aSz = (word32)keySz; + ret = wc_ecc_shared_secret(&signKey, &peerKey, secA, &aSz); + if (ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecdh); + ret = 0; + wc_ecc_free(&signKey); + signInit = 0; + +#ifdef ECC_TIMING_RESISTANT + ret = wc_ecc_set_rng(&devKey, &rng); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + ret = wc_ecc_set_rng(&peerKey, &rng); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); +#endif + + /* Match the public size-query contract before the successful operation. */ + aSz = (word32)keySz - 1; + ret = wc_ecc_shared_secret(&devKey, &peerKey, secA, &aSz); + if (ret != WC_NO_ERR_TRACE(BUFFER_E) || aSz != (word32)keySz) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecdh); + + /* device side: SDM key with the peer public point */ + aSz = (word32)keySz; + ret = wc_ecc_shared_secret(&devKey, &peerKey, secA, &aSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + /* peer side: software, using the device public point */ + ret = wc_ecc_export_public_raw(&devKey, pubX, &xSz, pubY, &ySz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + ret = wc_ecc_init_ex(&pubKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + pubInit = 1; + + ret = wc_ecc_import_unsigned(&pubKey, pubX, pubY, NULL, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + ret = wc_ecc_shared_secret(&peerKey, &pubKey, secB, &bSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + + if (aSz != bSz || XMEMCMP(secA, secB, aSz) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecdh); + + xSz = (word32)keySz; + ySz = (word32)keySz; + ret = wc_ecc_export_public_raw(&peerKey, pubX, &xSz, pubY, &ySz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + XMEMSET(pubY, 0, ySz); + wc_ecc_free(&pubKey); + pubInit = 0; + ret = wc_ecc_init_ex(&pubKey, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + pubInit = 1; + ret = wc_ecc_import_unsigned(&pubKey, pubX, pubY, NULL, curveId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_ecdh); + aSz = (word32)keySz; + ret = wc_ecc_shared_secret(&devKey, &pubKey, secA, &aSz); + if (ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_ecdh); + ret = 0; + +exit_fcs_ecdh: + if (signInit) + wc_ecc_free(&signKey); + if (pubInit) + wc_ecc_free(&pubKey); + if (peerInit) + wc_ecc_free(&peerKey); + if (devInit) + wc_ecc_free(&devKey); + wc_FreeRng(&rng); + return ret; +} +#endif /* HAVE_ECC_DHE && HAVE_ECC_KEY_EXPORT */ +#endif /* WC_ALTERA_FCS_HAVE_ECC && HAVE_ECC && SIGN && VERIFY && RNG */ + +#if defined(WOLFSSL_ALTERA_FCS_HMAC) && !defined(NO_HMAC) && \ + (!defined(NO_SHA256) || defined(WOLFSSL_SHA384) || \ + defined(WOLFSSL_SHA512)) +static wc_test_ret_t altera_fcs_hmac_test(void) +{ + wc_test_ret_t ret = 0; + Hmac hmac; + byte key[64]; + byte data[64]; + byte mac[WC_MAX_DIGEST_SIZE]; + int hashTypes[3]; + int hmacTypes[3]; + int keySizes[3]; + word32 macSizes[3]; + word32 keyId = 0; + int isValid = 0; + int haveKey = 0; + int hashCount = 0; + int keyBits; + int digest; + int i; + + for (i = 0; i < (int)sizeof(key); i++) + key[i] = 0x0b; + for (i = 0; i < (int)sizeof(data); i++) + data[i] = (byte)i; + +#ifndef NO_SHA256 + hashTypes[hashCount] = WC_HASH_TYPE_SHA256; + hmacTypes[hashCount] = WC_SHA256; + keySizes[hashCount] = 256; + macSizes[hashCount++] = WC_SHA256_DIGEST_SIZE; +#endif +#ifdef WOLFSSL_SHA384 + hashTypes[hashCount] = WC_HASH_TYPE_SHA384; + hmacTypes[hashCount] = WC_SHA384; + keySizes[hashCount] = 384; + macSizes[hashCount++] = WC_SHA384_DIGEST_SIZE; +#endif +#ifdef WOLFSSL_SHA512 + hashTypes[hashCount] = WC_HASH_TYPE_SHA512; + hmacTypes[hashCount] = WC_SHA512; + keySizes[hashCount] = 512; + macSizes[hashCount++] = WC_SHA512_DIGEST_SIZE; +#endif + + for (digest = 0; digest < hashCount; digest++) { + keyBits = keySizes[digest]; + if (digest == 0) { + if (wc_AlteraFcs_HmacImportKey(NULL, keyBits, &keyId) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacImportKey(key, keyBits, NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacMakeKey(128, &keyId) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacMakeKey(keyBits, NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + } + } + ret = wc_AlteraFcs_HmacImportKey(key, keyBits, &keyId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + haveKey = 1; + + /* Zero is the empty tracker value and must never consume a resource. */ + if (wc_AlteraFcs_HmacRemoveKey(0) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + + ret = wc_HmacInit(&hmac, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + ret = wc_HmacSetKey(&hmac, hmacTypes[digest], key, + (word32)keyBits / 8); + if (ret == 0) + ret = wc_HmacUpdate(&hmac, data, (word32)sizeof(data)); + if (ret == 0) + ret = wc_HmacFinal(&hmac, mac); + wc_HmacFree(&hmac); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + + ret = wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), mac, + macSizes[digest], &isValid); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + if (isValid != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + + if (wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], NULL, 1, + mac, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), NULL, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), mac, macSizes[digest], NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId + 1, hashTypes[digest], data, + (word32)sizeof(data), mac, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, WC_HASH_TYPE_NONE, data, + (word32)sizeof(data), mac, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), mac, macSizes[digest] - 1, &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)0xFFFFFFFFU, mac, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + } + + mac[0] ^= 0xFF; + ret = wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), mac, + macSizes[digest], &isValid); + mac[0] ^= 0xFF; + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + if (isValid != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + + ret = wc_AlteraFcs_HmacRemoveKey(keyId); + haveKey = 0; + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + if (wc_AlteraFcs_HmacRemoveKey(keyId) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + wc_AlteraFcs_HmacVerify(keyId, hashTypes[digest], data, + (word32)sizeof(data), mac, macSizes[digest], &isValid) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + } + } + + /* The generate branch takes a different libfcs call than import. */ + ret = wc_AlteraFcs_HmacMakeKey(keySizes[hashCount - 1], &keyId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + haveKey = 1; + + /* A direct HMAC key owns a persistent session resource. Unregister must + * defer until removal, while direct operations needed to retire the key + * remain available. */ + wc_AlteraFcsCryptoCb_UnRegisterDevice(WOLFSSL_ALTERA_FCS_DEVID); + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) + ERROR_OUT(WC_TEST_RET_ENC_NC, exit_fcs_hmac); + ret = 0; + + /* the tag above was made with a different key, so it must not verify */ + isValid = 1; + ret = wc_AlteraFcs_HmacVerify(keyId, hashTypes[hashCount - 1], data, + (word32)sizeof(data), mac, + macSizes[hashCount - 1], &isValid); + if (ret == 0 && isValid != 0) + ret = WC_TEST_RET_ENC_NC; + if (ret == 0) + ret = wc_AlteraFcs_HmacRemoveKey(keyId); + if (ret == 0) + haveKey = 0; + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + ret = wc_AlteraFcsCryptoCb_RegisterDeviceMask( + WOLFSSL_ALTERA_FCS_DEVID, WOLFSSL_ALTERA_FCS_AUTO_MASK); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_fcs_hmac); + +exit_fcs_hmac: + if (haveKey) + (void)wc_AlteraFcs_HmacRemoveKey(keyId); + return ret; +} +#endif /* WOLFSSL_ALTERA_FCS_HMAC && !NO_HMAC && supported hash */ + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t altera_fcs_test(void) +{ + wc_test_ret_t ret = 0; +#if (defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256)) || \ + (defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC)) + byte* buf = NULL; + byte* out1 = NULL; + byte* out2 = NULL; + int i; +#endif + + WOLFSSL_ENTER("altera_fcs_test"); + +#if (defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256)) || \ + (defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC)) + buf = (byte*)XMALLOC(8192, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + out1 = (byte*)XMALLOC(8192, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + out2 = (byte*)XMALLOC(8192, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL || out1 == NULL || out2 == NULL) { + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + } + else { + for (i = 0; i < 8192; i++) + buf[i] = (byte)(i & 0xFF); + } +#endif + +#if defined(WOLFSSL_ALTERA_FCS_RNG) && !defined(WC_NO_RNG) + if (ret == 0 && wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_RNG)) + ret = altera_fcs_rng_test(); +#endif +#if defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256) + if (ret == 0 && wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_HASH)) + ret = altera_fcs_hash_test(buf, 5000); +#endif +#if defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) + if (ret == 0 && wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_AES)) + ret = altera_fcs_aes_test(buf, out1, out2); +#endif +#if defined(WC_ALTERA_FCS_HAVE_ECC) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) && \ + !defined(WC_NO_RNG) && (!defined(NO_ECC256) || defined(HAVE_ECC384)) + if (ret == 0 && wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_ECC)) { +#ifndef NO_ECC256 + ret = altera_fcs_ecc_test(ECC_SECP256R1); +#endif +#ifdef HAVE_ECC384 + if (ret == 0) + ret = altera_fcs_ecc_test(ECC_SECP384R1); +#endif + } +#if defined(HAVE_ECC_DHE) && defined(HAVE_ECC_KEY_EXPORT) + if (ret == 0 && wc_AlteraFcs_AlgoEnabled(WC_ALTERA_FCS_ALGO_ECC)) { +#ifndef NO_ECC256 + ret = altera_fcs_ecdh_test(ECC_SECP256R1, 32); +#endif +#ifdef HAVE_ECC384 + if (ret == 0) + ret = altera_fcs_ecdh_test(ECC_SECP384R1, 48); +#endif + } +#endif +#endif +#if defined(WOLFSSL_ALTERA_FCS_HMAC) && !defined(NO_HMAC) && \ + (!defined(NO_SHA256) || defined(WOLFSSL_SHA384) || \ + defined(WOLFSSL_SHA512)) + if (ret == 0 && + wc_AlteraFcs_AlgoEnabled(WOLFSSL_ALTERA_FCS_AUTO_MASK)) { + ret = altera_fcs_hmac_test(); + } +#endif + +#if (defined(WOLFSSL_ALTERA_FCS_HASH) && !defined(NO_SHA256)) || \ + (defined(WOLFSSL_ALTERA_FCS_AES) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC)) + if (out2 != NULL) + XFREE(out2, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (out1 != NULL) + XFREE(out1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (buf != NULL) + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} +#endif /* WOLFSSL_ALTERA_FCS */ + #ifdef WOLF_CRYPTO_CB /* Example custom context for crypto callback */ @@ -81200,6 +82412,77 @@ static int myCryptoCbFind(int currentId, int algoType) } #endif /* WOLF_CRYPTO_CB_FIND */ +#ifdef WOLF_CRYPTO_CB_CMD +#define CRYPTOCB_BUSY_TEST_DEVID 0x43425553 +typedef struct CryptoCbBusyTestCtx { + int busy; +} CryptoCbBusyTestCtx; + +static int cryptoCbBusyTestCb(int devIdArg, wc_CryptoInfo* info, void* ctx) +{ + CryptoCbBusyTestCtx* testCtx = (CryptoCbBusyTestCtx*)ctx; + + (void)devIdArg; + if (info == NULL || testCtx == NULL) { + return BAD_FUNC_ARG; + } + if (info->algo_type == WC_ALGO_TYPE_NONE && + info->cmd.type == WC_CRYPTOCB_CMD_TYPE_UNREGISTER && + testCtx->busy) { + testCtx->busy = 0; + return BUSY_E; + } + return 0; +} + +static wc_test_ret_t cryptoCbBusyUnregisterTest(void) +{ + CryptoCbBusyTestCtx testCtx; + int ret; + + testCtx.busy = 1; + ret = wc_CryptoCb_RegisterDevice(CRYPTOCB_BUSY_TEST_DEVID, + cryptoCbBusyTestCb, &testCtx); + if (ret != 0) { + return WC_TEST_RET_ENC_EC(ret); + } + wc_CryptoCb_UnRegisterDevice(CRYPTOCB_BUSY_TEST_DEVID); + if (!wc_CryptoCb_IsDeviceRegistered(CRYPTOCB_BUSY_TEST_DEVID)) { + return WC_TEST_RET_ENC_NC; + } + wc_CryptoCb_UnRegisterDevice(CRYPTOCB_BUSY_TEST_DEVID); + if (wc_CryptoCb_IsDeviceRegistered(CRYPTOCB_BUSY_TEST_DEVID)) { + return WC_TEST_RET_ENC_NC; + } + + testCtx.busy = 1; + ret = wc_CryptoCb_RegisterDevice(CRYPTOCB_BUSY_TEST_DEVID, + cryptoCbBusyTestCb, &testCtx); + if (ret != 0) { + return WC_TEST_RET_ENC_EC(ret); + } + ret = wolfCrypt_Cleanup(); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) { + return WC_TEST_RET_ENC_NC; + } + if (!wc_CryptoCb_IsDeviceRegistered(CRYPTOCB_BUSY_TEST_DEVID)) { + return WC_TEST_RET_ENC_NC; + } + ret = wolfCrypt_Cleanup(); + if (ret != 0) { + return WC_TEST_RET_ENC_EC(ret); + } + if (wc_CryptoCb_IsDeviceRegistered(CRYPTOCB_BUSY_TEST_DEVID)) { + return WC_TEST_RET_ENC_NC; + } + ret = wolfCrypt_Init(); + if (ret != 0) { + return WC_TEST_RET_ENC_EC(ret); + } + return 0; +} +#endif /* WOLF_CRYPTO_CB_CMD */ + #if !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) @@ -81209,6 +82492,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) myCryptoDevCtx myCtx; WOLFSSL_ENTER("cryptocb_test"); +#ifdef WOLF_CRYPTO_CB_CMD + ret = cryptoCbBusyUnregisterTest(); +#endif + /* example data for callback */ myCtx.exampleVar = 1; #ifdef HAVE_ECC @@ -81223,7 +82510,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) /* set devId to something other than INVALID_DEVID */ devId = 1; - ret = wc_CryptoCb_RegisterDevice(devId, myCryptoDevCb, &myCtx); + if (ret == 0) + ret = wc_CryptoCb_RegisterDevice(devId, myCryptoDevCb, &myCtx); if (ret != 0) ret = WC_TEST_RET_ENC_EC(ret); /* don't overwrite find cb when using WOLFSSL_SWDEV */ diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index bdce20d8b1..b0a16a4afe 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -782,7 +782,7 @@ typedef int (*CryptoDevCallbackFunc)(int devId, struct wc_CryptoInfo* info, void #endif WOLFSSL_LOCAL void wc_CryptoCb_Init(void); -WOLFSSL_LOCAL void wc_CryptoCb_Cleanup(void); +WOLFSSL_LOCAL int wc_CryptoCb_Cleanup(void); WOLFSSL_LOCAL int wc_CryptoCb_GetDevIdAtIndex(int startIdx); WOLFSSL_API int wc_CryptoCb_RegisterDevice(int devId, CryptoDevCallbackFunc cb, void* ctx); WOLFSSL_API void wc_CryptoCb_UnRegisterDevice(int devId); diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 6deb44ebc9..67a2f9b89a 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -2,6 +2,7 @@ # All paths should be given relative to the root nobase_include_HEADERS+= \ + wolfssl/wolfcrypt/port/altera/altera_fcs.h \ wolfssl/wolfcrypt/aes.h \ wolfssl/wolfcrypt/arc4.h \ wolfssl/wolfcrypt/ascon.h \ diff --git a/wolfssl/wolfcrypt/port/altera/altera_fcs.h b/wolfssl/wolfcrypt/port/altera/altera_fcs.h new file mode 100644 index 0000000000..5e86aaf853 --- /dev/null +++ b/wolfssl/wolfcrypt/port/altera/altera_fcs.h @@ -0,0 +1,218 @@ +/* altera_fcs.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLF_CRYPT_PORT_ALTERA_FCS_H +#define WOLF_CRYPT_PORT_ALTERA_FCS_H + +#include + +#ifdef WOLFSSL_ALTERA_FCS + +#include + +/* These backends deliberately decline operations the SDM cannot service. The + * corresponding software implementations must therefore remain available. */ +#if defined(WOLFSSL_ALTERA_FCS_HASH) && \ + defined(WOLF_CRYPTO_CB_ONLY_SHA256) + #error "Altera FCS hash requires software SHA-256 fallback" +#endif +#if defined(WOLFSSL_ALTERA_FCS_AES) && defined(WOLF_CRYPTO_CB_ONLY_AES) + #error "Altera FCS AES requires software AES fallback" +#endif +#if defined(WOLFSSL_ALTERA_FCS_ECC) && defined(WOLF_CRYPTO_CB_ONLY_ECC) + #error "Altera FCS ECC requires software ECC fallback" +#endif +#if defined(WOLFSSL_ALTERA_FCS_ECC) && \ + defined(NO_ECC_CHECK_PUBKEY_ORDER) + #error "Altera FCS ECC requires public key order validation" +#endif + +/* ECC is an optional part of the combined FCS build. Raw point and signature + * conversion require both key import and export. */ +#if defined(WOLFSSL_ALTERA_FCS_ECC) && \ + defined(HAVE_ECC_KEY_IMPORT) && defined(HAVE_ECC_KEY_EXPORT) && \ + !defined(NO_ASN) + #define WC_ALTERA_FCS_HAVE_ECC +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Default devId for the SDM. Callers opt in explicitly: registering on + * INVALID_DEVID would route every hash in the process through one chip-wide + * hardware session. */ +#ifndef WOLFSSL_ALTERA_FCS_DEVID + #define WOLFSSL_ALTERA_FCS_DEVID 0x4143 +#endif + +/* Largest single SDM transaction (FCS_CRYPTO_BLOCK_SZ in the kernel driver). */ +#define WC_ALTERA_FCS_MAX_XFER (4 * 1024 * 1024) + +/* Requests below these configurable thresholds remain in software. */ +#ifndef WOLFSSL_ALTERA_FCS_HASH_MIN + #define WOLFSSL_ALTERA_FCS_HASH_MIN 4096 +#endif +#ifndef WOLFSSL_ALTERA_FCS_AES_MIN + #define WOLFSSL_ALTERA_FCS_AES_MIN 4096 +#endif + +/* Context id tagging every request this port makes. */ +#ifndef WOLFSSL_ALTERA_FCS_CTX_ID + #define WOLFSSL_ALTERA_FCS_CTX_ID 0x574F4C46 +#endif + +/* First key id handed out. Must be non-zero, the device rejects id 0. */ +#ifndef WOLFSSL_ALTERA_FCS_KEY_ID_BASE + #define WOLFSSL_ALTERA_FCS_KEY_ID_BASE 0x57420001 +#endif + +WOLFSSL_LOCAL int wc_AlteraFcs_Init(void); +WOLFSSL_LOCAL int wc_AlteraFcs_Cleanup(void); +WOLFSSL_LOCAL int wc_AlteraFcs_SessionAcquire(void** sessionId); +WOLFSSL_LOCAL void wc_AlteraFcs_SessionRelease(void); +WOLFSSL_LOCAL int wc_AlteraFcs_MapError(int fcsRet); +WOLFSSL_LOCAL int wc_AlteraFcs_KeyIdNew(word32* keyId); +WOLFSSL_LOCAL int wc_AlteraFcs_RemoveServiceKey(word32 keyId); +WOLFSSL_LOCAL int wc_AlteraFcs_OrphanKey(word32 keyId); +WOLFSSL_LOCAL void wc_AlteraFcs_DiscardServiceKey(word32 keyId); +WOLFSSL_LOCAL int wc_AlteraFcs_ResourceAcquire(void); +WOLFSSL_LOCAL void wc_AlteraFcs_ResourceAdd(void); +WOLFSSL_LOCAL void wc_AlteraFcs_ResourceRemove(void); +WOLFSSL_LOCAL int wc_AlteraFcs_ResourceActive(void); +WOLFSSL_LOCAL void wc_AlteraFcsCryptoCb_UnRegisterPending(void); +WOLFSSL_LOCAL int wc_AlteraFcsCryptoCb_UnRegisterDeviceEx(int devId); +WOLFSSL_LOCAL int wc_AlteraFcs_UnregisterPending(void); +WOLFSSL_LOCAL int wc_AlteraFcs_RegisterActive(void); +WOLFSSL_LOCAL void wc_AlteraFcs_StateAtForkPrepare(void); +WOLFSSL_LOCAL void wc_AlteraFcs_StateAtForkParent(void); +WOLFSSL_LOCAL void wc_AlteraFcs_StateAtForkChild(void); +WOLFSSL_LOCAL void wc_AlteraFcs_StateForkChildReset(void); +WOLFSSL_API int wc_AlteraFcs_AlgoEnabled(word32 algoMask); +/* Non-zero when the process can open the shared SDM service session. */ +WOLFSSL_API int wc_AlteraFcs_HardwareAvailable(void); +WOLFSSL_API void wc_AlteraFcs_TestHwReset(void); +WOLFSSL_API word32 wc_AlteraFcs_TestHwGet(void); + +#define WC_ALTERA_FCS_TEST_HW_RNG 0x01 +#define WC_ALTERA_FCS_TEST_HW_HASH 0x02 +#define WC_ALTERA_FCS_TEST_HW_AES 0x04 +WOLFSSL_LOCAL void wc_AlteraFcs_TestHwMark(word32 operation); + +/* Which algorithm classes the callback accepts. Signing and key exchange are + * the operations that gain key isolation; hashing and AES with a plaintext key + * do not, and both are slower on the device than in software, so a deployment + * can register a subset. */ +#define WC_ALTERA_FCS_ALGO_RNG 0x01 +#define WC_ALTERA_FCS_ALGO_HASH 0x02 +#define WC_ALTERA_FCS_ALGO_AES 0x04 +#define WC_ALTERA_FCS_ALGO_ECC 0x08 +#ifdef WOLFSSL_ALTERA_FCS_RNG + #define WC_ALTERA_FCS_HAVE_RNG_MASK WC_ALTERA_FCS_ALGO_RNG +#else + #define WC_ALTERA_FCS_HAVE_RNG_MASK 0 +#endif +#ifdef WOLFSSL_ALTERA_FCS_HASH + #define WC_ALTERA_FCS_HAVE_HASH_MASK WC_ALTERA_FCS_ALGO_HASH +#else + #define WC_ALTERA_FCS_HAVE_HASH_MASK 0 +#endif +#ifdef WOLFSSL_ALTERA_FCS_AES + #define WC_ALTERA_FCS_HAVE_AES_MASK WC_ALTERA_FCS_ALGO_AES +#else + #define WC_ALTERA_FCS_HAVE_AES_MASK 0 +#endif +#ifdef WC_ALTERA_FCS_HAVE_ECC + #define WC_ALTERA_FCS_HAVE_ECC_MASK WC_ALTERA_FCS_ALGO_ECC +#else + #define WC_ALTERA_FCS_HAVE_ECC_MASK 0 +#endif +#define WC_ALTERA_FCS_ALGO_ALL (WC_ALTERA_FCS_HAVE_RNG_MASK | \ + WC_ALTERA_FCS_HAVE_HASH_MASK | \ + WC_ALTERA_FCS_HAVE_AES_MASK | \ + WC_ALTERA_FCS_HAVE_ECC_MASK) + +/* Mask used by the automatic registration in wolfCrypt_Init(). Deployments + * that only want the operations gaining key isolation can build with + * WOLFSSL_ALTERA_FCS_AUTO_MASK set to, say, ECC | RNG so TLS keeps its + * transcript hashing and record ciphers on the faster software paths. */ +#ifndef WOLFSSL_ALTERA_FCS_AUTO_MASK + #define WOLFSSL_ALTERA_FCS_AUTO_MASK WC_ALTERA_FCS_ALGO_ALL +#endif + +/* The SDM uses WOLFSSL_ALTERA_FCS_DEVID exclusively. The mask is fixed once + * registered because live contexts may hold state owned by the port. */ +WOLFSSL_API int wc_AlteraFcsCryptoCb_RegisterDevice(int devId); +WOLFSSL_API int wc_AlteraFcsCryptoCb_RegisterDeviceMask(int devId, + word32 algoMask); +WOLFSSL_API void wc_AlteraFcsCryptoCb_UnRegisterDevice(int devId); + +#ifdef WOLFSSL_ALTERA_FCS_RNG +WOLFSSL_LOCAL int wc_AlteraFcs_Rng(wc_CryptoInfo* info); +#endif + +#ifdef WOLFSSL_ALTERA_FCS_HASH +WOLFSSL_LOCAL int wc_AlteraFcs_Hash(wc_CryptoInfo* info); +#endif + +#ifdef WOLFSSL_ALTERA_FCS_AES +WOLFSSL_LOCAL int wc_AlteraFcs_Aes(wc_CryptoInfo* info); +#endif + +#ifdef WC_ALTERA_FCS_HAVE_ECC +WOLFSSL_LOCAL int wc_AlteraFcs_Ecc(wc_CryptoInfo* info); +/* Non-zero when the private key lives inside the SDM rather than in HPS + * memory. Callers that depend on key isolation should assert this. */ +WOLFSSL_API int wc_AlteraFcsEcc_IsDeviceKey(const ecc_key* key); +/* Device resident keys are created explicitly rather than through + * wc_ecc_make_key_ex, because an SDM key object must commit to Sign/Verify or + * Exchange usage at creation and the two are mutually exclusive. Sign and ECDH + * are then offloaded automatically for keys made this way. */ +#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) +WOLFSSL_API int wc_AlteraFcsEcc_MakeSigningKey(ecc_key* key, int curveId); +#endif +#ifdef HAVE_ECC_DHE +WOLFSSL_API int wc_AlteraFcsEcc_MakeExchangeKey(ecc_key* key, int curveId); +#endif +#endif + +#ifdef WOLFSSL_ALTERA_FCS_HMAC +/* MAC verification under a key the HPS cannot read. Not a crypto callback: the + * device only verifies, returning a verdict rather than a tag, while wolfSSL's + * HMAC API generates one, so there is no matching entry point to hook. The SDM + * supports matched key/digest sizes: 256/SHA-256, 384/SHA-384, and + * 512/SHA-512. */ +WOLFSSL_API int wc_AlteraFcs_HmacMakeKey(int keyBits, word32* keyId); +WOLFSSL_API int wc_AlteraFcs_HmacImportKey(const byte* key, int keyBits, + word32* keyId); +WOLFSSL_API int wc_AlteraFcs_HmacRemoveKey(word32 keyId); +WOLFSSL_API int wc_AlteraFcs_HmacVerify(word32 keyId, int hashType, + const byte* data, word32 dataSz, + const byte* mac, word32 macSz, + int* isValid); +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* WOLFSSL_ALTERA_FCS */ +#endif /* WOLF_CRYPT_PORT_ALTERA_FCS_H */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index e234ba0667..0a8aaf4ad2 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -5800,6 +5800,12 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #endif /* Crypto Callback Rules */ +#if defined(WOLFSSL_ALTERA_FCS) && !defined(WOLF_CRYPTO_CB_CMD) + #define WOLF_CRYPTO_CB_CMD +#endif +#if defined(WOLFSSL_ALTERA_FCS) && !defined(WOLF_CRYPTO_CB_SETKEY) + #define WOLF_CRYPTO_CB_SETKEY +#endif #if defined(WC_TEST_NO_CRYPTOCB_SW_TEST) && !defined(WOLF_CRYPTO_CB) #error "Crypto callback SW test" \ " (WC_TEST_NO_CRYPTOCB_SW_TEST)" \