diff --git a/.github/workflows/sec-qoriq.yml b/.github/workflows/sec-qoriq.yml new file mode 100644 index 00000000000..9948c412825 --- /dev/null +++ b/.github/workflows/sec-qoriq.yml @@ -0,0 +1,74 @@ +name: NXP QorIQ SEC port + +# 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 simulated backend (--enable-sec-qoriq=sim) models the SEC job ring on +# the build host, so the driver's submission, timeout, reset-escalation and +# status-decoding logic plus AES/GCM/hash/RNG/modexp known answers run under +# make check with fault injection and no hardware. The cross jobs keep the +# real PowerPC backends compiling. A real-hardware smoke gate (T2080/T1040 +# known-answer harness) remains outside CI; see the port README. +jobs: + sec_qoriq_sim: + name: simulated backend, autotools and cmake + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + # cmake first: it builds from the pristine checkout and relies on the + # in-tree wolfssl/version.h, which an autotools "make distclean" would + # have removed. cmake removes a stale in-tree wolfssl/options.h itself. + - name: cmake build and wolfCrypt test + run: | + cmake -B build-cmake -DWOLFSSL_SEC_QORIQ=sim -DWOLFSSL_EXAMPLES=no + cmake --build build-cmake -j + cd build-cmake && ./wolfcrypt/test/testwolfcrypt + + - name: autotools build and check + run: | + ./autogen.sh + ./configure --enable-sec-qoriq=sim --enable-aesctr \ + --enable-aesecb --enable-keygen + make -j + make check + + sec_qoriq_cross: + name: PowerPC cross-compile, ${{ matrix.backend }} backend + 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: + backend: [ baremetal, linux ] + steps: + - uses: actions/checkout@v4 + + - name: install PowerPC toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-powerpc-linux-gnu + + - name: cross-compile the library + run: | + ./autogen.sh + ./configure --host=powerpc-linux-gnu CC=powerpc-linux-gnu-gcc \ + --enable-sec-qoriq=${{ matrix.backend }} --disable-shared \ + --disable-examples --disable-crypttests + make -j src/libwolfssl.la diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index ae97fc045f1..730b98b7aa0 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -1129,6 +1129,10 @@ WOLFSSL_SE050_NO_RSA_VERIFY WOLFSSL_SE050_NO_TRNG WOLFSSL_SE050_SCP03_ROTATE WOLFSSL_SECURE_RENEGOTIATION_ON_BY_DEFAULT +WOLFSSL_SEC_QORIQ_NO_CRYPTOCB +WOLFSSL_SEC_QORIQ_NO_PKHA +WOLFSSL_SEC_QORIQ_NO_RSA +WOLFSSL_SEC_QORIQ_SWAP_REGS WOLFSSL_SERVER_EXAMPLE WOLFSSL_SETTINGS_FILE WOLFSSL_SGX_CPUID_AVX512_VAES diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aef3bbc3ca..70ebdf69c19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1660,6 +1660,10 @@ add_option("WOLFSSL_KEYGEN" "Enable key generation (default: disabled)" "no" "yes;no") +add_option("WOLFSSL_DH_GEN_PARAMS" + "Enable DH domain parameter generation with key generation -- protocols use the fixed FFDHE groups (default: enabled)" + "yes" "yes;no") + add_option("WOLFSSL_CERTGEN" "Enable cert generation (default: disabled)" "no" "yes;no") @@ -3188,6 +3192,9 @@ endif() if(WOLFSSL_KEYGEN) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_KEY_GEN") + if(NOT WOLFSSL_DH_GEN_PARAMS) + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_NO_DH_GEN_PARAMS") + endif() endif() if(WOLFSSL_CERTGEN) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CERT_GEN") @@ -3268,6 +3275,28 @@ if (WOLFSSL_CAAM) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_CAAM") endif() +# NXP QorIQ SEC (the PowerPC T-series security engine). The value selects +# the environment backend, mirroring --enable-sec-qoriq: "yes"/"baremetal" +# for a flat physically addressed target, "linux" for user space on a +# running kernel, "sim" for the host-runnable simulated backend the in-tree +# tests use. +add_option("WOLFSSL_SEC_QORIQ" + "Enable NXP QorIQ SEC engine, T1040/T2080 (default: disabled)" + "no" "yes;no;baremetal;linux;sim") +if (WOLFSSL_SEC_QORIQ AND NOT WOLFSSL_SEC_QORIQ STREQUAL "no") + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_SEC_QORIQ") + if (WOLFSSL_SEC_QORIQ STREQUAL "linux") + set(WOLFSSL_SEC_QORIQ_LINUX "yes") + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_SEC_QORIQ_LINUX") + elseif (WOLFSSL_SEC_QORIQ STREQUAL "sim") + set(WOLFSSL_SEC_QORIQ_SIM "yes") + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_SEC_QORIQ_SIM") + else() + set(WOLFSSL_SEC_QORIQ_BAREMETAL "yes") + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_SEC_QORIQ_BAREMETAL") + endif() +endif() + if (WOLFSSL_ARIA) list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_ARIA") endif() diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 6c5fa97658f..43834cf39ae 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -359,7 +359,10 @@ function(generate_build_flags) set(BUILD_MCAPI ${WOLFSSL_MCAPI} PARENT_SCOPE) set(BUILD_ASYNCCRYPT ${WOLFSSL_ASYNCCRYPT} PARENT_SCOPE) set(BUILD_WOLFEVENT ${WOLFSSL_ASYNCCRYPT} PARENT_SCOPE) - if(WOLFSSL_CRYPTOCB OR WOLFSSL_USER_SETTINGS) + if(WOLFSSL_CRYPTOCB OR WOLFSSL_USER_SETTINGS OR + (WOLFSSL_SEC_QORIQ AND NOT WOLFSSL_SEC_QORIQ STREQUAL "no")) + # settings.h forces WOLF_CRYPTO_CB on for the QorIQ SEC port, so the + # dispatcher source has to come along too. set(BUILD_CRYPTOCB "yes" PARENT_SCOPE) endif() if(WOLFSSL_VAULTIC) @@ -403,6 +406,9 @@ function(generate_build_flags) if(WOLFSSL_CAAM) set(BUILD_CAAM "yes" PARENT_SCOPE) endif() + if(WOLFSSL_SEC_QORIQ AND NOT WOLFSSL_SEC_QORIQ STREQUAL "no") + set(BUILD_SEC_QORIQ "yes" PARENT_SCOPE) + endif() if(WOLFSSL_HPKE OR WOLFSSL_USER_SETTINGS) set(BUILD_HPKE "yes" PARENT_SCOPE) endif() @@ -1342,6 +1348,19 @@ function(generate_lib_src_list LIB_SOURCES) list(APPEND LIB_SOURCES wolfcrypt/src/port/sealsq/vaultic.c) endif() + if(BUILD_SEC_QORIQ) + list(APPEND LIB_SOURCES + wolfcrypt/src/port/nxp/sec_qoriq.c + wolfcrypt/src/port/nxp/sec_qoriq_cb.c + wolfcrypt/src/port/nxp/sec_qoriq_hash.c + wolfcrypt/src/port/nxp/sec_qoriq_aes.c + wolfcrypt/src/port/nxp/sec_qoriq_rng.c + wolfcrypt/src/port/nxp/sec_qoriq_pkha.c + wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c + wolfcrypt/src/port/nxp/sec_qoriq_linux.c + wolfcrypt/src/port/nxp/sec_qoriq_sim.c) + endif() + if(BUILD_CAAM) list(APPEND LIB_SOURCES wolfcrypt/src/port/caam/wolfcaam_init.c diff --git a/cmake/options.h.in b/cmake/options.h.in index bcf1aaef3e2..30dc2982e7a 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -347,6 +347,9 @@ extern "C" { #cmakedefine WOLFSSL_IP_ALT_NAME #undef WOLFSSL_KEY_GEN #cmakedefine WOLFSSL_KEY_GEN + +#undef WOLFSSL_NO_DH_GEN_PARAMS +#cmakedefine WOLFSSL_NO_DH_GEN_PARAMS #undef WOLFSSL_NO_ASM #cmakedefine WOLFSSL_NO_ASM #undef WOLFSSL_NO_SHAKE128 @@ -363,6 +366,14 @@ extern "C" { #cmakedefine WOLFSSL_PUBLIC_MP #undef WOLFSSL_QUIC #cmakedefine WOLFSSL_QUIC +#undef WOLFSSL_SEC_QORIQ +#cmakedefine WOLFSSL_SEC_QORIQ +#undef WOLFSSL_SEC_QORIQ_BAREMETAL +#cmakedefine WOLFSSL_SEC_QORIQ_BAREMETAL +#undef WOLFSSL_SEC_QORIQ_LINUX +#cmakedefine WOLFSSL_SEC_QORIQ_LINUX +#undef WOLFSSL_SEC_QORIQ_SIM +#cmakedefine WOLFSSL_SEC_QORIQ_SIM #undef WOLFSSL_SEND_HRR_COOKIE #cmakedefine WOLFSSL_SEND_HRR_COOKIE #undef WOLFSSL_SHA224 diff --git a/configure.ac b/configure.ac index 68f50334240..4efdb28ae77 100644 --- a/configure.ac +++ b/configure.ac @@ -4977,6 +4977,40 @@ then done fi +# NXP QorIQ SEC (the PowerPC T-series security engine, a CAAM derivative). +# Takes a comma separated list selecting the environment backend, e.g. +# --enable-sec-qoriq=baremetal +# "sim" selects the host-runnable simulated backend used by the in-tree +# tests; it needs no hardware and runs on any architecture. +AC_ARG_ENABLE([sec-qoriq], + [AS_HELP_STRING([--enable-sec-qoriq],[Enable wolfSSL support for the NXP QorIQ SEC engine, T1040/T2080 (default: disabled)])], + [ ENABLED_SEC_QORIQ=$enableval ], + [ ENABLED_SEC_QORIQ=no ] + ) + +if test "$ENABLED_SEC_QORIQ" != "no" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SEC_QORIQ" + + for v in `echo $ENABLED_SEC_QORIQ | tr "," " "` + do + case $v in + yes | baremetal) + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SEC_QORIQ_BAREMETAL" + ;; + linux) + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SEC_QORIQ_LINUX" + ;; + sim) + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_SEC_QORIQ_SIM" + ;; + *) + AC_MSG_ERROR([Invalid choice for --enable-sec-qoriq: $v (want baremetal, linux or sim)]) + ;; + esac + done +fi + AC_ARG_ENABLE([caam], [AS_HELP_STRING([--enable-caam],[Enable wolfSSL support for CAAM (default: disabled)])], [ ENABLED_CAAM=$enableval ], @@ -5804,11 +5838,36 @@ fi # KEY GENERATION AC_ARG_ENABLE([keygen], - [AS_HELP_STRING([--enable-keygen],[Enable key generation (only applies to RSA key generation) (default: disabled)])], + [AS_HELP_STRING([--enable-keygen],[Enable key generation. Takes yes, no, or a comma separated list of: all, no-dh-params (drop DH domain parameter generation, keeping DH key generation; protocols use the fixed FFDHE groups) (default: disabled)])], [ ENABLED_KEYGEN=$enableval ], [ ENABLED_KEYGEN=no ] ) +# Expand the list form of --enable-keygen into the individual sub-features. +ENABLED_DH_GEN_PARAMS=yes +if test "$ENABLED_KEYGEN" != "no" && test "$ENABLED_KEYGEN" != "yes" +then + for kg in `echo $ENABLED_KEYGEN | tr ',' ' '` + do + case $kg in + all) + ;; + no-dh-params | nodhparams | nodhparamgen) + ENABLED_DH_GEN_PARAMS=no + ;; + *) + AC_MSG_ERROR([Invalid choice for --enable-keygen: $kg. Use yes, no, all or no-dh-params.]) + ;; + esac + done + ENABLED_KEYGEN=yes +fi + +if test "$ENABLED_DH_GEN_PARAMS" = "no" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_NO_DH_GEN_PARAMS" +fi + if test "$ENABLED_BIND" = "yes" || test "$ENABLED_NTP" = "yes" || \ test "$ENABLED_LIBSSH2" = "yes" || test "$ENABLED_OPENRESTY" = "yes" || \ test "$ENABLED_NGINX" = "yes" || test "$ENABLED_WOLFENGINE" = "yes" || \ @@ -11995,7 +12054,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_SILABS_CRYPTOCB" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" +if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "$ENABLED_SEC_QORIQ" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_SILABS_CRYPTOCB" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" then ENABLED_CRYPTOCB=yes fi @@ -14099,6 +14158,7 @@ AM_CONDITIONAL([BUILD_BENCHMARK],[test "$ENABLED_BENCHMARK" = "yes"]) AM_CONDITIONAL([BUILD_RC2],[test "x$ENABLED_RC2" = "xyes"]) AM_CONDITIONAL([BUILD_CUDA],[test "x$ENABLED_CUDA" = "xyes"]) AM_CONDITIONAL([BUILD_CAAM],[test "x$ENABLED_CAAM" != "xno"]) +AM_CONDITIONAL([BUILD_SEC_QORIQ],[test "x$ENABLED_SEC_QORIQ" != "xno"]) AM_CONDITIONAL([BUILD_QNXCAAM],[test "x$ENABLED_CAAM_QNX" = "xyes"]) AM_CONDITIONAL([BUILD_CAAM_LINUX],[test "x$ENABLED_CAAM_LINUX" = "xyes"]) AM_CONDITIONAL([BUILD_IOTSAFE],[test "x$ENABLED_IOTSAFE" = "xyes"]) @@ -14525,6 +14585,7 @@ echo " * BLAKE2S: $ENABLED_BLAKE2S" echo " * SipHash: $ENABLED_SIPHASH" echo " * CMAC: $ENABLED_CMAC" echo " * keygen: $ENABLED_KEYGEN" +echo " * DH parameter generation: $ENABLED_DH_GEN_PARAMS" echo " * acert: $ENABLED_ACERT" echo " * certgen: $ENABLED_CERTGEN" echo " * certreq: $ENABLED_CERTREQ" diff --git a/src/pk.c b/src/pk.c index a7343eca8bc..5654385b279 100644 --- a/src/pk.c +++ b/src/pk.c @@ -4693,11 +4693,17 @@ int wolfSSL_DH_generate_parameters_ex(WOLFSSL_DH* dh, int prime_len, } } if (ret == 1) { + #ifndef WOLFSSL_NO_DH_GEN_PARAMS /* Generate parameters into internal DH key. */ if (wc_DhGenerateParams(rng, prime_len, key) != 0) { WOLFSSL_ERROR_MSG("wc_DhGenerateParams error"); ret = 0; } + #else + WOLFSSL_ERROR_MSG("DH parameter generation disabled in this build"); + (void)prime_len; + ret = 0; + #endif } /* Free local random number generator if created. */ diff --git a/tests/api/test_dh.c b/tests/api/test_dh.c index df5a4caec17..2a71224ffc0 100644 --- a/tests/api/test_dh.c +++ b/tests/api/test_dh.c @@ -1010,6 +1010,7 @@ int test_wc_DhGenerateParams_and_ExportRaw(void) * generate-and-export flow below is only valid with the full SP math * (WOLFSSL_SP_MATH_ALL), fastmath or heapmath backends. */ #if !defined(NO_DH) && defined(WOLFSSL_KEY_GEN) && !defined(WOLFSSL_SP_MATH) && \ + !defined(WOLFSSL_NO_DH_GEN_PARAMS) && \ !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) DhKey dh; WC_RNG rng; diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index e692f3f35ca..dff8fab086e 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -1203,6 +1203,7 @@ int test_wolfSSL_EVP_PKEY_copy_parameters(void) { EXPECT_DECLS; #if defined(OPENSSL_EXTRA) && !defined(NO_DH) && defined(WOLFSSL_KEY_GEN) && \ + !defined(WOLFSSL_NO_DH_GEN_PARAMS) && \ !defined(HAVE_SELFTEST) && defined(WOLFSSL_DH_EXTRA) && \ (defined(OPENSSL_ALL) || defined(WOLFSSL_QT)) && !defined(NO_FILESYSTEM) WOLFSSL_EVP_PKEY* params = NULL; diff --git a/tests/api/test_ossl_dh.c b/tests/api/test_ossl_dh.c index 76b0da42f07..5574d9d3ceb 100644 --- a/tests/api/test_ossl_dh.c +++ b/tests/api/test_ossl_dh.c @@ -171,11 +171,19 @@ int test_wolfSSL_DH(void) DH_free(dh); dh = NULL; -#ifdef WOLFSSL_KEY_GEN +#if defined(WOLFSSL_KEY_GEN) && !defined(WOLFSSL_NO_DH_GEN_PARAMS) ExpectNotNull(dh = DH_generate_parameters(2048, 2, NULL, NULL)); ExpectIntEQ(wolfSSL_DH_generate_parameters_ex(NULL, 2048, 2, NULL), 0); DH_free(dh); dh = NULL; +#elif defined(WOLFSSL_KEY_GEN) + /* Domain parameter generation is compiled out: the calls must fail + * cleanly on a valid DH object rather than generate. */ + ExpectNull(dh = DH_generate_parameters(2048, 2, NULL, NULL)); + ExpectNotNull(dh = wolfSSL_DH_new()); + ExpectIntEQ(wolfSSL_DH_generate_parameters_ex(dh, 2048, 2, NULL), 0); + DH_free(dh); + dh = NULL; #endif #endif /* !HAVE_FIPS || (HAVE_FIPS_VERSION && HAVE_FIPS_VERSION > 2) */ #endif /* OPENSSL_ALL */ diff --git a/tests/unit-mcdc/test_dh_fault_whitebox.c b/tests/unit-mcdc/test_dh_fault_whitebox.c index 0cc3382d20f..1f5ca11903d 100644 --- a/tests/unit-mcdc/test_dh_fault_whitebox.c +++ b/tests/unit-mcdc/test_dh_fault_whitebox.c @@ -920,7 +920,9 @@ static void test_agree_nonblock(void) /* 2162 idx1 FALSE with idx0 TRUE: a prime that is none of the three SP * sizes leaves "dispatched" clear all the way through the chain, which - * is the only way the 4096 test is reached and false. */ + * is the only way the 4096 test is reached and false. The only source of + * a non-SP-size prime here is a generated one. */ +#ifndef WOLFSSL_NO_DH_GEN_PARAMS XMEMSET(&nb, 0, sizeof(nb)); wc_InitDhKey(&key); mcdc_sr_arm(WB_DH_SEED); @@ -944,6 +946,7 @@ static void test_agree_nonblock(void) wc_DhSetNonBlock(&key, NULL); } wc_FreeDhKey(&key); +#endif /* !WOLFSSL_NO_DH_GEN_PARAMS */ wc_FreeRng(&rng); } @@ -962,6 +965,7 @@ static void test_agree_nonblock(void) * The rows this function cannot produce -- the mp_set guard actually * failing, and the g-search do-while actually repeating -- are driven by * test_generate_params_levers() below, in this same binary. */ +#ifndef WOLFSSL_NO_DH_GEN_PARAMS static void test_generate_params(void) { WC_RNG rng; @@ -985,6 +989,7 @@ static void test_generate_params(void) wc_FreeRng(&rng); } +#endif /* !WOLFSSL_NO_DH_GEN_PARAMS */ /* ---- _DhSetKey init guard, dh.c:2771 -------------------------------------- * if (ret == 0 && mp_init(&key->g) != MP_OKAY) @@ -1016,6 +1021,7 @@ static void test_setkey_init_fault(void) wc_FreeDhKey(&key); } +#ifndef WOLFSSL_NO_DH_GEN_PARAMS /* ---- wc_DhGenerateParams tail guards, dh.c:3365/3374 ---------------------- * if ((ret == 0) && (mp_set(&dh->g, 1) != MP_OKAY)) (3365) * } while (ret == 0 && mp_cmp_d(tmp, 1) == MP_EQ); (3374) @@ -1303,6 +1309,7 @@ static void test_mp_fault_sweeps(void) wc_FreeRng(&rng); WB_NOTE("big-integer fault sweeps done"); } +#endif /* !WOLFSSL_NO_DH_GEN_PARAMS */ int main(void) { @@ -1333,10 +1340,17 @@ int main(void) WB_NOTE("WC_DH_NONBLOCK not built; nb cache decisions " "(2070/2085/2098/2116) skipped"); #endif +#ifndef WOLFSSL_NO_DH_GEN_PARAMS test_generate_params(); +#else + WB_NOTE("WOLFSSL_NO_DH_GEN_PARAMS built; parameter generation decisions " + "(3293/3299) skipped"); +#endif test_setkey_init_fault(); +#ifndef WOLFSSL_NO_DH_GEN_PARAMS test_generate_params_levers(); test_mp_fault_sweeps(); +#endif printf("done (%s)\n", wb_fail ? "FAILURES" : "ok"); return 0; diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 8df5aa91193..3d458e6df0f 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -34265,13 +34265,18 @@ int DecodeECC_DSA_Sig_Ex(const byte* sig, word32 sigLen, mp_int* r, mp_int* s, /* Clear dynamic data and set mp_ints to put r and s into. */ XMEMSET(dataASN, 0, sizeof(dataASN)); if (init) { - GetASN_MP(&dataASN[DSASIGASN_IDX_R], r); - GetASN_MP(&dataASN[DSASIGASN_IDX_S], s); - } - else { - GetASN_MP_Inited(&dataASN[DSASIGASN_IDX_R], r); - GetASN_MP_Inited(&dataASN[DSASIGASN_IDX_S], s); + /* Initialize here rather than leaving it to the item store, which + * only does so once an item has parsed. A decode that fails before + * that, or that stores r and then fails on s, would otherwise reach + * the mp_clear() calls below with values that were never + * initialized. */ + ret = mp_init_multi(r, s, NULL, NULL, NULL, NULL); + if (ret != MP_OKAY) { + return ret; + } } + GetASN_MP_Inited(&dataASN[DSASIGASN_IDX_R], r); + GetASN_MP_Inited(&dataASN[DSASIGASN_IDX_S], s); /* Decode the DSA signature. */ ret = GetASN_Items(dsaSigASN, dataASN, dsaSigASN_Length, 0, sig, &idx, diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index 97ee72ba8dd..1f58fda7cdf 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -898,6 +898,38 @@ int wc_CryptoCb_RsaGetSize(const RsaKey* key, int* keySize) } #endif /* !NO_RSA */ +#ifndef NO_DH +int wc_CryptoCb_Dh(DhKey* key, const byte* priv, word32 privSz, + const byte* otherPub, word32 pubSz, byte* agree, word32* agreeSz) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (key == NULL) + return ret; + + /* locate registered callback */ + dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_DH; + cryptoInfo.pk.dh.key = key; + cryptoInfo.pk.dh.priv = priv; + cryptoInfo.pk.dh.privSz = privSz; + cryptoInfo.pk.dh.otherPub = otherPub; + cryptoInfo.pk.dh.pubSz = pubSz; + cryptoInfo.pk.dh.agree = agree; + cryptoInfo.pk.dh.agreeSz = agreeSz; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* !NO_DH */ + #ifdef HAVE_ECC #ifdef HAVE_ECC_DHE int wc_CryptoCb_MakeEccKey(WC_RNG* rng, int keySize, ecc_key* key, int curveId) diff --git a/wolfcrypt/src/dh.c b/wolfcrypt/src/dh.c index 7aa02588153..0886c2eea04 100644 --- a/wolfcrypt/src/dh.c +++ b/wolfcrypt/src/dh.c @@ -36,6 +36,9 @@ #endif #include +#ifdef WOLF_CRYPTO_CB + #include +#endif #ifdef WOLFSSL_HAVE_SP_DH #include @@ -973,6 +976,9 @@ int wc_InitDhKey_ex(DhKey* key, void* heap, int devId) key->heap = heap; /* for XMALLOC/XFREE in future */ key->trustedGroup = 0; +#ifdef WOLF_CRYPTO_CB + key->devId = devId; +#endif #ifdef WC_DH_INITIAL_RUNTIME_ENABLEMENT if (! wc_dh_enabled) @@ -990,7 +996,9 @@ int wc_InitDhKey_ex(DhKey* key, void* heap, int devId) /* handle as async */ ret = wolfAsync_DevCtxInit(&key->asyncDev, WOLFSSL_ASYNC_MARKER_DH, key->heap, devId); -#else +#endif +#if !defined(WOLF_CRYPTO_CB) && \ + !(defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_DH)) (void)devId; #endif @@ -2137,6 +2145,55 @@ static int wc_DhAgree_Sync(DhKey* key, byte* agree, word32* agreeSz, #endif } +#ifdef WOLF_CRYPTO_CB + /* Dispatched here, after the checks above, so a device gets validated + * inputs and the SP 800-56A guarantees do not depend on each driver + * reimplementing them. Placing it here rather than at the top of + * wc_DhAgree() also means the validation runs once, not twice, when a + * callback declines and software takes over. + * + * Never on the ct path: wc_DhAgree_ct() exists to give the caller a + * constant-time agreement, and a callback makes no such promise. Offload + * there would silently drop the guarantee the caller asked for, so that + * entry point stays in software. */ + if (ct == 0) + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + ret = wc_CryptoCb_Dh(key, priv, privSz, otherPub, pubSz, agree, + agreeSz); + if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + #ifdef WC_DH_NONBLOCK + /* The device completes the op in one call, so the cached + * validation is single-use. Clear it so the next agree on + * this DhNb re-runs the SP 800-56A peer-key check. */ + if (key->nb != NULL) { + key->nb->pubKeyValidated = 0; + } + #endif + /* The shared secret must not be 1 (SP 800-56A 5.7.1.1). The + * software path below checks the mp_int; the device has already + * produced bytes, so check those. */ + if (ret == 0) { + word32 i; + byte acc = 0; + + for (i = 0; (i + 1) < *agreeSz; i++) { + acc |= agree[i]; + } + if ((acc == 0) && (*agreeSz > 0) && + (agree[*agreeSz - 1] == 1)) { + WOLFSSL_MSG("wc_DhAgree shared secret is one"); + return MP_VAL; + } + } + return ret; + } + ret = 0; /* fall through to software */ + } +#endif + #if defined(WC_DH_NONBLOCK) && defined(WOLFSSL_HAVE_SP_DH) && \ defined(WOLFSSL_SP_NONBLOCK) && defined(WOLFSSL_SP_SMALL) && \ !defined(WOLFSSL_SP_FAST_MODEXP) @@ -3181,7 +3238,7 @@ int wc_DhCopyNamedKey(int name, } -#ifdef WOLFSSL_KEY_GEN +#if defined(WOLFSSL_KEY_GEN) && !defined(WOLFSSL_NO_DH_GEN_PARAMS) /* modulus_size in bits */ int wc_DhGenerateParams(WC_RNG *rng, int modSz, DhKey *dh) diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index 061bf15e2ad..6bc3bb40cee 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -91,6 +91,15 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/nxp/dcp_port.c \ wolfcrypt/src/port/nxp/se050_port.c \ wolfcrypt/src/port/nxp/README.md \ + wolfcrypt/src/port/nxp/sec_qoriq.c \ + wolfcrypt/src/port/nxp/sec_qoriq_cb.c \ + wolfcrypt/src/port/nxp/sec_qoriq_hash.c \ + wolfcrypt/src/port/nxp/sec_qoriq_aes.c \ + wolfcrypt/src/port/nxp/sec_qoriq_rng.c \ + wolfcrypt/src/port/nxp/sec_qoriq_pkha.c \ + wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c \ + wolfcrypt/src/port/nxp/sec_qoriq_linux.c \ + wolfcrypt/src/port/nxp/sec_qoriq_sim.c \ wolfcrypt/src/port/nxp/casper_port.c \ wolfcrypt/src/port/nxp/hashcrypt_port.c \ wolfcrypt/src/port/atmel/README.md \ @@ -279,6 +288,18 @@ src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caam/caam_linux.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caam/wolfcaam_linux.c endif +if BUILD_SEC_QORIQ +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_cb.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_hash.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_aes.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_rng.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_pkha.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_linux.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/nxp/sec_qoriq_sim.c +endif + if BUILD_CAAM src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caam/wolfcaam_init.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/caam/wolfcaam_qnx.c diff --git a/wolfcrypt/src/port/nxp/README.md b/wolfcrypt/src/port/nxp/README.md index 446a70e4583..f9145a2b659 100644 --- a/wolfcrypt/src/port/nxp/README.md +++ b/wolfcrypt/src/port/nxp/README.md @@ -1,6 +1,7 @@ # wolfSSL NXP Hardware Acceleration Ports -wolfSSL supports hardware acceleration on NXP DCP, LTC (KSDK), LPC55S69, and SE050. +wolfSSL supports hardware acceleration on NXP DCP, LTC (KSDK), LPC55S69, +SE050, and the QorIQ SEC. ## NXP LPC55S69 @@ -46,7 +47,495 @@ NOTE: Both can be defined with no problem. For details on wolfSSL integration with NXP SE050, see [README_SE050.md](./README_SE050.md). +## NXP QorIQ SEC + +The SEC is the security engine on NXP's QorIQ PowerPC T-series parts. It +shares the CAAM descriptor architecture used by the i.MX parts, but this is a +separate, self-contained port under `wolfcrypt/src/port/nxp/`; it does not +build any of `wolfcrypt/src/port/caam/`. + +### Supported hardware + +Verified on real silicon: + +| Part | Board | SEC | Era | CCSRBAR | +|---|---|---|---|---| +| T2080E rev 1.1 | Curtiss-Wright VPX3-152 | 5.2 | 6 | `0xEF000000` | +| T1040E rev 1.1 | NXP T1040D4RDB | 5.0 | 6 | `0xFE000000` | + +Both place the SEC at `CCSRBAR + 0x300000` with four job rings at `+0x1000`, +`+0x2000`, `+0x3000` and `+0x4000`, and both declare `fsl,sec-v4.0` +compatibility, so one driver covers them. They differ only in instance counts +and PKHA version, which affect throughput rather than the programming model: + +| | T2080 | T1040 | +|---|---|---| +| Job rings / DECOs | 4 / 4 | 4 / 2 | +| AESA / MDHA units | 4 / 4 | 2 / 2 | +| RNG / PKHA units | 1 / 1 | 1 / 1 | +| PKHA version | 2 | 1 | + +Note the device trees for both parts declare `fsl,sec-era = <5>`, but the +`CCBVID` register reports era 6 on both. The driver trusts the register. + +#### The "E" suffix matters + +QorIQ parts ship in security-enabled and security-disabled orderable +variants. A part without the SEC has no engine to talk to. `wc_SecQoriqInit()` +checks this at run time from the SVR (bit `0x80000`) and returns +`NOT_COMPILED_IN` on a non-E part rather than touching the block. U-Boot +prints the same thing as a trailing `E` on the CPU name, for example +`CPU0: T2080E`. + +### What is accelerated + +| Algorithm | Status | +|---|---| +| AES-CBC, AES-CTR, AES-ECB (128/192/256) | Supported | +| AES-GCM, including AAD | Supported with a 12 byte IV and a full 16 byte tag; engine checks the tag on decrypt | +| ECDSA sign and verify | Supported on any prime curve wolfCrypt carries, up to 1023 bits | +| ECDH shared secret | Supported on the same curves | +| RSA public and private operations | Supported up to 4096 bit, private key form 1 (d and n) | +| Finite field DH key agreement | Supported on the fixed FFDHE groups, via the same modular exponentiation | +| SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, MD5 | Driver API only, see below | +| RNG4 | Supported, seeds the wolfCrypt DRBG | +| DES/3DES, HMAC, CMAC, XTS, CCM | Not implemented yet | + +Everything not implemented returns `CRYPTOCB_UNAVAILABLE`, so wolfCrypt falls +back to software. An unimplemented algorithm is a performance question, never +a correctness one. + +#### Public key passes the curve parameters explicitly + +The i.MX CAAM parts let a descriptor name one of a handful of built-in curves +with a small index (ECDSEL) instead of carrying the domain parameters, and +wolfSSL's i.MX port uses that. **It does not work on the QorIQ parts tested +here.** Every flag position in bits 25:17 of the protocol block, combined with +every curve index 0 to 31, is refused with DECO error `0x82`. So is the +"message representative is already hashed" flag in the operation's PROTINFO, +which comes back as DECO error `0x81`. + +This port therefore supplies the prime, order, base point and curve +coefficients in the descriptor. That costs four extra words and a small +conversion from wolfCrypt's parameter tables, and in exchange it works for +**any prime curve wolfCrypt carries** rather than the five the engine has +built in, Brainpool and the SECP-K1 curves included. The only ceiling is +the PKHA's 1023 bit width. + +Two cases fall back to software because the engine cannot express them: + +- A key with no public point loaded. wolfCrypt derives one during a software + verify; the engine has no equivalent, and handing it zeros would produce a + spurious "bad signature". +- A message representative that reduces to zero. ECDSA is degenerate there + and the engine refuses it. + +Signing draws its per-signature nonce from RNG4 inside the engine, so +`wc_SecQoriqEccSign()` instantiates the RNG if it is not already running. +A descriptor submitted without it fails with CCB error `0x54`, "RNG not +instantiated". + +#### RSA and Diffie-Hellman + +RSA reaches the callback at `wc_RsaFunction`, which is the raw exponentiation +with padding already applied or not yet stripped, so it maps straight onto the +engine's two RSA protocols. Private key operations use form 1, which takes `d` +and `n` directly; the CRT forms want the prime factors and buy speed this port +does not need to chase yet. + +Offloading a private key operation bypasses wolfCrypt's own base blinding. +That blinding exists to protect the *software* modular exponentiation from +timing analysis, and the engine is not the code it defends, but a build that +would rather keep it can leave RSA in software with +`WOLFSSL_SEC_QORIQ_NO_RSA`. + +Finite field Diffie-Hellman is that same modular exponentiation with the group +prime as the modulus, so DH offload comes and goes with the RSA half of the +port: a build without RSA (`NO_RSA`, or `WOLFSSL_SEC_QORIQ_NO_RSA`) keeps FFDHE +in software even though the PKHA itself could compute it. Reaching it needed a +new callback hook in wolfCrypt: `WC_PK_TYPE_DH` was in the enum but had no +entry in `wc_CryptoInfo`, no `wc_CryptoCb_Dh()` and no call site in `dh.c`, so +no port could offload DH at all. That hook is part of this work. + +Only key agreement is routed, and only over the fixed FFDHE groups. That is +what TLS uses, and it is the whole intended scope: **DH parameter generation +is not supported and will not be**, on the engine or through the callback. +Key generation also stays in software, so the private exponent comes from the +caller's `WC_RNG` rather than from a device. + +#### Hashing is not routed through the crypto callback + +The driver implements single-shot hashing (`wc_SecQoriqSha256()` and friends) +but does not answer `WC_ALGO_TYPE_HASH`. wolfCrypt calls the device once per +`Update` and again for `Final`, which needs either the streaming descriptor +(class 2 context saved and restored around each call) or `WOLFSSL_HASH_KEEP` +so wolfCrypt accumulates the message and asks once. The hardware supports +streaming; the driver does not implement it yet. Until then hashing through +the normal wolfCrypt API stays in software. + +#### RNG feeds the DRBG, it does not replace it + +The router answers `WC_ALGO_TYPE_SEED` only. `WC_ALGO_TYPE_RNG` is +deliberately left unhandled so `wc_RNG_GenerateBlock()` keeps running +wolfCrypt's own DRBG, seeded from the SEC through `wc_GenerateSeed()`. +Answering RNG directly would hand callers raw engine output and take +wolfCrypt's DRBG, its reseeding policy and its health checks out of the path. + +RNG4 powers up with no DRBG state handle instantiated, and neither U-Boot nor +wolfBoot does it on the boards tested (`RDSTA` reads 0). `wc_SecQoriqRngInit()` +performs the instantiation, retrying with a wider entropy sample (up to and +including the documented maximum delay) if the statistical checks reject the +attempt. It runs exactly once, inside `wc_SecQoriqInit()` before the crypto +callback is registered, because the status check and TRNG programming are not +atomic and must never race a second initialiser; if it fails, hardware +seeding is recorded unavailable and seeding stays with the platform source. + +### Building + + ./configure --host=powerpc-linux-gnu CC=powerpc-linux-gnu-gcc \ + --enable-sec-qoriq=baremetal --enable-aesgcm + +`--enable-sec-qoriq` selects the environment backend: + +- `baremetal` (default) is a flat, identity mapped address space, which is how + U-Boot and wolfBoot leave the e5500/e6500. DMA memory comes from a static + pool. This is the tested path. +- `linux` maps the SEC through `/dev/mem`. The engine never DMAs through + ordinary process pages (they can be migrated or copied-on-write behind any + `/proc/self/pagemap` snapshot): every job's descriptor and data buffers + are staged through one pinned, pre-faulted, contiguity-verified bounce + arena (`SEC_QORIQ_ARENA_SZ`, default 128 KB), and pagemap is consulted + only for that driver-owned memory, re-verified before each job. It + initialises on a stock kernel but **does not offload anything on a typical + 36-bit part**; see "The Linux backend on a 36-bit part" below before using + it. +- `sim` is a host-runnable model of the job ring used by the in-tree tests: + it runs on any architecture, executes descriptors with wolfCrypt's own + software crypto, and adds fault injection for the timeout, reset and + status-decode paths. It exists for `make check` and development, never for + production. + +The same port builds through CMake with +`-DWOLFSSL_SEC_QORIQ=baremetal|linux|sim` (or `yes`, which selects +`baremetal`). + +##### The Linux backend on a 36-bit part + +Exercised on a T1040D4RDB running the board's stock Linux 3.12. Three things +have to be right before the engine is even reachable, and one of them is +currently a wall: + +1. **Use the operating system's view of CCSR.** These parts have a 36-bit + physical address space, so under Linux CCSR is at `0xF_FE000000`, not the + `0xFE000000` that bare metal sees. Set `SEC_QORIQ_CCSRBAR_PHYS` (a 64-bit + value, separate from `SEC_QORIQ_CCSRBAR`) and build with + `-D_FILE_OFFSET_BITS=64` so `off_t` can carry the mmap offset. Getting this + wrong maps ordinary DRAM, and `CONFIG_STRICT_DEVMEM` refuses it with + "Program ... tried to access /dev/mem between fe0e0000->fe0e1000". +2. **Release the job ring from the kernel.** The in-tree `caam` driver claims + all four rings at boot. Unbind the one this driver uses: + `echo ffe301000.jr > /sys/bus/platform/drivers/caam_jr/unbind`. +3. **The 4 GB pointer limit stops the offload.** The driver runs the engine in + 32-bit descriptor pointer mode, so it refuses any buffer whose physical + address does not fit in 32 bits. On a 4 GB board ordinary user pages sit + well above that (measured: virtual `0x100b4f10` -> physical + `0x1_EF9C2F10`), so every job is refused, the callback returns + `CRYPTOCB_UNAVAILABLE`, and software quietly does the work. + `wolfcrypt_test` therefore passes in full while offloading nothing, and the + benchmark's HW and SW rows come out identical (RSA-2048 public 790.6 vs + 788.9 ops/s). Making this useful needs either 64-bit descriptor pointer + mode (`MCFGR[PS]`), or DMA buffers below 4 GB. The second is reachable + without a kernel driver: boot with `mem=` so Linux stops managing the top + of DDR, then carve the arena out of that reserved range through + `/dev/mem`. It is contiguous, it is below the limit, and being outside + System RAM is also what lets `CONFIG_STRICT_DEVMEM` map it. That costs a + boot argument rather than a code change, and is how the separate + `--enable-caam=linux` backend reaches the same engine. + +The fallback behaving silently is by design, but it does mean a Linux user has +no signal that nothing is being accelerated. Check the offload counters in +`SecQoriqDev` rather than assuming. + +##### Bare metal chain-loaded from U-Boot + +The same silent fallback bites on bare metal when the application is loaded by +something that has already brought the CAAM up. wolfBoot never touches the +engine, so an application it starts finds `MCFGR[PS]` at its reset value and +offloads normally. U-Boot does initialise the CAAM, and leaves it in 64-bit +pointer mode: on a T1040D4RDB, `MCFGR` reads `0x000122F1` at the U-Boot prompt. +`wc_SecQoriqInit()` refuses that rather than hand the engine descriptor +pointers it will read as 64-bit, and returns `NOT_COMPILED_IN` (-174), so +everything runs in software. + +The distinction matters because the symptom is one line of output: +`QorIQ SEC: init failed (-174), software only` instead of +`QorIQ SEC: enabled`. The test suite passes either way. When measuring or +regression-testing, read the offload counters, and either start the +application from wolfBoot or clear `MCFGR[PS]` before entry. + +Enabling the port forces `WOLF_CRYPTO_CB` on. A minimal build that wants to +call the driver API directly without the callback layer can define +`WOLFSSL_SEC_QORIQ_NO_CRYPTOCB`. + +#### Configuration macros + +| Macro | Default | Meaning | +|---|---|---| +| `SEC_QORIQ_CCSRBAR` | `0xFE000000` | Physical base of the CCSR window. Board and boot-loader specific: the CW VPX3-152 U-Boot relocates it to `0xEF000000`. | +| `SEC_QORIQ_CCSRBAR_PHYS` | `SEC_QORIQ_CCSRBAR` | The same window as the OS sees it, as a 64-bit value. Only the Linux backend uses it. On a 36-bit part CCSR is `0xF_FE000000`. | +| `SEC_QORIQ_JR_INDEX` | 0 | Which of the four job rings to claim. | +| `SEC_QORIQ_RING_SIZE` | 4 | Entries per ring. | +| `SEC_QORIQ_MIN_OFFLOAD_SZ` | 256 | Below this many bytes, AES-CBC/CTR/ECB go to software. See the benchmarks. | +| `SEC_QORIQ_DMA_POOL_SZ` | 8192 | Bare-metal static pool backing the job rings. | +| `SEC_QORIQ_ARENA_SZ` | 131072 | Linux pinned bounce arena. Bounds the largest job, since every descriptor and buffer is staged through it. | +| `WOLFSSL_SEC_QORIQ_DEVID` | `0x53454351` | devId used to select the engine. | +| `WOLFSSL_SEC_QORIQ_NO_PKHA` | undefined | Leave ECDSA and ECDH in software even on a part with a PKHA. | +| `WOLFSSL_SEC_QORIQ_NO_RSA` | undefined | Leave RSA in software, keeping wolfCrypt's base blinding. Also keeps FFDHE in software, which rides the same protocol. | +| `WOLFSSL_SEC_QORIQ_SWAP_REGS` | undefined | Reserved. Defining it is currently a compile error: it would swap MMIO access but not descriptor or ring words, so a little-endian host needs that added first. | + +### Verification + +`wolfcrypt_test()` passes in full with the engine enabled: **28 tests, zero +failures**, on the CW VPX3-152 (T2080E) bare metal under wolfBoot's test-app. +That run pushed **3,407,201 descriptors** through the job ring. + +The port also exposes offload counters on `SecQoriqDev` (`jobCount`, +`cbHashCount` / `cbHashOffload`, `cbCipherCount` / `cbCipherOffload`, +`cbPkCount` / `cbPkOffload`, `cbSeedCount`) so a build can prove what actually +reached the engine rather than inferring it from timings. On the run above: +cipher 3,407,450 seen and 3,407,201 offloaded, hash 343,747 seen and **0 +offloaded**, which is exactly what the identical SHA software/hardware rows +below should look like. + +### Performance + +#### wolfCrypt benchmark, software versus engine + +`wolfcrypt/benchmark`, `BENCH_EMBEDDED` (1 KB buffers), on the CW VPX3-152 +(T2080E, CPU 1200 MHz). wolfCrypt prints the software and hardware rows from +the same run, so these are directly comparable. MiB/s. + +| Algorithm | Software | SEC | Speedup | +|---|---:|---:|---:| +| AES-128-CBC-enc | 21.96 | **122.83** | 5.6x | +| AES-128-CTR | 22.08 | **119.06** | 5.4x | +| AES-128-GCM-enc | 8.07 | **107.42** | 13.3x | +| AES-256-GCM-enc | 6.94 | **102.50** | 14.8x | +| AES-128-GCM-enc-no_AAD | 8.19 | **118.99** | 14.5x | +| AES-256-GCM-enc-no_AAD | 7.19 | **107.20** | 14.9x | +| SHA-256 | 36.83 | 36.31 | not offloaded | +| SHA-384 | 20.25 | 20.09 | not offloaded | + +Public key, same run, in operations per second: + +| Operation | Software | SEC | Speedup | +|---|---:|---:|---:| +| ECDSA P-256 verify | 49.39 | **591.98** | **12.0x** | +| ECDSA P-256 sign | 89.53 | **785.66** | **8.8x** | +| ECDHE P-256 agree | 100.18 | **803.40** | **8.0x** | + +Verify gains the most, which is the right way round: it is the operation a +TLS client performs on every handshake and the slowest of the three in +software. At 1.69 ms against 20.2 ms, certificate chain validation stops being +the dominant cost of a connection on this silicon. + +AES-GCM is the standout at 13-15x, and it is also the TLS 1.3 record cipher. +Software GHASH on this core has no carryless-multiply support and runs at a +flat 7-8 MiB/s no matter the key size, so the engine wins by a wide margin. +The SHA rows are unchanged because hashing is deliberately not routed; see +above. + +These numbers were taken with `NO_ASM=1`. The PPC assembly cannot be enabled +in the same run because wolfSSL's PPC asm SHA-512/384 currently fails the +large streaming-input case in `wolfcrypt_test()` (`test.c:7069`), a +pre-existing issue unrelated to this port. + +#### wolfCrypt benchmark on the T1040D4RDB + +`wolfcrypt/benchmark`, 1 KB blocks, on the NXP T1040D4RDB (T1040E, e5500, 1400 +MHz), built with the port's default configuration: SP math with PowerPC +assembly enabled. Both rows come from the same run. Offload was confirmed from +the driver counters (2,896,257 jobs submitted, cipher 100% offloaded), not +inferred from the timings. + +| Algorithm | Software | SEC | Speedup | +|---|---:|---:|---:| +| AES-128-CBC-enc | 55.22 | **99.11** MiB/s | 1.8x | +| AES-256-CBC-enc | 40.06 | **97.88** MiB/s | 2.4x | +| AES-128-GCM-enc | 35.83 | **96.80** MiB/s | 2.7x | +| AES-256-GCM-enc | 29.19 | **93.07** MiB/s | **3.2x** | + +Public key, same run, operations per second: + +| Operation | Software | SEC | Speedup | +|---|---:|---:|---:| +| ECDHE P-256 agree | 479.24 | 499.33 | 1.0x | +| ECDSA P-256 verify | 422.21 | 355.79 | **0.8x** | +| ECDSA P-256 sign | 769.24 | 490.34 | **0.6x** | + +Note the ECC rows: against SP math on a 1.4 GHz e5500, **the SEC does not win at +P-256**. It ties on ECDH and loses on sign, because software does the operation +in about 1.3 ms while a PKHA job costs what it costs regardless of operand +size. This differs from the T2080 table above, which was taken with `NO_ASM=1` +against a much slower software baseline. Offload is a per-operation decision, +not a per-chip one: on this part the win is bulk symmetric crypto. + +RSA and DH are not built into this configuration, so they are not measured +here. + +Reproducing this does not need TRACE32 or a NOR flash. Build the wolfBoot +bare-metal test app with `SEC_QORIQ=1 SEC_QORIQ_CCSRBAR=0xFE000000UL +WOLFCRYPT_BENCHMARK=1 CFLAGS_EXTRA=-DAPP_SKIP_WOLFBOOT_STATE`, then from the +board's U-Boot prompt `tftp 0x70000000 ` and `go 0x70000000`. +**Clear the long-pointer bit first**: stock U-Boot leaves `MCFGR[PS]` set +(`md 0xFE300004` reads `0x000122f1`) and the driver then declines with +`NOT_COMPILED_IN`, printing "init failed (-174)". `mw 0xFE300004 0x000022f1` +clears it. Miss that step and the benchmark's HW rows silently equal its SW +rows. + +#### Size sweep from the bring-up harness + +A separate bare-metal harness measured the same operations across buffer +sizes to find where offloading starts to pay. Both sides ran in the same +binary over the same buffers, timed with the e6500 timebase, in KB/s. +`SEC/SW` above 1.00 means the engine wins. + +Software here is the configuration wolfBoot ships on this board: PPC32 +assembly for SHA, plain C for AES. + +| Algorithm | Size | SEC | Software | SEC/SW | +|---|---:|---:|---:|---:| +| SHA-256 | 64 | 17568 | 20782 | 0.84x | +| SHA-256 | 256 | 63891 | 36286 | 1.76x | +| SHA-256 | 1400 | 218644 | 45021 | 4.85x | +| SHA-256 | 16384 | 440574 | 48021 | 9.17x | +| SHA-384 | 64 | 17459 | 7288 | 2.39x | +| SHA-384 | 256 | 58757 | 13123 | 4.47x | +| SHA-384 | 1400 | 194183 | 22217 | 8.74x | +| SHA-384 | 16384 | 368240 | 26954 | 13.66x | +| AES-128-CBC | 64 | 13720 | 22389 | 0.61x | +| AES-128-CBC | 256 | 49356 | 35612 | 1.38x | +| AES-128-CBC | 1392 | 165630 | 42376 | 3.90x | +| AES-128-CBC | 16384 | 322236 | 44089 | 7.30x | +| AES-128-GCM | 64 | 13269 | 5760 | 2.30x | +| AES-128-GCM | 256 | 47029 | 7528 | 6.24x | +| AES-128-GCM | 1400 | 161533 | 8169 | 19.77x | +| AES-128-GCM | 16384 | 320900 | 8335 | 38.50x | + +There is a fixed per-job cost of roughly 3.5 microseconds: descriptor +construction, two cache flushes, the ring write, the MMIO kick and the +completion poll. That is why every SEC row at 64 bytes lands near 13-17.5 +MB/s regardless of algorithm. Above about 1 KB it amortises away and the +engine reaches 320-440 MB/s. + +Consequences worth knowing: + +- **AES-GCM wins at every size**, including 64 bytes, because software GHASH + on this core has no carryless-multiply support and runs at a flat 8 MB/s. + This is also the TLS 1.3 record cipher, so it is the case that matters most. +- **SHA-384 wins at every size**, because the 64-bit SHA-512 core is expensive + in 32-bit software. +- **SHA-256 and AES-CBC lose below roughly 128 bytes.** Both have fast + software paths here. `SEC_QORIQ_MIN_OFFLOAD_SZ` keeps small AES buffers in + software for this reason. + +The driver currently uses one job ring, submits one descriptor at a time and +busy-waits for completion. The T2080 has four rings and four DECOs, so +batching or overlapping submissions would cut the effective per-job cost and +move the crossover down toward small-record sizes. + +### Limitations + +- Buffers must be physically contiguous. There is no scatter-gather support, + so fragmented TLS records need to be linearised by the caller. The Linux + backend verifies contiguity page by page and refuses a buffer that + straddles non-adjacent frames rather than DMAing past the first one. +- A single command carries at most 64 KB. Hashing chains multiple FIFO loads + to cover longer messages; AES does not, so the callback declines larger AES + buffers and lets software take them. +- AES-GCM accepts only a 12 byte IV. The AESA derives J0 = IV || 0^31 || 1, + which is the SP 800-38D construction for that length only; other lengths + need J0 = GHASH(IV || pad || len(IV)), which the engine does not do. +- AES-GCM accepts only a full 16 byte tag. The engine performs the ICV + comparison itself on decrypt and rejects a truncated tag, so shorter tags + go to software. TLS always uses 16. +- Public key covers ECDSA, ECDH, RSA and finite-field DH agreement. Key + generation stays in software for all of them, so that the caller's `WC_RNG` + remains the source of the private key. RSA private-key operations use the + plain private exponent `d`; the CRT form (`p`, `q`, `dP`, `dQ`, `qInv`) is + not implemented, so a CRT key still exponentiates against the full modulus + rather than taking the faster path. +- The domain parameters are converted from wolfCrypt's tables on every public + key call. It is a few hundred bytes of byte-string work against a + millisecond of engine time, so it does not show up in the measurements + above, but caching them per curve is the obvious next saving. +- ECDSA sign uses the engine's internal per-signature nonce rather than the + `WC_RNG` the caller passed, because the protocol descriptor generates it in + hardware. Deterministic ECDSA (RFC 6979) and a nonce installed with + `wc_ecc_sign_set_k()` therefore cannot be offloaded and fall back to + software, so the configured nonce is honoured rather than silently + replaced. +- A digest wider than the curve order is reduced to the leftmost order bits, + as ECDSA prescribes, so SHA-384 or SHA-512 over P-256 offloads normally. + The one shape declined is a curve whose order does not fill the fixed width + block, because wolfCrypt finishes such a truncation with a sub-byte shift + that this layout cannot express, and copying whole bytes instead would + build a signature its own verifier rejects. No curve in common use is + affected: P-256 and P-384 have byte aligned orders, and P-521 never + truncates, no digest wolfCrypt carries being wider than its 521 bit order. +- The port is stack hungry on the public key paths, roughly 900 bytes on top + of what wolfCrypt's own ECC frames need (a `MAX_ECC_BYTES * 9` domain + parameter block plus a `MAX_ECC_BYTES * 4` scratch in the router). Bare + metal targets should budget for it or build with `WOLFSSL_SMALL_STACK`, + which moves both to the heap. Every driver call also carries a + `SecQoriqDesc` on its stack, about 800 bytes now that it records the + buffer table resolved at submission. +- 32-bit descriptor pointers only. All DMA buffers must live below 4 GB. + `wc_SecQoriqInit()` refuses to run if the engine is already in 64-bit + pointer mode, and buffer translation fails rather than truncating. +- AES-CBC/CTR/ECB require whole 16-byte blocks. Partial-block CTR, where + wolfCrypt keeps leftover keystream, falls back to software. +- One job ring, polled, serialised on the wolfSSL hardware mutex. No + interrupt support and no concurrent submission. +- Output buffers should not share a cache line with data another thread + writes while a job is in flight. The driver flushes before submitting and + invalidates afterwards, which is safe for its own accesses, but a + concurrent writer to an adjacent object in the same line can still lose an + update on a non-coherent path. +- Errors that can only occur before a descriptor is handed to the engine + (argument and shape validation, an unmappable buffer, a missing + capability) fall back to software through the crypto callback. `WC_HW_E` + is a hard error by design: it can be raised after submission, when the + engine may already have modified an in-place buffer or a chaining IV, and + a silent software retry would compute over corrupted state. A dead engine + still falls back cleanly for subsequent operations because the router's + device checks decline before dispatch. +- Single-shot hashing is bounded by the 64-word descriptor at 30 FIFO loads, + about 1.9 MB of message; larger inputs are refused with `BAD_FUNC_ARG` + (`wc_SecQoriqHash` is only reachable through the direct driver API, the + callback router never routes hashing). +- RNG4 instantiation runs exactly once, inside `wc_SecQoriqInit()` before + the crypto callback is registered, because the RDSTA check and TRNG + programming are not atomic. If every entropy delay up to the maximum is + rejected, hardware seeding is recorded unavailable and seeding stays with + the platform source; later RNG users only re-read RDSTA (side-effect free) + in case another agent instantiated the handle. +- In-tree tests run against the simulated backend: `./configure + --enable-sec-qoriq=sim && make check` (any host) exercises the job ring + state machine with fault injection - submission, completion, timeout, the + ring-reset / DMA-reset / poisoned-device escalation, status decoding, + `jobCount` accounting - plus known answers for AES CBC (out-of-place and + in-place), GCM including corrupted-tag rejection and plaintext scrubbing, + multi-chunk hashing, RNG4 instantiation retry and exhaustion, and + FFDHE-2048 agreement through the modexp protocol descriptor, each + cross-checked against the software implementation. The `sec-qoriq.yml` + workflow runs this plus PowerPC cross-compiles of both real backends on + every PR. +- Real hardware remains the final gate: the port was validated with an + external bare-metal known-answer harness covering hashing, AES CBC/CTR/ECB, + GCM including a corrupted-tag rejection, RNG4, and ECDSA/ECDH cross-checked + against the software implementation. + ## Support For questions please email support@wolfssl.com - diff --git a/wolfcrypt/src/port/nxp/sec_qoriq.c b/wolfcrypt/src/port/nxp/sec_qoriq.c new file mode 100644 index 00000000000..c665372382c --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq.c @@ -0,0 +1,726 @@ +/* sec_qoriq.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 + */ + +/* + * Core of the QorIQ SEC driver: bring-up, job ring management, descriptor + * assembly and submission. Platform specifics live behind the seam declared + * at the bottom of sec_qoriq.h. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_SEC_QORIQ + +#include +#include +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* The one device this driver manages; a second would need its own ring. */ +static SecQoriqDev secQoriqDev; + +/* Every job's descriptor is copied here before submission, so the engine is + * never handed the address of a caller's stack frame: if a job hangs and + * survives every reset, the memory it can still read has static lifetime. + * One job is in flight at a time (the hardware mutex), so one slot + * suffices. */ +static word32 secDescStage[SEC_QORIQ_DESC_MAX_WORDS] +#if defined(__GNUC__) + __attribute__((aligned(64))) +#endif + ; + +/****************************************************************************** + Register access. CCSR registers are big endian and so is the CPU, so no + swap is needed; the swap path exists for a little endian QorIQ host. + ****************************************************************************/ + +#ifdef WOLFSSL_SEC_QORIQ_SWAP_REGS + #define SEC_SWAP32(x) \ + ((((x) & 0x000000FFU) << 24) | (((x) & 0x0000FF00U) << 8) | \ + (((x) & 0x00FF0000U) >> 8) | (((x) & 0xFF000000U) >> 24)) +#else + #define SEC_SWAP32(x) (x) +#endif + +/* The simulated backend models the registers itself. */ +#ifndef WOLFSSL_SEC_QORIQ_SIM +word32 wc_SecQoriqRead(const byte* base, word32 off) +{ + return SEC_SWAP32(*(volatile const word32*)(base + off)); +} + +void wc_SecQoriqWrite(byte* base, word32 off, word32 val) +{ + *(volatile word32*)(base + off) = SEC_SWAP32(val); +} +#endif /* !WOLFSSL_SEC_QORIQ_SIM */ + +#define secRead wc_SecQoriqRead +#define secWrite wc_SecQoriqWrite + +SecQoriqDev* wc_SecQoriqGetDev(void) +{ + if (secQoriqDev.initialized == 0) { + return NULL; + } + return &secQoriqDev; +} + +/****************************************************************************** + Descriptor assembly + ****************************************************************************/ + +int wc_SecQoriqDescInit(SecQoriqDesc* desc) +{ + if (desc == NULL) { + return BAD_FUNC_ARG; + } + + XMEMSET(desc, 0, sizeof(SecQoriqDesc)); + desc->desc[0] = SEC_QORIQ_CMD_HEAD; + desc->idx = 1; + desc->startIdx = 1; + + return 0; +} + +int wc_SecQoriqDescAddWord(SecQoriqDesc* desc, word32 in) +{ + if (desc == NULL) { + return BAD_FUNC_ARG; + } + if (desc->idx >= SEC_QORIQ_DESC_MAX_WORDS) { + WOLFSSL_MSG("sec_qoriq: descriptor full"); + return BUFFER_E; + } + + desc->desc[desc->idx++] = in; + return 0; +} + +/* Record one buffer and reserve the descriptor word for its address. The + * word is filled in at submission, when the platform maps the buffer for + * DMA; nothing appends a raw physical address. */ +static int secDescRecordBuf(SecQoriqDesc* desc, const byte* buf, word32 bufSz, + byte dir) +{ + if (desc->bufCnt >= SEC_QORIQ_DESC_MAX_BUFS) { + WOLFSSL_MSG("sec_qoriq: descriptor references too many buffers"); + return BUFFER_E; + } + + desc->bufs[desc->bufCnt].virt = (void*)buf; + desc->bufs[desc->bufCnt].sz = bufSz; + desc->bufs[desc->bufCnt].wordIdx = desc->idx; + desc->bufs[desc->bufCnt].dir = dir; + desc->bufCnt++; + + return wc_SecQoriqDescAddWord(desc, 0); +} + +/* Append a command word carrying a length, followed by the buffer address. + * FIFO STORE and STORE commands are engine writes; everything else (KEY, + * LOAD, FIFO LOAD) is an engine read. */ +int wc_SecQoriqDescAddBuf(SecQoriqDesc* desc, word32 cmd, const byte* buf, + word32 bufSz) +{ + word32 ctype; + int ret; + + if (desc == NULL || buf == NULL) { + return BAD_FUNC_ARG; + } + if (bufSz > SEC_QORIQ_MAX_XFER_SZ) { + WOLFSSL_MSG("sec_qoriq: buffer too large for a single command"); + return BUFFER_E; + } + + ret = wc_SecQoriqDescAddWord(desc, cmd | bufSz); + if (ret != 0) { + return ret; + } + + ctype = cmd & 0xF8000000U; + return secDescRecordBuf(desc, buf, bufSz, + (ctype == SEC_QORIQ_CMD_FIFO_S || ctype == SEC_QORIQ_CMD_STORE) ? + SEC_QORIQ_DIR_OUT : SEC_QORIQ_DIR_IN); +} + +/* Append a bare PDB pointer word. The protocol data block carries no + * per-pointer command, so the direction comes from the caller. */ +int wc_SecQoriqDescAddPdbPtr(SecQoriqDesc* desc, const byte* buf, + word32 bufSz, int isOut) +{ + if (desc == NULL || buf == NULL || bufSz == 0) { + return BAD_FUNC_ARG; + } + + return secDescRecordBuf(desc, buf, bufSz, + isOut ? SEC_QORIQ_DIR_OUT : SEC_QORIQ_DIR_IN); +} + +/* Patch the header with the final length. Low 7 bits are the total word + * count, bits 22:16 the index at which the job descriptor proper starts. */ +static void secDescFinalize(SecQoriqDesc* desc) +{ + desc->desc[0] &= 0xFFFFFF80U; + desc->desc[0] |= (desc->idx & 0x7FU) | ((desc->startIdx & 0x7FU) << 16); +} + +/****************************************************************************** + Error decoding + ****************************************************************************/ + +int wc_SecQoriqParseError(word32 status) +{ + word32 src = (status & SEC_QORIQ_SSRC_MASK) >> SEC_QORIQ_SSRC_SHIFT; + + if (status == 0) { + return 0; + } + + /* A CCB error with error id ICVCHK is the AEAD integrity check failing: + * an authentication failure, not a hardware fault. */ + if (src == SEC_QORIQ_SSRC_CCB && + (status & SEC_QORIQ_CCBERR_ERRID_MASK) == + SEC_QORIQ_CCBERR_ERRID_ICV) { + WOLFSSL_MSG("sec_qoriq: ICV check failed"); + return AES_GCM_AUTH_E; + } + +#ifdef DEBUG_WOLFSSL + { + const char* srcStr; + + switch (src) { + case SEC_QORIQ_SSRC_CCB: srcStr = "CCB"; break; + case SEC_QORIQ_SSRC_JUMP: srcStr = "jump halt, user"; break; + case SEC_QORIQ_SSRC_DECO: srcStr = "DECO"; break; + case SEC_QORIQ_SSRC_QI: srcStr = "queue interface"; break; + case SEC_QORIQ_SSRC_JR: srcStr = "job ring"; break; + case SEC_QORIQ_SSRC_JUMP_CC: srcStr = "jump halt, condition"; + break; + default: srcStr = "unknown"; break; + } + WOLFSSL_MSG_EX("sec_qoriq: job failed, status 0x%08x, source %s", + status, srcStr); + } +#endif + + return WC_HW_E; +} + +/****************************************************************************** + Job submission + ****************************************************************************/ + +/* One job ring, one descriptor in flight. Without this mutex two threads + * racing through the callback overwrite each other's ring slot and + * desynchronise the index mirrors. */ +static int secRunLocked(SecQoriqDev* dev, SecQoriqDesc* desc); +static int secJrReset(SecQoriqDev* dev); +static void secRingsProgram(SecQoriqDev* dev); + +int wc_SecQoriqRun(SecQoriqDev* dev, SecQoriqDesc* desc) +{ + return wc_SecQoriqRunEx(dev, desc, NULL); +} + +int wc_SecQoriqRunEx(SecQoriqDev* dev, SecQoriqDesc* desc, word32* statusOut) +{ + int ret; + + if (dev == NULL || desc == NULL) { + return BAD_FUNC_ARG; + } + + if (statusOut != NULL) { + *statusOut = 0; + } + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + + ret = secRunLocked(dev, desc); + + /* Read the status while still holding the lock: dev->lastStatus belongs + * to whichever job retired most recently, which under concurrency is not + * necessarily this one. */ + if (statusOut != NULL) { + *statusOut = dev->lastStatus; + } + + wolfSSL_CryptHwMutexUnLock(); + + return ret; +} + +void wc_SecQoriqForceZeroDma(void* buf, word32 sz) +{ + if (buf == NULL || sz == 0) { + return; + } + + ForceZero(buf, sz); + (void)wc_SecQoriqCacheFlush(buf, sz); +} + +/* Halt the SEC's DMA engine. Controller wide, so it disturbs any other job + * ring too; only used when a hung job already survived the ring reset and + * the alternative is the engine writing through stale physical addresses. */ +static int secDmaHalt(SecQoriqDev* dev) +{ + word32 waited = 0; + word32 mcfgr = secRead(dev->regs, SEC_QORIQ_MCFGR); + + secWrite(dev->regs, SEC_QORIQ_MCFGR, mcfgr | SEC_QORIQ_MCFGR_DMA_RESET); + while (secRead(dev->regs, SEC_QORIQ_MCFGR) & SEC_QORIQ_MCFGR_DMA_RESET) { + if (++waited > SEC_QORIQ_POLL_MAX) { + WOLFSSL_MSG("sec_qoriq: DMA reset did not complete"); + return WC_HW_E; + } + wc_SecQoriqCpuRelax(); + } + + return 0; +} + +static int secRunLocked(SecQoriqDev* dev, SecQoriqDesc* desc) +{ + word64 descPhys; + word64 phys; + word32 descBytes; + word32 status; + word32 waited; + word32 slot; + word32 i; + int ret; + + if (dev == NULL || desc == NULL) { + return BAD_FUNC_ARG; + } + if (dev->initialized == 0) { + return WC_HW_E; + } + + /* The failure paths below return before a new status is read, so a + * caller classifying the outcome must not see a stale one. */ + dev->lastStatus = 0; + + secDescFinalize(desc); + descBytes = desc->idx * (word32)sizeof(word32); + + ret = wc_SecQoriqDmaJobBegin(); + if (ret != 0) { + return ret; + } + + /* Resolve every recorded buffer through the platform's DMA staging, and + * patch its descriptor word. Only 32-bit pointer mode is supported, so + * an address above 4 GB is refused rather than silently truncated. */ + for (i = 0; i < desc->bufCnt; i++) { + phys = wc_SecQoriqDmaMapBuf(desc->bufs[i].virt, desc->bufs[i].sz, + desc->bufs[i].dir); + if (phys == 0 || (phys >> 32) != 0) { + WOLFSSL_MSG("sec_qoriq: buffer not mappable for DMA"); + wc_SecQoriqDmaJobEnd(0, 0); + return BAD_FUNC_ARG; + } + desc->desc[desc->bufs[i].wordIdx] = (word32)phys; + } + + /* Stage the finalized descriptor in static storage, push it out to + * memory, then map it like any other engine-read buffer. */ + XMEMCPY(secDescStage, desc->desc, descBytes); + ret = wc_SecQoriqCacheFlush(secDescStage, descBytes); + if (ret != 0) { + wc_SecQoriqDmaJobEnd(0, 0); + return ret; + } + descPhys = wc_SecQoriqDmaMapBuf(secDescStage, descBytes, + SEC_QORIQ_DIR_IN); + if (descPhys == 0 || (descPhys >> 32) != 0) { + WOLFSSL_MSG("sec_qoriq: descriptor not reachable by the engine"); + wc_SecQoriqDmaJobEnd(0, 0); + return BAD_FUNC_ARG; + } + + /* Cannot occur while the mutex serialises one job at a time, so it is + * treated as a hardware fault rather than a software fallback. */ + if (secRead(dev->jr, SEC_QORIQ_IRSA) == 0) { + WOLFSSL_MSG("sec_qoriq: no room on the input ring"); + wc_SecQoriqDmaJobEnd(0, 0); + return WC_HW_E; + } + + dev->inRing[dev->inIdx] = (word32)descPhys; + ret = wc_SecQoriqCacheFlush(&dev->inRing[dev->inIdx], sizeof(word32)); + if (ret != 0) { + wc_SecQoriqDmaJobEnd(0, 0); + return ret; + } + + /* Publish the entry, then tell the engine one job was added. Everything + * before this point provably never reached the engine; everything after + * it may have, which is what the error contract in sec_qoriq.h hangs + * on. */ + secWrite(dev->jr, SEC_QORIQ_IRJA, 1); + dev->jobCount++; + dev->inIdx = (dev->inIdx + 1) % SEC_QORIQ_RING_SIZE; + + waited = 0; + while (secRead(dev->jr, SEC_QORIQ_ORSF) == 0) { + if (++waited > SEC_QORIQ_POLL_MAX) { + /* The engine may still hold this job's addresses (the static + * descriptor stage, the rings, and on a flat-mapped backend the + * caller's buffers), and the index mirrors no longer track it. + * Escalate until DMA provably cannot reach reusable memory: + * 1. ring reset - drains or halts this ring; device stays up. + * 2. controller DMA reset - halts every ring's DMA; the device + * is dead but memory is safe to reuse. + * 3. neither completed - the engine may still master the bus, + * so poison the device and retire everything driver-owned it + * could reach: wc_SecQoriqFree() leaks it deliberately, and + * a staged backend's arena is abandoned via JobEnd. On a + * flat-mapped backend the caller's own buffers remain the + * one residual risk only a board reset removes. */ + WOLFSSL_MSG("sec_qoriq: timed out waiting for job completion"); + if (secJrReset(dev) == 0) { + secRingsProgram(dev); + } + else if (secDmaHalt(dev) == 0) { + dev->initialized = 0; + } + else { + dev->initialized = 0; + dev->quiesceFailed = 1; + } + wc_SecQoriqDmaJobEnd(0, dev->quiesceFailed); + return WC_HW_E; + } + wc_SecQoriqCpuRelax(); + } + + /* Output entries are two words wide: {descriptor address, status}. */ + slot = dev->outIdx * 2; + + ret = wc_SecQoriqCacheInval(&dev->outRing[slot], 2 * sizeof(word32)); + if (ret != 0) { + wc_SecQoriqDmaJobEnd(0, 0); + return ret; + } + + /* Confirm the engine retired our descriptor before trusting its + * status. */ + if (dev->outRing[slot] != (word32)descPhys) { + WOLFSSL_MSG("sec_qoriq: output ring returned an unexpected job"); + secWrite(dev->jr, SEC_QORIQ_ORJR, 1); + dev->outIdx = (dev->outIdx + 1) % SEC_QORIQ_RING_SIZE; + wc_SecQoriqDmaJobEnd(0, 0); + return WC_HW_E; + } + status = dev->outRing[slot + 1]; + dev->lastStatus = status; + + /* Release the slot back to the engine. */ + secWrite(dev->jr, SEC_QORIQ_ORJR, 1); + dev->outIdx = (dev->outIdx + 1) % SEC_QORIQ_RING_SIZE; + + ret = wc_SecQoriqParseError(status); + + /* Copy engine output back to the callers only on a clean job; staging + * memory is scrubbed and reclaimed either way. */ + wc_SecQoriqDmaJobEnd(ret == 0, 0); + + return ret; +} + +/****************************************************************************** + Bring-up + ****************************************************************************/ + +/* Reset the job ring; the engine clears JRCR[RESET] once drained. */ +static int secJrReset(SecQoriqDev* dev) +{ + word32 waited = 0; + + secWrite(dev->jr, SEC_QORIQ_JRCR, SEC_QORIQ_JRCR_RESET); + while (secRead(dev->jr, SEC_QORIQ_JRCR) & SEC_QORIQ_JRCR_RESET) { + if (++waited > SEC_QORIQ_POLL_MAX) { + WOLFSSL_MSG("sec_qoriq: job ring reset did not complete"); + return WC_HW_E; + } + wc_SecQoriqCpuRelax(); + } + + /* Clear any latched interrupt status left over from a previous owner. */ + secWrite(dev->jr, SEC_QORIQ_JRINT, secRead(dev->jr, SEC_QORIQ_JRINT)); + + /* The engine restarts at slot 0 after a ring reset, so our mirrors do + * too. */ + dev->inIdx = 0; + dev->outIdx = 0; + + return 0; +} + +static int secRingsAlloc(SecQoriqDev* dev) +{ + word32 inSz = SEC_QORIQ_RING_SIZE * (word32)sizeof(word32); + word32 outSz = SEC_QORIQ_RING_SIZE * 2 * (word32)sizeof(word32); + + dev->inRing = (word32*)wc_SecQoriqDmaAlloc(inSz, &dev->inRingPhys); + if (dev->inRing == NULL) { + return MEMORY_E; + } + + dev->outRing = (word32*)wc_SecQoriqDmaAlloc(outSz, &dev->outRingPhys); + if (dev->outRing == NULL) { + wc_SecQoriqDmaFree(dev->inRing, dev->inRingPhys, inSz); + dev->inRing = NULL; + return MEMORY_E; + } + + /* Valid memory the engine cannot address in 32-bit pointer mode is a + * platform limitation, not an allocation failure: NOT_COMPILED_IN so + * wolfCrypt_Init() treats the whole backend as unavailable and stays on + * software, exactly like a non-E part. A 36-bit Linux system whose + * kernel hands out high pages lands here. */ + if ((dev->inRingPhys >> 32) != 0 || (dev->outRingPhys >> 32) != 0) { + WOLFSSL_MSG("sec_qoriq: rings above 4GB, engine cannot reach them"); + return NOT_COMPILED_IN; + } + + XMEMSET(dev->inRing, 0, inSz); + XMEMSET(dev->outRing, 0, outSz); + + if (wc_SecQoriqCacheFlush(dev->inRing, inSz) != 0 || + wc_SecQoriqCacheFlush(dev->outRing, outSz) != 0) { + return WC_HW_E; + } + + return 0; +} + +/* Newest first: the bare-metal backend is a bump allocator that can only + * rewind its most recent allocation. */ +static void secRingsFree(SecQoriqDev* dev) +{ + if (dev->outRing != NULL) { + wc_SecQoriqDmaFree(dev->outRing, dev->outRingPhys, + SEC_QORIQ_RING_SIZE * 2 * (word32)sizeof(word32)); + dev->outRing = NULL; + } + if (dev->inRing != NULL) { + wc_SecQoriqDmaFree(dev->inRing, dev->inRingPhys, + SEC_QORIQ_RING_SIZE * (word32)sizeof(word32)); + dev->inRing = NULL; + } +} + +/* The base address registers are 64-bit even in 32-bit pointer mode. */ +static void secRingsProgram(SecQoriqDev* dev) +{ + secWrite(dev->jr, SEC_QORIQ_IRBA_MS, (word32)(dev->inRingPhys >> 32)); + secWrite(dev->jr, SEC_QORIQ_IRBA_LS, (word32)dev->inRingPhys); + secWrite(dev->jr, SEC_QORIQ_IRS, SEC_QORIQ_RING_SIZE); + + secWrite(dev->jr, SEC_QORIQ_ORBA_MS, (word32)(dev->outRingPhys >> 32)); + secWrite(dev->jr, SEC_QORIQ_ORBA_LS, (word32)dev->outRingPhys); + secWrite(dev->jr, SEC_QORIQ_ORS, SEC_QORIQ_RING_SIZE); +} + +/* Unified failure path for wc_SecQoriqInit() once the registers are mapped: + * release whatever was built and clear the whole device so nothing dangling + * (dev->regs especially) is left for wc_SecQoriqFree() to trip over. */ +static int secInitFail(SecQoriqDev* dev, int ret) +{ + secRingsFree(dev); + wc_SecQoriqUnmapRegs(dev->regs); + XMEMSET(dev, 0, sizeof(SecQoriqDev)); + return ret; +} + +int wc_SecQoriqInit(void) +{ + SecQoriqDev* dev = &secQoriqDev; + word32 svr = 0; + word32 mcfgr, scfgr, chanumMs; + int ret; + + if (dev->initialized) { + return 0; + } + + /* After a failed quiesce everything the wedged engine could reach was + * leaked (rings, mappings, a staged backend's arena) and the pointers + * are cleared, never freed, so re-initialisation cannot hand the engine + * fresh memory it already references. The ring reset below then probes + * whether the engine actually recovered; if it is still wedged the + * bring-up fails cleanly. */ + XMEMSET(dev, 0, sizeof(SecQoriqDev)); + dev->jrIndex = SEC_QORIQ_JR_INDEX; + + /* A part without the engine has no SEC block, and reading its address + * space is not guaranteed to fault cleanly. */ + ret = wc_SecQoriqGetSvr(&svr); + if (ret != 0) { + return ret; + } + if ((svr & SEC_QORIQ_SVR_E_BIT) == 0) { + WOLFSSL_MSG("sec_qoriq: part has no security engine (not an E SKU)"); + return NOT_COMPILED_IN; + } + + ret = wc_SecQoriqMapRegs(&dev->regs); + if (ret != 0) { + return ret; + } + dev->jr = dev->regs + SEC_QORIQ_JR_OFFSET(dev->jrIndex); + + /* Every failure from here on funnels through secInitFail() so no + * partially built device survives: wc_SecQoriqFree() keys on dev->regs, + * and a nonfatal decline (NOT_COMPILED_IN, which wolfCrypt_Init() + * tolerates) must not leave it pointing at an unmapped window for a + * later wolfCrypt_Cleanup() to dereference. */ + + /* Cache what later code needs to make capability decisions. */ + dev->era = (secRead(dev->regs, SEC_QORIQ_CCBVID) & + SEC_QORIQ_CCBVID_ERA_MASK) >> SEC_QORIQ_CCBVID_ERA_SHIFT; + dev->chaNumLs = secRead(dev->regs, SEC_QORIQ_CHANUM_LS); + dev->chaVidLs = secRead(dev->regs, SEC_QORIQ_CHAVID_LS); + + chanumMs = secRead(dev->regs, SEC_QORIQ_CHANUM_MS); + if (((chanumMs >> SEC_QORIQ_CHANUM_MS_JRNUM_SHIFT) & 0xF) <= + dev->jrIndex) { + WOLFSSL_MSG("sec_qoriq: requested job ring does not exist"); + return secInitFail(dev, BAD_FUNC_ARG); + } + + /* Refuse rather than hand the engine addresses it reads as 64-bit. */ + mcfgr = secRead(dev->regs, SEC_QORIQ_MCFGR); + if (mcfgr & SEC_QORIQ_MCFGR_LONG_PTR) { + WOLFSSL_MSG("sec_qoriq: engine is in 64-bit pointer mode, unsupported"); + return secInitFail(dev, NOT_COMPILED_IN); + } + dev->longPtr = 0; + + ret = secRingsAlloc(dev); + if (ret != 0) { + return secInitFail(dev, ret); + } + + ret = secJrReset(dev); + if (ret != 0) { + return secInitFail(dev, ret); + } + + secRingsProgram(dev); + + /* With virtualization enabled the ring stays parked until started. + * Both boards tested have VIRT_EN clear, but honour it anyway. */ + scfgr = secRead(dev->regs, SEC_QORIQ_SCFGR); + if (scfgr & SEC_QORIQ_SCFGR_VIRT_EN) { + word32 jrstart = secRead(dev->regs, SEC_QORIQ_JRSTART); + secWrite(dev->regs, SEC_QORIQ_JRSTART, + jrstart | SEC_QORIQ_JRSTART_JR(dev->jrIndex)); + } + + /* RNG offload needs state handle 0 instantiated, which the boot loaders + * tested do not do. Record it and let the RNG paths decide. */ + dev->rngReady = (secRead(dev->regs, SEC_QORIQ_RDSTA) & + SEC_QORIQ_RDSTA_IF0) ? 1 : 0; + + dev->initialized = 1; + +#ifndef WC_NO_RNG + /* Instantiate RNG4 now, while nothing else can reach the device: the + * status check and TRNG programming inside are not atomic, so they must + * never race a second initialiser or a live job. A failure only means + * hardware seeding stays unavailable (rngReady 0); the rest of the + * engine is still worth having. */ + if (dev->rngReady == 0 && wc_SecQoriqRngInit() != 0) { + WOLFSSL_MSG("sec_qoriq: RNG4 unavailable, seeding stays in software"); + } +#endif + +#if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) + ret = wc_SecQoriqRegisterCryptoCb(); + if (ret != 0) { + WOLFSSL_MSG("sec_qoriq: could not register the crypto callback"); + return secInitFail(dev, ret); + } +#endif + + WOLFSSL_MSG("sec_qoriq: initialized"); + return 0; +} + +/* Keyed on the register mapping rather than on initialized: a device + * poisoned by a failed quiesce has initialized cleared but still owns the + * callback registration and mappings, and must still be torn down. */ +int wc_SecQoriqFree(void) +{ + SecQoriqDev* dev = &secQoriqDev; + + if (dev->regs == NULL) { + return 0; + } + +#if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) + wc_SecQoriqUnregisterCryptoCb(); +#endif + + if (dev->quiesceFailed) { + /* The engine survived both the ring reset and the DMA reset, so it + * may still master the bus holding our physical addresses. Leak the + * rings and the register mapping deliberately: freeing them would + * let the allocator hand the engine's DMA targets to someone else. + * A later wc_SecQoriqInit() starts over with fresh storage and + * probes the engine with a ring reset. */ + WOLFSSL_MSG("sec_qoriq: engine not quiesced, leaking DMA memory"); + dev->initialized = 0; + return 0; + } + + (void)secJrReset(dev); + secRingsFree(dev); + wc_SecQoriqUnmapRegs(dev->regs); + + XMEMSET(dev, 0, sizeof(SecQoriqDev)); + + return 0; +} + +#endif /* WOLFSSL_SEC_QORIQ */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_aes.c b/wolfcrypt/src/port/nxp/sec_qoriq_aes.c new file mode 100644 index 00000000000..58363b120ee --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_aes.c @@ -0,0 +1,443 @@ +/* sec_qoriq_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 + */ + +/* + * Confidentiality-only AES modes on the QorIQ SEC, driven through the AESA. + * CBC, CTR and ECB. The authenticated modes need a different descriptor + * shape and live elsewhere. + * + * Descriptor shape, following the engine's KEY -> context -> input -> output + * ordering: + * + * KEY class 1, no write back + * LOAD_CTX class 1, the IV or counter block (CBC and CTR only) + * OPERATION class 1, the mode, encrypt or decrypt + * FIFO_LOAD class 1, the message, last + * FIFO_STORE the result + * STORE_CTX class 1, the updated IV or counter (CBC and CTR only) + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && !defined(NO_AES) + +#include +#include +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#define SEC_QORIQ_AES_BLOCK 16 + + + + +static int secAesModeCtxOffset(word32 mode, word32* ofstOut, int* needsIv) +{ + switch (mode) { + case SEC_QORIQ_AESCBC: + *ofstOut = SEC_QORIQ_CTX_OFST_CBC; + *needsIv = 1; + return 0; + case SEC_QORIQ_AESCTR: + *ofstOut = SEC_QORIQ_CTX_OFST_CTR; + *needsIv = 1; + return 0; + case SEC_QORIQ_AESECB: + *ofstOut = 0; + *needsIv = 0; + return 0; + default: + break; + } + + WOLFSSL_MSG("sec_qoriq: unsupported AES mode"); + return BAD_FUNC_ARG; +} + +/* iv is updated in place when the mode keeps a chaining value, matching what + * wolfCrypt expects of wc_AesCbcEncrypt and friends. */ +int wc_SecQoriqAes(word32 mode, int encrypt, const byte* key, word32 keySz, + byte* iv, const byte* in, word32 inSz, byte* out) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecQoriqDesc desc; + word32 ofst = 0; + int needsIv = 0; + int ret; + + if (key == NULL || in == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + if (keySz != 16 && keySz != 24 && keySz != 32) { + WOLFSSL_MSG("sec_qoriq: bad AES key size"); + return BAD_FUNC_ARG; + } + if (inSz == 0 || (inSz % SEC_QORIQ_AES_BLOCK) != 0) { + /* the partial-block handling of CTR is done by the caller */ + WOLFSSL_MSG("sec_qoriq: AES input must be a whole number of blocks"); + return BAD_FUNC_ARG; + } + if (dev == NULL) { + return WC_HW_E; + } + + ret = secAesModeCtxOffset(mode, &ofst, &needsIv); + if (ret != 0) { + return ret; + } + if (needsIv && iv == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_SecQoriqDescInit(&desc); + if (ret != 0) { + return ret; + } + + /* NWB: the engine must not write the expanded key back to memory. */ + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_KEY | SEC_QORIQ_CLASS1 | + SEC_QORIQ_CMD_NWB, key, keySz); + if (ret != 0) { + return ret; + } + + if (needsIv) { + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_LOAD_CTX | + SEC_QORIQ_CLASS1 | ofst, iv, SEC_QORIQ_AES_BLOCK); + if (ret != 0) { + return ret; + } + } + + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | SEC_QORIQ_CLASS1 | + mode | SEC_QORIQ_ALG_UPDATE | + (encrypt ? SEC_QORIQ_ENC : SEC_QORIQ_DEC)); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_L | SEC_QORIQ_CLASS1 | + SEC_QORIQ_FIFOL_TYPE_MSG | SEC_QORIQ_FIFOL_TYPE_LC1, in, inSz); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_S | + SEC_QORIQ_FIFOS_TYPE_MSG, out, inSz); + if (ret != 0) { + return ret; + } + + if (needsIv) { + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_STORE_CTX | + SEC_QORIQ_CLASS1 | ofst, iv, SEC_QORIQ_AES_BLOCK); + if (ret != 0) { + return ret; + } + } + + /* Everything the engine reads has to be in memory, and anything it + * writes must not be shadowed by a dirty line. */ + ret = wc_SecQoriqCacheFlush((void*)key, keySz); + if (ret == 0) { + ret = wc_SecQoriqCacheFlush((void*)in, inSz); + } + if (ret == 0) { + ret = wc_SecQoriqCacheFlush(out, inSz); + } + if (ret == 0 && needsIv) { + ret = wc_SecQoriqCacheFlush(iv, SEC_QORIQ_AES_BLOCK); + } + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqRun(dev, &desc); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqCacheInval(out, inSz); + if (ret == 0 && needsIv) { + ret = wc_SecQoriqCacheInval(iv, SEC_QORIQ_AES_BLOCK); + } + + return ret; +} + +int wc_SecQoriqAesCbcEncrypt(const byte* key, word32 keySz, byte* iv, + const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqAes(SEC_QORIQ_AESCBC, 1, key, keySz, iv, in, inSz, out); +} + +int wc_SecQoriqAesCbcDecrypt(const byte* key, word32 keySz, byte* iv, + const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqAes(SEC_QORIQ_AESCBC, 0, key, keySz, iv, in, inSz, out); +} + +int wc_SecQoriqAesCtrEncrypt(const byte* key, word32 keySz, byte* iv, + const byte* in, word32 inSz, byte* out) +{ + /* CTR is its own inverse. */ + return wc_SecQoriqAes(SEC_QORIQ_AESCTR, 1, key, keySz, iv, in, inSz, out); +} + +int wc_SecQoriqAesEcbEncrypt(const byte* key, word32 keySz, const byte* in, + word32 inSz, byte* out) +{ + return wc_SecQoriqAes(SEC_QORIQ_AESECB, 1, key, keySz, NULL, in, inSz, + out); +} + +int wc_SecQoriqAesEcbDecrypt(const byte* key, word32 keySz, const byte* in, + word32 inSz, byte* out) +{ + return wc_SecQoriqAes(SEC_QORIQ_AESECB, 0, key, keySz, NULL, in, inSz, + out); +} + +#ifdef HAVE_AESGCM + +/* + * GCM is laid out differently from the confidentiality-only modes: + * + * - the OPERATION comes before the IV, and the IV arrives as a FIFO LOAD + * of type IV rather than a context load, + * - the output FIFO STORE is emitted before the input FIFO LOAD, + * - every FIFO LOAD carries FC1 (flush class 1) except the last, which + * carries LC1. Which load is last depends on the direction and on + * whether there is AAD, so the flag is patched in afterwards, + * - on decrypt the expected tag is loaded as type ICV and the engine + * compares it itself, reporting a mismatch as a job error. + * + * Unlike CBC and friends the message length is arbitrary; GCM handles a + * partial trailing block. + */ +int wc_SecQoriqAesGcm(int encrypt, const byte* key, word32 keySz, + const byte* iv, word32 ivSz, const byte* aad, word32 aadSz, + const byte* in, word32 inSz, byte* out, byte* tag, word32 tagSz) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecQoriqDesc desc; + word32 lastFifo = 0; + int ret; + + if (key == NULL || iv == NULL || tag == NULL) { + return BAD_FUNC_ARG; + } + if (in == NULL && inSz > 0) { + return BAD_FUNC_ARG; + } + if (out == NULL && inSz > 0) { + return BAD_FUNC_ARG; + } + if (aad == NULL && aadSz > 0) { + return BAD_FUNC_ARG; + } + if (keySz != 16 && keySz != 24 && keySz != 32) { + return BAD_FUNC_ARG; + } + /* Full-length tag only, enforced here at the driver boundary and not + * just in the callback router: the engine performs the ICV comparison + * itself and its descriptor contract does not cover truncated tags. */ + if (tagSz != SEC_QORIQ_GCM_TAG_SZ) { + return BAD_FUNC_ARG; + } + /* The AESA derives J0 = IV || 0^31 || 1, which is only the SP 800-38D + * construction for a 12 byte IV. For any other length J0 is + * GHASH(IV || pad || len(IV)), which the engine does not do and this + * port does not compute in software. Accepting other lengths would + * silently produce ciphertext and tags no peer can verify. */ + if (ivSz != SEC_QORIQ_GCM_IV_SZ) { + WOLFSSL_MSG("sec_qoriq: GCM requires a 12 byte IV"); + return BAD_FUNC_ARG; + } + if (dev == NULL) { + return WC_HW_E; + } + + ret = wc_SecQoriqDescInit(&desc); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_KEY | SEC_QORIQ_CLASS1 | + SEC_QORIQ_CMD_NWB, key, keySz); + if (ret != 0) { + return ret; + } + + /* INITF, not UPDATE: this is a single shot, so the engine has to both + * initialise the GHASH state and finalise the tag. With UPDATE the + * ciphertext still comes out correct but the stored "tag" is an + * unfinalised intermediate. */ + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | SEC_QORIQ_CLASS1 | + SEC_QORIQ_AESGCM | SEC_QORIQ_ALG_INITF | + (encrypt ? SEC_QORIQ_ENC : (SEC_QORIQ_DEC | SEC_QORIQ_ALG_ICV))); + if (ret != 0) { + return ret; + } + + lastFifo = desc.idx; + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_L | SEC_QORIQ_CLASS1 | + SEC_QORIQ_FIFOL_TYPE_FC1 | SEC_QORIQ_FIFOL_TYPE_IV, iv, ivSz); + if (ret != 0) { + return ret; + } + + if (aadSz > 0) { + lastFifo = desc.idx; + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_L | + SEC_QORIQ_CLASS1 | SEC_QORIQ_FIFOL_TYPE_FC1 | + SEC_QORIQ_FIFOL_TYPE_AAD, aad, aadSz); + if (ret != 0) { + return ret; + } + } + + if (inSz > 0) { + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_S | + SEC_QORIQ_FIFOS_TYPE_MSG, out, inSz); + if (ret != 0) { + return ret; + } + + lastFifo = desc.idx; + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_L | + SEC_QORIQ_CLASS1 | SEC_QORIQ_FIFOL_TYPE_FC1 | + SEC_QORIQ_FIFOL_TYPE_MSG, in, inSz); + if (ret != 0) { + return ret; + } + } + + if (!encrypt) { + /* hand the engine the tag to check against */ + lastFifo = desc.idx; + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_L | + SEC_QORIQ_CLASS1 | SEC_QORIQ_FIFOL_TYPE_FC1 | + SEC_QORIQ_FIFOL_TYPE_ICV, tag, tagSz); + if (ret != 0) { + return ret; + } + } + + /* Whichever load ended up last must terminate the class 1 stream. */ + desc.desc[lastFifo] &= ~(word32)SEC_QORIQ_FIFOL_TYPE_FC1; + desc.desc[lastFifo] |= SEC_QORIQ_FIFOL_TYPE_LC1; + + if (encrypt) { + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_STORE_CTX | + SEC_QORIQ_CLASS1, tag, tagSz); + if (ret != 0) { + return ret; + } + } + + ret = wc_SecQoriqCacheFlush((void*)key, keySz); + if (ret == 0) { + ret = wc_SecQoriqCacheFlush((void*)iv, ivSz); + } + if (ret == 0 && aadSz > 0) { + ret = wc_SecQoriqCacheFlush((void*)aad, aadSz); + } + if (ret == 0 && inSz > 0) { + ret = wc_SecQoriqCacheFlush((void*)in, inSz); + } + if (ret == 0 && inSz > 0) { + ret = wc_SecQoriqCacheFlush(out, inSz); + } + if (ret == 0) { + ret = wc_SecQoriqCacheFlush(tag, tagSz); + } + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqRun(dev, &desc); + + /* Every failure returns here. Falling through would let the cache + * maintenance below overwrite ret with its own success, turning a + * hardware fault into a "decrypted" buffer the caller trusts. */ + if (ret != 0) { + /* The engine writes the decrypted text before it checks the tag, so + * on an authentication failure the caller's buffer already holds + * unauthenticated plaintext. Destroy that. + * + * Only then, though. Any other post-submission failure propagates + * as a hard error (see the wc_SecQoriqRun() contract), and wiping + * the buffer would destroy the ciphertext of an in-place decrypt + * the caller may still own. */ + if ((ret == WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) && !encrypt && + (inSz > 0) && (out != NULL)) { + (void)wc_SecQoriqCacheInval(out, inSz); + ForceZero(out, inSz); + (void)wc_SecQoriqCacheFlush(out, inSz); + } + return ret; + } + + if (inSz > 0) { + ret = wc_SecQoriqCacheInval(out, inSz); + if (ret != 0) { + return ret; + } + } + if (encrypt) { + ret = wc_SecQoriqCacheInval(tag, tagSz); + } + + return ret; +} + +int wc_SecQoriqAesGcmEncrypt(const byte* key, word32 keySz, const byte* iv, + word32 ivSz, const byte* aad, word32 aadSz, const byte* in, word32 inSz, + byte* out, byte* tag, word32 tagSz) +{ + return wc_SecQoriqAesGcm(1, key, keySz, iv, ivSz, aad, aadSz, in, inSz, + out, tag, tagSz); +} + +int wc_SecQoriqAesGcmDecrypt(const byte* key, word32 keySz, const byte* iv, + word32 ivSz, const byte* aad, word32 aadSz, const byte* in, word32 inSz, + byte* out, const byte* tag, word32 tagSz) +{ + /* the engine only reads the tag on decrypt, the cast keeps the public + * shape const-correct for callers */ + return wc_SecQoriqAesGcm(0, key, keySz, iv, ivSz, aad, aadSz, in, inSz, + out, (byte*)tag, tagSz); +} + +#endif /* HAVE_AESGCM */ + +#endif /* WOLFSSL_SEC_QORIQ && !NO_AES */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c b/wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c new file mode 100644 index 00000000000..ca42aaad9ff --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_baremetal.c @@ -0,0 +1,211 @@ +/* sec_qoriq_baremetal.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 + */ + +/* + * Bare-metal backend for the QorIQ SEC driver. + * + * Assumes a flat, identity mapped address space, which is how both wolfBoot + * and U-Boot leave the e5500/e6500 for the code they hand control to. There + * is no allocator, so DMA memory comes from a static pool. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_BAREMETAL) + +#include +#include +#include + +/* e5500 and e6500 both use a 64 byte cache line. */ +#ifndef SEC_QORIQ_CACHE_LINE + #define SEC_QORIQ_CACHE_LINE 64 +#endif + +/* Backing store for job rings and any bounce buffers. Descriptors live on + * the caller's stack, which is also DMA reachable on this target. */ +#ifndef SEC_QORIQ_DMA_POOL_SZ + #define SEC_QORIQ_DMA_POOL_SZ 8192 +#endif + +static byte secDmaPool[SEC_QORIQ_DMA_POOL_SZ] + __attribute__((aligned(SEC_QORIQ_CACHE_LINE))); +static word32 secDmaUsed = 0; + +int wc_SecQoriqMapRegs(byte** regsOut) +{ + if (regsOut == NULL) { + return BAD_FUNC_ARG; + } + + /* Physically addressed, nothing to map. */ + *regsOut = (byte*)(SEC_QORIQ_CCSRBAR + SEC_QORIQ_OFFSET); + return 0; +} + +void wc_SecQoriqUnmapRegs(byte* regs) +{ + (void)regs; +} + +/* Bump allocator. The driver allocates its rings once at init and never + * frees them in normal operation, so reclaiming space is not worth the + * complexity; wc_SecQoriqDmaFree only rewinds the most recent allocation. */ +void* wc_SecQoriqDmaAlloc(word32 sz, word64* physOut) +{ + word32 aligned; + byte* ptr; + + if (physOut == NULL || sz == 0) { + return NULL; + } + + aligned = (sz + (SEC_QORIQ_CACHE_LINE - 1)) & + ~(word32)(SEC_QORIQ_CACHE_LINE - 1); + + if (secDmaUsed + aligned > SEC_QORIQ_DMA_POOL_SZ) { + WOLFSSL_MSG("sec_qoriq: DMA pool exhausted"); + return NULL; + } + + ptr = &secDmaPool[secDmaUsed]; + secDmaUsed += aligned; + + *physOut = (word64)(wolfssl_word)ptr; + return ptr; +} + +void wc_SecQoriqDmaFree(void* virt, word64 phys, word32 sz) +{ + word32 aligned = (sz + (SEC_QORIQ_CACHE_LINE - 1)) & + ~(word32)(SEC_QORIQ_CACHE_LINE - 1); + + (void)phys; + + if (virt == NULL) { + return; + } + + /* Only the most recent allocation can be given back. Check the size + * first: secDmaUsed and aligned are both word32, so a mismatched size + * would wrap the subtraction and form a pointer outside the pool, which + * is undefined behaviour even though it is only ever compared. */ + if ((aligned == 0) || (aligned > secDmaUsed)) { + return; + } + if ((byte*)virt == &secDmaPool[secDmaUsed - aligned]) { + secDmaUsed -= aligned; + } +} + +/* The address space is flat and identity mapped, so every buffer is + * physically contiguous by construction and the length does not matter. */ +word64 wc_SecQoriqVirtToPhysLen(void* virt, word32 len) +{ + (void)len; + return (word64)(wolfssl_word)virt; +} + +/* No staging on a flat map: memory neither moves nor copies-on-write, so + * the engine can use every buffer in place. Cache maintenance around the + * data stays with the drivers, which know each buffer's role. */ +int wc_SecQoriqDmaJobBegin(void) +{ + return 0; +} + +word64 wc_SecQoriqDmaMapBuf(void* virt, word32 sz, int dir) +{ + (void)dir; + return wc_SecQoriqVirtToPhysLen(virt, sz); +} + +void wc_SecQoriqDmaJobEnd(int ok, int abandoned) +{ + (void)ok; + (void)abandoned; +} + +/* dcbf writes back and invalidates, which is correct in both directions: + * pushing our writes out to the engine, and dropping any stale line before + * reading what the engine wrote. dcbi would be marginally cheaper on the + * inbound path but risks discarding a dirty line if a buffer is ever shared + * with CPU-written data, so it is deliberately not used. */ +static void secCacheOp(void* virt, word32 sz) +{ + wolfssl_word addr = (wolfssl_word)virt; + wolfssl_word end = addr + sz; + + addr &= ~(wolfssl_word)(SEC_QORIQ_CACHE_LINE - 1); + + __asm__ __volatile__("msync" ::: "memory"); + while (addr < end) { + __asm__ __volatile__("dcbf 0,%0" :: "r"(addr) : "memory"); + addr += SEC_QORIQ_CACHE_LINE; + } + __asm__ __volatile__("msync" ::: "memory"); +} + +int wc_SecQoriqCacheFlush(void* virt, word32 sz) +{ + if (virt == NULL) { + return BAD_FUNC_ARG; + } + secCacheOp(virt, sz); + return 0; +} + +int wc_SecQoriqCacheInval(void* virt, word32 sz) +{ + if (virt == NULL) { + return BAD_FUNC_ARG; + } + secCacheOp(virt, sz); + return 0; +} + +void wc_SecQoriqCpuRelax(void) +{ + /* Keep the compiler from hoisting the polled register read out of the + * loop. There is no useful wait instruction here. */ + __asm__ __volatile__("" ::: "memory"); +} + +int wc_SecQoriqGetSvr(word32* svrOut) +{ + word32 svr; + + if (svrOut == NULL) { + return BAD_FUNC_ARG; + } + + /* SVR is SPR 1023 on the e500 family. */ + __asm__ __volatile__("mfspr %0, 1023" : "=r"(svr)); + *svrOut = svr; + + return 0; +} + +#endif /* WOLFSSL_SEC_QORIQ && WOLFSSL_SEC_QORIQ_BAREMETAL */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_cb.c b/wolfcrypt/src/port/nxp/sec_qoriq_cb.c new file mode 100644 index 00000000000..4495864199f --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_cb.c @@ -0,0 +1,960 @@ +/* sec_qoriq_cb.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 + */ + +/* + * Crypto callback router for the QorIQ SEC. + * + * Anything the engine cannot do returns CRYPTOCB_UNAVAILABLE so wolfCrypt + * falls back to software. That fallback is deliberately the default for + * every case that is not explicitly handled, so an unimplemented algorithm + * is a performance question rather than a correctness one. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) + +#include +#include +#include +#include + +#if defined(WOLFSSL_SEC_QORIQ_PKHA) || defined(WOLFSSL_SEC_QORIQ_RSA) + #ifdef WOLFSSL_SEC_QORIQ_PKHA + #include + #include + #endif + #ifdef WOLFSSL_SEC_QORIQ_RSA + #include + #ifndef NO_DH + #include + #endif + #endif + + #ifdef NO_INLINE + #include + #else + #define WOLFSSL_MISC_INCLUDED + #include + #endif +#endif + +/* The fallback promise covers run time declines too, but only those proven + * to happen before a descriptor reached the engine: argument and shape + * validation, an unmappable buffer, a full descriptor, a failed staging + * allocation, a missing capability. Under the wc_SecQoriqRun() error + * contract those are exactly BAD_FUNC_ARG, BUFFER_E, MEMORY_E and + * NOT_COMPILED_IN, and software can safely redo the work over the same + * buffers. + * + * WC_HW_E is deliberately not translated: it can be raised after submission + * (a timeout, or a job the engine ran and faulted), by which point the + * engine may already have modified the caller's output, an in-place RSA + * block or a CBC/CTR chaining IV, and a software retry would compute over + * corrupted state. Failing the one call in flight is the safe answer; later + * calls still fall back cleanly because the router's device checks decline + * before dispatch once the device is dead. + * + * AES_GCM_AUTH_E is not translated either: that is the engine's answer + * about the data, not about itself, and the caller must see it. */ +static int secCbFallback(int ret) +{ + if (ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG) || + ret == WC_NO_ERR_TRACE(BUFFER_E) || + ret == WC_NO_ERR_TRACE(MEMORY_E) || + ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + WOLFSSL_MSG("sec_qoriq: engine declined at run time, using software"); + return CRYPTOCB_UNAVAILABLE; + } + + return ret; +} + +#if defined(WOLFSSL_SEC_QORIQ_PKHA) || defined(WOLFSSL_SEC_QORIQ_RSA) +/* Scratch for key material handed to the engine. Stack resident unless the + * build asks otherwise, and always zeroed back to memory: it was flushed to + * DRAM for DMA, so zeroing cache alone leaves the secret behind. */ +#ifdef WOLFSSL_SMALL_STACK + #define SEC_CB_DECL(name, units) byte* name = NULL + #define SEC_CB_ALLOC(name, sz) \ + do { \ + (name) = (byte*)XMALLOC((sz), NULL, DYNAMIC_TYPE_TMP_BUFFER); \ + if ((name) == NULL) { \ + return MEMORY_E; \ + } \ + XMEMSET((name), 0, (sz)); \ + } while (0) + #define SEC_CB_FREE(name, sz) \ + do { \ + wc_SecQoriqForceZeroDma((name), (sz)); \ + XFREE((name), NULL, DYNAMIC_TYPE_TMP_BUFFER); \ + } while (0) +#else + #define SEC_CB_DECL(name, units) byte name[units] + #define SEC_CB_ALLOC(name, sz) XMEMSET((name), 0, (sz)) + #define SEC_CB_FREE(name, sz) wc_SecQoriqForceZeroDma((name), (sz)) +#endif +#endif + +#ifndef NO_AES + +#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || defined(HAVE_AES_ECB) +/* Anything not describable in one descriptor comes back as + * CRYPTOCB_UNAVAILABLE rather than an error, which would propagate straight + * out of wc_AesCbcEncrypt and break calls software handles fine (a buffer + * over 64 KB, or a length that is not a whole number of blocks). */ +static int secCbCanDo(word32 sz, int needBlockMultiple) +{ + if (wc_SecQoriqGetDev() == NULL) { + return 0; + } + if (sz < SEC_QORIQ_MIN_OFFLOAD_SZ || sz > SEC_QORIQ_MAX_XFER_SZ) { + return 0; + } + if (needBlockMultiple && (sz % WC_AES_BLOCK_SIZE) != 0) { + return 0; + } + return 1; +} +#endif + +#ifdef HAVE_AESGCM +/* The one GCM shape the engine takes, shared by the encrypt and decrypt + * arms so their preconditions cannot drift apart: SP 800-38D 12 byte IV, + * full 16 byte tag, and message/AAD each within a single command. */ +static int secCbGcmCanDo(word32 ivSz, word32 tagSz, word32 sz, word32 aadSz) +{ + if (wc_SecQoriqGetDev() == NULL) { + return 0; + } + if (ivSz != SEC_QORIQ_GCM_IV_SZ || tagSz != SEC_QORIQ_GCM_TAG_SZ) { + return 0; + } + if (sz > SEC_QORIQ_MAX_XFER_SZ || aadSz > SEC_QORIQ_MAX_XFER_SZ) { + return 0; + } + return 1; +} +#endif /* HAVE_AESGCM */ + +/* wolfCrypt hands the callback the whole buffer for these modes, so they map + * straight onto the driver. */ +static int secCbAes(wc_CryptoInfo* info) +{ + switch (info->cipher.type) { + #ifdef HAVE_AES_CBC + case WC_CIPHER_AES_CBC: + { + Aes* aes = info->cipher.aescbc.aes; + + if (aes == NULL || !secCbCanDo(info->cipher.aescbc.sz, 1)) { + return CRYPTOCB_UNAVAILABLE; + } + if (info->cipher.enc) { + return wc_SecQoriqAesCbcEncrypt((const byte*)aes->devKey, + aes->keylen, (byte*)aes->reg, info->cipher.aescbc.in, + info->cipher.aescbc.sz, info->cipher.aescbc.out); + } + return wc_SecQoriqAesCbcDecrypt((const byte*)aes->devKey, + aes->keylen, (byte*)aes->reg, info->cipher.aescbc.in, + info->cipher.aescbc.sz, info->cipher.aescbc.out); + } + #endif + + #ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + { + Aes* aes = info->cipher.aesctr.aes; + + if (aes == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + /* wolfCrypt allows a partial trailing block and keeps the + * leftover keystream in the context; the engine works in whole + * blocks only. */ + if (aes->left != 0 || !secCbCanDo(info->cipher.aesctr.sz, 1)) { + return CRYPTOCB_UNAVAILABLE; + } + return wc_SecQoriqAesCtrEncrypt((const byte*)aes->devKey, + aes->keylen, (byte*)aes->reg, info->cipher.aesctr.in, + info->cipher.aesctr.sz, info->cipher.aesctr.out); + } + #endif + + #ifdef HAVE_AES_ECB + case WC_CIPHER_AES_ECB: + { + Aes* aes = info->cipher.aesecb.aes; + + if (aes == NULL || !secCbCanDo(info->cipher.aesecb.sz, 1)) { + return CRYPTOCB_UNAVAILABLE; + } + if (info->cipher.enc) { + return wc_SecQoriqAesEcbEncrypt((const byte*)aes->devKey, + aes->keylen, info->cipher.aesecb.in, + info->cipher.aesecb.sz, info->cipher.aesecb.out); + } + return wc_SecQoriqAesEcbDecrypt((const byte*)aes->devKey, + aes->keylen, info->cipher.aesecb.in, + info->cipher.aesecb.sz, info->cipher.aesecb.out); + } + #endif + + #ifdef HAVE_AESGCM + case WC_CIPHER_AES_GCM: + if (info->cipher.enc) { + Aes* aes = info->cipher.aesgcm_enc.aes; + + if (aes == NULL || + !secCbGcmCanDo(info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authTagSz, + info->cipher.aesgcm_enc.sz, + info->cipher.aesgcm_enc.authInSz)) { + return CRYPTOCB_UNAVAILABLE; + } + return wc_SecQoriqAesGcmEncrypt((const byte*)aes->devKey, + aes->keylen, + info->cipher.aesgcm_enc.iv, info->cipher.aesgcm_enc.ivSz, + info->cipher.aesgcm_enc.authIn, + info->cipher.aesgcm_enc.authInSz, + info->cipher.aesgcm_enc.in, info->cipher.aesgcm_enc.sz, + info->cipher.aesgcm_enc.out, + info->cipher.aesgcm_enc.authTag, + info->cipher.aesgcm_enc.authTagSz); + } + else { + Aes* aes = info->cipher.aesgcm_dec.aes; + + if (aes == NULL || + !secCbGcmCanDo(info->cipher.aesgcm_dec.ivSz, + info->cipher.aesgcm_dec.authTagSz, + info->cipher.aesgcm_dec.sz, + info->cipher.aesgcm_dec.authInSz)) { + return CRYPTOCB_UNAVAILABLE; + } + return wc_SecQoriqAesGcmDecrypt((const byte*)aes->devKey, + aes->keylen, + info->cipher.aesgcm_dec.iv, info->cipher.aesgcm_dec.ivSz, + info->cipher.aesgcm_dec.authIn, + info->cipher.aesgcm_dec.authInSz, + info->cipher.aesgcm_dec.in, info->cipher.aesgcm_dec.sz, + info->cipher.aesgcm_dec.out, + info->cipher.aesgcm_dec.authTag, + info->cipher.aesgcm_dec.authTagSz); + } + #endif + + default: + break; + } + + return CRYPTOCB_UNAVAILABLE; +} +#endif /* !NO_AES */ + +#ifdef WOLFSSL_SEC_QORIQ_PKHA + +/* Scratch layout, in units of the curve size: the private key or public + * point, plus r and s. Verify needs the most at four. */ +#define SEC_QORIQ_PK_SCRATCH_UNITS 4 + + +/* The curve id lives in one of two places. -1 matches no curve. */ +static int secCbCurveId(ecc_key* key) +{ + const ecc_set_type* dp = key->dp; + + if (dp == NULL) { + dp = wc_ecc_get_curve_params(key->idx); + } + if (dp == NULL) { + return -1; + } + + return dp->id; +} + +/* Common entry checks for the three public key operations. */ +static int secCbEccSetup(ecc_key* key, int* curveId, int* keySz) +{ + if (key == NULL || wc_SecQoriqGetDev() == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + *curveId = secCbCurveId(key); + *keySz = wc_ecc_size(key); + if (*keySz <= 0 || *keySz > MAX_ECC_BYTES) { + return CRYPTOCB_UNAVAILABLE; + } + + if (wc_SecQoriqEccSupported(*curveId, (word32)*keySz) != 0) { + return CRYPTOCB_UNAVAILABLE; + } + + return 0; +} + +/* Export the public point as raw fixed-width x || y (no 0x04 prefix). + * + * A key loaded from a private scalar alone has no public point until + * something derives it, which wolfCrypt does lazily inside its software + * verify. The engine cannot, and handing it zeros would produce a spurious + * "bad signature", so an absent point goes back to software. */ +static int secCbExportPub(ecc_key* key, byte* out, int keySz) +{ + if (key->type == ECC_PRIVATEKEY_ONLY) { + return CRYPTOCB_UNAVAILABLE; + } + if (mp_iszero(key->pubkey.x) && mp_iszero(key->pubkey.y)) { + return CRYPTOCB_UNAVAILABLE; + } + + if (mp_to_unsigned_bin_len(key->pubkey.x, out, keySz) != MP_OKAY) { + return MP_TO_E; + } + if (mp_to_unsigned_bin_len(key->pubkey.y, out + keySz, keySz) != MP_OKAY) { + return MP_TO_E; + } + + return 0; +} + +#if defined(HAVE_ECC_SIGN) || defined(HAVE_ECC_DHE) +/* wolfCrypt's own private key check runs only after the crypto callback + * has been dispatched, in both wc_ecc_sign_hash() and + * wc_ecc_shared_secret(), so a key carrying no scalar would reach the + * engine as an all zero d. Declining lets software run and answer with the + * ECC_BAD_ARG_E the caller is owed. */ +static int secCbHasPriv(ecc_key* key) +{ + if (key->type != ECC_PRIVATEKEY && key->type != ECC_PRIVATEKEY_ONLY) { + return CRYPTOCB_UNAVAILABLE; + } + + return 0; +} +#endif /* HAVE_ECC_SIGN || HAVE_ECC_DHE */ + +#ifdef HAVE_ECC_SIGN +static int secCbEccSign(wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.eccsign.key; + word32 hashSz; + int curveId = 0; + int keySz = 0; + int ret; + byte* priv; + byte* r; + byte* s; + mp_int mpr; + mp_int mps; + SEC_CB_DECL(buf, MAX_ECC_BYTES * SEC_QORIQ_PK_SCRATCH_UNITS); + + if (info->pk.eccsign.in == NULL || info->pk.eccsign.out == NULL || + info->pk.eccsign.outlen == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + +#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + /* The protocol descriptor generates the per-signature nonce inside the + * engine, so RFC 6979 cannot be honoured on this path. */ + if (key != NULL && key->deterministic) { + return CRYPTOCB_UNAVAILABLE; + } +#endif +#if defined(WOLFSSL_ECDSA_SET_K) || defined(WOLFSSL_ECDSA_SET_K_ONE_LOOP) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + /* Same reason for a nonce installed with wc_ecc_sign_set_k(): the + * engine would sign with its own RNG4 nonce, silently ignoring the one + * the caller configured and leaving it armed for a later signature. */ + #ifndef WOLFSSL_NO_MALLOC + if (key != NULL && key->sign_k != NULL) { + return CRYPTOCB_UNAVAILABLE; + } + #else + if (key != NULL && key->sign_k_set) { + return CRYPTOCB_UNAVAILABLE; + } + #endif +#endif + + ret = secCbEccSetup(key, &curveId, &keySz); + if (ret != 0) { + return ret; + } + + ret = secCbHasPriv(key); + if (ret != 0) { + return ret; + } + + /* A digest wider than the group order is reduced the way ECDSA + * prescribes down in the PKHA layer, which declines the one shape it + * cannot express. */ + hashSz = info->pk.eccsign.inlen; + if (hashSz == 0) { + return CRYPTOCB_UNAVAILABLE; + } + + SEC_CB_ALLOC(buf, (word32)keySz * 3); + priv = buf; + r = buf + keySz; + s = buf + (keySz * 2); + + if (mp_to_unsigned_bin_len(wc_ecc_key_get_priv(key), priv, keySz) != + MP_OKAY) { + ret = MP_TO_E; + } + else { + ret = wc_SecQoriqEccSign(curveId, priv, info->pk.eccsign.in, hashSz, + r, s, (word32)keySz); + } + + /* Rebuild the ASN.1 SEQUENCE the caller expects from the raw pair. */ + if (ret == 0) { + if (mp_init(&mpr) != MP_OKAY) { + ret = MP_INIT_E; + } + else { + if (mp_init(&mps) != MP_OKAY) { + ret = MP_INIT_E; + mp_free(&mpr); + } + else { + if (mp_read_unsigned_bin(&mpr, r, (word32)keySz) != MP_OKAY || + mp_read_unsigned_bin(&mps, s, (word32)keySz) != MP_OKAY) { + ret = MP_READ_E; + } + else { + ret = StoreECC_DSA_Sig(info->pk.eccsign.out, + info->pk.eccsign.outlen, &mpr, &mps); + } + mp_free(&mpr); + mp_free(&mps); + } + } + } + + SEC_CB_FREE(buf, (word32)keySz * 3); + + return ret; +} +#endif /* HAVE_ECC_SIGN */ + +#ifdef HAVE_ECC_VERIFY +static int secCbEccVerify(wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.eccverify.key; + word32 hashSz; + int curveId = 0; + int keySz = 0; + int ret; + byte* pub; + byte* r; + byte* s; + mp_int mpr; + mp_int mps; + SEC_CB_DECL(buf, MAX_ECC_BYTES * SEC_QORIQ_PK_SCRATCH_UNITS); + + if (info->pk.eccverify.sig == NULL || info->pk.eccverify.hash == NULL || + info->pk.eccverify.res == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = secCbEccSetup(key, &curveId, &keySz); + if (ret != 0) { + return ret; + } + + hashSz = info->pk.eccverify.hashlen; + if (hashSz == 0) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Initialise first and use the form that expects it. The decoder clears + * both halves on its error path, and clearing an mp_int that was never + * initialised makes sp_clear() zero a garbage number of digits off the + * stack. wc_ecc_verify_hash() takes the same route for the same reason. */ + if (mp_init_multi(&mpr, &mps, NULL, NULL, NULL, NULL) != MP_OKAY) { + return CRYPTOCB_UNAVAILABLE; + } + ret = DecodeECC_DSA_Sig_Ex(info->pk.eccverify.sig, + info->pk.eccverify.siglen, &mpr, &mps, 0); + if (ret != 0) { + /* A signature this malformed is software's to reject, so that the + * caller sees exactly the error it would have without the engine. + * The decoder has already cleared both halves. */ + return CRYPTOCB_UNAVAILABLE; + } + +#ifdef WOLFSSL_SMALL_STACK + buf = (byte*)XMALLOC((word32)keySz * 4, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + mp_free(&mpr); + mp_free(&mps); + return MEMORY_E; + } +#endif + XMEMSET(buf, 0, (word32)keySz * 4); + pub = buf; /* public data only, so no zeroization needed on the way out */ + r = buf + (keySz * 2); + s = buf + (keySz * 3); + + /* r or s wider than the curve cannot be valid and does not fit the + * fixed width block either; let software reject it. */ + if (mp_to_unsigned_bin_len(&mpr, r, keySz) != MP_OKAY || + mp_to_unsigned_bin_len(&mps, s, keySz) != MP_OKAY) { + ret = CRYPTOCB_UNAVAILABLE; + } + else { + ret = secCbExportPub(key, pub, keySz); + } + + if (ret == 0) { + ret = wc_SecQoriqEccVerify(curveId, pub, info->pk.eccverify.hash, + hashSz, r, s, (word32)keySz, info->pk.eccverify.res); + } + + mp_free(&mpr); + mp_free(&mps); +#ifdef WOLFSSL_SMALL_STACK + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return ret; +} +#endif /* HAVE_ECC_VERIFY */ + +#ifdef HAVE_ECC_DHE +static int secCbEcdh(wc_CryptoInfo* info) +{ + ecc_key* privKey = info->pk.ecdh.private_key; + ecc_key* pubKey = info->pk.ecdh.public_key; + int curveId = 0; + int keySz = 0; + int ret; + byte* priv; + byte* peer; + SEC_CB_DECL(buf, MAX_ECC_BYTES * 3); + + if (pubKey == NULL || info->pk.ecdh.out == NULL || + info->pk.ecdh.outlen == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = secCbEccSetup(privKey, &curveId, &keySz); + if (ret != 0) { + return ret; + } + + ret = secCbHasPriv(privKey); + if (ret != 0) { + return ret; + } + + /* Both keys have to be on the curve the descriptor names. */ + if (curveId != secCbCurveId(pubKey)) { + return CRYPTOCB_UNAVAILABLE; + } + + if (*info->pk.ecdh.outlen < (word32)keySz) { + return CRYPTOCB_UNAVAILABLE; + } + + SEC_CB_ALLOC(buf, (word32)keySz * 3); + priv = buf; + peer = buf + keySz; + + if (mp_to_unsigned_bin_len(wc_ecc_key_get_priv(privKey), priv, keySz) != + MP_OKAY) { + ret = MP_TO_E; + } + else { + ret = secCbExportPub(pubKey, peer, keySz); + } + + if (ret == 0) { + ret = wc_SecQoriqEcdh(curveId, priv, peer, info->pk.ecdh.out, + (word32)keySz); + } + if (ret == 0) { + *info->pk.ecdh.outlen = (word32)keySz; + } + + /* priv was flushed to memory for the engine, so zeroing cache alone + * would leave the scalar in DRAM. */ + SEC_CB_FREE(buf, (word32)keySz * 3); + + return ret; +} +#endif /* HAVE_ECC_DHE */ + +#endif /* WOLFSSL_SEC_QORIQ_PKHA */ + +#ifdef WOLFSSL_SEC_QORIQ_RSA + +/* Sized by the largest key the build admits, not the largest the engine + * takes, so an RSA-2048 build carries no 4096 bit buffer. */ +#if (RSA_MAX_SIZE / 8) < SEC_QORIQ_PKHA_MAX_RSA_BYTES + #define SEC_QORIQ_RSA_SCRATCH_BYTES (RSA_MAX_SIZE / 8) +#else + #define SEC_QORIQ_RSA_SCRATCH_BYTES SEC_QORIQ_PKHA_MAX_RSA_BYTES +#endif + +/* The callback is reached at wc_RsaFunction, the raw exponentiation with + * padding already applied or not yet stripped, so it maps straight onto the + * engine's RSA protocols. + * + * This bypasses wolfCrypt's base blinding for private key operations. That + * blinding defends the software exponentiation from timing analysis, which + * is not the code running here; a build that wants it kept can set + * WOLFSSL_SEC_QORIQ_NO_RSA. */ +static int secCbRsa(wc_CryptoInfo* info) +{ + RsaKey* key = info->pk.rsa.key; + word32 nSz; + word32 expSz; +#ifndef WOLFSSL_RSA_PUBLIC_ONLY + int isPrivate; +#endif + int ret; + byte* nbuf; + byte* xbuf; + SEC_CB_DECL(buf, SEC_QORIQ_RSA_SCRATCH_BYTES * 2); + + if (key == NULL || info->pk.rsa.in == NULL || info->pk.rsa.out == NULL || + info->pk.rsa.outLen == NULL || wc_SecQoriqGetDev() == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + switch (info->pk.rsa.type) { + case RSA_PUBLIC_ENCRYPT: + case RSA_PUBLIC_DECRYPT: + #ifndef WOLFSSL_RSA_PUBLIC_ONLY + isPrivate = 0; + #endif + break; + case RSA_PRIVATE_ENCRYPT: + case RSA_PRIVATE_DECRYPT: + #ifdef WOLFSSL_RSA_PUBLIC_ONLY + return CRYPTOCB_UNAVAILABLE; + #else + isPrivate = 1; + break; + #endif + default: + return CRYPTOCB_UNAVAILABLE; + } + + ret = mp_unsigned_bin_size(&key->n); + if (ret <= 0) { + return CRYPTOCB_UNAVAILABLE; + } + nSz = (word32)ret; + if (nSz > SEC_QORIQ_RSA_SCRATCH_BYTES) { + return CRYPTOCB_UNAVAILABLE; + } + + /* The engine works on a whole modulus-width block. */ + if (info->pk.rsa.inLen != nSz || *info->pk.rsa.outLen < nSz) { + return CRYPTOCB_UNAVAILABLE; + } + +#ifndef WOLFSSL_RSA_PUBLIC_ONLY + ret = mp_unsigned_bin_size(isPrivate ? &key->d : &key->e); +#else + ret = mp_unsigned_bin_size(&key->e); +#endif + if (ret <= 0 || (word32)ret > nSz) { + return CRYPTOCB_UNAVAILABLE; + } + expSz = (word32)ret; + + SEC_CB_ALLOC(buf, nSz * 2); + nbuf = buf; + xbuf = buf + nSz; + + if (mp_to_unsigned_bin_len(&key->n, nbuf, (int)nSz) != MP_OKAY) { + ret = MP_TO_E; + } +#ifndef WOLFSSL_RSA_PUBLIC_ONLY + else if (isPrivate) { + if (mp_to_unsigned_bin_len(&key->d, xbuf, (int)expSz) != MP_OKAY) { + ret = MP_TO_E; + } + else { + ret = wc_SecQoriqModExp(info->pk.rsa.in, xbuf, expSz, nbuf, nSz, + info->pk.rsa.out); + } + } +#endif + else { + if (mp_to_unsigned_bin_len(&key->e, xbuf, (int)expSz) != MP_OKAY) { + ret = MP_TO_E; + } + else { + ret = wc_SecQoriqRsaPublic(info->pk.rsa.in, info->pk.rsa.inLen, + nbuf, nSz, xbuf, expSz, info->pk.rsa.out); + } + } + + if (ret == 0) { + *info->pk.rsa.outLen = nSz; + } + + SEC_CB_FREE(buf, nSz * 2); /* xbuf held the private exponent */ + + return ret; +} + +#ifndef NO_DH +/* Finite field DH is the RSA private path's exponentiation: otherPub^priv + * mod p, so it rides on wc_SecQoriqModExp() and is only compiled when the + * RSA half of the port is (WOLFSSL_SEC_QORIQ_RSA). A build that disables + * RSA offload with WOLFSSL_SEC_QORIQ_NO_RSA keeps DH in software too; see + * the README's "RSA and Diffie-Hellman" section. */ +static int secCbDh(wc_CryptoInfo* info) +{ + DhKey* key = info->pk.dh.key; + word32 pSz; + int ret; + byte* pbuf; + byte* ubuf; + SEC_CB_DECL(buf, SEC_QORIQ_RSA_SCRATCH_BYTES * 2); + + if (key == NULL || info->pk.dh.priv == NULL || + info->pk.dh.otherPub == NULL || info->pk.dh.agree == NULL || + info->pk.dh.agreeSz == NULL || wc_SecQoriqGetDev() == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = mp_unsigned_bin_size(&key->p); + if (ret <= 0 || (word32)ret > SEC_QORIQ_RSA_SCRATCH_BYTES) { + return CRYPTOCB_UNAVAILABLE; + } + pSz = (word32)ret; + + /* wolfCrypt hands the peer public value at its natural length, which is + * usually but not always the full width: a value whose top byte is zero + * arrives one byte short, about one time in 256. The engine wants a whole + * modulus-width block, so left pad rather than decline. */ + if (info->pk.dh.pubSz == 0 || info->pk.dh.pubSz > pSz || + info->pk.dh.privSz == 0 || info->pk.dh.privSz > pSz) { + return CRYPTOCB_UNAVAILABLE; + } + + /* wc_SecQoriqModExp() writes a whole modulus width, where the software + * path writes only the significant bytes, so an output buffer shorter + * than the modulus has to go to software. secCbRsa() and secCbEcdh() + * guard their output capacity the same way. */ + if (*info->pk.dh.agreeSz < pSz) { + return CRYPTOCB_UNAVAILABLE; + } + + /* Two blocks: the group prime, then the padded peer public value. */ + SEC_CB_ALLOC(buf, pSz * 2); + pbuf = buf; + ubuf = buf + pSz; + + XMEMCPY(ubuf + (pSz - info->pk.dh.pubSz), info->pk.dh.otherPub, + info->pk.dh.pubSz); + + if (mp_to_unsigned_bin_len(&key->p, pbuf, (int)pSz) != MP_OKAY) { + ret = MP_TO_E; + } + else { + ret = wc_SecQoriqModExp(ubuf, info->pk.dh.priv, + info->pk.dh.privSz, pbuf, pSz, info->pk.dh.agree); + } + + if (ret == 0) { + /* wc_DhAgree()'s software paths return the shared secret with + * leading zero bytes stripped and agreeSz shortened to match; the + * fixed-width form is the separate wc_DhAgree_ct() contract. The + * engine always writes a whole modulus, so strip here or a TLS 1.2 + * DHE premaster secret comes out one byte long roughly one time in + * 256 and the handshake fails. */ + word32 lead = 0; + + while ((lead + 1 < pSz) && (info->pk.dh.agree[lead] == 0)) { + lead++; + } + if (lead > 0) { + XMEMMOVE(info->pk.dh.agree, info->pk.dh.agree + lead, + pSz - lead); + /* The move leaves the last 'lead' bytes of the secret sitting + * beyond the length reported below, where a caller that clears + * only agreeSz bytes would miss them. */ + ForceZero(info->pk.dh.agree + (pSz - lead), lead); + } + *info->pk.dh.agreeSz = pSz - lead; + } + + /* The prime and the peer public value are both public, but free the + * whole block uniformly. */ + SEC_CB_FREE(buf, pSz * 2); + + return ret; +} +#endif /* !NO_DH */ + +#endif /* WOLFSSL_SEC_QORIQ_RSA */ + +#if defined(WOLFSSL_SEC_QORIQ_PKHA) || defined(WOLFSSL_SEC_QORIQ_RSA) + +static int secCbPk(wc_CryptoInfo* info) +{ + switch (info->pk.type) { + #ifdef WOLFSSL_SEC_QORIQ_RSA + case WC_PK_TYPE_RSA: + return secCbRsa(info); + #ifndef NO_DH + case WC_PK_TYPE_DH: + return secCbDh(info); + #endif + #endif + #ifdef WOLFSSL_SEC_QORIQ_PKHA + #ifdef HAVE_ECC_SIGN + case WC_PK_TYPE_ECDSA_SIGN: + return secCbEccSign(info); + #endif + #ifdef HAVE_ECC_VERIFY + case WC_PK_TYPE_ECDSA_VERIFY: + return secCbEccVerify(info); + #endif + #ifdef HAVE_ECC_DHE + case WC_PK_TYPE_ECDH: + return secCbEcdh(info); + #endif + #endif + default: + break; + } + + /* Key generation stays in software so the caller's WC_RNG remains the + * source of the private key. */ + return CRYPTOCB_UNAVAILABLE; +} + +#endif /* WOLFSSL_SEC_QORIQ_PKHA || WOLFSSL_SEC_QORIQ_RSA */ + +/* Hashing is single shot only, but wolfCrypt calls once per Update and + * again for Final. Serving that needs either the streaming descriptor (class + * 2 context round tripped per call) or WOLFSSL_HASH_KEEP. */ +static int secCbHash(wc_CryptoInfo* info) +{ + (void)info; + return CRYPTOCB_UNAVAILABLE; +} + +static int secQoriqRouter(int devId, wc_CryptoInfo* info, void* ctx) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + int ret = CRYPTOCB_UNAVAILABLE; + + (void)devId; + (void)ctx; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + + switch (info->algo_type) { + case WC_ALGO_TYPE_CIPHER: + #ifndef NO_AES + if (dev != NULL) { + dev->cbCipherCount++; + } + ret = secCbFallback(secCbAes(info)); + if (dev != NULL && ret != CRYPTOCB_UNAVAILABLE) { + dev->cbCipherOffload++; + } + #endif + break; + + case WC_ALGO_TYPE_PK: + #if defined(WOLFSSL_SEC_QORIQ_PKHA) || defined(WOLFSSL_SEC_QORIQ_RSA) + if (dev != NULL) { + dev->cbPkCount++; + } + ret = secCbFallback(secCbPk(info)); + if (dev != NULL && ret != CRYPTOCB_UNAVAILABLE) { + dev->cbPkOffload++; + } + #endif + break; + + case WC_ALGO_TYPE_HASH: + if (dev != NULL) { + dev->cbHashCount++; + } + ret = secCbHash(info); + if (dev != NULL && ret != CRYPTOCB_UNAVAILABLE) { + dev->cbHashOffload++; + } + break; + + #ifndef WC_NO_RNG + /* Seed only. WC_ALGO_TYPE_RNG is left unhandled so + * wc_RNG_GenerateBlock keeps running wolfCrypt's DRBG, seeded from + * the SEC. Answering RNG here would hand callers raw engine output + * and drop the DRBG's reseeding policy and health checks. */ + case WC_ALGO_TYPE_SEED: + if (dev != NULL) { + dev->cbSeedCount++; + } + ret = wc_SecQoriqRandomBlock(info->seed.seed, info->seed.sz); + if (ret != 0) { + /* Fall back to the platform entropy source rather than + * leave the caller with no seed. */ + WOLFSSL_MSG("sec_qoriq: seed failed, falling back"); + ret = CRYPTOCB_UNAVAILABLE; + } + break; + #endif + + default: + /* everything else falls back to software */ + break; + } + + return ret; +} + +int wc_SecQoriqRegisterCryptoCb(void) +{ + return wc_CryptoCb_RegisterDevice(WOLFSSL_SEC_QORIQ_DEVID, + secQoriqRouter, NULL); +} + +void wc_SecQoriqUnregisterCryptoCb(void) +{ + wc_CryptoCb_UnRegisterDevice(WOLFSSL_SEC_QORIQ_DEVID); +} + +#endif /* WOLFSSL_SEC_QORIQ && WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_hash.c b/wolfcrypt/src/port/nxp/sec_qoriq_hash.c new file mode 100644 index 00000000000..180a2c732c9 --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_hash.c @@ -0,0 +1,204 @@ +/* sec_qoriq_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 + */ + +/* + * Message digests on the QorIQ SEC, driven through the MDHA. + * + * Only the single shot form is implemented here: one descriptor that + * initialises, absorbs the whole message and finalises. Streaming update or + * final needs the class 2 context loaded and stored around each call, which + * is a later addition. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_SEC_QORIQ + +#include +#include +#include + +/* Largest byte count a single FIFO LOAD can carry without the extended + * length form. */ +#define SEC_QORIQ_FIFO_MAX 0xFFFF + +/* MD5, SHA-1, SHA-224 and SHA-256 absorb 64 byte blocks; SHA-384 and + * SHA-512 absorb 128. A non final chunk must be a whole number of blocks, + * so the chunk size has to follow the algorithm rather than a fixed 64. */ +static word32 secHashBlockSz(word32 algo) +{ + switch (algo) { + case SEC_QORIQ_SHA384: + case SEC_QORIQ_SHA512: + return 128; + default: + return 64; + } +} + +int wc_SecQoriqHash(word32 algo, word32 digestSz, const byte* in, word32 inSz, + byte* out) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecQoriqDesc desc; + word32 offset = 0; + word32 blockSz; + int ret; + + word32 chunkMax; + word32 maxIn; + + if (out == NULL || digestSz == 0) { + return BAD_FUNC_ARG; + } + if (in == NULL && inSz > 0) { + return BAD_FUNC_ARG; + } + if (dev == NULL) { + return WC_HW_E; + } + + blockSz = secHashBlockSz(algo); + + /* The 64-word descriptor bounds how much message the chunk loop below + * can describe: header and OPERATION take 2 words, the digest store 2, + * leaving 30 two-word FIFO loads. Check up front so an oversized + * message is refused explicitly (the documented limit, about 1.9 MB) + * instead of surfacing as a mid-assembly descriptor overflow. */ + chunkMax = SEC_QORIQ_FIFO_MAX - (SEC_QORIQ_FIFO_MAX % blockSz); + maxIn = ((((SEC_QORIQ_DESC_MAX_WORDS - 4) / 2) - 1) * chunkMax) + + SEC_QORIQ_FIFO_MAX; + if (inSz > maxIn) { + WOLFSSL_MSG("sec_qoriq: message too large for a single-shot hash"); + return BAD_FUNC_ARG; + } + + ret = wc_SecQoriqDescInit(&desc); + if (ret != 0) { + return ret; + } + + /* Init and final in one pass over the message. */ + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | SEC_QORIQ_CLASS2 | + algo | SEC_QORIQ_ALG_INITF); + if (ret != 0) { + return ret; + } + + /* The engine reads the message straight out of memory. */ + if (inSz > 0) { + ret = wc_SecQoriqCacheFlush((void*)in, inSz); + if (ret != 0) { + return ret; + } + } + + /* Feed the message in chunks a single command can describe. Only the + * last chunk carries LC2, which is what tells the MDHA to finalise. + * The loop is pre-tested so an empty message never emits a data load. */ + while (offset < inSz) { + word32 chunk = inSz - offset; + word32 cmd = SEC_QORIQ_CMD_FIFO_L | SEC_QORIQ_CLASS2 | + SEC_QORIQ_FIFOL_TYPE_MSG; + + if (chunk > SEC_QORIQ_FIFO_MAX) { + /* a non final chunk must be a whole number of blocks */ + chunk = SEC_QORIQ_FIFO_MAX - (SEC_QORIQ_FIFO_MAX % blockSz); + } + else { + cmd |= SEC_QORIQ_FIFOL_TYPE_LC2; + } + + ret = wc_SecQoriqDescAddBuf(&desc, cmd, in + offset, chunk); + if (ret != 0) { + return ret; + } + offset += chunk; + } + + /* An empty message still needs the class 2 stream terminated so the + * digest of the empty string comes out. An immediate load of zero + * length does that without referencing a buffer, which is why in may + * legitimately be NULL here. */ + if (inSz == 0) { + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_FIFO_L | + SEC_QORIQ_CLASS2 | SEC_QORIQ_FIFOL_TYPE_MSG | + SEC_QORIQ_FIFOL_TYPE_LC2 | SEC_QORIQ_CMD_IMM); + if (ret != 0) { + return ret; + } + } + + /* Pull the digest out of the class 2 context register. Routed through + * AddBuf so the address translation is checked; a hand-rolled AddPtr + * would happily append a failed translation of 0 and let the engine DMA + * the digest to physical address 0. */ + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_STORE_CTX | + SEC_QORIQ_CLASS2, out, digestSz); + if (ret != 0) { + return ret; + } + + /* Push any dirty lines covering the output buffer out of the way before + * the engine writes it. */ + ret = wc_SecQoriqCacheFlush(out, digestSz); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqRun(dev, &desc); + if (ret != 0) { + return ret; + } + + return wc_SecQoriqCacheInval(out, digestSz); +} + +int wc_SecQoriqSha1(const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqHash(SEC_QORIQ_SHA1, 20, in, inSz, out); +} + +int wc_SecQoriqSha224(const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqHash(SEC_QORIQ_SHA224, 28, in, inSz, out); +} + +int wc_SecQoriqSha256(const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqHash(SEC_QORIQ_SHA256, 32, in, inSz, out); +} + +int wc_SecQoriqSha384(const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqHash(SEC_QORIQ_SHA384, 48, in, inSz, out); +} + +int wc_SecQoriqSha512(const byte* in, word32 inSz, byte* out) +{ + return wc_SecQoriqHash(SEC_QORIQ_SHA512, 64, in, inSz, out); +} + +#endif /* WOLFSSL_SEC_QORIQ */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_linux.c b/wolfcrypt/src/port/nxp/sec_qoriq_linux.c new file mode 100644 index 00000000000..4eb4a10a47f --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_linux.c @@ -0,0 +1,485 @@ +/* sec_qoriq_linux.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 + */ + +/* + * Linux user space backend for the QorIQ SEC driver. + * + * Maps the SEC through /dev/mem. The kernel's caam driver must not hold the + * job ring this driver claims; blacklist it or unbind that ring first. + * + * The engine never DMAs through ordinary process pages: they can migrate, + * collapse or copy-on-write after any pagemap lookup, and an untouched + * buffer may still be the shared zero page. Every job's descriptor and data + * are staged instead through one pinned (MAP_LOCKED), pre-faulted, + * physically contiguous arena; pagemap is consulted only for that + * driver-owned memory, and the arena's translation is re-verified before + * every job rather than trusted. + * + * NOTE: this backend initializes and has been exercised on a T1040D4RDB, but + * it offloads nothing on a typical 36-bit part: the job ring takes 32-bit + * descriptor pointers and Linux hands out memory above 4 GB, so every + * request falls back to software. See "The Linux backend on a 36-bit part" + * in README.md. sec_qoriq_baremetal.c is the path that actually offloads. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_LINUX) + +#include +#include +#include + +#include +#include +#include +#include + +#ifndef SEC_QORIQ_MEM_DEV + #define SEC_QORIQ_MEM_DEV "/dev/mem" +#endif + +#ifndef SEC_QORIQ_PAGEMAP_DEV + #define SEC_QORIQ_PAGEMAP_DEV "/proc/self/pagemap" +#endif + +/* Held open for the life of the mapping rather than reopened per page: + * wc_SecQoriqVirtToPhysLen() walks one page at a time, so an open/close pair + * per page dominated the translation cost on multi-page buffers. Opened by + * wc_SecQoriqMapRegs() and closed by wc_SecQoriqUnmapRegs(), both of which + * the threading contract already requires be called from a quiesced state, + * so there is no lazy-initialisation race. Reads use pread() so concurrent + * translations do not share a file offset. */ +static int secPagemapFd = -1; + +#ifndef SEC_QORIQ_PAGE_SZ + #define SEC_QORIQ_PAGE_SZ 4096 +#endif + +#ifndef SEC_QORIQ_CACHE_LINE + #define SEC_QORIQ_CACHE_LINE 64 +#endif + +/* Size of the pinned bounce arena every job is staged through; a job whose + * buffers exceed it falls back to software. */ +#ifndef SEC_QORIQ_ARENA_SZ + #define SEC_QORIQ_ARENA_SZ (128UL * 1024UL) +#endif + +/* Bounce arena state; the machinery lives further down with JobBegin/ + * MapBuf/JobEnd, but wc_SecQoriqMapRegs() retires a lost arena and + * wc_SecQoriqUnmapRegs() releases a live one. */ +typedef struct SecArenaEnt { + void* caller; + byte* stage; + word32 sz; + byte dir; +} SecArenaEnt; + +static byte* secArena = NULL; +static word64 secArenaPhys = 0; +static word32 secArenaUsed = 0; +static word32 secArenaCnt = 0; +static int secArenaLost = 0; /* abandoned to an unquiesced engine */ +/* One entry per descriptor buffer plus the descriptor itself. */ +static SecArenaEnt secArenaEnt[SEC_QORIQ_DESC_MAX_BUFS + 1]; + +int wc_SecQoriqMapRegs(byte** regsOut) +{ + int fd; + void* map; + + if (regsOut == NULL) { + return BAD_FUNC_ARG; + } + + fd = open(SEC_QORIQ_MEM_DEV, O_RDWR | O_SYNC); + if (fd < 0) { + WOLFSSL_MSG("sec_qoriq: cannot open " SEC_QORIQ_MEM_DEV); + return WC_HW_E; + } + + /* CCSR sits high in the physical map, so a 32-bit off_t cannot address + * it. Fail loudly instead of mapping a truncated, wrong address, which on + * a 36-bit part lands in DRAM and is refused by CONFIG_STRICT_DEVMEM. */ + if ((sizeof(off_t) < sizeof(word64)) && + ((SEC_QORIQ_CCSRBAR_PHYS + SEC_QORIQ_OFFSET) > 0x7FFFFFFFULL)) { + close(fd); + WOLFSSL_MSG("sec_qoriq: build with 64-bit off_t to map CCSR"); + return WC_HW_E; + } + + map = mmap(NULL, SEC_QORIQ_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + (off_t)(SEC_QORIQ_CCSRBAR_PHYS + SEC_QORIQ_OFFSET)); + close(fd); + + if (map == MAP_FAILED) { + WOLFSSL_MSG("sec_qoriq: cannot map the SEC block"); + return WC_HW_E; + } + + if (secPagemapFd < 0) { + /* Not fatal: secPhysOfPage() reports failure and the caller falls + * back to software, same as a missing translation. */ + secPagemapFd = open(SEC_QORIQ_PAGEMAP_DEV, O_RDONLY); + if (secPagemapFd < 0) { + WOLFSSL_MSG("sec_qoriq: cannot open " SEC_QORIQ_PAGEMAP_DEV); + } + } + + /* A fresh mapping is a fresh session. An arena abandoned to a still-live + * engine on a prior quiesce failure was leaked deliberately; retire the + * stale pointer and clear the latch so the next job allocates a new one. */ + if (secArenaLost) { + secArena = NULL; + secArenaPhys = 0; + secArenaLost = 0; + } + + *regsOut = (byte*)map; + return 0; +} + +void wc_SecQoriqUnmapRegs(byte* regs) +{ + if (regs != NULL) { + munmap(regs, SEC_QORIQ_SIZE); + } + /* An arena lost to an unquiesced engine is leaked deliberately. */ + if (secArena != NULL && !secArenaLost) { + wc_SecQoriqDmaFree(secArena, secArenaPhys, (word32)SEC_QORIQ_ARENA_SZ); + secArena = NULL; + secArenaPhys = 0; + } + if (secPagemapFd >= 0) { + close(secPagemapFd); + secPagemapFd = -1; + } +} + +/* Locked, page aligned pages so the physical address stays put for the life + * of the allocation. */ +void* wc_SecQoriqDmaAlloc(word32 sz, word64* physOut) +{ + void* ptr; + word64 phys; + + if (physOut == NULL || sz == 0) { + return NULL; + } + + ptr = mmap(NULL, sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0); + if (ptr == MAP_FAILED) { + return NULL; + } + + /* Fault it in before asking for the translation. */ + XMEMSET(ptr, 0, sz); + + /* MAP_LOCKED pins the pages but does not make them physically + * contiguous, and the engine gets one base address for the whole + * region, so verify the entire span rather than just the first frame. + * A ring bigger than a page is the case that needs it. */ + phys = wc_SecQoriqVirtToPhysLen(ptr, sz); + if (phys == 0) { + munmap(ptr, sz); + return NULL; + } + + *physOut = phys; + return ptr; +} + +void wc_SecQoriqDmaFree(void* virt, word64 phys, word32 sz) +{ + (void)phys; + + if (virt != NULL) { + munmap(virt, sz); + } +} + +/* Resolve one page. The engine is handed a single {physical address, + * length} pair per buffer, which is only valid while the buffer stays inside + * one physical frame, so callers must bound the length; see + * wc_SecQoriqVirtToPhysLen(). */ +static word64 secPhysOfPage(word64 vaddr) +{ + word64 entry = 0; + word64 offset; + word64 pfn; + + if (secPagemapFd < 0) { + return 0; + } + + offset = (vaddr / SEC_QORIQ_PAGE_SZ) * (word64)sizeof(word64); + if (pread(secPagemapFd, &entry, sizeof(entry), (off_t)offset) != + (ssize_t)sizeof(entry)) { + return 0; + } + + /* bit 63 says the page is present; the frame number is bits 54:0 */ + if ((entry & (1ULL << 63)) == 0) { + return 0; + } + + pfn = entry & 0x7FFFFFFFFFFFFFULL; + if (pfn == 0) { + /* Either the mapping really is physical page 0 or, far more likely, + * the process lacks CAP_SYS_ADMIN and the kernel zeroed the PFN. + * Aiming engine DMA at physical 0 is not an acceptable guess. */ + WOLFSSL_MSG("sec_qoriq: pagemap PFN is zero, need CAP_SYS_ADMIN"); + return 0; + } + + return (pfn * SEC_QORIQ_PAGE_SZ) + (vaddr % SEC_QORIQ_PAGE_SZ); +} + +/* A region is only usable by the engine if it is physically contiguous. + * Only driver-owned pinned memory (the rings and the bounce arena) is ever + * translated; verify frame by frame and refuse rather than let the engine + * run off the end of the first frame into an unrelated page. */ +word64 wc_SecQoriqVirtToPhysLen(void* virt, word32 len) +{ + word64 vaddr = (word64)(wolfssl_word)virt; + word64 base = secPhysOfPage(vaddr); + word64 off; + + if (base == 0 || len == 0) { + return base; + } + + for (off = SEC_QORIQ_PAGE_SZ - (vaddr % SEC_QORIQ_PAGE_SZ); + off < (word64)len; off += SEC_QORIQ_PAGE_SZ) { + if (secPhysOfPage(vaddr + off) != base + off) { + WOLFSSL_MSG("sec_qoriq: buffer is not physically contiguous"); + return 0; + } + } + + return base; +} + +/****************************************************************************** + Per-job DMA staging: the pinned bounce arena. + + Caller buffers are ordinary process pages, which the kernel may migrate or + copy-on-write after any pagemap snapshot, so the engine is never handed + their addresses. Each job's descriptor and buffers are copied into one + MAP_LOCKED, pre-faulted, contiguity-verified arena instead, and outputs + are copied back after a clean completion. One job is in flight at a time + (the job ring mutex), so a bump allocator reset per job suffices. A job + whose buffers exceed the arena is refused before submission, which the + crypto callback router turns into a software fallback. + ****************************************************************************/ + +int wc_SecQoriqDmaJobBegin(void) +{ + /* MEMORY_E, not WC_HW_E: nothing was submitted, so the router falls back + * to software rather than hard-failing the caller. A fresh + * wc_SecQoriqMapRegs() clears the latch. */ + if (secArenaLost) { + return MEMORY_E; + } + + if (secArena == NULL) { + secArena = (byte*)wc_SecQoriqDmaAlloc((word32)SEC_QORIQ_ARENA_SZ, + &secArenaPhys); + if (secArena == NULL) { + WOLFSSL_MSG("sec_qoriq: cannot allocate the bounce arena"); + return MEMORY_E; + } + } + + /* The pages are locked, but locking does not forbid migration by + * compaction. Detect a moved arena before the engine trusts a stale + * address, rather than hoping. MEMORY_E so the router falls back. */ + if (wc_SecQoriqVirtToPhysLen(secArena, (word32)SEC_QORIQ_ARENA_SZ) != + secArenaPhys) { + WOLFSSL_MSG("sec_qoriq: bounce arena moved, refusing DMA"); + return MEMORY_E; + } + + secArenaUsed = 0; + secArenaCnt = 0; + return 0; +} + +word64 wc_SecQoriqDmaMapBuf(void* virt, word32 sz, int dir) +{ + word32 off; + byte* stage; + + if (virt == NULL || sz == 0 || secArena == NULL || secArenaLost) { + return 0; + } + if (secArenaCnt >= (word32)(SEC_QORIQ_DESC_MAX_BUFS + 1)) { + return 0; + } + + /* Cache-line align each slot so the engine's writes to one buffer can + * never share a line with another. */ + off = (secArenaUsed + (SEC_QORIQ_CACHE_LINE - 1)) & + ~(word32)(SEC_QORIQ_CACHE_LINE - 1); + if ((off + sz < off) || (off + sz > (word32)SEC_QORIQ_ARENA_SZ)) { + WOLFSSL_MSG("sec_qoriq: job too large for the bounce arena"); + return 0; + } + stage = secArena + off; + + if (dir == SEC_QORIQ_DIR_IN) { + XMEMCPY(stage, virt, sz); + } + else { + XMEMSET(stage, 0, sz); + } + (void)wc_SecQoriqCacheFlush(stage, sz); + + secArenaEnt[secArenaCnt].caller = virt; + secArenaEnt[secArenaCnt].stage = stage; + secArenaEnt[secArenaCnt].sz = sz; + secArenaEnt[secArenaCnt].dir = (byte)dir; + secArenaCnt++; + secArenaUsed = off + sz; + + return secArenaPhys + off; +} + +void wc_SecQoriqDmaJobEnd(int ok, int abandoned) +{ + word32 i; + + if (abandoned) { + /* The engine may still write into the arena at any time. Retire it + * permanently: never scrub (a late DMA would just overwrite), never + * reuse, never free. */ + secArenaLost = 1; + return; + } + + for (i = 0; i < secArenaCnt; i++) { + if (ok && secArenaEnt[i].dir == SEC_QORIQ_DIR_OUT) { + (void)wc_SecQoriqCacheInval(secArenaEnt[i].stage, + secArenaEnt[i].sz); + XMEMCPY(secArenaEnt[i].caller, secArenaEnt[i].stage, + secArenaEnt[i].sz); + } + } + + /* The arena held key material; scrub it back to DRAM. */ + if (secArenaUsed > 0) { + wc_SecQoriqForceZeroDma(secArena, secArenaUsed); + } + secArenaUsed = 0; + secArenaCnt = 0; +} + +/* The register window is uncached, but everything these hooks are actually + * called with (the descriptor on the caller's stack, and the caller's key, + * IV, AAD, input, output and tag buffers) is ordinary write-back cached + * memory. dcbf is unprivileged on PowerPC, so the same maintenance the + * bare-metal backend performs works from user space. */ +static void secCacheOp(void* virt, word32 sz) +{ + wolfssl_word addr = (wolfssl_word)virt; + wolfssl_word end = addr + sz; + + addr &= ~(wolfssl_word)(SEC_QORIQ_CACHE_LINE - 1); + + __asm__ __volatile__("msync" ::: "memory"); + while (addr < end) { + __asm__ __volatile__("dcbf 0,%0" :: "r"(addr) : "memory"); + addr += SEC_QORIQ_CACHE_LINE; + } + __asm__ __volatile__("msync" ::: "memory"); +} + +int wc_SecQoriqCacheFlush(void* virt, word32 sz) +{ + if (virt == NULL) { + return BAD_FUNC_ARG; + } + secCacheOp(virt, sz); + return 0; +} + +int wc_SecQoriqCacheInval(void* virt, word32 sz) +{ + if (virt == NULL) { + return BAD_FUNC_ARG; + } + secCacheOp(virt, sz); + return 0; +} + +void wc_SecQoriqCpuRelax(void) +{ + __asm__ __volatile__("" ::: "memory"); +} + +/* SVR is a supervisor register, so read it from the device configuration + * block rather than with mfspr. */ +int wc_SecQoriqGetSvr(word32* svrOut) +{ + int fd; + volatile word32* map; + void* page; + + if (svrOut == NULL) { + return BAD_FUNC_ARG; + } + + fd = open(SEC_QORIQ_MEM_DEV, O_RDONLY | O_SYNC); + if (fd < 0) { + return WC_HW_E; + } + + /* Same truncation guard as wc_SecQoriqMapRegs(): this is the same CCSR + * window, so a 32-bit off_t cannot address it either. */ + if ((sizeof(off_t) < sizeof(word64)) && + ((SEC_QORIQ_CCSRBAR_PHYS + 0xE0000ULL) > 0x7FFFFFFFULL)) { + close(fd); + WOLFSSL_MSG("sec_qoriq: build with 64-bit off_t to read SVR"); + return WC_HW_E; + } + + page = mmap(NULL, SEC_QORIQ_PAGE_SZ, PROT_READ, MAP_SHARED, fd, + (off_t)(SEC_QORIQ_CCSRBAR_PHYS + 0xE0000ULL)); + close(fd); + + if (page == MAP_FAILED) { + return WC_HW_E; + } + + map = (volatile word32*)((byte*)page + 0xA4); + *svrOut = *map; + munmap(page, SEC_QORIQ_PAGE_SZ); + + return 0; +} + +#endif /* WOLFSSL_SEC_QORIQ && WOLFSSL_SEC_QORIQ_LINUX */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_pkha.c b/wolfcrypt/src/port/nxp/sec_qoriq_pkha.c new file mode 100644 index 00000000000..79e8713f91a --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_pkha.c @@ -0,0 +1,637 @@ +/* sec_qoriq_pkha.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 + */ + +/* + * Public key on the QorIQ SEC, driven through the PKHA. + * + * These are protocol descriptors: the engine reads a protocol data block + * (PDB) of parameters and pointers, and the HEADER's start index points at + * the OPERATION word after it. Every operation has the same shape, a size + * word then buffers then an OPERATION, which secPkhaRun() implements once + * for all of them. Domain parameters are always explicit; this silicon + * refuses the built-in curve shortcut (see SEC_QORIQ_PKHA_PDB_L_SHIFT in + * sec_qoriq.h). + * + * Block layouts, all fields big endian and left padded to a fixed width: + * + * ECDSA sign L|N, q, r, G(x||y), s, f, c, d, a||b + * ECDSA verify L|N, q, r, G(x||y), W(x||y), f, c, d, tmp, a||b + * ECDH L|N, q, r, W(x||y), s, z, a||b + * RSA public e|n, f, g, n, e, f_len + * RSA private d|n, g, f, n, d (key form 1) + * + * where q is the field prime, r the group order, G the base point, s a + * private scalar, W a public point, f the message representative, c and d + * the signature halves, z the shared secret and tmp a scratch block. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_SEC_QORIQ + +#include + +#if defined(WOLFSSL_SEC_QORIQ_PKHA) || defined(WOLFSSL_SEC_QORIQ_RSA) + +#ifdef WOLFSSL_SEC_QORIQ_PKHA + #include +#endif +#include +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Longest block above is verify: nine pointers plus room for a length. */ +#define SEC_PKHA_MAX_ITEMS 10 + +/* A buffer when buf is set, otherwise the literal value in sz. */ +typedef struct SecPkhaItem { + const byte* buf; + word32 sz; + byte out; /* engine writes it, so invalidate afterwards */ +} SecPkhaItem; + +/* Assemble the block, submit it, and do cache maintenance both ways. + * Everything the engine touches is flushed first, outputs included, so no + * dirty line can later be written back over what it produced. */ +static int secPkhaRun(word32 pdbWord0, word32 op, const SecPkhaItem* items, + int count, word32* statusOut) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecQoriqDesc desc; + int ret; + int i; + + if (dev == NULL) { + return WC_HW_E; + } + + ret = wc_SecQoriqDescInit(&desc); + if (ret == 0) { + ret = wc_SecQoriqDescAddWord(&desc, pdbWord0); + } + + for (i = 0; (ret == 0) && (i < count); i++) { + if (items[i].buf == NULL) { + ret = wc_SecQoriqDescAddWord(&desc, items[i].sz); + continue; + } + /* No length accompanies a PDB pointer, so the recorded size only + * confirms the platform can make the whole buffer DMA safe; the + * direction has to be stated because the bare word carries none. */ + ret = wc_SecQoriqDescAddPdbPtr(&desc, items[i].buf, items[i].sz, + items[i].out); + } + + if (ret == 0) { + /* Execution starts at the OPERATION; everything above is the PDB. */ + desc.startIdx = desc.idx; + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | + SEC_QORIQ_PROT_UNIDI | op); + } + + for (i = 0; (ret == 0) && (i < count); i++) { + if (items[i].buf != NULL) { + ret = wc_SecQoriqCacheFlush((void*)items[i].buf, items[i].sz); + } + } + + if (ret == 0) { + ret = wc_SecQoriqRunEx(dev, &desc, statusOut); + } + + for (i = 0; i < count; i++) { + if ((items[i].buf != NULL) && items[i].out) { + (void)wc_SecQoriqCacheInval((void*)items[i].buf, items[i].sz); + } + } + + return ret; +} + +#ifdef WOLFSSL_SEC_QORIQ_PKHA + +/* The PKHA is a 1023 bit engine, so P-521 is the largest curve it takes. */ +#define SEC_QORIQ_PKHA_MAX_BYTES 128 + +/* Working set in units of the field size L: q + r + 2 G + 2 ab + f + 2 tmp. */ +#define SEC_QORIQ_PKHA_UNITS 9 + +/* Domain parameters and per-operation buffers in one contiguous block. */ +typedef struct SecPkhaCurve { + byte* q; /* field prime, L */ + byte* r; /* group order, L */ + byte* g; /* base point, 2L */ + byte* ab; /* a and b, 2L */ + byte* f; /* message rep, L */ + byte* tmp; /* verify scratch, 2L */ + word32 L; + word32 sz; +} SecPkhaCurve; + +/* Stack resident by default, bounded by the largest curve the build carries: + * 288 bytes for a P-256 only build, 594 for P-521. */ +#ifdef WOLFSSL_SMALL_STACK + #define SEC_PKHA_DECL(name) byte* name = NULL + #define SEC_PKHA_ALLOC(name, keySz) \ + do { \ + (name) = (byte*)XMALLOC((keySz) * SEC_QORIQ_PKHA_UNITS, NULL, \ + DYNAMIC_TYPE_TMP_BUFFER); \ + if ((name) == NULL) { \ + return MEMORY_E; \ + } \ + } while (0) + #define SEC_PKHA_FREE(name, sz) \ + do { \ + wc_SecQoriqForceZeroDma((name), (sz)); \ + XFREE((name), NULL, DYNAMIC_TYPE_TMP_BUFFER); \ + } while (0) +#else + #define SEC_PKHA_DECL(name) \ + byte name[MAX_ECC_BYTES * SEC_QORIQ_PKHA_UNITS] + #define SEC_PKHA_ALLOC(name, keySz) do { (void)(keySz); } while (0) + #define SEC_PKHA_FREE(name, sz) wc_SecQoriqForceZeroDma((name), (sz)) +#endif + +static int secPkhaHexVal(char c) +{ + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'A') && (c <= 'F')) { + return (c - 'A') + 10; + } + if ((c >= 'a') && (c <= 'f')) { + return (c - 'a') + 10; + } + + return -1; +} + +/* Convert a wolfCrypt hex parameter string to fixed width big endian, right + * aligned and zero padded. Done directly rather than via mp_read_radix() so + * no multi-precision temporary is needed: under SP math an mp_int is sized + * for the build's largest number, hundreds of stack bytes for a string. */ +static int secPkhaParam(const char* hex, byte* out, word32 len) +{ + word32 hlen; + word32 pad; + word32 i; + int v; + + if ((hex == NULL) || (out == NULL) || (len == 0)) { + return BAD_FUNC_ARG; + } + + hlen = (word32)XSTRLEN(hex); + if ((hlen == 0) || (hlen > (len * 2))) { + return BAD_FUNC_ARG; /* wider than the field it has to fit */ + } + + XMEMSET(out, 0, len); + pad = (len * 2) - hlen; + + for (i = 0; i < hlen; i++) { + word32 nib = pad + i; + + v = secPkhaHexVal(hex[i]); + if (v < 0) { + return BAD_FUNC_ARG; + } + if ((nib & 1) == 0) { + out[nib / 2] |= (byte)(v << 4); + } + else { + out[nib / 2] |= (byte)v; + } + } + + return 0; +} + +int wc_SecQoriqEccSupported(int curveId, word32 keySz) +{ + const ecc_set_type* dp; + int idx; + +#ifdef WOLFSSL_SEC_QORIQ_SIM + /* The simulated backend does not model PKHA curve arithmetic, so it + * declares no ECC capability and curve work falls back to software. */ + return NOT_COMPILED_IN; +#endif + + if ((keySz == 0) || (keySz > SEC_QORIQ_PKHA_MAX_BYTES) || + (keySz > MAX_ECC_BYTES)) { + return NOT_COMPILED_IN; + } + + idx = wc_ecc_get_curve_idx(curveId); + if (idx < 0) { + return NOT_COMPILED_IN; + } + dp = wc_ecc_get_curve_params(idx); + if (dp == NULL) { + return NOT_COMPILED_IN; + } + + /* Every parameter goes into a fixed keySz-wide slot, keySz being the + * field size. A few curves have a group order wider than the field + * (SECP160R1/R2/K1, SECP224K1) that would not fit. Refuse here, where + * the answer becomes a software fallback. */ + if ((XSTRLEN(dp->prime) > (keySz * 2)) || + (XSTRLEN(dp->order) > (keySz * 2)) || + (XSTRLEN(dp->Gx) > (keySz * 2)) || + (XSTRLEN(dp->Gy) > (keySz * 2)) || + (XSTRLEN(dp->Af) > (keySz * 2)) || + (XSTRLEN(dp->Bf) > (keySz * 2))) { + WOLFSSL_MSG("sec_qoriq: curve parameters do not fit the fixed block"); + return NOT_COMPILED_IN; + } + + return 0; +} + +/* Lay the working block over caller storage and fill in the parameters. */ +static int secPkhaLoadCurve(int curveId, word32 keySz, byte* buf, + SecPkhaCurve* c) +{ + const ecc_set_type* dp; + int idx; + int ret; + + XMEMSET(c, 0, sizeof(SecPkhaCurve)); + + idx = wc_ecc_get_curve_idx(curveId); + if (idx < 0) { + return NOT_COMPILED_IN; + } + dp = wc_ecc_get_curve_params(idx); + if (dp == NULL) { + return NOT_COMPILED_IN; + } + + c->L = keySz; + c->sz = keySz * SEC_QORIQ_PKHA_UNITS; + + XMEMSET(buf, 0, c->sz); + c->q = buf; + c->r = c->q + keySz; + c->g = c->r + keySz; + c->ab = c->g + (keySz * 2); + c->f = c->ab + (keySz * 2); + c->tmp = c->f + keySz; + + ret = secPkhaParam(dp->prime, c->q, keySz); + if (ret == 0) { + ret = secPkhaParam(dp->order, c->r, keySz); + } + if (ret == 0) { + ret = secPkhaParam(dp->Gx, c->g, keySz); + } + if (ret == 0) { + ret = secPkhaParam(dp->Gy, c->g + keySz, keySz); + } + if (ret == 0) { + ret = secPkhaParam(dp->Af, c->ab, keySz); + } + if (ret == 0) { + ret = secPkhaParam(dp->Bf, c->ab + keySz, keySz); + } + + return ret; +} + +/* Bit length of a fixed width big endian value. */ +static word32 secPkhaBitLen(const byte* val, word32 len) +{ + word32 i; + word32 bits; + byte b; + + for (i = 0; (i < len) && (val[i] == 0); i++) { + /* skip the zero padding */ + } + if (i == len) { + return 0; + } + + bits = (len - i - 1) * 8; + for (b = val[i]; b != 0; b >>= 1) { + bits++; + } + + return bits; +} + +/* Reduce the digest to the group size as ECDSA prescribes: the leftmost + * orderBits bits when the digest is longer, otherwise a left pad with zeros. + * order is the group order, in the same fixed width block as out. + * + * Returns 1 when the caller must fall back to software, for either of two + * reasons. The representative can come out zero, which makes the ECDSA + * equation degenerate; the engine refuses that, but wolfCrypt's tests sign an + * all-zero digest, so it has to be a fallback rather than a failure. Or a + * truncation is needed and the order does not fill the block: wolfCrypt + * finishes that case with a sub-byte shift, and copying whole bytes here + * would build a different representative and so a signature its own verifier + * rejects. P-256 and P-384 have byte aligned orders and take the fast path; + * P-521 never truncates, no digest being wider than its 521 bit order. */ +static int secPkhaMsgRep(const byte* hash, word32 hashSz, const byte* order, + byte* out, word32 len) +{ + byte acc = 0; + word32 i; + word32 orderBits = secPkhaBitLen(order, len); + + if ((hashSz * 8) > orderBits) { + if (orderBits != (len * 8)) { + return 1; + } + XMEMCPY(out, hash, len); + } + else { + XMEMSET(out, 0, len - hashSz); + XMEMCPY(out + (len - hashSz), hash, hashSz); + } + + for (i = 0; i < len; i++) { + acc |= out[i]; + } + + return acc == 0; +} + +/* First PDB word for the curve protocols: field and order sizes. */ +static word32 secPkhaSizes(word32 L) +{ + return ((L & SEC_QORIQ_PKHA_PDB_L_MASK) << SEC_QORIQ_PKHA_PDB_L_SHIFT) | + (L & SEC_QORIQ_PKHA_PDB_N_MASK); +} + +/* Fill one PDB entry. */ +static void secPkhaSet(SecPkhaItem* it, const byte* buf, word32 sz, int out) +{ + it->buf = buf; + it->sz = sz; + it->out = (byte)out; +} + +int wc_SecQoriqEccSign(int curveId, const byte* priv, const byte* hash, + word32 hashSz, byte* r, byte* s, word32 keySz) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecPkhaCurve c; + SecPkhaItem it[SEC_PKHA_MAX_ITEMS]; + SEC_PKHA_DECL(buf); + int ret; + + if ((priv == NULL) || (hash == NULL) || (r == NULL) || (s == NULL) || + (hashSz == 0)) { + return BAD_FUNC_ARG; + } + if (dev == NULL) { + return WC_HW_E; + } + ret = wc_SecQoriqEccSupported(curveId, keySz); + if (ret != 0) { + return ret; + } + +#ifndef WC_NO_RNG + /* The nonce comes from RNG4; without a live state handle the job + * returns a CCB "RNG not instantiated" error. Instantiation happened + * once inside wc_SecQoriqInit(); here only re-read RDSTA (side-effect + * free, and rngReady only ever goes 0 -> 1) in case another agent + * instantiated the handle since. NOT_COMPILED_IN so the callback + * router sends the signature to software. */ + if (dev->rngReady == 0) { + if (wc_SecQoriqRead(dev->regs, SEC_QORIQ_RDSTA) & + SEC_QORIQ_RDSTA_IF0) { + dev->rngReady = 1; + } + else { + return NOT_COMPILED_IN; + } + } +#else + return NOT_COMPILED_IN; +#endif + + SEC_PKHA_ALLOC(buf, keySz); + + ret = secPkhaLoadCurve(curveId, keySz, buf, &c); + if ((ret == 0) && secPkhaMsgRep(hash, hashSz, c.r, c.f, keySz)) { + ret = CRYPTOCB_UNAVAILABLE; + } + + if (ret == 0) { + secPkhaSet(&it[0], c.q, keySz, 0); + secPkhaSet(&it[1], c.r, keySz, 0); + secPkhaSet(&it[2], c.g, keySz * 2, 0); + secPkhaSet(&it[3], priv, keySz, 0); + secPkhaSet(&it[4], c.f, keySz, 0); + secPkhaSet(&it[5], r, keySz, 1); + secPkhaSet(&it[6], s, keySz, 1); + secPkhaSet(&it[7], c.ab, keySz * 2, 0); + + ret = secPkhaRun(secPkhaSizes(keySz), + SEC_QORIQ_ECDSA_SIGN | SEC_QORIQ_PKHA_ECC, it, 8, NULL); + } + + SEC_PKHA_FREE(buf, keySz * SEC_QORIQ_PKHA_UNITS); + + return ret; +} + +int wc_SecQoriqEccVerify(int curveId, const byte* pubXY, const byte* hash, + word32 hashSz, const byte* r, const byte* s, word32 keySz, int* res) +{ + SecPkhaCurve c; + SecPkhaItem it[SEC_PKHA_MAX_ITEMS]; + SEC_PKHA_DECL(buf); + word32 status = 0; + int ret; + + if ((pubXY == NULL) || (hash == NULL) || (r == NULL) || (s == NULL) || + (res == NULL) || (hashSz == 0)) { + return BAD_FUNC_ARG; + } + + *res = 0; /* fail closed: only a clean job sets this */ + + ret = wc_SecQoriqEccSupported(curveId, keySz); + if (ret != 0) { + return ret; + } + + SEC_PKHA_ALLOC(buf, keySz); + + ret = secPkhaLoadCurve(curveId, keySz, buf, &c); + if ((ret == 0) && secPkhaMsgRep(hash, hashSz, c.r, c.f, keySz)) { + ret = CRYPTOCB_UNAVAILABLE; + } + + if (ret == 0) { + secPkhaSet(&it[0], c.q, keySz, 0); + secPkhaSet(&it[1], c.r, keySz, 0); + secPkhaSet(&it[2], c.g, keySz * 2, 0); + secPkhaSet(&it[3], pubXY, keySz * 2, 0); + secPkhaSet(&it[4], c.f, keySz, 0); + secPkhaSet(&it[5], r, keySz, 0); + secPkhaSet(&it[6], s, keySz, 0); + secPkhaSet(&it[7], c.tmp, keySz * 2, 1); + secPkhaSet(&it[8], c.ab, keySz * 2, 0); + + ret = secPkhaRun(secPkhaSizes(keySz), + SEC_QORIQ_ECDSA_VERIFY | SEC_QORIQ_PKHA_ECC, it, 9, &status); + + if (ret == 0) { + *res = 1; + } + else if ((((status & SEC_QORIQ_SSRC_MASK) >> SEC_QORIQ_SSRC_SHIFT) == + SEC_QORIQ_SSRC_DECO) && + ((status & 0xFFU) == SEC_QORIQ_DECOERR_SIGVERIFY)) { + /* The engine ran the verify and rejected it: an answer, not a + * failure. The caller reads it from res. */ + WOLFSSL_MSG("sec_qoriq: ECDSA signature rejected"); + ret = 0; + } + } + + SEC_PKHA_FREE(buf, keySz * SEC_QORIQ_PKHA_UNITS); + + return ret; +} + +int wc_SecQoriqEcdh(int curveId, const byte* priv, const byte* peerXY, + byte* out, word32 keySz) +{ + SecPkhaCurve c; + SecPkhaItem it[SEC_PKHA_MAX_ITEMS]; + SEC_PKHA_DECL(buf); + int ret; + + if ((priv == NULL) || (peerXY == NULL) || (out == NULL)) { + return BAD_FUNC_ARG; + } + ret = wc_SecQoriqEccSupported(curveId, keySz); + if (ret != 0) { + return ret; + } + + SEC_PKHA_ALLOC(buf, keySz); + + ret = secPkhaLoadCurve(curveId, keySz, buf, &c); + if (ret == 0) { + /* No base point: the peer's public point stands in for it. */ + secPkhaSet(&it[0], c.q, keySz, 0); + secPkhaSet(&it[1], c.r, keySz, 0); + secPkhaSet(&it[2], peerXY, keySz * 2, 0); + secPkhaSet(&it[3], priv, keySz, 0); + secPkhaSet(&it[4], out, keySz, 1); + secPkhaSet(&it[5], c.ab, keySz * 2, 0); + + ret = secPkhaRun(secPkhaSizes(keySz), + SEC_QORIQ_ECDSA_ECDH | SEC_QORIQ_PKHA_ECC, it, 6, NULL); + } + if (ret != 0) { + wc_SecQoriqForceZeroDma(out, keySz); /* no partial secrets */ + } + + SEC_PKHA_FREE(buf, keySz * SEC_QORIQ_PKHA_UNITS); + + return ret; +} + +#endif /* WOLFSSL_SEC_QORIQ_PKHA */ + +#ifdef WOLFSSL_SEC_QORIQ_RSA + +/* RSA block word 0: exponent length at bit 12, modulus at bit 0. */ +static word32 secRsaSizes(word32 expSz, word32 nSz) +{ + return ((expSz & SEC_QORIQ_RSA_PDB_LEN_MASK) << + SEC_QORIQ_RSA_PDB_E_SHIFT) | + (nSz & SEC_QORIQ_RSA_PDB_LEN_MASK); +} + +int wc_SecQoriqRsaPublic(const byte* in, word32 inSz, const byte* n, + word32 nSz, const byte* e, word32 eSz, byte* out) +{ + SecPkhaItem it[SEC_PKHA_MAX_ITEMS]; + + if ((in == NULL) || (n == NULL) || (e == NULL) || (out == NULL)) { + return BAD_FUNC_ARG; + } + if ((inSz == 0) || (eSz == 0) || (nSz == 0) || + (nSz > SEC_QORIQ_PKHA_MAX_RSA_BYTES) || + (inSz > nSz) || (eSz > nSz)) { + return BAD_FUNC_ARG; + } + + it[0].buf = in; it[0].sz = inSz; it[0].out = 0; + it[1].buf = out; it[1].sz = nSz; it[1].out = 1; + it[2].buf = n; it[2].sz = nSz; it[2].out = 0; + it[3].buf = e; it[3].sz = eSz; it[3].out = 0; + it[4].buf = NULL; it[4].sz = inSz; it[4].out = 0; /* f_len, inline */ + + return secPkhaRun(secRsaSizes(eSz, nSz), SEC_QORIQ_RSA_ENCRYPT, it, 5, + NULL); +} + +int wc_SecQoriqModExp(const byte* in, const byte* d, word32 dSz, + const byte* n, word32 nSz, byte* out) +{ + SecPkhaItem it[SEC_PKHA_MAX_ITEMS]; + + if ((in == NULL) || (d == NULL) || (n == NULL) || (out == NULL)) { + return BAD_FUNC_ARG; + } + if ((dSz == 0) || (nSz == 0) || (nSz > SEC_QORIQ_PKHA_MAX_RSA_BYTES) || + (dSz > nSz)) { + return BAD_FUNC_ARG; + } + + it[0].buf = in; it[0].sz = nSz; it[0].out = 0; + it[1].buf = out; it[1].sz = nSz; it[1].out = 1; + it[2].buf = n; it[2].sz = nSz; it[2].out = 0; + it[3].buf = d; it[3].sz = dSz; it[3].out = 0; + + /* Deliberately no zeroization of out on failure: wolfCrypt calls + * wc_RsaFunction in place, so out aliases in at every call site, and + * wiping it would destroy the ciphertext the software fallback needs. */ + return secPkhaRun(secRsaSizes(dSz, nSz), + SEC_QORIQ_RSA_DECRYPT | SEC_QORIQ_RSA_PRIV_FRM_1, it, 4, NULL); +} + +#endif /* WOLFSSL_SEC_QORIQ_RSA */ + +#endif /* WOLFSSL_SEC_QORIQ_PKHA || WOLFSSL_SEC_QORIQ_RSA */ +#endif /* WOLFSSL_SEC_QORIQ */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_rng.c b/wolfcrypt/src/port/nxp/sec_qoriq_rng.c new file mode 100644 index 00000000000..8e4d9ede957 --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_rng.c @@ -0,0 +1,224 @@ +/* sec_qoriq_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 + */ + +/* + * RNG4 on the QorIQ SEC. + * + * The block powers up with no DRBG state handle, and neither U-Boot nor + * wolfBoot instantiates one on the boards this was written against (RDSTA + * reads 0 on both T2080 and T1040), so the driver does it itself. + * + * Instantiation goes through the job ring, not direct DECO0 access: Linux + * needs DECO0 because it must instantiate before handing out rings, whereas + * we already own one. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && !defined(WC_NO_RNG) + +#include +#include +#include + +/* Program the TRNG sampling parameters. Program mode only, and only with no + * state handle live: changing these under one would invalidate it. */ +static void secKickTrng(SecQoriqDev* dev, word32 entDelay) +{ + word32 val; + + /* program mode */ + val = wc_SecQoriqRead(dev->regs, SEC_QORIQ_RTMCTL); + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_RTMCTL, + val | SEC_QORIQ_RTMCTL_PRGM); + + /* entropy delay: system clocks per entropy sample */ + val = wc_SecQoriqRead(dev->regs, SEC_QORIQ_RTSDCTL); + val &= ~(word32)SEC_QORIQ_RTSDCTL_ENT_DLY_MASK; + val |= entDelay << SEC_QORIQ_RTSDCTL_ENT_DLY_SHIFT; + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_RTSDCTL, val); + + /* the statistical checker's frequency bounds track the sample length */ + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_RTFRQMIN, entDelay >> 2); + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_RTFRQMAX, entDelay << 3); + + /* back to run mode */ + val = wc_SecQoriqRead(dev->regs, SEC_QORIQ_RTMCTL); + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_RTMCTL, + val & ~(word32)SEC_QORIQ_RTMCTL_PRGM); +} + +/* Instantiate state handle 0, then generate the secure keys (JDKEK, TDKEK, + * TDSK). The JUMP waits for the first operation; the LOAD clears the done + * interrupt and returns the RNG to idle. */ +static int secInstantiateRng(SecQoriqDev* dev) +{ + SecQoriqDesc desc; + int ret; + + ret = wc_SecQoriqDescInit(&desc); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | SEC_QORIQ_CLASS1 | + SEC_QORIQ_RNG | SEC_QORIQ_ALG_INIT); + if (ret == 0) { + /* local jump, test all conditions, target the next command */ + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_JUMP | + SEC_QORIQ_JUMP_CLASS1 | 0x01); + } + if (ret == 0) { + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_LOAD_CLRW); + } + if (ret == 0) { + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CLRW_RESET); + } + if (ret == 0) { + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | + SEC_QORIQ_CLASS1 | SEC_QORIQ_RNG | SEC_QORIQ_RNG4_SK); + } + if (ret != 0) { + return ret; + } + + return wc_SecQoriqRun(dev, &desc); +} + +/* Runs exactly once, from wc_SecQoriqInit() before the crypto callback is + * registered: the RDSTA check, the TRNG programming and the instantiation + * job below are not atomic, and two initialisers interleaving their + * RTSDCTL writes would leave the TRNG programmed by one and instantiated by + * the other. Nothing else may call this once the device is visible. */ +int wc_SecQoriqRngInit(void) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + word32 entDelay = SEC_QORIQ_RTSDCTL_ENT_DLY_MIN; + word32 scfgr; + int ret = WC_HW_E; + + if (dev == NULL) { + return WC_HW_E; + } + + /* Do not touch the TRNG parameters if a handle is already live. */ + if (wc_SecQoriqRead(dev->regs, SEC_QORIQ_RDSTA) & SEC_QORIQ_RDSTA_IF0) { + dev->rngReady = 1; + return 0; + } + + /* A failure usually means the statistical checks rejected the entropy + * at this sample length, so widen it and retry. Inclusive bound: the + * documented maximum delay is itself a valid, and last, attempt. */ + while (entDelay <= SEC_QORIQ_RTSDCTL_ENT_DLY_MAX) { + secKickTrng(dev, entDelay); + + ret = secInstantiateRng(dev); + if (ret == 0) { + break; + } + + entDelay += SEC_QORIQ_RTSDCTL_ENT_DLY_STEP; + } + + if (ret != 0) { + WOLFSSL_MSG("sec_qoriq: RNG4 instantiation failed"); + return ret; + } + + if ((wc_SecQoriqRead(dev->regs, SEC_QORIQ_RDSTA) & + SEC_QORIQ_RDSTA_IF0) == 0) { + WOLFSSL_MSG("sec_qoriq: RNG4 job succeeded but no state handle"); + return WC_HW_E; + } + + /* Read back the deterministic blocks rather than re-deriving them. */ + scfgr = wc_SecQoriqRead(dev->regs, SEC_QORIQ_SCFGR); + wc_SecQoriqWrite(dev->regs, SEC_QORIQ_SCFGR, + scfgr | SEC_QORIQ_SCFGR_RDBENABLE); + + dev->rngReady = 1; + WOLFSSL_MSG("sec_qoriq: RNG4 instantiated"); + + return 0; +} + +int wc_SecQoriqRandomBlock(byte* out, word32 sz) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + SecQoriqDesc desc; + int ret; + + if (out == NULL || sz == 0) { + return BAD_FUNC_ARG; + } + if (dev == NULL) { + return WC_HW_E; + } + if (dev->rngReady == 0) { + /* Never (re)program the TRNG here: that ran once, single threaded, + * in wc_SecQoriqInit(). Re-reading RDSTA is side-effect free and + * picks up a handle another agent instantiated since; rngReady only + * ever goes 0 -> 1, so the check is race free. */ + if (wc_SecQoriqRead(dev->regs, SEC_QORIQ_RDSTA) & + SEC_QORIQ_RDSTA_IF0) { + dev->rngReady = 1; + } + else { + return WC_HW_E; + } + } + + ret = wc_SecQoriqDescInit(&desc); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddWord(&desc, SEC_QORIQ_CMD_OP | SEC_QORIQ_CLASS1 | + SEC_QORIQ_RNG); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqDescAddBuf(&desc, SEC_QORIQ_CMD_FIFO_S | + SEC_QORIQ_FIFOS_TYPE_RNG, out, sz); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqCacheFlush(out, sz); + if (ret != 0) { + return ret; + } + + ret = wc_SecQoriqRun(dev, &desc); + if (ret != 0) { + return ret; + } + + return wc_SecQoriqCacheInval(out, sz); +} + +#endif /* WOLFSSL_SEC_QORIQ && !WC_NO_RNG */ diff --git a/wolfcrypt/src/port/nxp/sec_qoriq_sim.c b/wolfcrypt/src/port/nxp/sec_qoriq_sim.c new file mode 100644 index 00000000000..84a92b7154e --- /dev/null +++ b/wolfcrypt/src/port/nxp/sec_qoriq_sim.c @@ -0,0 +1,1028 @@ +/* sec_qoriq_sim.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 + */ + +/* + * Simulated backend for the QorIQ SEC driver: a host-runnable model of the + * job ring state machine, so descriptor assembly, submission, completion, + * timeout, reset escalation and status decoding run under make check on any + * machine, with fault injection for paths real hardware cannot be asked to + * take on demand. + * + * The model is CPU-native end to end (it implements wc_SecQoriqRead/Write + * itself and interprets the descriptor words the driver wrote), so it runs + * the same on little and big endian hosts. Data buffers keep the port's big + * endian fixed-width conventions, which mp_read/to_unsigned_bin share. + * + * Descriptors execute against wolfCrypt's own software crypto: AES CBC/CTR/ + * ECB, AES-GCM (including the engine's write-plaintext-then-check-ICV + * decrypt behaviour), the MDHA hashes, RNG4 instantiation and generation, + * and the RSA/modexp protocols. ECDSA/ECDH jobs report a DECO error unless + * a status is injected; modelling the PKHA's curve arithmetic is out of + * scope. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) + +#include +#include +#include +#include +#include +#include + +/* Fake physical address ranges, both below 4 GB so 32-bit pointer mode + * accepts them. Persistent allocations (rings) and per-job mappings bump + * separately so a stale job address can never alias a ring. */ +#define SIM_PHYS_ALLOC_BASE 0x08000000UL +#define SIM_PHYS_JOB_BASE 0x04000000UL + +#define SIM_MAX_ALLOCS 8 +#define SIM_MAX_JOBBUF (SEC_QORIQ_DESC_MAX_BUFS + 1) + +/* A CCB-sourced status that is not an ICV failure, for injected faults. */ +#define SIM_STATUS_CCB_ERR 0x20000004UL + +typedef struct SimMapEnt { + void* virt; + word64 phys; + word32 sz; +} SimMapEnt; + +/* Engine and injection state. */ +static struct { + /* controller registers */ + word32 scfgr; + word32 jrStart; + word32 rdsta; + word32 rtmctl; + word32 rtsdctl; + word32 rtfrqmin; + word32 rtfrqmax; + /* job ring registers and internal indexes */ + word32 irbaLs; + word32 orbaLs; + word32 irs; + word32 ors; + word32 orsf; + word32 inRd; + word32 outWr; + /* fault injection */ + word32 nextStatus; + int nextStatusSet; + int timeoutNext; + int stickJrReset; + int stickDmaReset; + int jrResetStuck; + int dmaResetStuck; + int rngFailNext; + int highPhysAlloc; + int longPtr; + word32 lastEntDelay; + byte rngCounter; +} sim; + +/* The register window handed to the driver; never stores live state, it + * only anchors the base + offset arithmetic. */ +static byte simRegsPage[SEC_QORIQ_SIZE]; + +static SimMapEnt simAllocs[SIM_MAX_ALLOCS]; +static word32 simAllocPhysUsed = 0; +static SimMapEnt simJobBufs[SIM_MAX_JOBBUF]; +static word32 simJobCnt = 0; +static word32 simJobPhysUsed = 0; + +/****************************************************************************** + Fault injection API + ****************************************************************************/ + +void wc_SecQoriqSimReset(void) +{ + XMEMSET(&sim, 0, sizeof(sim)); +} + +void wc_SecQoriqSimNextStatus(word32 status) +{ + sim.nextStatus = status; + sim.nextStatusSet = 1; +} + +void wc_SecQoriqSimTimeoutNext(void) +{ + sim.timeoutNext = 1; +} + +void wc_SecQoriqSimStickJrReset(int stick) +{ + sim.stickJrReset = stick; +} + +void wc_SecQoriqSimStickDmaReset(int stick) +{ + sim.stickDmaReset = stick; +} + +void wc_SecQoriqSimRngFailNext(int n) +{ + sim.rngFailNext = n; +} + +word32 wc_SecQoriqSimRngEntDelay(void) +{ + return sim.lastEntDelay; +} + +void wc_SecQoriqSimHighPhysAlloc(int on) +{ + sim.highPhysAlloc = on; +} + +void wc_SecQoriqSimLongPtr(int on) +{ + sim.longPtr = on; +} + +/****************************************************************************** + Address registry + ****************************************************************************/ + +static void* simPhysToVirt(word64 phys, word32 len) +{ + word32 i; + + for (i = 0; i < SIM_MAX_ALLOCS; i++) { + if ((simAllocs[i].virt != NULL) && (phys >= simAllocs[i].phys) && + (phys + len <= simAllocs[i].phys + simAllocs[i].sz)) { + return (byte*)simAllocs[i].virt + (phys - simAllocs[i].phys); + } + } + for (i = 0; i < simJobCnt; i++) { + if ((phys >= simJobBufs[i].phys) && + (phys + len <= simJobBufs[i].phys + simJobBufs[i].sz)) { + return (byte*)simJobBufs[i].virt + (phys - simJobBufs[i].phys); + } + } + + return NULL; +} + +/****************************************************************************** + Descriptor interpreter + ****************************************************************************/ + +typedef struct SimJob { + const byte* key; word32 keySz; + const byte* ctx; word32 ctxSz; word32 ctxOfst; int haveCtx; + const byte* msg[SEC_QORIQ_DESC_MAX_BUFS]; + word32 msgSz[SEC_QORIQ_DESC_MAX_BUFS]; + int msgCnt; + const byte* iv; word32 ivSz; + const byte* aad; word32 aadSz; + const byte* icv; word32 icvSz; + byte* outMsg; word32 outMsgSz; + byte* outRng; word32 outRngSz; + byte* outCtx; word32 outCtxSz; + /* A job descriptor can carry several OPERATIONs (RNG instantiation runs + * INIT then the secure-key generation); ops[0] selects the dispatch. */ + word32 ops[4]; int opCnt; + word32 op; +} SimJob; + +#ifndef NO_AES +/* One flat message buffer for the AES paths, which the driver only ever + * feeds as a single chunk. */ +static const byte* simSingleMsg(const SimJob* job, word32* szOut) +{ + if (job->msgCnt != 1) { + return NULL; + } + *szOut = job->msgSz[0]; + return job->msg[0]; +} + +static word32 simAesRun(const SimJob* job) +{ + Aes aes; + const byte* in; + word32 inSz = 0; + word32 aai = (job->op >> 4) & 0x1FF; +#if defined(HAVE_AES_CBC) || defined(HAVE_AES_ECB) + int enc = (int)(job->op & SEC_QORIQ_ENC); +#endif + int ret = NOT_COMPILED_IN; +#ifdef HAVE_AES_CBC + byte lastIn[WC_AES_BLOCK_SIZE]; +#endif + + if (job->key == NULL || job->outMsg == NULL) { + return SIM_STATUS_CCB_ERR; + } + in = simSingleMsg(job, &inSz); + if (in == NULL || inSz != job->outMsgSz) { + return SIM_STATUS_CCB_ERR; + } + + if (wc_AesInit(&aes, NULL, INVALID_DEVID) != 0) { + return SIM_STATUS_CCB_ERR; + } + + switch (aai) { +#ifdef HAVE_AES_CBC + case 0x10: /* CBC */ + if (job->haveCtx && job->ctxSz >= WC_AES_BLOCK_SIZE) { + /* The updated context is the last ciphertext block, which on + * decrypt is the input. An in-place request maps in and + * outMsg to the same buffer, so take the copy before the + * operation overwrites it with plaintext. */ + XMEMSET(lastIn, 0, sizeof(lastIn)); + if (inSz >= WC_AES_BLOCK_SIZE) { + XMEMCPY(lastIn, in + inSz - WC_AES_BLOCK_SIZE, + WC_AES_BLOCK_SIZE); + } + ret = wc_AesSetKey(&aes, job->key, job->keySz, job->ctx, + enc ? AES_ENCRYPTION : AES_DECRYPTION); + if (ret == 0) { + ret = enc ? + wc_AesCbcEncrypt(&aes, job->outMsg, in, inSz) : + wc_AesCbcDecrypt(&aes, job->outMsg, in, inSz); + } + if (ret == 0 && job->outCtx != NULL && + job->outCtxSz == WC_AES_BLOCK_SIZE && + inSz >= WC_AES_BLOCK_SIZE) { + XMEMCPY(job->outCtx, + enc ? job->outMsg + inSz - WC_AES_BLOCK_SIZE : lastIn, + WC_AES_BLOCK_SIZE); + } + } + break; +#endif +#ifdef WOLFSSL_AES_COUNTER + case 0x00: /* CTR */ + if (job->haveCtx && job->ctxOfst == 16 && + job->ctxSz >= WC_AES_BLOCK_SIZE) { + ret = wc_AesSetKey(&aes, job->key, job->keySz, job->ctx, + AES_ENCRYPTION); + if (ret == 0) { + ret = wc_AesCtrEncrypt(&aes, job->outMsg, in, inSz); + } + if (ret == 0 && job->outCtx != NULL && + job->outCtxSz == WC_AES_BLOCK_SIZE) { + XMEMCPY(job->outCtx, aes.reg, WC_AES_BLOCK_SIZE); + } + } + break; +#endif +#ifdef HAVE_AES_ECB + case 0x20: /* ECB */ + ret = wc_AesSetKey(&aes, job->key, job->keySz, NULL, + enc ? AES_ENCRYPTION : AES_DECRYPTION); + if (ret == 0) { + ret = enc ? + wc_AesEcbEncrypt(&aes, job->outMsg, in, inSz) : + wc_AesEcbDecrypt(&aes, job->outMsg, in, inSz); + } + break; +#endif + default: + break; + } + + wc_AesFree(&aes); + return (ret == 0) ? 0 : SIM_STATUS_CCB_ERR; +} + +#ifdef HAVE_AESGCM +static word32 simGcmRun(const SimJob* job) +{ + Aes aes; + const byte* in = NULL; + word32 inSz = 0; + byte scratchTag[SEC_QORIQ_GCM_TAG_SZ]; + int enc = (int)(job->op & SEC_QORIQ_ENC); + int ret; + + if (job->key == NULL || job->iv == NULL || + job->ivSz != SEC_QORIQ_GCM_IV_SZ) { + return SIM_STATUS_CCB_ERR; + } + if (job->msgCnt > 1) { + return SIM_STATUS_CCB_ERR; + } + if (job->msgCnt == 1) { + in = job->msg[0]; + inSz = job->msgSz[0]; + if (job->outMsg == NULL || job->outMsgSz != inSz) { + return SIM_STATUS_CCB_ERR; + } + } + + if (wc_AesInit(&aes, NULL, INVALID_DEVID) != 0) { + return SIM_STATUS_CCB_ERR; + } + ret = wc_AesGcmSetKey(&aes, job->key, job->keySz); + + if (ret == 0 && enc) { + if (job->outCtx == NULL || job->outCtxSz != SEC_QORIQ_GCM_TAG_SZ) { + ret = BAD_FUNC_ARG; + } + else { + ret = wc_AesGcmEncrypt(&aes, job->outMsg, in, inSz, job->iv, + job->ivSz, job->outCtx, SEC_QORIQ_GCM_TAG_SZ, job->aad, + job->aadSz); + } + } + else if (ret == 0) { + if (job->icv == NULL || job->icvSz != SEC_QORIQ_GCM_TAG_SZ) { + ret = BAD_FUNC_ARG; + } + else { + ret = wc_AesGcmDecrypt(&aes, job->outMsg, in, inSz, job->iv, + job->ivSz, job->icv, job->icvSz, job->aad, job->aadSz); + if (ret == WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) { + /* The engine writes the plaintext before it compares the + * ICV. GCM's keystream is direction independent, so + * "encrypting" the ciphertext reproduces exactly what the + * engine leaves in the output buffer. */ + if (inSz > 0) { + (void)wc_AesGcmEncrypt(&aes, job->outMsg, in, inSz, + job->iv, job->ivSz, scratchTag, + SEC_QORIQ_GCM_TAG_SZ, job->aad, job->aadSz); + } + wc_AesFree(&aes); + return SEC_QORIQ_SSRC_CCB << SEC_QORIQ_SSRC_SHIFT | + SEC_QORIQ_CCBERR_ERRID_ICV; + } + } + } + + wc_AesFree(&aes); + return (ret == 0) ? 0 : SIM_STATUS_CCB_ERR; +} +#endif /* HAVE_AESGCM */ +#endif /* !NO_AES */ + +static word32 simHashRun(const SimJob* job) +{ + wc_HashAlg alg; + enum wc_HashType type; + byte digest[WC_MAX_DIGEST_SIZE]; + word32 digestSz; + int i; + int ret; + + switch ((job->op >> 16) & 0xFF) { + case 0x40: type = WC_HASH_TYPE_MD5; break; + case 0x41: type = WC_HASH_TYPE_SHA; break; + case 0x42: type = WC_HASH_TYPE_SHA224; break; + case 0x43: type = WC_HASH_TYPE_SHA256; break; + case 0x44: type = WC_HASH_TYPE_SHA384; break; + case 0x45: type = WC_HASH_TYPE_SHA512; break; + default: return SIM_STATUS_CCB_ERR; + } + + ret = wc_HashGetDigestSize(type); + if (ret <= 0) { + return SIM_STATUS_CCB_ERR; + } + digestSz = (word32)ret; + + if (job->outCtx == NULL || job->outCtxSz != digestSz) { + return SIM_STATUS_CCB_ERR; + } + + ret = wc_HashInit(&alg, type); + for (i = 0; (ret == 0) && (i < job->msgCnt); i++) { + ret = wc_HashUpdate(&alg, type, job->msg[i], job->msgSz[i]); + } + if (ret == 0) { + ret = wc_HashFinal(&alg, type, digest); + } + (void)wc_HashFree(&alg, type); + + if (ret != 0) { + return SIM_STATUS_CCB_ERR; + } + XMEMCPY(job->outCtx, digest, digestSz); + return 0; +} + +static word32 simRngOne(const SimJob* job, word32 op) +{ + word32 i; + + if (op & SEC_QORIQ_RNG4_SK) { + /* secure key generation: nothing observable to model */ + return 0; + } + if (op & SEC_QORIQ_ALG_INIT) { + if (sim.rngFailNext > 0) { + sim.rngFailNext--; + return SIM_STATUS_CCB_ERR; + } + sim.rdsta |= SEC_QORIQ_RDSTA_IF0; + return 0; + } + + /* data generation */ + if ((sim.rdsta & SEC_QORIQ_RDSTA_IF0) == 0) { + return SIM_STATUS_CCB_ERR; /* no state handle */ + } + if (job->outRng == NULL) { + return SIM_STATUS_CCB_ERR; + } + for (i = 0; i < job->outRngSz; i++) { + job->outRng[i] = sim.rngCounter++; + } + return 0; +} + +/* Run every RNG OPERATION in descriptor order, stopping at a failure the + * way the engine's error halt does. */ +static word32 simRngRun(const SimJob* job) +{ + word32 status = 0; + int i; + + for (i = 0; (status == 0) && (i < job->opCnt); i++) { + status = simRngOne(job, job->ops[i]); + } + return status; +} + +#ifdef WOLFSSL_SEC_QORIQ_RSA +/* RSA protocol descriptors, executed with wolfCrypt's own big number code. + * PDB layouts follow sec_qoriq_pkha.c: + * RSA public {e|n, f(in), g(out), n, e, f_len} + * RSA private {d|n, g(in), f(out), n, d} + */ +static word32 simModExpRun(const word32* desc, word32 startIdx, int isPub) +{ + mp_int base, e, n, r; + const byte* inV; + const byte* nV; + const byte* eV; + byte* outV; + word32 pdb0 = desc[1]; + word32 expSz = (pdb0 >> SEC_QORIQ_RSA_PDB_E_SHIFT) & + SEC_QORIQ_RSA_PDB_LEN_MASK; + word32 nSz = pdb0 & SEC_QORIQ_RSA_PDB_LEN_MASK; + word32 inSz; + int ret; + + if (startIdx < (word32)(isPub ? 6 : 5)) { + return SIM_STATUS_CCB_ERR; + } + inSz = isPub ? desc[6] : nSz; + + inV = (const byte*)simPhysToVirt(desc[2], inSz); + outV = (byte*)simPhysToVirt(desc[3], nSz); + nV = (const byte*)simPhysToVirt(desc[4], nSz); + eV = (const byte*)simPhysToVirt(desc[5], expSz); + if (inV == NULL || outV == NULL || nV == NULL || eV == NULL) { + return SIM_STATUS_CCB_ERR; + } + + if (mp_init_multi(&base, &e, &n, &r, NULL, NULL) != MP_OKAY) { + return SIM_STATUS_CCB_ERR; + } + + ret = mp_read_unsigned_bin(&base, inV, inSz); + if (ret == 0) { + ret = mp_read_unsigned_bin(&e, eV, expSz); + } + if (ret == 0) { + ret = mp_read_unsigned_bin(&n, nV, nSz); + } + if (ret == 0) { + ret = mp_exptmod(&base, &e, &n, &r); + } + if (ret == 0) { + ret = mp_to_unsigned_bin_len(&r, outV, (int)nSz); + } + + mp_forcezero(&base); + mp_forcezero(&e); + mp_clear(&n); + mp_forcezero(&r); + + return (ret == 0) ? 0 : SIM_STATUS_CCB_ERR; +} +#endif /* WOLFSSL_SEC_QORIQ_RSA */ + +/* Execute one descriptor and return its raw job status word. */ +static word32 simRunDesc(const word32* desc) +{ + SimJob job; + word32 len = desc[0] & 0x7F; + word32 startIdx = (desc[0] >> 16) & 0x7F; + word32 i; + + if (len < 2 || len > SEC_QORIQ_DESC_MAX_WORDS || startIdx >= len) { + return SIM_STATUS_CCB_ERR; + } + + /* Protocol descriptor: a PDB precedes the OPERATION at startIdx. */ + if (startIdx > 1) { + word32 op = desc[startIdx]; + word32 proto = op & 0x00FF0000; + + switch (proto) { +#ifdef WOLFSSL_SEC_QORIQ_RSA + case SEC_QORIQ_RSA_ENCRYPT: + return simModExpRun(desc, startIdx, 1); + case SEC_QORIQ_RSA_DECRYPT: + return simModExpRun(desc, startIdx, 0); +#endif + default: + /* PKHA curve arithmetic is not modelled: report the DECO + * "invalid protocol" error the real parts use. */ + return (SEC_QORIQ_SSRC_DECO << SEC_QORIQ_SSRC_SHIFT) | 0x82; + } + } + + XMEMSET(&job, 0, sizeof(job)); + + for (i = 1; i < len; i++) { + word32 w = desc[i]; + word32 ctype = w >> 27; + + switch (ctype) { + case 0x00: /* KEY */ + job.keySz = w & 0x3FF; + job.key = (const byte*)simPhysToVirt(desc[++i], job.keySz); + if (job.key == NULL) { + return SIM_STATUS_CCB_ERR; + } + break; + + case 0x02: /* LOAD */ + if (w & 0x00200000) { /* context load, pointer follows */ + job.ctxSz = w & 0xFF; + job.ctxOfst = (w >> 8) & 0xFF; + job.ctx = (const byte*)simPhysToVirt(desc[++i], + job.ctxSz); + if (job.ctx == NULL) { + return SIM_STATUS_CCB_ERR; + } + job.haveCtx = 1; + } + else { + /* immediate load (CLRW): data words follow inline */ + i += ((w & 0xFF) + 3) / 4; + } + break; + + case 0x04: /* FIFO LOAD */ + { + word32 type = w & 0x00380000; + word32 sz = w & 0xFFFF; + const byte* p = NULL; + + if (w & SEC_QORIQ_CMD_IMM) { + i += (sz + 3) / 4; + break; + } + p = (const byte*)simPhysToVirt(desc[++i], sz); + if (p == NULL) { + return SIM_STATUS_CCB_ERR; + } + if (type == SEC_QORIQ_FIFOL_TYPE_IV) { + job.iv = p; job.ivSz = sz; + } + else if (type == SEC_QORIQ_FIFOL_TYPE_AAD) { + job.aad = p; job.aadSz = sz; + } + else if (type == SEC_QORIQ_FIFOL_TYPE_ICV) { + job.icv = p; job.icvSz = sz; + } + else { /* message */ + if (job.msgCnt >= SEC_QORIQ_DESC_MAX_BUFS) { + return SIM_STATUS_CCB_ERR; + } + job.msg[job.msgCnt] = p; + job.msgSz[job.msgCnt] = sz; + job.msgCnt++; + } + break; + } + + case 0x0A: /* STORE (context) */ + job.outCtxSz = w & 0xFF; + job.outCtx = (byte*)simPhysToVirt(desc[++i], job.outCtxSz); + if (job.outCtx == NULL) { + return SIM_STATUS_CCB_ERR; + } + break; + + case 0x0C: /* FIFO STORE */ + { + word32 type = w & 0x003C0000; + word32 sz = w & 0xFFFF; + byte* p = (byte*)simPhysToVirt(desc[++i], sz); + + if (p == NULL) { + return SIM_STATUS_CCB_ERR; + } + if (type == (SEC_QORIQ_FIFOS_TYPE_RNG & 0x003C0000)) { + job.outRng = p; job.outRngSz = sz; + } + else { + job.outMsg = p; job.outMsgSz = sz; + } + break; + } + + case 0x10: /* OPERATION */ + if (job.opCnt < 4) { + job.ops[job.opCnt++] = w; + } + break; + + case 0x14: /* JUMP */ + break; + + default: + return SIM_STATUS_CCB_ERR; + } + } + + if (job.opCnt == 0) { + return SIM_STATUS_CCB_ERR; + } + job.op = job.ops[0]; + + switch (job.op & 0x00F00000) { +#ifndef NO_AES + case 0x00100000: /* AESA */ +#ifdef HAVE_AESGCM + if (((job.op >> 4) & 0x1FF) == 0x90) { + return simGcmRun(&job); + } +#endif + return simAesRun(&job); +#endif + case 0x00400000: /* MDHA */ + return simHashRun(&job); + case 0x00500000: /* RNG4 */ + return simRngRun(&job); + default: + return SIM_STATUS_CCB_ERR; + } +} + +/* Fetch the descriptor the driver just published and retire it. */ +static void simProcessJob(void) +{ + word32* inRing; + word32* outRing; + word32 descPhys; + word32* descVirt; + word32 status; + + if (sim.timeoutNext) { + /* The job vanishes into the engine: no completion is ever posted, + * so the driver's poll loop must time out and reset. */ + sim.timeoutNext = 0; + return; + } + + inRing = (word32*)simPhysToVirt(sim.irbaLs, sim.irs * + (word32)sizeof(word32)); + outRing = (word32*)simPhysToVirt(sim.orbaLs, sim.ors * 2 * + (word32)sizeof(word32)); + if (inRing == NULL || outRing == NULL || sim.irs == 0 || sim.ors == 0) { + return; + } + + descPhys = inRing[sim.inRd]; + sim.inRd = (sim.inRd + 1) % sim.irs; + + if (sim.nextStatusSet) { + sim.nextStatusSet = 0; + status = sim.nextStatus; + } + else { + /* Resolve the header word; the descriptor array behind it is + * always SEC_QORIQ_DESC_MAX_WORDS wide in the driver. */ + descVirt = (word32*)simPhysToVirt(descPhys, (word32)sizeof(word32)); + status = (descVirt != NULL) ? simRunDesc(descVirt) : + SIM_STATUS_CCB_ERR; + } + + outRing[sim.outWr * 2] = descPhys; + outRing[sim.outWr * 2 + 1] = status; + sim.outWr = (sim.outWr + 1) % sim.ors; + sim.orsf = 1; +} + +/****************************************************************************** + Register model + ****************************************************************************/ + +word32 wc_SecQoriqRead(const byte* base, word32 off) +{ + word32 eff = (word32)(base - simRegsPage) + off; + + switch (eff) { + case SEC_QORIQ_MCFGR: + return (sim.dmaResetStuck ? SEC_QORIQ_MCFGR_DMA_RESET : 0) | + (sim.longPtr ? SEC_QORIQ_MCFGR_LONG_PTR : 0); + case SEC_QORIQ_SCFGR: + return sim.scfgr; + case SEC_QORIQ_JRSTART: + return sim.jrStart; + case SEC_QORIQ_RDSTA: + return sim.rdsta; + case SEC_QORIQ_RTMCTL: + return sim.rtmctl; + case SEC_QORIQ_RTSDCTL: + return sim.rtsdctl; + case SEC_QORIQ_RTFRQMIN: + return sim.rtfrqmin; + case SEC_QORIQ_RTFRQMAX: + return sim.rtfrqmax; + case SEC_QORIQ_CCBVID: + return 6UL << SEC_QORIQ_CCBVID_ERA_SHIFT; + case SEC_QORIQ_CHANUM_MS: + return (4UL << SEC_QORIQ_CHANUM_MS_JRNUM_SHIFT) | + (1UL << SEC_QORIQ_CHANUM_MS_DECONUM_SHIFT); + case SEC_QORIQ_CHANUM_LS: + return (1UL << SEC_QORIQ_CHA_AES_SHIFT) | + (1UL << SEC_QORIQ_CHA_MD_SHIFT) | + (1UL << SEC_QORIQ_CHA_RNG_SHIFT) | + (1UL << SEC_QORIQ_CHA_PK_SHIFT); + case SEC_QORIQ_CHAVID_LS: + return 4UL << SEC_QORIQ_CHA_RNG_SHIFT; /* RNG4 */ + default: + break; + } + + /* job ring 0 page */ + switch (eff - (word32)SEC_QORIQ_JR_OFFSET(0)) { + case SEC_QORIQ_IRSA: + return sim.irs != 0 ? sim.irs : SEC_QORIQ_RING_SIZE; + case SEC_QORIQ_ORSF: + return sim.orsf; + case SEC_QORIQ_JRCR: + return sim.jrResetStuck ? SEC_QORIQ_JRCR_RESET : 0; + case SEC_QORIQ_JRINT: + return 0; + default: + break; + } + + return 0; +} + +void wc_SecQoriqWrite(byte* base, word32 off, word32 val) +{ + word32 eff = (word32)(base - simRegsPage) + off; + + switch (eff) { + case SEC_QORIQ_MCFGR: + if (val & SEC_QORIQ_MCFGR_DMA_RESET) { + /* completes instantly unless injected to stick; either way + * any job the engine still held is gone */ + sim.dmaResetStuck = sim.stickDmaReset; + } + return; + case SEC_QORIQ_SCFGR: + sim.scfgr = val; + return; + case SEC_QORIQ_JRSTART: + sim.jrStart = val; + return; + case SEC_QORIQ_RTMCTL: + sim.rtmctl = val; + return; + case SEC_QORIQ_RTSDCTL: + sim.rtsdctl = val; + sim.lastEntDelay = (val & SEC_QORIQ_RTSDCTL_ENT_DLY_MASK) >> + SEC_QORIQ_RTSDCTL_ENT_DLY_SHIFT; + return; + case SEC_QORIQ_RTFRQMIN: + sim.rtfrqmin = val; + return; + case SEC_QORIQ_RTFRQMAX: + sim.rtfrqmax = val; + return; + default: + break; + } + + switch (eff - (word32)SEC_QORIQ_JR_OFFSET(0)) { + case SEC_QORIQ_IRBA_MS: + case SEC_QORIQ_ORBA_MS: + return; /* 32-bit mode: upper halves are zero */ + case SEC_QORIQ_IRBA_LS: + sim.irbaLs = val; + return; + case SEC_QORIQ_ORBA_LS: + sim.orbaLs = val; + return; + case SEC_QORIQ_IRS: + sim.irs = val; + return; + case SEC_QORIQ_ORS: + sim.ors = val; + return; + case SEC_QORIQ_IRJA: + simProcessJob(); + return; + case SEC_QORIQ_ORJR: + sim.orsf = 0; + return; + case SEC_QORIQ_JRCR: + if (val & SEC_QORIQ_JRCR_RESET) { + if (sim.stickJrReset) { + sim.jrResetStuck = 1; + } + else { + /* ring reset: engine restarts at slot 0, any pending + * (timed out) job is dropped */ + sim.jrResetStuck = 0; + sim.orsf = 0; + sim.inRd = 0; + sim.outWr = 0; + } + } + return; + case SEC_QORIQ_JRINT: + return; /* write-one-to-clear, nothing latched */ + default: + break; + } +} + +/****************************************************************************** + Environment seam + ****************************************************************************/ + +int wc_SecQoriqMapRegs(byte** regsOut) +{ + if (regsOut == NULL) { + return BAD_FUNC_ARG; + } + *regsOut = simRegsPage; + return 0; +} + +void wc_SecQoriqUnmapRegs(byte* regs) +{ + (void)regs; +} + +void* wc_SecQoriqDmaAlloc(word32 sz, word64* physOut) +{ + word32 i; + void* ptr; + + if (physOut == NULL || sz == 0) { + return NULL; + } + + for (i = 0; i < SIM_MAX_ALLOCS; i++) { + if (simAllocs[i].virt == NULL) { + break; + } + } + if (i == SIM_MAX_ALLOCS) { + return NULL; + } + + ptr = XMALLOC(sz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (ptr == NULL) { + return NULL; + } + + simAllocs[i].virt = ptr; + simAllocs[i].phys = SIM_PHYS_ALLOC_BASE + simAllocPhysUsed; + if (sim.highPhysAlloc) { + /* model a 36-bit system whose pages sit above 4 GB */ + simAllocs[i].phys += 0x100000000ULL; + } + simAllocs[i].sz = sz; + simAllocPhysUsed += (sz + 63U) & ~63U; + + *physOut = simAllocs[i].phys; + return ptr; +} + +void wc_SecQoriqDmaFree(void* virt, word64 phys, word32 sz) +{ + word32 i; + + (void)phys; + (void)sz; + + if (virt == NULL) { + return; + } + for (i = 0; i < SIM_MAX_ALLOCS; i++) { + if (simAllocs[i].virt == virt) { + simAllocs[i].virt = NULL; + break; + } + } + XFREE(virt, NULL, DYNAMIC_TYPE_TMP_BUFFER); +} + +word64 wc_SecQoriqVirtToPhysLen(void* virt, word32 len) +{ + word32 i; + + for (i = 0; i < SIM_MAX_ALLOCS; i++) { + if ((simAllocs[i].virt != NULL) && (virt >= simAllocs[i].virt) && + ((byte*)virt + len <= + (byte*)simAllocs[i].virt + simAllocs[i].sz)) { + return simAllocs[i].phys + + (word64)((byte*)virt - (byte*)simAllocs[i].virt); + } + } + return 0; +} + +int wc_SecQoriqDmaJobBegin(void) +{ + simJobCnt = 0; + simJobPhysUsed = 0; + return 0; +} + +word64 wc_SecQoriqDmaMapBuf(void* virt, word32 sz, int dir) +{ + word64 phys; + + (void)dir; + + if (virt == NULL || sz == 0 || simJobCnt >= SIM_MAX_JOBBUF) { + return 0; + } + + phys = SIM_PHYS_JOB_BASE + simJobPhysUsed; + simJobBufs[simJobCnt].virt = virt; + simJobBufs[simJobCnt].phys = phys; + simJobBufs[simJobCnt].sz = sz; + simJobCnt++; + simJobPhysUsed += (sz + 63U) & ~63U; + + return phys; +} + +void wc_SecQoriqDmaJobEnd(int ok, int abandoned) +{ + (void)ok; + (void)abandoned; + simJobCnt = 0; + simJobPhysUsed = 0; +} + +int wc_SecQoriqCacheFlush(void* virt, word32 sz) +{ + (void)sz; + return (virt == NULL) ? BAD_FUNC_ARG : 0; +} + +int wc_SecQoriqCacheInval(void* virt, word32 sz) +{ + (void)sz; + return (virt == NULL) ? BAD_FUNC_ARG : 0; +} + +void wc_SecQoriqCpuRelax(void) +{ +} + +int wc_SecQoriqGetSvr(word32* svrOut) +{ + if (svrOut == NULL) { + return BAD_FUNC_ARG; + } + *svrOut = SEC_QORIQ_SVR_E_BIT; + return 0; +} + +#endif /* WOLFSSL_SEC_QORIQ && WOLFSSL_SEC_QORIQ_SIM */ diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index bfc06cf1624..95527d195f8 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -192,6 +192,9 @@ Threading/Mutex options: #if defined(WOLFSSL_CAAM) #include #endif +#if defined(WOLFSSL_SEC_QORIQ) + #include +#endif #if defined(HAVE_ARIA) #include #endif @@ -1101,6 +1104,19 @@ int wolfCrypt_Init(void) } #endif +#if defined(WOLFSSL_SEC_QORIQ) + /* A part without the security engine is not an error: the SEC is + * only fitted on the "E" orderable variants, and everything simply + * stays in software there. */ + ret = wc_SecQoriqInit(); + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + ret = 0; + } + else if (ret != 0) { + WOLFCRYPT_INIT_RAISE_BAD_STATE(); + } +#endif + #if defined(HAVE_ARIA) if ((ret = wc_AriaInit()) != 0) { WOLFCRYPT_INIT_RAISE_BAD_STATE(); @@ -1231,6 +1247,9 @@ int wolfCrypt_Cleanup(void) #if defined(WOLFSSL_CAAM) wc_caamFree(); #endif + #if defined(WOLFSSL_SEC_QORIQ) + wc_SecQoriqFree(); + #endif #if defined(WOLFSSL_CRYPTOCELL) cc310_Free(); #endif diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index dfe2c3f4f98..5bbcc3ead1e 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -502,6 +502,10 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #ifdef WOLFSSL_CAAM #include #endif + +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) + #include +#endif #ifdef WOLF_CRYPTO_CB #include #ifdef WOLFSSL_SILABS_CRYPTOCB @@ -1211,6 +1215,9 @@ WOLFSSL_TEST_SUBROUTINE int ariagcm_test(MC_ALGID); #if defined(WOLF_CRYPTO_CB) && !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void); #endif +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sec_qoriq_test(void); +#endif #ifdef WOLFSSL_CERT_PIV WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certpiv_test(void); #endif @@ -3729,6 +3736,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("crypto callback test passed!\n"); #endif +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) + if ( (ret = sec_qoriq_test()) != 0) + TEST_FAIL("QorIQ SEC sim test failed!\n", ret); + else + TEST_PASS("QorIQ SEC sim test passed!\n"); +#endif + #if defined(WOLFSSL_RTL8735B_HUK) && defined(WOLFSSL_RTL8735B_HOST_TEST) if ( (ret = wc_Rtl8735b_HukSelfTest()) != 0) TEST_FAIL("RTL8735B HUK self-test failed!\n", ret); @@ -39308,7 +39322,7 @@ static wc_test_ret_t dh_fips_generate_test(WC_RNG *rng) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_gen_test); } -#ifdef WOLFSSL_KEY_GEN +#if defined(WOLFSSL_KEY_GEN) && !defined(WOLFSSL_NO_DH_GEN_PARAMS) wc_FreeDhKey(key); ret = wc_InitDhKey_ex(key, HEAP_HINT, devId); if (ret != 0) @@ -39327,7 +39341,7 @@ static wc_test_ret_t dh_fips_generate_test(WC_RNG *rng) #endif if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_gen_test); -#endif /* WOLFSSL_KEY_GEN */ +#endif /* WOLFSSL_KEY_GEN && !WOLFSSL_NO_DH_GEN_PARAMS */ #endif /* HAVE_SELFTEST */ ret = 0; @@ -39406,7 +39420,8 @@ static wc_test_ret_t dh_generate_test(WC_RNG *rng) ret = 0; #endif -#if !defined(HAVE_FIPS) && defined(WOLFSSL_NO_DH186) +#if !defined(HAVE_FIPS) && defined(WOLFSSL_NO_DH186) && \ + !defined(WOLFSSL_NO_DH_GEN_PARAMS) { byte priv[260]; byte pub[260]; @@ -88522,6 +88537,11 @@ typedef struct { int pbkdf2Count; /* PBKDF2 callback invocations */ int pbkdf2Decline; /* when set, decline so software fallback runs */ #endif +#ifndef NO_DH + int dhAgreeCount; /* DH agree callback invocations */ + int dhAgreeUnavail; /* when set, DH agree declines the op so the + * CRYPTOCB_UNAVAILABLE software fallback runs */ +#endif } myCryptoDevCtx; #ifdef WOLF_CRYPTO_CB_ONLY_RSA @@ -89661,6 +89681,33 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) WOLFSSL_MSG_EX("CryptoDevCb: Pk Type %d\n", info->pk.type); #endif + #ifndef NO_DH + if (info->pk.type == WC_PK_TYPE_DH) { + DhKey* dhKey = info->pk.dh.key; + int dhSaveDevId; + + if (dhKey == NULL) + return BAD_FUNC_ARG; + + /* Decline without counting so wc_DhAgree_Sync's software + * fallback path can be exercised. */ + if (myCtx->dhAgreeUnavail) + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + + myCtx->dhAgreeCount++; + + /* Perform the agreement in software, with the device detached so + * wc_DhAgree() does not dispatch straight back here. */ + dhSaveDevId = dhKey->devId; + dhKey->devId = INVALID_DEVID; + ret = wc_DhAgree(dhKey, info->pk.dh.agree, info->pk.dh.agreeSz, + info->pk.dh.priv, info->pk.dh.privSz, + info->pk.dh.otherPub, info->pk.dh.pubSz); + dhKey->devId = dhSaveDevId; + + return ret; + } + #endif /* !NO_DH */ #if defined(WC_RSA_PSS) && defined(WOLF_CRYPTO_CB_RSA_PAD) && \ !defined(WOLF_CRYPTO_CB_ONLY_RSA) if (info->pk.type == WC_PK_TYPE_RSA_PSS_VERIFY) { @@ -93319,6 +93366,240 @@ static wc_test_ret_t cryptocb_nested_register_test(void) #endif /* NESTED_CB_TEST */ #if !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) && !defined(WC_NO_RNG) +/* Own buffer size rather than DH_TEST_BUF_SIZE: that macro is defined inside + * the HAVE_FFDHE/WC_NO_RNG nest above and is not visible in every config that + * reaches here. FFDHE-2048 values are 256 bytes. */ +#define DH_CB_TEST_BUF_SIZE 256 + +/* One FFDHE-2048 agreement bound to the device, to prove WC_PK_TYPE_DH + * reaches the callback. Uses a named group so nothing depends on parsing a + * key file, and generates only the ephemeral key pair, not the group. */ +static wc_test_ret_t dh_cryptocb_test(int cbDevId, myCryptoDevCtx* ctx) +{ + wc_test_ret_t ret; + word32 privSz = DH_CB_TEST_BUF_SIZE; + word32 pubSz = DH_CB_TEST_BUF_SIZE; + word32 agreeSz = DH_CB_TEST_BUF_SIZE; + word32 agree2Sz = DH_CB_TEST_BUF_SIZE; + word32 i; + int cbCnt; + int saveDevId; + int keyInit = 0, rngInit = 0; +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + /* A DhKey holds several mp_int, which are large enough with SP math to + * blow a kernel module's 4 KB frame budget on their own. */ + DhKey* key = (DhKey*)XMALLOC(sizeof *key, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + WC_RNG* rng = (WC_RNG*)XMALLOC(sizeof *rng, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* priv = (byte*)XMALLOC(DH_CB_TEST_BUF_SIZE, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* pub = (byte*)XMALLOC(DH_CB_TEST_BUF_SIZE, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* agree = (byte*)XMALLOC(DH_CB_TEST_BUF_SIZE, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* agree2 = (byte*)XMALLOC(DH_CB_TEST_BUF_SIZE, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + + if (key == NULL || rng == NULL || priv == NULL || pub == NULL || + agree == NULL || agree2 == NULL) { + ret = WC_TEST_RET_ENC_ERRNO; + goto exit_dh_cb; + } +#else + DhKey key[1]; + WC_RNG rng[1]; + byte priv[DH_CB_TEST_BUF_SIZE]; + byte pub[DH_CB_TEST_BUF_SIZE]; + byte agree[DH_CB_TEST_BUF_SIZE]; + byte agree2[DH_CB_TEST_BUF_SIZE]; +#endif + + /* INVALID_DEVID, not cbDevId: cryptocb_test()'s seed callback fakes + * entropy with an incrementing word32, so a device-bound RNG draws its + * seed from that counter and advances it. That shifts which values land + * in the next device-bound seed, and the SP 800-90B adaptive proportion + * test then rejects the repeated high bytes -- failing an unrelated later + * key generation. Only the DhKey below needs the device; the RNG just + * supplies the ephemeral private value. */ +#ifdef WOLF_CRYPTO_CB_SEED_ONLY_TEST + /* In this build the callback is the sole entropy source, so the RNG + * must run through the device to be seeded at all. */ + ret = wc_InitRng_ex(rng, HEAP_HINT, cbDevId); +#else + ret = wc_InitRng_ex(rng, HEAP_HINT, INVALID_DEVID); +#endif + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + rngInit = 1; + + ret = wc_InitDhKey_ex(key, HEAP_HINT, cbDevId); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + keyInit = 1; + + ret = wc_DhSetNamedKey(key, WC_FFDHE_2048); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + + ret = wc_DhGenerateKeyPair(key, rng, priv, &privSz, pub, &pubSz); +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + + /* Agree with our own public value through the device. The counter + * confirms the operation crossed the callback boundary rather than + * quietly staying in software. */ + ret = wc_DhAgree(key, agree, &agreeSz, priv, privSz, pub, pubSz); +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + if (ctx->dhAgreeCount == 0) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + + /* Same agreement in software with the device detached: a mis-wired + * callback dispatch (an argument bound to the wrong wc_CryptoInfo + * field) shows up as a mismatched size or secret. */ + saveDevId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_DhAgree(key, agree2, &agree2Sz, priv, privSz, pub, pubSz); +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + key->devId = saveDevId; + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + if ((agree2Sz != agreeSz) || (XMEMCMP(agree, agree2, agreeSz) != 0)) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + + /* wc_DhAgree_ct promises constant time, which a callback cannot, so + * it must stay in software: no new callback invocation and the same + * secret, left-padded with zeros to the fixed prime size. */ + cbCnt = ctx->dhAgreeCount; + agree2Sz = DH_CB_TEST_BUF_SIZE; + ret = wc_DhAgree_ct(key, agree2, &agree2Sz, priv, privSz, pub, pubSz); + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + if (ctx->dhAgreeCount != cbCnt) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + if ((agree2Sz < agreeSz) || + (XMEMCMP(agree2 + (agree2Sz - agreeSz), agree, agreeSz) != 0)) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + for (i = 0; i < agree2Sz - agreeSz; i++) { + if (agree2[i] != 0) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + } + + /* A callback that returns CRYPTOCB_UNAVAILABLE must fall back to + * software and still produce the same secret. */ + ctx->dhAgreeUnavail = 1; + agree2Sz = DH_CB_TEST_BUF_SIZE; + ret = wc_DhAgree(key, agree2, &agree2Sz, priv, privSz, pub, pubSz); +#ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); +#endif + ctx->dhAgreeUnavail = 0; + if (ret != 0) { + ret = WC_TEST_RET_ENC_EC(ret); + goto exit_dh_cb; + } + if (ctx->dhAgreeCount != cbCnt) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + if ((agree2Sz != agreeSz) || (XMEMCMP(agree, agree2, agreeSz) != 0)) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + + /* The SP 800-56A peer-key check must run on every agreement, even + * right after callback-backed successes on the same DhKey: a peer value + * of 1 is outside [2, p-2] and must be rejected, so the callback counter + * must not move either. An async build reaches the check one step later, + * since the async marker sends wc_DhAgree() down wc_DhAgree_Async() and + * the software simulator yields WC_PENDING_E first, so resolve that the + * same way the agreements above do before reading the result. */ + { + byte badPub = 1; + + agree2Sz = DH_CB_TEST_BUF_SIZE; + ret = wc_DhAgree(key, agree2, &agree2Sz, priv, privSz, &badPub, 1); + #ifdef WOLFSSL_ASYNC_CRYPT + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); + #endif + if (ret != WC_NO_ERR_TRACE(DH_CHECK_PUB_E)) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + ret = 0; + if (ctx->dhAgreeCount != cbCnt) { + ret = WC_TEST_RET_ENC_NC; + goto exit_dh_cb; + } + } + +exit_dh_cb: + if (keyInit) + wc_FreeDhKey(key); + if (rngInit) + wc_FreeRng(rng); + /* priv is the DH private exponent and agree the shared secret; clear + * both before they go back to the heap or out of scope. XMEMSET, not + * ForceZero: NO_INLINE builds give test.c only ForceZero's hidden + * (WOLFSSL_LOCAL) declaration, which does not link against a shared + * library, and these are throwaway test vectors. */ +#if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_NO_MALLOC) + if (priv != NULL) + XMEMSET(priv, 0, DH_CB_TEST_BUF_SIZE); + if (agree != NULL) + XMEMSET(agree, 0, DH_CB_TEST_BUF_SIZE); + if (agree2 != NULL) + XMEMSET(agree2, 0, DH_CB_TEST_BUF_SIZE); + XFREE(agree2, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(agree, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pub, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(priv, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(rng, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); +#else + XMEMSET(priv, 0, DH_CB_TEST_BUF_SIZE); + XMEMSET(agree, 0, DH_CB_TEST_BUF_SIZE); + XMEMSET(agree2, 0, DH_CB_TEST_BUF_SIZE); +#endif + + return ret; +} +#endif /* !NO_DH && HAVE_FFDHE_2048 && !WC_NO_RNG */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) { wc_test_ret_t ret = 0; @@ -93381,6 +93662,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) myCtx.pbkdf2Count = 0; myCtx.pbkdf2Decline = 0; #endif +#ifndef NO_DH + myCtx.dhAgreeCount = 0; + myCtx.dhAgreeUnavail = 0; +#endif /* set devId to something other than INVALID_DEVID */ devId = 1; @@ -93722,6 +94007,18 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) XFREE(cpPoly, HEAP_HINT, DYNAMIC_TYPE_CIPHER); } #endif /* HAVE_CHACHA && HAVE_POLY1305 && !WOLFSSL_NO_MALLOC */ +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) && !defined(WC_NO_RNG) + /* Agree once through the device on a fixed group. The counter confirms + * the agreement really crossed the callback boundary rather than quietly + * staying in software. + * + * Deliberately not dh_test(): the callback contract needs one agreement, + * not dh_generate_test()'s 2056 bit domain-parameter search, which is one + * of the slowest operations in the suite and is already covered by the + * standalone DH test. */ + if (ret == 0) + ret = dh_cryptocb_test(devId, &myCtx); +#endif /* !NO_DH && HAVE_FFDHE_2048 && !WC_NO_RNG */ #if defined(HAVE_ECC) PRIVATE_KEY_UNLOCK(); if (ret == 0) @@ -94691,6 +94988,654 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #endif /* ! WC_TEST_NO_CRYPTOCB_SW_TEST */ #endif /* WOLF_CRYPTO_CB */ +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) +/* QorIQ SEC driver against the simulated backend: job ring mechanics, + * status decoding, timeout and reset escalation, and known answers for the + * offloaded algorithms cross-checked against wolfCrypt's software + * implementations, with fault injection for the paths real hardware cannot + * be asked to take on demand. + * + * The reset-escalation cases run last: the final one deliberately leaves + * the device poisoned (a quiesce failure), after which re-initialisation + * is refused by design and the rings stay leaked. */ + +/* One 64-byte AES-CBC encrypt through the driver; used by several cases. */ +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +#define SEC_QORIQ_TEST_AES_SZ 64 + +static int sec_qoriq_aes_op(byte* out) +{ + byte key[16]; + byte iv[16]; + byte pt[SEC_QORIQ_TEST_AES_SZ]; + word32 i; + + for (i = 0; i < sizeof(key); i++) + key[i] = (byte)i; + for (i = 0; i < sizeof(iv); i++) + iv[i] = (byte)(0xA0 + i); + for (i = 0; i < sizeof(pt); i++) + pt[i] = (byte)(i * 3); + + return wc_SecQoriqAesCbcEncrypt(key, sizeof(key), iv, pt, sizeof(pt), + out); +} + +static wc_test_ret_t sec_qoriq_aes_test(void) +{ + Aes aes; + byte key[16]; + byte iv[16]; + byte ivHw[16]; + byte pt[SEC_QORIQ_TEST_AES_SZ]; + byte sw[SEC_QORIQ_TEST_AES_SZ]; + byte hw[SEC_QORIQ_TEST_AES_SZ]; + word32 jobs; + word32 i; + int ret; + SecQoriqDev* dev = wc_SecQoriqGetDev(); + + if (dev == NULL) + return WC_TEST_RET_ENC_NC; + + for (i = 0; i < sizeof(key); i++) + key[i] = (byte)i; + for (i = 0; i < sizeof(iv); i++) + iv[i] = (byte)(0xA0 + i); + for (i = 0; i < sizeof(pt); i++) + pt[i] = (byte)(i * 3); + + /* software reference */ + ret = wc_AesInit(&aes, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_AesSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&aes, sw, pt, sizeof(pt)); + wc_AesFree(&aes); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + /* engine, out of place; one job must reach the ring */ + jobs = dev->jobCount; + XMEMCPY(ivHw, iv, sizeof(iv)); + ret = wc_SecQoriqAesCbcEncrypt(key, sizeof(key), ivHw, pt, sizeof(pt), + hw); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(hw, sw, sizeof(sw)) != 0) + return WC_TEST_RET_ENC_NC; + /* chained IV comes back as the last ciphertext block */ + if (XMEMCMP(ivHw, sw + sizeof(sw) - 16, 16) != 0) + return WC_TEST_RET_ENC_NC; + if (dev->jobCount != jobs + 1) + return WC_TEST_RET_ENC_NC; + + /* engine, in place */ + XMEMCPY(hw, pt, sizeof(pt)); + XMEMCPY(ivHw, iv, sizeof(iv)); + ret = wc_SecQoriqAesCbcEncrypt(key, sizeof(key), ivHw, hw, sizeof(hw), + hw); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(hw, sw, sizeof(sw)) != 0) + return WC_TEST_RET_ENC_NC; + + /* engine decrypt, in place, back to the plaintext */ + XMEMCPY(ivHw, iv, sizeof(iv)); + ret = wc_SecQoriqAesCbcDecrypt(key, sizeof(key), ivHw, hw, sizeof(hw), + hw); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(hw, pt, sizeof(pt)) != 0) + return WC_TEST_RET_ENC_NC; + + /* a rejected argument must not count as a submitted job */ + jobs = dev->jobCount; + ret = wc_SecQoriqAesCbcEncrypt(key, 20, ivHw, pt, sizeof(pt), hw); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + if (dev->jobCount != jobs) + return WC_TEST_RET_ENC_NC; + + return 0; +} +#endif /* !NO_AES && HAVE_AES_CBC */ + +#if !defined(NO_AES) && defined(HAVE_AESGCM) +static wc_test_ret_t sec_qoriq_gcm_test(void) +{ + Aes aes; + byte key[16]; + byte iv[SEC_QORIQ_GCM_IV_SZ]; + byte aad[16]; + byte pt[48]; + byte swCt[48]; + byte swTag[SEC_QORIQ_GCM_TAG_SZ]; + byte hwOut[48]; + byte hwTag[SEC_QORIQ_GCM_TAG_SZ]; + byte badTag[SEC_QORIQ_GCM_TAG_SZ]; + byte acc; + word32 i; + int ret; + + for (i = 0; i < sizeof(key); i++) + key[i] = (byte)(0x40 + i); + for (i = 0; i < sizeof(iv); i++) + iv[i] = (byte)(0x90 + i); + for (i = 0; i < sizeof(aad); i++) + aad[i] = (byte)(0x10 + i); + for (i = 0; i < sizeof(pt); i++) + pt[i] = (byte)(i * 7); + + /* software reference */ + ret = wc_AesInit(&aes, HEAP_HINT, INVALID_DEVID); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_AesGcmSetKey(&aes, key, sizeof(key)); + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, swCt, pt, sizeof(pt), iv, sizeof(iv), + swTag, sizeof(swTag), aad, sizeof(aad)); + } + wc_AesFree(&aes); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + /* engine encrypt */ + ret = wc_SecQoriqAesGcmEncrypt(key, sizeof(key), iv, sizeof(iv), aad, + sizeof(aad), pt, sizeof(pt), hwOut, hwTag, sizeof(hwTag)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(hwOut, swCt, sizeof(swCt)) != 0 || + XMEMCMP(hwTag, swTag, sizeof(swTag)) != 0) + return WC_TEST_RET_ENC_NC; + + /* engine decrypt with the good tag */ + ret = wc_SecQoriqAesGcmDecrypt(key, sizeof(key), iv, sizeof(iv), aad, + sizeof(aad), swCt, sizeof(swCt), hwOut, swTag, sizeof(swTag)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(hwOut, pt, sizeof(pt)) != 0) + return WC_TEST_RET_ENC_NC; + + /* corrupted tag: authentication failure, and the unauthenticated + * plaintext the engine wrote must have been destroyed */ + XMEMCPY(badTag, swTag, sizeof(badTag)); + badTag[0] ^= 0x01; + ret = wc_SecQoriqAesGcmDecrypt(key, sizeof(key), iv, sizeof(iv), aad, + sizeof(aad), swCt, sizeof(swCt), hwOut, badTag, sizeof(badTag)); + if (ret != WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) + return WC_TEST_RET_ENC_NC; + acc = 0; + for (i = 0; i < sizeof(hwOut); i++) + acc |= hwOut[i]; + if (acc != 0) + return WC_TEST_RET_ENC_NC; + + /* a truncated tag is refused at the driver boundary */ + ret = wc_SecQoriqAesGcmDecrypt(key, sizeof(key), iv, sizeof(iv), aad, + sizeof(aad), swCt, sizeof(swCt), hwOut, swTag, 12); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + ret = wc_SecQoriqAesGcmEncrypt(key, sizeof(key), iv, sizeof(iv), aad, + sizeof(aad), pt, sizeof(pt), hwOut, hwTag, 12); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + + return 0; +} +#endif /* !NO_AES && HAVE_AESGCM */ + +#ifndef NO_SHA256 +/* Big enough that the driver must split the message into several FIFO + * loads (the per-command cap is 64 KB). */ +#define SEC_QORIQ_TEST_HASH_SZ (150 * 1024) + +static wc_test_ret_t sec_qoriq_hash_test(void) +{ + byte* msg; + byte sw[WC_SHA256_DIGEST_SIZE]; + byte hw[WC_SHA256_DIGEST_SIZE]; + word32 i; + int ret; + + msg = (byte*)XMALLOC(SEC_QORIQ_TEST_HASH_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (msg == NULL) + return WC_TEST_RET_ENC_ERRNO; + for (i = 0; i < SEC_QORIQ_TEST_HASH_SZ; i++) + msg[i] = (byte)(i ^ (i >> 8)); + + ret = wc_Sha256Hash(msg, SEC_QORIQ_TEST_HASH_SZ, sw); + if (ret == 0) + ret = wc_SecQoriqSha256(msg, SEC_QORIQ_TEST_HASH_SZ, hw); + if (ret == 0 && XMEMCMP(sw, hw, sizeof(sw)) != 0) + ret = WC_TEST_RET_ENC_NC; + + /* over the documented single-shot limit: refused up front, before any + * buffer is touched (only the length is inspected) */ + if (ret == 0) { + if (wc_SecQoriqSha256(msg, 0x200000, hw) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + ret = WC_TEST_RET_ENC_NC; + } + } + + XFREE(msg, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} +#endif /* !NO_SHA256 */ + +#ifndef WC_NO_RNG +static wc_test_ret_t sec_qoriq_rng_test(void) +{ + SecQoriqDev* dev; + byte out[32]; + byte acc; + word32 i; + int ret; + + /* the block draws from the handle wc_SecQoriqInit() instantiated */ + XMEMSET(out, 0, sizeof(out)); + ret = wc_SecQoriqRandomBlock(out, sizeof(out)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + acc = 0; + for (i = 0; i < sizeof(out); i++) + acc |= out[i]; + if (acc == 0) + return WC_TEST_RET_ENC_NC; + + /* entropy retry: three rejected instantiations must step the sample + * delay three times before the fourth succeeds */ + (void)wc_SecQoriqFree(); + wc_SecQoriqSimReset(); + wc_SecQoriqSimRngFailNext(3); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + dev = wc_SecQoriqGetDev(); + if (dev == NULL || dev->rngReady != 1) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqSimRngEntDelay() != SEC_QORIQ_RTSDCTL_ENT_DLY_MIN + + 3 * SEC_QORIQ_RTSDCTL_ENT_DLY_STEP) + return WC_TEST_RET_ENC_NC; + + /* exhaustion: every attempt fails, the loop must have tried the + * documented maximum delay itself before giving up, and the device + * still comes up with hardware seeding recorded unavailable */ + (void)wc_SecQoriqFree(); + wc_SecQoriqSimReset(); + wc_SecQoriqSimRngFailNext(1000000); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + dev = wc_SecQoriqGetDev(); + if (dev == NULL || dev->rngReady != 0) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqSimRngEntDelay() != SEC_QORIQ_RTSDCTL_ENT_DLY_MAX) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqRandomBlock(out, sizeof(out)) == 0) + return WC_TEST_RET_ENC_NC; + + /* restore a healthy device for the cases that follow */ + (void)wc_SecQoriqFree(); + wc_SecQoriqSimReset(); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + return 0; +} +#endif /* !WC_NO_RNG */ + +#if !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) +static wc_test_ret_t sec_qoriq_cb_test(void) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + Aes hwAes; + Aes swAes; + byte key[16]; + byte iv[16]; + byte* pt = NULL; + byte* sw = NULL; + byte* hw = NULL; + word32 offloads; + word32 i; + int ret = 0; + int hwInit = 0; + int swInit = 0; + + if (dev == NULL) + return WC_TEST_RET_ENC_NC; + + pt = (byte*)XMALLOC(256 * 3, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (pt == NULL) + return WC_TEST_RET_ENC_ERRNO; + sw = pt + 256; + hw = pt + 512; + + for (i = 0; i < sizeof(key); i++) + key[i] = (byte)(0x77 - i); + for (i = 0; i < sizeof(iv); i++) + iv[i] = (byte)(0x30 + i); + for (i = 0; i < 256; i++) + pt[i] = (byte)(255 - i); + + /* software reference */ + ret = wc_AesInit(&swAes, HEAP_HINT, INVALID_DEVID); + if (ret == 0) + swInit = 1; + if (ret == 0) + ret = wc_AesSetKey(&swAes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, sw, pt, 256); + + /* device-bound context: a 256 byte buffer clears the offload + * threshold, so the counter must show the engine really served it */ + if (ret == 0) + ret = wc_AesInit(&hwAes, HEAP_HINT, WOLFSSL_SEC_QORIQ_DEVID); + if (ret == 0) + hwInit = 1; + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) { + offloads = dev->cbCipherOffload; + ret = wc_AesCbcEncrypt(&hwAes, hw, pt, 256); + if (ret == 0 && XMEMCMP(hw, sw, 256) != 0) + ret = WC_TEST_RET_ENC_NC; + if (ret == 0 && dev->cbCipherOffload != offloads + 1) + ret = WC_TEST_RET_ENC_NC; + } + + /* below the offload threshold the router declines and software still + * produces the right answer through the same call */ + if (ret == 0) + ret = wc_AesSetKey(&swAes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) + ret = wc_AesCbcEncrypt(&swAes, sw, pt, 64); + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) { + offloads = dev->cbCipherOffload; + ret = wc_AesCbcEncrypt(&hwAes, hw, pt, 64); + if (ret == 0 && XMEMCMP(hw, sw, 64) != 0) + ret = WC_TEST_RET_ENC_NC; + if (ret == 0 && dev->cbCipherOffload != offloads) + ret = WC_TEST_RET_ENC_NC; + } + + /* a job the engine ran and faulted is a hard error for the call in + * flight, never a silent software retry over possibly-mutated state */ + if (ret == 0) + ret = wc_AesSetKey(&hwAes, key, sizeof(key), iv, AES_ENCRYPTION); + if (ret == 0) { + wc_SecQoriqSimNextStatus( + ((word32)SEC_QORIQ_SSRC_DECO << SEC_QORIQ_SSRC_SHIFT) | 0x42); + if (wc_AesCbcEncrypt(&hwAes, hw, pt, 256) != + WC_NO_ERR_TRACE(WC_HW_E)) { + ret = WC_TEST_RET_ENC_NC; + } + } + + if (hwInit) + wc_AesFree(&hwAes); + if (swInit) + wc_AesFree(&swAes); + XFREE(pt, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* !WOLFSSL_SEC_QORIQ_NO_CRYPTOCB && !NO_AES && HAVE_AES_CBC */ + +#if !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) && !defined(NO_DH) && \ + defined(HAVE_FFDHE_2048) && defined(WOLFSSL_SEC_QORIQ_RSA) && \ + !defined(WC_NO_RNG) +/* One FFDHE-2048 agreement through the device (the sim executes the RSA/ + * modexp protocol descriptor with real big-number math), cross-checked + * against the software path over the same keys. */ +static wc_test_ret_t sec_qoriq_dh_test(void) +{ + SecQoriqDev* dev = wc_SecQoriqGetDev(); + DhKey key; + WC_RNG rng; + byte* buf; + byte* priv; + byte* pub; + byte* hwAgree; + byte* swAgree; + word32 privSz = 256; + word32 pubSz = 256; + word32 hwSz = 256; + word32 swSz = 256; + word32 offloads; + int saveDevId; + int keyInit = 0; + int rngInit = 0; + int ret; + + if (dev == NULL) + return WC_TEST_RET_ENC_NC; + + buf = (byte*)XMALLOC(256 * 4, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) + return WC_TEST_RET_ENC_ERRNO; + priv = buf; + pub = buf + 256; + hwAgree = buf + 512; + swAgree = buf + 768; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, INVALID_DEVID); + if (ret == 0) + rngInit = 1; + if (ret == 0) + ret = wc_InitDhKey_ex(&key, HEAP_HINT, WOLFSSL_SEC_QORIQ_DEVID); + if (ret == 0) + keyInit = 1; + if (ret == 0) + ret = wc_DhSetNamedKey(&key, WC_FFDHE_2048); + if (ret == 0) + ret = wc_DhGenerateKeyPair(&key, &rng, priv, &privSz, pub, &pubSz); + + if (ret == 0) { + offloads = dev->cbPkOffload; + ret = wc_DhAgree(&key, hwAgree, &hwSz, priv, privSz, pub, pubSz); + if (ret == 0 && dev->cbPkOffload != offloads + 1) + ret = WC_TEST_RET_ENC_NC; + } + if (ret == 0) { + saveDevId = key.devId; + key.devId = INVALID_DEVID; + ret = wc_DhAgree(&key, swAgree, &swSz, priv, privSz, pub, pubSz); + key.devId = saveDevId; + } + if (ret == 0 && + (hwSz != swSz || XMEMCMP(hwAgree, swAgree, hwSz) != 0)) { + ret = WC_TEST_RET_ENC_NC; + } + + if (keyInit) + wc_FreeDhKey(&key); + if (rngInit) + wc_FreeRng(&rng); + XMEMSET(buf, 0, 256 * 4); /* throwaway test vectors; see dh_cryptocb */ + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* cb && !NO_DH && HAVE_FFDHE_2048 && RSA && !WC_NO_RNG */ + +/* Nonfatal bring-up declines: each must return NOT_COMPILED_IN (which + * wolfCrypt_Init() tolerates as "no engine, stay in software"), leave no + * dangling device state behind for wc_SecQoriqFree() / wolfCrypt_Cleanup() + * to dereference, and permit a clean re-initialisation afterwards. */ +static wc_test_ret_t sec_qoriq_init_decline_test(void) +{ + int ret; + + /* engine already in 64-bit descriptor pointer mode */ + if (wc_SecQoriqFree() != 0) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimReset(); + wc_SecQoriqSimLongPtr(1); + if (wc_SecQoriqInit() != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqGetDev() != NULL) + return WC_TEST_RET_ENC_NC; + /* the cleanup path wolfCrypt_Cleanup() takes must be a safe no-op */ + if (wc_SecQoriqFree() != 0) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimLongPtr(0); + + /* ring memory with physical addresses above 4 GB, as a 36-bit Linux + * system hands out: the backend is unavailable, not broken */ + wc_SecQoriqSimReset(); + wc_SecQoriqSimHighPhysAlloc(1); + if (wc_SecQoriqInit() != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqGetDev() != NULL) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqFree() != 0) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimHighPhysAlloc(0); + + /* and a healthy device comes back up afterwards */ + wc_SecQoriqSimReset(); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + return 0; +} + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) +/* Timeout and reset escalation. Runs last: the final case leaves the + * device deliberately poisoned. */ +static wc_test_ret_t sec_qoriq_reset_test(void) +{ + byte out[SEC_QORIQ_TEST_AES_SZ]; + int ret; + + /* 1: timeout, ring reset succeeds, device survives */ + wc_SecQoriqSimTimeoutNext(); + ret = sec_qoriq_aes_op(out); + if (ret != WC_NO_ERR_TRACE(WC_HW_E)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqGetDev() == NULL) + return WC_TEST_RET_ENC_NC; + ret = sec_qoriq_aes_op(out); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + /* 2: timeout and the ring reset sticks; the controller DMA reset + * still quiesces, so the device dies but stays reclaimable */ + wc_SecQoriqSimTimeoutNext(); + wc_SecQoriqSimStickJrReset(1); + ret = sec_qoriq_aes_op(out); + if (ret != WC_NO_ERR_TRACE(WC_HW_E)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqGetDev() != NULL) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimStickJrReset(0); + if (wc_SecQoriqFree() != 0) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimReset(); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = sec_qoriq_aes_op(out); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + /* 3: nothing quiesces. The device is poisoned and teardown must leak + * the rings by design. While the engine stays wedged, re-init must + * probe it and fail cleanly; once it recovers (a power cycle, modelled + * by resetting the sim), a fresh bring-up succeeds on fresh storage. */ + wc_SecQoriqSimTimeoutNext(); + wc_SecQoriqSimStickJrReset(1); + wc_SecQoriqSimStickDmaReset(1); + ret = sec_qoriq_aes_op(out); + if (ret != WC_NO_ERR_TRACE(WC_HW_E)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqGetDev() != NULL) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqFree() != 0) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqInit() != WC_NO_ERR_TRACE(WC_HW_E)) + return WC_TEST_RET_ENC_NC; + wc_SecQoriqSimStickJrReset(0); + wc_SecQoriqSimStickDmaReset(0); + wc_SecQoriqSimReset(); + ret = wc_SecQoriqInit(); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = sec_qoriq_aes_op(out); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + + return 0; +} +#endif /* !NO_AES && HAVE_AES_CBC */ + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sec_qoriq_test(void) +{ + wc_test_ret_t ret = 0; + + if (wc_SecQoriqGetDev() == NULL) + return WC_TEST_RET_ENC_NC; + + /* status decoding, no hardware interaction */ + if (wc_SecQoriqParseError(0) != 0) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqParseError( + ((word32)SEC_QORIQ_SSRC_CCB << SEC_QORIQ_SSRC_SHIFT) | + SEC_QORIQ_CCBERR_ERRID_ICV) != + WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) + return WC_TEST_RET_ENC_NC; + if (wc_SecQoriqParseError( + ((word32)SEC_QORIQ_SSRC_DECO << SEC_QORIQ_SSRC_SHIFT) | 0x82) != + WC_NO_ERR_TRACE(WC_HW_E)) + return WC_TEST_RET_ENC_NC; + +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + if (ret == 0) + ret = sec_qoriq_aes_test(); +#endif +#if !defined(NO_AES) && defined(HAVE_AESGCM) + if (ret == 0) + ret = sec_qoriq_gcm_test(); +#endif +#ifndef NO_SHA256 + if (ret == 0) + ret = sec_qoriq_hash_test(); +#endif +#ifndef WC_NO_RNG + if (ret == 0) + ret = sec_qoriq_rng_test(); +#endif +#if !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) && !defined(NO_AES) && \ + defined(HAVE_AES_CBC) + if (ret == 0) + ret = sec_qoriq_cb_test(); +#endif +#if !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) && !defined(NO_DH) && \ + defined(HAVE_FFDHE_2048) && defined(WOLFSSL_SEC_QORIQ_RSA) && \ + !defined(WC_NO_RNG) + if (ret == 0) + ret = sec_qoriq_dh_test(); +#endif + if (ret == 0) + ret = sec_qoriq_init_decline_test(); +#if !defined(NO_AES) && defined(HAVE_AES_CBC) + if (ret == 0) + ret = sec_qoriq_reset_test(); +#endif + + return ret; +} +#endif /* WOLFSSL_SEC_QORIQ && WOLFSSL_SEC_QORIQ_SIM */ + #ifdef WOLFSSL_CERT_PIV WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certpiv_test(void) { diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index dc8d1cb4825..d0d4bd9d27b 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -480,6 +480,9 @@ extern WOLFSSL_TEST_SUBROUTINE int ariagcm_test(MC_ALGID); #if defined(WOLF_CRYPTO_CB) && !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void); #endif +#if defined(WOLFSSL_SEC_QORIQ) && defined(WOLFSSL_SEC_QORIQ_SIM) +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sec_qoriq_test(void); +#endif #ifdef WOLFSSL_CERT_PIV extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certpiv_test(void); #endif diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 2fa6ac3df17..16374c78c34 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -35,6 +35,9 @@ #ifdef WOLF_CRYPTO_CB +#ifndef NO_DH + #include +#endif #ifndef NO_RSA #include #endif @@ -314,6 +317,20 @@ typedef struct wc_CryptoInfo { } rsa_pss_verify; #endif #endif + #ifndef NO_DH + /* Finite field Diffie-Hellman shared secret. Key generation is + * deliberately not routed: the private exponent should come from + * the caller's WC_RNG, not from a device. */ + struct { + DhKey* key; + const byte* priv; + word32 privSz; + const byte* otherPub; + word32 pubSz; + byte* agree; + word32* agreeSz; + } dh; + #endif #ifdef HAVE_ECC #ifdef HAVE_ECC_DHE struct { @@ -1200,6 +1217,11 @@ WOLFSSL_LOCAL int wc_CryptoCb_RsaCheckPrivKey(RsaKey* key, const byte* pubKey, WOLFSSL_LOCAL int wc_CryptoCb_RsaGetSize(const RsaKey* key, int* keySize); #endif /* !NO_RSA */ +#ifndef NO_DH +WOLFSSL_LOCAL int wc_CryptoCb_Dh(DhKey* key, const byte* priv, word32 privSz, + const byte* otherPub, word32 pubSz, byte* agree, word32* agreeSz); +#endif + #ifdef HAVE_ECC WOLFSSL_LOCAL int wc_CryptoCb_MakeEccKey(WC_RNG* rng, int keySize, ecc_key* key, int curveId); diff --git a/wolfssl/wolfcrypt/dh.h b/wolfssl/wolfcrypt/dh.h index 85d3e95f4a2..b25d9382395 100644 --- a/wolfssl/wolfcrypt/dh.h +++ b/wolfssl/wolfcrypt/dh.h @@ -112,6 +112,9 @@ struct DhKey { #ifdef WC_DH_NONBLOCK DhNb* nb; /* non-blocking context, NULL when not in non-block mode */ #endif +#ifdef WOLF_CRYPTO_CB + int devId; +#endif }; #ifndef WC_DH_TYPE_DEFINED @@ -249,7 +252,11 @@ WOLFSSL_API int wc_DhCheckPrivKey_ex(DhKey* key, const byte* priv, word32 privSz, const byte* prime, word32 primeSz); WOLFSSL_API int wc_DhCheckKeyPair(DhKey* key, const byte* pub, word32 pubSz, const byte* priv, word32 privSz); -#ifdef WOLFSSL_KEY_GEN +/* Domain parameter generation is a separate, expensive facility from key + * generation: it searches for a safe prime. Protocols in practice use the + * fixed groups (FFDHE, RFC 3526), so a build can keep DH key generation and + * drop parameter generation with WOLFSSL_NO_DH_GEN_PARAMS. */ +#if defined(WOLFSSL_KEY_GEN) && !defined(WOLFSSL_NO_DH_GEN_PARAMS) WOLFSSL_API int wc_DhGenerateParams(WC_RNG *rng, int modSz, DhKey *dh); #endif WOLFSSL_API int wc_DhExportParamsRaw(DhKey* dh, byte* p, word32* pSz, diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index ec987acdd05..a24c448a6e7 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -264,6 +264,12 @@ if BUILD_SE050 nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nxp/se050_port.h endif +if BUILD_SEC_QORIQ +nobase_include_HEADERS+= wolfssl/wolfcrypt/port/nxp/sec_qoriq.h +else +noinst_HEADERS+= wolfssl/wolfcrypt/port/nxp/sec_qoriq.h +endif + if BUILD_TROPIC01 nobase_include_HEADERS+= wolfssl/wolfcrypt/port/tropicsquare/tropic01.h endif diff --git a/wolfssl/wolfcrypt/port/nxp/sec_qoriq.h b/wolfssl/wolfcrypt/port/nxp/sec_qoriq.h new file mode 100644 index 00000000000..9799c1b3cd9 --- /dev/null +++ b/wolfssl/wolfcrypt/port/nxp/sec_qoriq.h @@ -0,0 +1,853 @@ +/* sec_qoriq.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 + */ + +/* + * NXP QorIQ SEC (CAAM) engine on the T-series PowerPC parts. Verified on + * T2080 (SEC 5.2) and T1040 (SEC 5.0): both report era 6 in CCBVID (their + * device trees claim 5), place the SEC at CCSR + 0x300000 with four job + * rings, and declare "fsl,sec-v4.0", so one descriptor format covers both. + * + * Only security-enabled ("E") part numbers carry the SEC; + * wc_SecQoriqInit() checks SVR bit 0x80000 at run time. + */ + +#ifndef WOLF_CRYPT_SEC_QORIQ_H +#define WOLF_CRYPT_SEC_QORIQ_H + +#include +#include + +#ifdef WOLFSSL_SEC_QORIQ + +/* Only the MMIO accessors byte swap, so a little endian host would get + * correct registers but wrongly ordered descriptors. Refuse rather than + * produce silently wrong crypto. */ +#ifdef WOLFSSL_SEC_QORIQ_SWAP_REGS + #error "WOLFSSL_SEC_QORIQ_SWAP_REGS is incomplete: descriptor and ring words are not swapped" +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Driver-internal API, not part of wolfSSL's public surface. The simulated + * backend exports it so the in-tree tests (a separate binary linking the + * shared library) can drive the driver and the fault injection directly. */ +#ifdef WOLFSSL_SEC_QORIQ_SIM + #define WOLFSSL_SECQ_API WOLFSSL_API +#else + #define WOLFSSL_SECQ_API WOLFSSL_LOCAL +#endif + +/****************************************************************************** + Build configuration + ****************************************************************************/ + +/* devId selecting the SEC through the crypto callback layer. Avoids values + * other ports claim: 7 (CAAM), 8 (SECO/ARIA), 9 (MAX3266X), 807-810 (STM32 + * SAES/DHUK, RealTek HUK). */ +#ifndef WOLFSSL_SEC_QORIQ_DEVID + #define WOLFSSL_SEC_QORIQ_DEVID 0x53454351 /* "SECQ" */ +#endif + + +/* Physical base of the CCSR window, which is board specific: the CW VPX3-152 + * U-Boot relocates it to 0xEF000000, the NXP RDBs default to 0xFE000000. */ +#ifndef SEC_QORIQ_CCSRBAR + #define SEC_QORIQ_CCSRBAR 0xFE000000UL +#endif + +/* The same window as the operating system sees it. Bare metal runs with the + * 32-bit view above, but these parts have a 36-bit physical address space and + * Linux uses the full address: CCSR on a T1040 is 0xF_FE000000, not + * 0xFE000000. Mapping the 32-bit value there hits ordinary DRAM instead, so + * the physical base is a separate 64-bit knob rather than a widened + * SEC_QORIQ_CCSRBAR, which several boards already set from a linker or board + * header as a 32-bit constant. Override per board, for example + * -DSEC_QORIQ_CCSRBAR_PHYS=0xFFE000000ULL. */ +#ifndef SEC_QORIQ_CCSRBAR_PHYS + #define SEC_QORIQ_CCSRBAR_PHYS ((word64)(SEC_QORIQ_CCSRBAR)) +#endif + +/* Offset of the SEC block within CCSR, fixed on every QorIQ part seen. */ +#ifndef SEC_QORIQ_OFFSET + #define SEC_QORIQ_OFFSET 0x300000UL +#endif +#define SEC_QORIQ_SIZE 0x10000UL + +/* Which of the four job rings to claim. */ +#ifndef SEC_QORIQ_JR_INDEX + #define SEC_QORIQ_JR_INDEX 0 +#endif + +/* Job ring pages start one page into the block, one 4 KB page each. */ +#define SEC_QORIQ_JR_OFFSET(n) (0x1000UL + ((unsigned long)(n) * 0x1000UL)) + +/* Descriptors are submitted one at a time, so a short ring suffices. */ +#ifndef SEC_QORIQ_RING_SIZE + #define SEC_QORIQ_RING_SIZE 4 +#endif + +/* Poll limit for job completion; a hung ring must not wedge the caller. */ +#ifndef SEC_QORIQ_POLL_MAX + #define SEC_QORIQ_POLL_MAX 1000000 +#endif + +/* Below this size the fixed per-job cost (about 3.5 us on a 1.2 GHz T2080: + * descriptor build, two cache flushes, ring write, MMIO kick, completion + * poll) outweighs what the engine saves for algorithms with a fast software + * path. Measured AES-CBC crossover is 64 to 256 bytes. GCM always wins on + * the engine and ignores this; hashing is not routed at all. */ +#ifndef SEC_QORIQ_MIN_OFFLOAD_SZ + #define SEC_QORIQ_MIN_OFFLOAD_SZ 256 +#endif + +/* Largest transfer one command can describe; the extended length form is + * not implemented, so anything bigger goes to software. */ +#define SEC_QORIQ_MAX_XFER_SZ 0xFFFF + +/* The only GCM IV length whose J0 construction the AESA implements. */ +#define SEC_QORIQ_GCM_IV_SZ 12 + +/* Full-length tag only: the engine does the ICV comparison itself and will + * not take a truncated tag. TLS always uses 16. */ +#define SEC_QORIQ_GCM_TAG_SZ 16 + +/* Descriptors are capped at 64 words by the hardware. */ +#define SEC_QORIQ_DESC_MAX_WORDS 64 + +/* Most data buffers one descriptor can reference. The largest consumer is + * the single-shot hash: 30 message chunks plus the digest store. */ +#define SEC_QORIQ_DESC_MAX_BUFS 32 + +/* Direction of a DMA buffer, from the engine's point of view. */ +#define SEC_QORIQ_DIR_IN 0 /* engine reads it */ +#define SEC_QORIQ_DIR_OUT 1 /* engine writes it */ + +/****************************************************************************** + Controller registers, relative to the start of the SEC block + ****************************************************************************/ + +#define SEC_QORIQ_MCFGR 0x0004 /* master configuration */ +#define SEC_QORIQ_SCFGR 0x000C /* security configuration */ +/* Job ring LIODN registers, two words per ring. U-Boot programs these. */ +#define SEC_QORIQ_JRLIODNR(n) (0x0010 + ((n) * 8)) +#define SEC_QORIQ_JRSTART 0x005C /* job ring start (only gates when + * SCFGR[VIRT_EN] is set) */ +#define SEC_QORIQ_DECORSR 0x0094 /* DECO request source */ +#define SEC_QORIQ_DECORR 0x009C /* DECO request */ +#define SEC_QORIQ_DECO_AVAIL 0x0120 /* DECO availability */ +#define SEC_QORIQ_DECO_RESET 0x0124 /* DECO reset */ + +/* DECORSR / DECORR bits, for direct DECO0 access. */ +#define SEC_QORIQ_DECORSR_JR0 0x00000001 +#define SEC_QORIQ_DECORSR_VALID 0x80000000 +#define SEC_QORIQ_DECORR_RQD0 0x00000001 /* request DECO0 direct access */ +#define SEC_QORIQ_DECORR_DEN0 0x00010000 /* DECO0 available */ + +/* MCFGR bits */ +#define SEC_QORIQ_MCFGR_SWRESET 0x80000000 +#define SEC_QORIQ_MCFGR_WDENABLE 0x40000000 +#define SEC_QORIQ_MCFGR_WDFAIL 0x20000000 +#define SEC_QORIQ_MCFGR_DMA_RESET 0x10000000 +#define SEC_QORIQ_MCFGR_LONG_PTR 0x00010000 /* >32-bit descriptor pointers */ + +/* SCFGR bits */ +#define SEC_QORIQ_SCFGR_VIRT_EN 0x00008000 +#define SEC_QORIQ_SCFGR_RDBENABLE 0x00000400 /* faster RNG reads */ + +/* JRSTART bits, one per ring */ +#define SEC_QORIQ_JRSTART_JR(n) (1U << (n)) + +/* Identification and capability registers (the perfmon page, +0xF00-0xFFF) */ +#define SEC_QORIQ_CTPR_MS 0x0FA8 /* compile time parameters, ms half */ +#define SEC_QORIQ_CTPR_LS 0x0FAC +#define SEC_QORIQ_CSTA 0x0FD4 /* controller status */ +#define SEC_QORIQ_RVID 0x0FE0 /* RTIC version */ +#define SEC_QORIQ_CCBVID 0x0FE4 /* CCB version, carries the SEC era */ +#define SEC_QORIQ_CHAVID_MS 0x0FE8 /* per-accelerator version */ +#define SEC_QORIQ_CHAVID_LS 0x0FEC +#define SEC_QORIQ_CHANUM_MS 0x0FF0 /* per-accelerator instance counts */ +#define SEC_QORIQ_CHANUM_LS 0x0FF4 +#define SEC_QORIQ_CAAMVID_MS 0x0FF8 +#define SEC_QORIQ_CAAMVID_LS 0x0FFC + +/* CTPR_MS bits */ +#define SEC_QORIQ_CTPR_MS_QI 0x02000000 /* QMan interface present */ +#define SEC_QORIQ_CTPR_MS_PS 0x00020000 /* >32-bit pointers supported */ +#define SEC_QORIQ_CTPR_MS_DPAA2 0x00002000 + +/* CCBVID: SEC era lives in the top byte */ +#define SEC_QORIQ_CCBVID_ERA_MASK 0xFF000000 +#define SEC_QORIQ_CCBVID_ERA_SHIFT 24 + +/* CHANUM_MS: instance counts */ +#define SEC_QORIQ_CHANUM_MS_JRNUM_SHIFT 28 /* number of job rings */ +#define SEC_QORIQ_CHANUM_MS_DECONUM_SHIFT 24 /* number of DECOs */ + +/* CHANUM_LS / CHAVID_LS share this nibble layout: CHANUM_LS gives the number + * of instances of each accelerator, CHAVID_LS gives their version. */ +#define SEC_QORIQ_CHA_AES_SHIFT 0 +#define SEC_QORIQ_CHA_DES_SHIFT 4 +#define SEC_QORIQ_CHA_ARC4_SHIFT 8 +#define SEC_QORIQ_CHA_MD_SHIFT 12 +#define SEC_QORIQ_CHA_RNG_SHIFT 16 +#define SEC_QORIQ_CHA_SNOW_SHIFT 20 +#define SEC_QORIQ_CHA_KAS_SHIFT 24 +#define SEC_QORIQ_CHA_PK_SHIFT 28 +#define SEC_QORIQ_CHA_MASK 0xF + +/****************************************************************************** + RNG4 registers, relative to the start of the SEC block + ****************************************************************************/ + +#define SEC_QORIQ_RTMCTL 0x0600 /* misc control */ +#define SEC_QORIQ_RTSCMISC 0x0604 +#define SEC_QORIQ_RTPKRRNG 0x0608 +#define SEC_QORIQ_RTPKRMAX 0x060C +#define SEC_QORIQ_RTSDCTL 0x0610 /* seed control, carries entropy delay */ +#define SEC_QORIQ_RTSBLIM 0x0614 +#define SEC_QORIQ_RTFRQMIN 0x0618 +#define SEC_QORIQ_RTFRQMAX 0x061C +#define SEC_QORIQ_RTSTATUS 0x063C +#define SEC_QORIQ_RDSTA 0x06C0 /* DRNG status */ + +/* RTMCTL bits */ +#define SEC_QORIQ_RTMCTL_PRGM 0x00010000 /* 1 = program, 0 = run */ +#define SEC_QORIQ_RTMCTL_ACC 0x00000020 /* TRNG access mode */ +#define SEC_QORIQ_RTMCTL_RESET 0x00000040 /* reset TRNG to defaults */ +#define SEC_QORIQ_RTMCTL_ERR 0x00001000 +#define SEC_QORIQ_RTMCTL_ENT_VAL 0x00000400 /* entropy ready */ + +/* RDSTA bits. IF0 clear means state handle 0 is not instantiated, which is + * how the boot loaders on both boards tested leave it. */ +#define SEC_QORIQ_RDSTA_IF0 0x00000001 +#define SEC_QORIQ_RDSTA_IF1 0x00000002 +#define SEC_QORIQ_RDSTA_PR0 0x00000010 +#define SEC_QORIQ_RDSTA_PR1 0x00000020 +#define SEC_QORIQ_RDSTA_SKVN 0x40000000 +#define SEC_QORIQ_RDSTA_SKVT 0x80000000 + +/* Entropy delay: system clocks per entropy sample, longer being better. The + * floor matches mainline Linux and NXP U-Boot. A shorter window samples the + * ring oscillator too briefly on some parts, leaving the TRNG self checks as + * the only guard against a weakly seeded handle. */ +#define SEC_QORIQ_RTSDCTL_ENT_DLY_SHIFT 16 +#define SEC_QORIQ_RTSDCTL_ENT_DLY_MASK 0xFFFF0000 +#ifndef SEC_QORIQ_RTSDCTL_ENT_DLY_MIN + #define SEC_QORIQ_RTSDCTL_ENT_DLY_MIN 3200 +#endif +#define SEC_QORIQ_RTSDCTL_ENT_DLY_MAX 12800 +#define SEC_QORIQ_RTSDCTL_ENT_DLY_STEP 400 + +/****************************************************************************** + Job ring registers, relative to the start of a job ring page + ****************************************************************************/ + +#define SEC_QORIQ_IRBA 0x0000 /* input ring base, 64-bit */ +#define SEC_QORIQ_IRBA_MS 0x0000 +#define SEC_QORIQ_IRBA_LS 0x0004 +#define SEC_QORIQ_IRS 0x000C /* input ring size */ +#define SEC_QORIQ_IRSA 0x0014 /* input ring slots available */ +#define SEC_QORIQ_IRJA 0x001C /* input ring jobs added */ +#define SEC_QORIQ_ORBA 0x0020 /* output ring base, 64-bit */ +#define SEC_QORIQ_ORBA_MS 0x0020 +#define SEC_QORIQ_ORBA_LS 0x0024 +#define SEC_QORIQ_ORS 0x002C /* output ring size */ +#define SEC_QORIQ_ORJR 0x0034 /* output ring jobs removed */ +#define SEC_QORIQ_ORSF 0x003C /* output ring slots full */ +#define SEC_QORIQ_JRSTA 0x0044 /* job ring output status */ +#define SEC_QORIQ_JRINT 0x004C /* job ring interrupt status */ +#define SEC_QORIQ_JRCFG_HI 0x0050 /* job ring configuration, ms half */ +#define SEC_QORIQ_JRCFG 0x0054 /* job ring configuration, ls half */ +#define SEC_QORIQ_IRRI 0x005C /* input ring read index */ +#define SEC_QORIQ_ORWI 0x0064 /* output ring write index */ +#define SEC_QORIQ_JRCR 0x006C /* job ring command */ + +/* JRCR bits */ +#define SEC_QORIQ_JRCR_RESET 0x00000001 + +/* JRINT bits: 0 = interrupt asserted, 1 = error, 3:2 = halt state. */ +#define SEC_QORIQ_JRINT_JRI 0x00000001 +#define SEC_QORIQ_JRINT_ERR 0x00000002 +#define SEC_QORIQ_JRINT_HALT_MASK 0x0000000C +#define SEC_QORIQ_JRINT_HALT_INPROGRESS 0x00000004 +#define SEC_QORIQ_JRINT_HALT_COMPLETE 0x00000008 + +/* Job status. The top nibble names what reported the failure, telling an + * operation the engine ran and rejected (a failed authentication or + * signature check) apart from one it could not run at all. */ +#define SEC_QORIQ_SSRC_SHIFT 28 +#define SEC_QORIQ_SSRC_MASK 0xF0000000U +#define SEC_QORIQ_SSRC_NONE 0x0 +#define SEC_QORIQ_SSRC_CCB 0x2 +#define SEC_QORIQ_SSRC_JUMP 0x3 +#define SEC_QORIQ_SSRC_DECO 0x4 +#define SEC_QORIQ_SSRC_QI 0x5 +#define SEC_QORIQ_SSRC_JR 0x6 +#define SEC_QORIQ_SSRC_JUMP_CC 0x7 + +/* For a CCB error the error id is the bottom nibble. */ +#define SEC_QORIQ_CCBERR_ERRID_MASK 0x0000000FU +#define SEC_QORIQ_CCBERR_ERRID_ICV 0x0A + +/****************************************************************************** + Descriptor commands, common to every SEC/CAAM era. + ****************************************************************************/ + +#define SEC_QORIQ_CMD_HEAD 0xB0800000 +#define SEC_QORIQ_CMD_KEY 0x00000000 +#define SEC_QORIQ_CMD_LOAD 0x10000000 +#define SEC_QORIQ_CMD_LOAD_CTX 0x10200000 +#define SEC_QORIQ_CMD_IMM 0x00800000 +#define SEC_QORIQ_CMD_FIFO_L 0x20000000 +#define SEC_QORIQ_CMD_FIFO_S 0x60000000 +#define SEC_QORIQ_CMD_STORE 0x50000000 +#define SEC_QORIQ_CMD_STORE_CTX 0x50200000 +#define SEC_QORIQ_CMD_MOVE 0x78000000 +#define SEC_QORIQ_CMD_OP 0x80000000 +#define SEC_QORIQ_CMD_JUMP 0xA0000000 +#define SEC_QORIQ_CMD_SEQI 0xF0000000 +#define SEC_QORIQ_CMD_SEQO 0xF8000000 +#define SEC_QORIQ_CMD_NWB 0x00200000 + +/* Accelerator class selection */ +#define SEC_QORIQ_CLASS1 0x02000000 /* AESA, PKHA */ +#define SEC_QORIQ_CLASS2 0x04000000 /* MDHA */ + +/* OPERATION command state and direction */ +#define SEC_QORIQ_ENC 0x00000001 +#define SEC_QORIQ_DEC 0x00000000 +#define SEC_QORIQ_ALG_UPDATE 0x00000000 +#define SEC_QORIQ_ALG_ICV 0x00000002 +#define SEC_QORIQ_ALG_INIT 0x00000004 +#define SEC_QORIQ_ALG_FINAL 0x00000008 +#define SEC_QORIQ_ALG_INITF 0x0000000C + +/* AES algorithm identifiers */ +#define SEC_QORIQ_AESCTR 0x00100000 +#define SEC_QORIQ_AESCBC 0x00100100 +#define SEC_QORIQ_AESECB 0x00100200 +#define SEC_QORIQ_AESCFB 0x00100300 +#define SEC_QORIQ_AESOFB 0x00100400 +#define SEC_QORIQ_AESCMAC 0x00100600 +#define SEC_QORIQ_AESCCM 0x00100800 +#define SEC_QORIQ_AESGCM 0x00100900 + +/* Message digest algorithm identifiers */ +#define SEC_QORIQ_MD5 0x00400000 +#define SEC_QORIQ_SHA1 0x00410000 +#define SEC_QORIQ_SHA224 0x00420000 +#define SEC_QORIQ_SHA256 0x00430000 +#define SEC_QORIQ_SHA384 0x00440000 +#define SEC_QORIQ_SHA512 0x00450000 + +/* HMAC is the same identifier with the AAI field set to 0x10 */ +#define SEC_QORIQ_HMAC_AAI 0x00000010 + +/* Context offset field used by LOAD/STORE CTX. CBC keeps its IV at offset 0 + * of the class 1 context; CTR's counter lives further in. */ +#define SEC_QORIQ_CTX_OFST_CBC 0x00000000 +#define SEC_QORIQ_CTX_OFST_CTR 0x00001000 + +/* Context sizes: the accelerator's full state width plus the 8 byte running + * message length. The truncated variants share the context width of their + * parent (SHA-224 with SHA-256, SHA-384 with SHA-512), so these do not follow + * from the digest size. */ +#define SEC_QORIQ_MD5_CTXSZ (16 + 8) +#define SEC_QORIQ_SHA1_CTXSZ (20 + 8) +#define SEC_QORIQ_SHA224_CTXSZ (32 + 8) +#define SEC_QORIQ_SHA256_CTXSZ (32 + 8) +#define SEC_QORIQ_SHA384_CTXSZ (64 + 8) +#define SEC_QORIQ_SHA512_CTXSZ (64 + 8) + +/* RNG */ +#define SEC_QORIQ_RNG 0x00500000 +#define SEC_QORIQ_ENTROPY 0x00500001 + +/* RNG4 "generate secure keys" variant, used once during instantiation to + * load the JDKEK, TDKEK and TDSK registers. */ +#define SEC_QORIQ_RNG4_SK 0x00001000 + +/* Pieces of the instantiation descriptor. The JUMP waits for the class 1 + * accelerator to finish; the LOAD writes 1 to the CLRW register, which + * clears the done interrupt and returns the RNG to idle. */ +#define SEC_QORIQ_JUMP_CLASS1 0x02000000 +#define SEC_QORIQ_LOAD_CLRW 0x10880004 +#define SEC_QORIQ_CLRW_RESET 0x00000001 + +/* FIFO LOAD input types */ +#define SEC_QORIQ_FIFOL_TYPE_MSG 0x00100000 +#define SEC_QORIQ_FIFOL_TYPE_IV 0x00200000 +#define SEC_QORIQ_FIFOL_TYPE_AAD 0x00300000 +#define SEC_QORIQ_FIFOL_TYPE_ICV 0x00380000 +#define SEC_QORIQ_FIFOL_TYPE_FC1 0x00010000 +#define SEC_QORIQ_FIFOL_TYPE_LC1 0x00020000 +#define SEC_QORIQ_FIFOL_TYPE_LC2 0x00040000 + +/* FIFO STORE output types */ +#define SEC_QORIQ_FIFOS_TYPE_MSG 0x00300000 +#define SEC_QORIQ_FIFOS_TYPE_RNG 0x00340000 +#define SEC_QORIQ_FIFOS_EXT 0x00400000 +#define SEC_QORIQ_FIFOS_CONT 0x00800000 + +/* PKHA operation identifiers */ +#define SEC_QORIQ_PKHA_OP 0x01000000 +#define SEC_QORIQ_ECDSA_KEYPAIR 0x00140000 +#define SEC_QORIQ_ECDSA_SIGN 0x00150000 +#define SEC_QORIQ_ECDSA_VERIFY 0x00160000 +#define SEC_QORIQ_ECDSA_ECDH 0x00170000 +#define SEC_QORIQ_RSA_ENCRYPT 0x00180000 +#define SEC_QORIQ_RSA_DECRYPT 0x00190000 + +/* PKHA protocol descriptor fields. These sit in the OPERATION word beside + * the operation identifier above. */ +#define SEC_QORIQ_PROT_UNIDI 0x00000000 +#define SEC_QORIQ_PKHA_ECC 0x00000002 + +/* First word of a PKHA protocol data block: field prime and group order + * sizes, in bytes. + * + * Two shortcuts the i.MX CAAM parts offer are refused here, both established + * by experiment on a T2080 (era 6, PKHA v2): + * + * - Naming a built-in curve by index instead of supplying the domain + * parameters: every flag position in bits 25:17 crossed with every curve + * index 0..31 returns DECO error 0x82. Passing the parameters explicitly + * costs a few descriptor words and gains any prime curve wolfCrypt knows + * rather than the handful built in. + * - The "message representative is already hashed" PROTINFO flag, refused + * with DECO error 0x81. Left clear the engine takes the representative + * as given, which is what wolfCrypt passes anyway. */ +#define SEC_QORIQ_PKHA_PDB_L_SHIFT 7 +#define SEC_QORIQ_PKHA_PDB_L_MASK 0x3FF +#define SEC_QORIQ_PKHA_PDB_N_MASK 0x7F + +/* DECO error id reported when the engine ran a verify and the signature did + * not check out. Distinct from a fault, so it must not be reported as one. */ +#define SEC_QORIQ_DECOERR_SIGVERIFY 0x86 + +/****************************************************************************** + Types + ****************************************************************************/ + +/* One data buffer a descriptor references. Recorded while the descriptor is + * assembled; the address word is filled in at submission, when the platform + * backend maps (or stages) the buffer for DMA. */ +typedef struct SecQoriqDescBuf { + void* virt; + word32 sz; + word32 wordIdx; /* descriptor word that receives the DMA address */ + byte dir; /* SEC_QORIQ_DIR_IN or SEC_QORIQ_DIR_OUT */ +} SecQoriqDescBuf; + +/* A descriptor under construction; the header is patched on submit. */ +typedef struct SecQoriqDesc { + word32 desc[SEC_QORIQ_DESC_MAX_WORDS]; + word32 idx; /* next free word */ + word32 startIdx; /* HEADER START INDEX: the word the engine begins + * executing at. 1 for a plain job descriptor, and the + * index of the OPERATION for a protocol descriptor, + * whose parameter block precedes it. */ + /* Buffers referenced by the words above, resolved at submission. */ + SecQoriqDescBuf bufs[SEC_QORIQ_DESC_MAX_BUFS]; + word32 bufCnt; +} SecQoriqDesc; + +/* Everything the driver needs to talk to one job ring. + * + * Threading: a process-wide singleton. Job submission is serialised on the + * wolfSSL hardware mutex inside wc_SecQoriqRun(); the rest is deliberately + * weaker: + * + * - lastStatus and the cb* counters are diagnostics, written under no lock. + * lastStatus belongs to whichever job finished last, not the caller's. + * The counters are bumped in the callback router before the ring mutex is + * taken, so concurrent callers lose updates: approximate under threads, + * exact when single-threaded. + * - The RNG4 programming sequence (status check, RTSDCTL entropy-delay + * writes, instantiation job) is not atomic, so wc_SecQoriqRngInit() runs + * exactly once, inside wc_SecQoriqInit(), before the crypto callback is + * registered and before worker threads can reach the device. There is no + * lazy path: wc_SecQoriqRandomBlock() and wc_SecQoriqEccSign() only + * re-read RDSTA (side-effect free) in case another agent instantiated the + * handle later, and never program the TRNG. + * - wc_SecQoriqInit()/Free() take no lock and do not wait for in-flight + * work. Quiesce every user before tearing the device down, or the + * register window is unmapped under a running job. */ +typedef struct SecQoriqDev { + byte* regs; /* mapped base of the SEC block */ + byte* jr; /* mapped base of the claimed job ring page */ + word32 jrIndex; + word32 era; + word32 chaNumLs; /* cached CHANUM_LS, for capability checks */ + word32 chaVidLs; /* cached CHAVID_LS */ + + /* Input ring: one descriptor pointer per entry. Output ring: two words, + * the descriptor pointer then its status. word32 because the driver runs + * the SEC in 32-bit pointer mode. */ + word32* inRing; + word64 inRingPhys; + word32* outRing; + word64 outRingPhys; + + /* Mirrors of the engine's own head and tail, so submissions land on the + * slot it is about to read and results come from the one it just wrote. + * Getting this wrong does not fail loudly: the stale contents of slot 0 + * look like a completed job. */ + word32 inIdx; + word32 outIdx; + + /* Raw status of the most recent job; the decoded error loses the source + * and error-id fields. */ + word32 lastStatus; + + /* Diagnostics: how much work reached the engine, answering "is this + * really being offloaded" without guessing from timings. */ + word32 jobCount; /* descriptors actually submitted to the ring */ + word32 cbHashCount; /* WC_ALGO_TYPE_HASH seen by the router */ + word32 cbHashOffload; /* ...of those, handed to the engine */ + word32 cbCipherCount; + word32 cbCipherOffload; + word32 cbPkCount; + word32 cbPkOffload; + word32 cbSeedCount; + + byte initialized; + byte rngReady; /* RDSTA[IF0] observed or successfully instantiated */ + byte longPtr; /* MCFGR[LONG_PTR], 0 means 32-bit descriptor pointers */ + byte quiesceFailed; /* a hung job survived both the ring reset and the + * DMA reset: the engine may still hold physical + * addresses of ours, so nothing it can reach is + * ever freed or reused. wc_SecQoriqFree() then + * unregisters the callback but deliberately leaks + * the rings and mappings. */ +} SecQoriqDev; + +/****************************************************************************** + Core API + ****************************************************************************/ + +/* Bring up the SEC and register the crypto callback under + * WOLFSSL_SEC_QORIQ_DEVID. Returns 0 on success. */ +WOLFSSL_API int wc_SecQoriqInit(void); + +/* Tear down and unregister. */ +WOLFSSL_API int wc_SecQoriqFree(void); + +/* Raw register access. Exposed because the RNG4 programming sequence is not + * expressible as a descriptor and has to touch registers directly. */ +WOLFSSL_SECQ_API word32 wc_SecQoriqRead(const byte* base, word32 off); +WOLFSSL_SECQ_API void wc_SecQoriqWrite(byte* base, word32 off, word32 val); + +/* Accessor for the singleton device, NULL before wc_SecQoriqInit(). */ +WOLFSSL_SECQ_API SecQoriqDev* wc_SecQoriqGetDev(void); + +#if defined(WOLF_CRYPTO_CB) && !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) +/* Called by wc_SecQoriqInit/Free; exposed so an application that manages + * registration itself can do so. */ +WOLFSSL_SECQ_API int wc_SecQoriqRegisterCryptoCb(void); +WOLFSSL_SECQ_API void wc_SecQoriqUnregisterCryptoCb(void); +#endif + +/* Descriptor assembly. Each returns 0 on success or a negative error. + * + * Buffers are recorded, not translated: the DMA address words are filled in + * at submission by the platform's wc_SecQoriqDmaMapBuf(), so a backend that + * stages data through a pinned arena (Linux) sees every buffer. There is + * deliberately no way to append a raw physical address. + * + * wc_SecQoriqDescAddBuf() infers direction from the command (FIFO STORE and + * STORE are engine writes, everything else reads); wc_SecQoriqDescAddPdbPtr() + * appends a bare PDB pointer word, whose command carries no direction, so + * the caller states it. */ +WOLFSSL_SECQ_API int wc_SecQoriqDescInit(SecQoriqDesc* desc); +WOLFSSL_SECQ_API int wc_SecQoriqDescAddWord(SecQoriqDesc* desc, word32 in); +WOLFSSL_SECQ_API int wc_SecQoriqDescAddBuf(SecQoriqDesc* desc, word32 cmd, + const byte* buf, word32 bufSz); +WOLFSSL_SECQ_API int wc_SecQoriqDescAddPdbPtr(SecQoriqDesc* desc, + const byte* buf, word32 bufSz, int isOut); + +/* Submit a descriptor to the job ring and wait for it to retire. + * + * Error contract, which the crypto callback router relies on: BAD_FUNC_ARG, + * BUFFER_E, MEMORY_E and NOT_COMPILED_IN are only ever raised before the + * job is handed to the engine, so a caller may retry in software over the + * same buffers. WC_HW_E can be raised after submission (a timeout, a job + * the engine ran and faulted), when caller buffers and chaining state may + * already have been modified, and must be treated as a hard error. */ +WOLFSSL_SECQ_API int wc_SecQoriqRun(SecQoriqDev* dev, SecQoriqDesc* desc); + +/* As wc_SecQoriqRun(), but also hands back the raw job status of this very + * job, captured while the lock is still held. Callers that classify the + * outcome must use this rather than SecQoriqDev.lastStatus, which belongs to + * whichever job finished last and is not written on every failure path. */ +WOLFSSL_SECQ_API int wc_SecQoriqRunEx(SecQoriqDev* dev, SecQoriqDesc* desc, + word32* statusOut); + +/* Decode a job ring status word into a wolfSSL error and, when logging is on, + * a human readable reason. */ +WOLFSSL_SECQ_API int wc_SecQoriqParseError(word32 status); + +/****************************************************************************** + Message digests. Single shot only for now: one descriptor initialises, + absorbs the message and finalises. Streaming needs the class 2 context + round tripped around each call and is not implemented yet. + + The 64-word descriptor bounds the message at 30 FIFO loads: 29 full + block-aligned chunks plus one final chunk of up to 64 KB, about 1.9 MB. + Anything larger is refused with BAD_FUNC_ARG. + ****************************************************************************/ + +WOLFSSL_SECQ_API int wc_SecQoriqHash(word32 algo, word32 digestSz, + const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqSha1(const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqSha224(const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqSha256(const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqSha384(const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqSha512(const byte* in, word32 inSz, byte* out); + +#ifndef NO_AES +/****************************************************************************** + AES, confidentiality-only modes. Input must be a whole number of blocks; + the caller handles any partial trailing block for CTR. Where the mode has a + chaining value, iv is updated in place. + ****************************************************************************/ + +WOLFSSL_SECQ_API int wc_SecQoriqAes(word32 mode, int encrypt, const byte* key, + word32 keySz, byte* iv, const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqAesCbcEncrypt(const byte* key, word32 keySz, + byte* iv, const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqAesCbcDecrypt(const byte* key, word32 keySz, + byte* iv, const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqAesCtrEncrypt(const byte* key, word32 keySz, + byte* iv, const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqAesEcbEncrypt(const byte* key, word32 keySz, + const byte* in, word32 inSz, byte* out); +WOLFSSL_SECQ_API int wc_SecQoriqAesEcbDecrypt(const byte* key, word32 keySz, + const byte* in, word32 inSz, byte* out); +#endif /* !NO_AES */ + +#ifndef WC_NO_RNG +/****************************************************************************** + RNG4. The boot loaders on the boards tested leave state handle 0 + uninstantiated, so wc_SecQoriqInit() instantiates it itself. + ****************************************************************************/ + +/* Program the TRNG and instantiate state handle 0. Called once from + * wc_SecQoriqInit(), before the crypto callback is registered: the status + * check and TRNG programming are not atomic, so this must not run + * concurrently with itself or with other jobs. A failure is recorded + * (rngReady stays 0) and hardware seeding is reported unavailable. */ +WOLFSSL_SECQ_API int wc_SecQoriqRngInit(void); +WOLFSSL_SECQ_API int wc_SecQoriqRandomBlock(byte* out, word32 sz); +#endif /* !WC_NO_RNG */ + +#ifdef HAVE_AESGCM +/* GCM takes an arbitrary message length. On decrypt the engine checks the + * tag itself and a mismatch comes back as AES_GCM_AUTH_E. */ +WOLFSSL_SECQ_API int wc_SecQoriqAesGcm(int encrypt, const byte* key, word32 keySz, + const byte* iv, word32 ivSz, const byte* aad, word32 aadSz, + const byte* in, word32 inSz, byte* out, byte* tag, word32 tagSz); +WOLFSSL_SECQ_API int wc_SecQoriqAesGcmEncrypt(const byte* key, word32 keySz, + const byte* iv, word32 ivSz, const byte* aad, word32 aadSz, + const byte* in, word32 inSz, byte* out, byte* tag, word32 tagSz); +WOLFSSL_SECQ_API int wc_SecQoriqAesGcmDecrypt(const byte* key, word32 keySz, + const byte* iv, word32 ivSz, const byte* aad, word32 aadSz, + const byte* in, word32 inSz, byte* out, const byte* tag, word32 tagSz); +#endif + +/****************************************************************************** + PKHA public key. + + The domain parameters are supplied explicitly in every descriptor. The + engine also offers a shortcut where a small index names one of a handful of + built in curves, but that form is refused on the parts this was written + against; see the note above SEC_QORIQ_PKHA_PDB_L_SHIFT. Passing the + parameters costs a few descriptor words and works for any prime curve + wolfCrypt carries, up to the PKHA's 1023 bit ceiling. + + Every buffer below is fixed length, big endian and zero padded on the left, + the same convention mp_to_unsigned_bin_len() produces. Public keys are the + raw x || y pair, 2 * keySz bytes, with no leading 0x04 point format byte. + ****************************************************************************/ + +/* The two PKHA users are gated independently: a build may want elliptic + * curve work in software (to keep RFC 6979 deterministic signing, say) while + * still offloading RSA, or the reverse. */ +#if !defined(WOLFSSL_SEC_QORIQ_NO_PKHA) && defined(HAVE_ECC) + #define WOLFSSL_SEC_QORIQ_PKHA +#endif +#if !defined(WOLFSSL_SEC_QORIQ_NO_RSA) && !defined(NO_RSA) + #define WOLFSSL_SEC_QORIQ_RSA +#endif + +#ifdef WOLFSSL_SEC_QORIQ_PKHA + +/* Is this curve one the port can drive? Returns 0 if so, NOT_COMPILED_IN + * otherwise. Any prime curve whose parameters wolfCrypt carries qualifies, + * up to the PKHA's 1023 bit ceiling. */ +WOLFSSL_SECQ_API int wc_SecQoriqEccSupported(int curveId, word32 keySz); + +/* Sign a message representative. hash is the already hashed message, of any + * length; it is reduced to the group size the way ECDSA prescribes. r and s + * each receive keySz bytes. The engine generates the per-signature nonce + * internally from RNG4, whose one-time bring-up happened in + * wc_SecQoriqInit(); without a live state handle this declines with + * NOT_COMPILED_IN so the signature falls back to software. */ +WOLFSSL_SECQ_API int wc_SecQoriqEccSign(int curveId, const byte* priv, + const byte* hash, word32 hashSz, byte* r, byte* s, word32 keySz); + +/* Verify. res is set to 1 when the signature is good and 0 when it is not; + * the return value is 0 in both of those cases and negative only when the + * engine itself failed. */ +WOLFSSL_SECQ_API int wc_SecQoriqEccVerify(int curveId, const byte* pubXY, + const byte* hash, word32 hashSz, const byte* r, const byte* s, + word32 keySz, int* res); + +/* ECDH. out receives the x coordinate of the shared point, keySz bytes. */ +WOLFSSL_SECQ_API int wc_SecQoriqEcdh(int curveId, const byte* priv, + const byte* peerXY, byte* out, word32 keySz); + +#endif /* WOLFSSL_SEC_QORIQ_PKHA */ + +#ifdef WOLFSSL_SEC_QORIQ_RSA + +/* Largest modulus the PKHA will take, 4096 bits. */ +#define SEC_QORIQ_PKHA_MAX_RSA_BYTES 512 + +/* First PDB word of an RSA block: the exponent length in bytes at bit 12 and + * the modulus length in bytes at bit 0. Both fields are 12 bits. */ +#define SEC_QORIQ_RSA_PDB_E_SHIFT 12 +#define SEC_QORIQ_RSA_PDB_LEN_MASK 0xFFF + +/* Private key form 1, the plain d and n pair. The CRT forms need the factors + * and are not used here. */ +#define SEC_QORIQ_RSA_PRIV_FRM_1 0 + +/* out = in^e mod n, with out and n both nSz bytes. */ +WOLFSSL_SECQ_API int wc_SecQoriqRsaPublic(const byte* in, word32 inSz, + const byte* n, word32 nSz, const byte* e, word32 eSz, byte* out); + +/* out = in^d mod n, with in, out and n all nSz bytes. This is the general + * modular exponentiation the engine offers, so it also covers finite field + * Diffie-Hellman once wolfCrypt grows a callback hook for it. */ +WOLFSSL_SECQ_API int wc_SecQoriqModExp(const byte* in, const byte* d, word32 dSz, + const byte* n, word32 nSz, byte* out); + +#endif /* WOLFSSL_SEC_QORIQ_RSA */ + +/****************************************************************************** + Environment seam. One backend is compiled in: sec_qoriq_baremetal.c for a + flat physically addressed target, sec_qoriq_linux.c for user space on a + running kernel. + ****************************************************************************/ + +/* Map SEC_QORIQ_SIZE bytes of the SEC block. */ +WOLFSSL_SECQ_API int wc_SecQoriqMapRegs(byte** regsOut); +WOLFSSL_SECQ_API void wc_SecQoriqUnmapRegs(byte* regs); + +/* Allocate DMA capable memory. Must be physically contiguous, and must sit + * below 4 GB while the driver runs in 32-bit descriptor pointer mode. */ +WOLFSSL_SECQ_API void* wc_SecQoriqDmaAlloc(word32 sz, word64* physOut); +WOLFSSL_SECQ_API void wc_SecQoriqDmaFree(void* virt, word64 phys, word32 sz); + +/* Translate an address and confirm the whole length is physically + * contiguous. The engine gets one {address, length} pair, so a buffer + * straddling non-adjacent frames must be refused. Returns 0 if unusable. + * Backend-internal: descriptors resolve their buffers through the per-job + * staging hooks below, never through a bare translation. */ +WOLFSSL_SECQ_API word64 wc_SecQoriqVirtToPhysLen(void* virt, word32 len); + +/* Per-job DMA staging, called by wc_SecQoriqRun() with the job ring mutex + * held, so at most one job's mappings exist at a time. + * + * JobBegin resets the staging state. MapBuf returns the physical address the + * engine must use for one buffer, or 0 when it cannot be made DMA safe; the + * mapping stays valid until JobEnd. A backend on a flat physical map returns + * the address directly; the Linux backend copies through a pinned, + * physically contiguous bounce arena because ordinary process pages may be + * migrated or copied-on-write after a pagemap lookup. + * + * JobEnd finishes the job: with ok set, engine-written buffers are copied + * back to their callers; staging memory is scrubbed and reclaimed either + * way. With abandoned set the engine may still master the bus (a hung job + * that survived every reset), so the backend must permanently retire the + * staging memory instead of reusing it. */ +WOLFSSL_SECQ_API int wc_SecQoriqDmaJobBegin(void); +WOLFSSL_SECQ_API word64 wc_SecQoriqDmaMapBuf(void* virt, word32 sz, int dir); +WOLFSSL_SECQ_API void wc_SecQoriqDmaJobEnd(int ok, int abandoned); + +/* Cache maintenance around every descriptor and data buffer. */ +WOLFSSL_SECQ_API int wc_SecQoriqCacheFlush(void* virt, word32 sz); +WOLFSSL_SECQ_API int wc_SecQoriqCacheInval(void* virt, word32 sz); + +/* Zero a buffer handed to the engine and push the zeros out to memory. + * ForceZero() alone is not enough for DMA memory: the flush that made the + * buffer readable wrote it back and invalidated the line, so zeroing + * afterwards only dirties cache and leaves the original bytes (a private + * scalar, say) in DRAM. */ +WOLFSSL_SECQ_API void wc_SecQoriqForceZeroDma(void* buf, word32 sz); + +/* Yield briefly inside the completion poll loop. */ +WOLFSSL_SECQ_API void wc_SecQoriqCpuRelax(void); + +/* Read the SVR so the caller can confirm this is a security enabled part. */ +WOLFSSL_SECQ_API int wc_SecQoriqGetSvr(word32* svrOut); +#define SEC_QORIQ_SVR_E_BIT 0x00080000 + +#ifdef WOLFSSL_SEC_QORIQ_SIM +/****************************************************************************** + Simulated backend (sec_qoriq_sim.c): a host-runnable model of the job ring + state machine with fault injection, so the driver's submission, timeout, + reset and status-decoding logic is exercised by make check on any machine. + The model executes AES, GCM, hash, RNG and RSA/modexp descriptors with + wolfCrypt's own software crypto; ECDSA/ECDH protocol jobs report a DECO + error unless a status is injected. + ****************************************************************************/ + +/* Reset the model: engine state, fault injections, RNG state handle. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimReset(void); +/* Force the raw status word of the next job; the job is not executed. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimNextStatus(word32 status); +/* The next submitted job never completes, driving the timeout path. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimTimeoutNext(void); +/* Make JRCR[RESET] stick (never complete) while enabled. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimStickJrReset(int stick); +/* Make MCFGR[DMA_RESET] stick while enabled. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimStickDmaReset(int stick); +/* Fail the next n RNG4 instantiation jobs, driving the entropy retry. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimRngFailNext(int n); +/* While set, DMA allocations report physical addresses above 4 GB, the way + * a 36-bit Linux system hands out high pages. */ +WOLFSSL_SECQ_API void wc_SecQoriqSimHighPhysAlloc(int on); +/* While set, MCFGR reads report MCFGR[LONG_PTR] (64-bit pointer mode). */ +WOLFSSL_SECQ_API void wc_SecQoriqSimLongPtr(int on); +/* Entropy delay most recently programmed into RTSDCTL. */ +WOLFSSL_SECQ_API word32 wc_SecQoriqSimRngEntDelay(void); +#endif /* WOLFSSL_SEC_QORIQ_SIM */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_SEC_QORIQ */ +#endif /* WOLF_CRYPT_SEC_QORIQ_H */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 6851759038c..d708844a093 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -3285,6 +3285,43 @@ #define WOLFSSL_NO_CAAM_HASH #endif +/* NXP QorIQ SEC, the T-series PowerPC security engine. Shares the CAAM + * descriptor architecture but is a separate, self-contained port. */ +#ifdef WOLFSSL_SEC_QORIQ + /* The engine is normally reached through the crypto callback layer. + * A minimal build (bring-up harness, boot loader) can call the driver + * API directly and skip that layer entirely. */ + #ifndef WOLFSSL_SEC_QORIQ_NO_CRYPTOCB + #undef WOLF_CRYPTO_CB + #define WOLF_CRYPTO_CB + #endif + + /* devId must be visible to every translation unit, not just the ones + * that include the port header: wolfcrypt/test/test.c and the benchmark + * select their device from WC_USE_DEVID and never include sec_qoriq.h. + * Defining it only there left the port registered but never called. */ + #ifndef WOLFSSL_SEC_QORIQ_DEVID + #define WOLFSSL_SEC_QORIQ_DEVID 0x53454351 /* "SECQ" */ + #endif + #if !defined(WC_USE_DEVID) && !defined(WOLFSSL_SEC_QORIQ_NO_CRYPTOCB) + #define WC_USE_DEVID WOLFSSL_SEC_QORIQ_DEVID + #endif + + /* pick a backend if the build did not name one */ + #if !defined(WOLFSSL_SEC_QORIQ_BAREMETAL) && \ + !defined(WOLFSSL_SEC_QORIQ_LINUX) && \ + !defined(WOLFSSL_SEC_QORIQ_SIM) + #define WOLFSSL_SEC_QORIQ_BAREMETAL + #endif + #if (defined(WOLFSSL_SEC_QORIQ_BAREMETAL) && \ + defined(WOLFSSL_SEC_QORIQ_LINUX)) || \ + (defined(WOLFSSL_SEC_QORIQ_BAREMETAL) && \ + defined(WOLFSSL_SEC_QORIQ_SIM)) || \ + (defined(WOLFSSL_SEC_QORIQ_LINUX) && defined(WOLFSSL_SEC_QORIQ_SIM)) + #error "Select only one WOLFSSL_SEC_QORIQ backend" + #endif +#endif /* WOLFSSL_SEC_QORIQ */ + #ifdef WOLFSSL_CAAM /* switch for all AES type algos */ #undef WOLFSSL_CAAM_CIPHER diff --git a/wolfssl/wolfcrypt/wc_port.h b/wolfssl/wolfcrypt/wc_port.h index 1ea75035471..90359200e60 100644 --- a/wolfssl/wolfcrypt/wc_port.h +++ b/wolfssl/wolfcrypt/wc_port.h @@ -1022,7 +1022,7 @@ WOLFSSL_LOCAL void wolfSSL_RefWithMutexDec_IfEquals(wolfSSL_RefWithMutex* ref, #if defined(FREESCALE_MMCAU) || defined(WOLFSSL_MICROCHIP_PIC32MZ) || \ defined(STM32_CRYPTO) || defined(STM32_HASH) || defined(STM32_RNG) || \ defined(WOLFSSL_MAX3266X) || defined(WOLFSSL_MAX3266X_OLD) || \ - defined(WOLFSSL_RTL8735B_HUK) + defined(WOLFSSL_RTL8735B_HUK) || defined(WOLFSSL_SEC_QORIQ) #ifndef WOLFSSL_CRYPT_HW_MUTEX #define WOLFSSL_CRYPT_HW_MUTEX 1 #endif