diff --git a/include/wolfprovider/internal.h b/include/wolfprovider/internal.h index da50bcb0..ec4ff257 100644 --- a/include/wolfprovider/internal.h +++ b/include/wolfprovider/internal.h @@ -162,7 +162,44 @@ int wp_provctx_lock_rng(WOLFPROV_CTX* provCtx); void wp_provctx_unlock_rng(WOLFPROV_CTX* provCtx); #ifdef HAVE_FIPS -wolfSSL_Mutex *wp_get_cast_mutex(void); +/* CAST self-test algorithm categories */ +#define WP_CAST_ALGO_AES 0 +#define WP_CAST_ALGO_HMAC 1 +#define WP_CAST_ALGO_DRBG 2 +#define WP_CAST_ALGO_RSA 3 +#define WP_CAST_ALGO_ECDSA 4 +#define WP_CAST_ALGO_ECDH 5 +#define WP_CAST_ALGO_DH 6 +#define WP_CAST_ALGO_COUNT 7 + +int wp_init_cast(int algo); + +/** + * Check FIPS CAST for algorithm. Returns 0 on failure. + * Use at function entry points that return int (1=success, 0=failure). + */ +#define WP_CHECK_FIPS_ALGO(algo) \ + do { \ + if (wp_init_cast(algo) != 1) { \ + return 0; \ + } \ + } while (0) + +/** + * Check FIPS CAST for algorithm. Returns NULL on failure. + * Use at function entry points that return pointers (NULL=failure). + */ +#define WP_CHECK_FIPS_ALGO_PTR(algo) \ + do { \ + if (wp_init_cast(algo) != 1) { \ + return NULL; \ + } \ + } while (0) + +#else +/* Non-FIPS: no-op */ +#define WP_CHECK_FIPS_ALGO(algo) do { } while (0) +#define WP_CHECK_FIPS_ALGO_PTR(algo) do { } while (0) #endif #endif diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index 3fec9821..fffd1abc 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -76,6 +76,9 @@ #define WP_HAVE_GMAC #endif +#ifndef NO_AES + #define WP_HAVE_AES +#endif #ifdef HAVE_AES_ECB #define WP_HAVE_AESECB #endif diff --git a/src/wp_aes_aead.c b/src/wp_aes_aead.c index 3908d637..ffa32457 100644 --- a/src/wp_aes_aead.c +++ b/src/wp_aes_aead.c @@ -1022,6 +1022,9 @@ static int wp_aesgcm_einit(wp_AeadCtx* ctx, const unsigned char *key, if (!wolfssl_prov_is_running()) { ok = 0; } + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + } #ifdef WOLFSSL_AESGCM_STREAM if (ok) { int rc; @@ -1108,6 +1111,9 @@ static int wp_aesgcm_dinit(wp_AeadCtx *ctx, const unsigned char *key, if (!wolfssl_prov_is_running()) { ok = 0; } + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + } #ifdef WOLFSSL_AESGCM_STREAM if (ok && key != NULL) { int rc = wc_AesGcmDecryptInit(aes, key, (word32)keyLen, iv, (word32)ivLen); @@ -1754,6 +1760,9 @@ static int wp_aesccm_init(wp_AeadCtx* ctx, const unsigned char *key, if (!wolfssl_prov_is_running()) { ok = 0; } + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + } if (ok && (key != NULL)) { rc = wc_AesCcmSetKey(&ctx->aes, key, (word32)keyLen); if (rc != 0) { diff --git a/src/wp_aes_block.c b/src/wp_aes_block.c index 71d8142a..cd60f5c4 100644 --- a/src/wp_aes_block.c +++ b/src/wp_aes_block.c @@ -328,7 +328,9 @@ static int wp_aes_block_init(wp_AesBlockCtx *ctx, const unsigned char *key, ok = 0; } if (ok) { - int rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, ctx->iv, + int rc; + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, ctx->iv, enc ? AES_ENCRYPTION : AES_DECRYPTION); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesSetKey", rc); diff --git a/src/wp_aes_stream.c b/src/wp_aes_stream.c index 187ec6b9..a2190a21 100644 --- a/src/wp_aes_stream.c +++ b/src/wp_aes_stream.c @@ -314,12 +314,14 @@ static int wp_aes_stream_init(wp_AesStreamCtx *ctx, const unsigned char *key, ok = 0; } if (ok) { + int rc; #if defined(WP_HAVE_AESCTS) if (ctx->mode == EVP_CIPH_CBC_MODE && !enc) { dir = AES_DECRYPTION; } #endif - int rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, iv, + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, iv, dir); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesSetKey", rc); diff --git a/src/wp_aes_wrap.c b/src/wp_aes_wrap.c index 4ab74ad9..8d43ee18 100644 --- a/src/wp_aes_wrap.c +++ b/src/wp_aes_wrap.c @@ -266,7 +266,9 @@ static int wp_aes_wrap_init(wp_AesWrapCtx *ctx, const unsigned char *key, } if (ok) { #if LIBWOLFSSL_VERSION_HEX >= 0x05000000 - int rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, iv, + int rc; + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_AES); + rc = wc_AesSetKey(&ctx->aes, key, (word32)ctx->keyLen, iv, wrap ? AES_ENCRYPTION : AES_DECRYPTION); if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_AesSetKey", rc); diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index d834efa0..16173f3e 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -1156,6 +1156,9 @@ static int wp_dh_import(wp_Dh* dh, int selection, const OSSL_PARAM params[]) if (!wolfssl_prov_is_running()) { ok = 0; } + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_DH); + } if (ok && (dh == NULL)) { ok = 0; } @@ -1832,6 +1835,8 @@ static wp_Dh* wp_dh_gen(wp_DhGenCtx *ctx, OSSL_CALLBACK *cb, void *cbArg) (void)cb; (void)cbArg; + WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_DH); + /* Create a new DH key object to hold generated data. */ dh = wp_dh_new(ctx->provCtx); if (dh != NULL) { @@ -2064,6 +2069,8 @@ static int wp_dh_decode_spki(wp_Dh* dh, unsigned char* data, word32 len) WOLFPROV_ENTER_SILENT(WP_LOG_COMP_DH, WOLFPROV_FUNC_NAME); + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_DH); + rc = wc_DhPublicKeyDecode(data, &idx, &dh->key, len); if (rc != 0) { ok = 0; @@ -2127,6 +2134,8 @@ static int wp_dh_decode_pki(wp_Dh* dh, unsigned char* data, word32 len) WOLFPROV_ENTER_SILENT(WP_LOG_COMP_DH, WOLFPROV_FUNC_NAME); + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_DH); + rc = wc_DhKeyDecode(data, &idx, &dh->key, len); if (rc != 0) { ok = 0; diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index c7ddf5d6..93d9a3fd 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -1767,6 +1767,9 @@ static wp_Ecc* wp_ecc_gen(wp_EccGenCtx *ctx, OSSL_CALLBACK *cb, void *cbArg) (void)cb; (void)cbArg; + WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_ECDSA); + WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_ECDH); + if (ctx->curveName[0] != '\0') { ecc = wp_ecc_new(ctx->provCtx); } diff --git a/src/wp_ecdh_exch.c b/src/wp_ecdh_exch.c index 7024721c..ee9aaf0c 100644 --- a/src/wp_ecdh_exch.c +++ b/src/wp_ecdh_exch.c @@ -184,6 +184,9 @@ static int wp_ecdh_init(wp_EcdhCtx* ctx, wp_Ecc* ecc, const OSSL_PARAM params[]) if (!wolfssl_prov_is_running()) { ok = 0; } + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_ECDH); + } if (ok && (ctx->key != ecc)) { /* Free old key and up reference new key. */ wp_ecc_free(ctx->key); diff --git a/src/wp_ecdsa_sig.c b/src/wp_ecdsa_sig.c index c09474fe..122848de 100644 --- a/src/wp_ecdsa_sig.c +++ b/src/wp_ecdsa_sig.c @@ -190,7 +190,10 @@ static int wp_ecdsa_signverify_init(wp_EcdsaSigCtx *ctx, wp_Ecc* ecc, if (ctx == NULL || (ecc == NULL && ctx->ecc == NULL)) { ok = 0; } - else if (ecc != NULL) { + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_ECDSA); + } + if (ok && (ecc != NULL)) { if (!wp_ecc_up_ref(ecc)) { ok = 0; } diff --git a/src/wp_hmac.c b/src/wp_hmac.c index 27121c5a..5ff78ae9 100644 --- a/src/wp_hmac.c +++ b/src/wp_hmac.c @@ -126,6 +126,8 @@ static int wp_hmac_set_key(wp_HmacCtx* macCtx, const unsigned char* key, WOLFPROV_ENTER(WP_LOG_COMP_MAC, "wp_hmac_set_key"); + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_HMAC); + if (macCtx->keyLen > 0) { OPENSSL_secure_clear_free(macCtx->key, macCtx->keyLen); } diff --git a/src/wp_internal.c b/src/wp_internal.c index f65bcdc2..595ec838 100644 --- a/src/wp_internal.c +++ b/src/wp_internal.c @@ -31,23 +31,140 @@ #include #if defined(HAVE_FIPS) && (!defined(WP_SINGLE_THREADED)) -static wolfSSL_Mutex castMutex; +/** + * Structure to hold CAST self-test state for each algorithm. + */ +typedef struct wp_cast_algo_state { + /** Mutex for the algorithm's CAST self-test. */ + wolfSSL_Mutex mutex; + /** Initialization state: 0 = not initialized, 1 = initialized. */ + int init; +} wp_cast_algo_state; + +static wp_cast_algo_state castAlgos[WP_CAST_ALGO_COUNT]; /** - * Initialize the cast mutex on library load. + * Initialize the cast mutexes on library load. * * This constructor runs when libwolfprov.so is loaded via dlopen() or at - * program startup. It ensures the castMutex is initialized under lock. + * program startup. It ensures the castAlgos are initialized before any + * wolfProvider functions are called. */ __attribute__((constructor)) static void wolfprov_init_cast_mutex(void) { - wc_InitMutex(&castMutex); + int i; + for (i = 0; i < WP_CAST_ALGO_COUNT; i++) { + wc_InitMutex(&castAlgos[i].mutex); + castAlgos[i].init = 0; + } } -wolfSSL_Mutex *wp_get_cast_mutex() + +/** + * Initialize a CAST self-test for a specific algorithm. + * + * Runs the algorithm-specific CAST self-test if not already initialized. + * Uses mutex to ensure thread safety. + * + * @param [in] algo Algorithm category (WP_CAST_ALGO_*). + * @return 1 on success or already initialized. + * @return 0 on failure. + */ +int wp_init_cast(int algo) { - return &castMutex; + int ok = 1; + + if (algo < 0 || algo >= WP_CAST_ALGO_COUNT) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER, + "FIPS CAST initialization failed: invalid algorithm"); + return 0; + } + + if (castAlgos[algo].init == 0) { + if (wp_lock(&castAlgos[algo].mutex) != 1) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER, + "FIPS CAST initialization failed: unable to acquire lock"); + return 0; + } + /* Make sure another thread did not complete already while we waited + * to acquire per algo lock */ + if (castAlgos[algo].init == 0) { + switch (algo) { +#ifdef WP_HAVE_AES + case WP_CAST_ALGO_AES: + if (wc_RunCast_fips(FIPS_CAST_AES_CBC) != 0 || + wc_RunCast_fips(FIPS_CAST_AES_GCM) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_HMAC + case WP_CAST_ALGO_HMAC: + if (wc_RunCast_fips(FIPS_CAST_HMAC_SHA1) != 0 || + wc_RunCast_fips(FIPS_CAST_HMAC_SHA2_256) != 0 || + wc_RunCast_fips(FIPS_CAST_HMAC_SHA2_512) != 0 || + wc_RunCast_fips(FIPS_CAST_HMAC_SHA3_256) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_RSA + case WP_CAST_ALGO_RSA: + if (wc_RunCast_fips(FIPS_CAST_RSA_SIGN_PKCS1v15) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_ECDSA + case WP_CAST_ALGO_ECDSA: + if (wc_RunCast_fips(FIPS_CAST_ECDSA) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_ECDH + case WP_CAST_ALGO_ECDH: + if (wc_RunCast_fips(FIPS_CAST_ECC_CDH) != 0 || + wc_RunCast_fips(FIPS_CAST_ECC_PRIMITIVE_Z) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_DH + case WP_CAST_ALGO_DH: + if (wc_RunCast_fips(FIPS_CAST_DH_PRIMITIVE_Z) != 0) { + ok = 0; + } + break; +#endif +#ifdef WP_HAVE_RANDOM + case WP_CAST_ALGO_DRBG: + if (wc_RunCast_fips(FIPS_CAST_DRBG) != 0) { + ok = 0; + } + break; +#endif + default: + ok = 0; + break; + } + + if (ok) { + castAlgos[algo].init = 1; + } + } + if (wp_unlock(&castAlgos[algo].mutex) != 1) { + ok = 0; + } + } + + if (!ok) { + WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER, + "FIPS CAST initialization failed"); + } + + return ok; } #endif diff --git a/src/wp_rsa_asym.c b/src/wp_rsa_asym.c index 73dbe784..66b20012 100644 --- a/src/wp_rsa_asym.c +++ b/src/wp_rsa_asym.c @@ -212,6 +212,8 @@ static int wp_rsaa_init(wp_RsaAsymCtx* ctx, wp_Rsa* rsa, WOLFPROV_ENTER(WP_LOG_COMP_RSA, "wp_rsaa_init"); + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_RSA); + if (ctx->rsa != rsa) { if (wp_rsa_get_type(rsa) != RSA_FLAG_TYPE_RSA) { ERR_raise_data(ERR_LIB_PROV, diff --git a/src/wp_rsa_kem.c b/src/wp_rsa_kem.c index a8744fd4..9aa379b5 100644 --- a/src/wp_rsa_kem.c +++ b/src/wp_rsa_kem.c @@ -156,6 +156,8 @@ static int wp_rsakem_init(wp_RsaKemCtx* ctx, wp_Rsa* rsa, /* TODO: check key type and size with operation. */ (void)operation; + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_RSA); + if (rsa != ctx->rsa) { wp_rsa_free(ctx->rsa); ctx->rsa = NULL; diff --git a/src/wp_rsa_kmgmt.c b/src/wp_rsa_kmgmt.c index c66a5dee..b168a587 100644 --- a/src/wp_rsa_kmgmt.c +++ b/src/wp_rsa_kmgmt.c @@ -1513,6 +1513,7 @@ static wp_Rsa* wp_rsa_gen(wp_RsaGenCtx* ctx, OSSL_CALLBACK* cb, void* cbArg) (void)cbArg; if (wolfssl_prov_is_running() && wp_rsagen_check_key_size(ctx)) { + WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_RSA); rsa = wp_rsa_base_new(ctx->provCtx, ctx->type); if (rsa != NULL) { /* wolfCrypt FIPS RSA keygen has a small chance it simply will not @@ -2236,7 +2237,6 @@ static int wp_rsa_decode_spki(wp_Rsa* rsa, unsigned char* data, word32 len) if (!wolfssl_prov_is_running()) { ok = 0; } - if (ok) { rc = wc_RsaPublicKeyDecode(data, &idx, &rsa->key, len); if (rc != 0) { @@ -2283,7 +2283,6 @@ static int wp_rsa_decode_pki(wp_Rsa* rsa, unsigned char* data, word32 len) if (!wolfssl_prov_is_running()) { ok = 0; } - if (ok) { rc = wc_RsaPrivateKeyDecode(data, &idx, &rsa->key, len); if (rc != 0) { diff --git a/src/wp_rsa_sig.c b/src/wp_rsa_sig.c index 51212c03..25c2094a 100644 --- a/src/wp_rsa_sig.c +++ b/src/wp_rsa_sig.c @@ -482,7 +482,10 @@ static int wp_rsa_signverify_init(wp_RsaSigCtx* ctx, wp_Rsa* rsa, if ((ctx == NULL) || (ctx->rsa == NULL && rsa == NULL)) { ok = 0; } - else if (rsa != NULL) { + if (ok) { + WP_CHECK_FIPS_ALGO(WP_CAST_ALGO_RSA); + } + if (ok && (rsa != NULL)) { if (!wp_rsa_up_ref(rsa)) { ok = 0; } diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c index b3f6520a..aa8201bc 100644 --- a/src/wp_wolfprov.c +++ b/src/wp_wolfprov.c @@ -214,6 +214,8 @@ static WOLFPROV_CTX* wolfssl_prov_ctx_new(void) { WOLFPROV_CTX* ctx; + WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_DRBG); + ctx = (WOLFPROV_CTX*)OPENSSL_zalloc(sizeof(WOLFPROV_CTX)); if ((ctx != NULL) && (wc_InitRng(&ctx->rng) != 0)) { OPENSSL_free(ctx); @@ -1315,25 +1317,7 @@ int wolfssl_provider_init(const OSSL_CORE_HANDLE* handle, #ifdef WC_RNG_SEED_CB wc_SetSeed_Cb(wc_GenerateSeed); #endif -#if defined(HAVE_FIPS) && (!defined(WP_SINGLE_THREADED)) - /* To avoid multi-threading issues in FIPS CAST tests, run all tests - * under a lock now */ - if (wp_lock(wp_get_cast_mutex()) != 1) { - WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER, - "Fatal Error: unable to acquire FIPS CAST lock"); - ok = 0; - } - if (ok) { - if (wc_RunAllCast_fips() != 0) { - WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER, - "Fatal Error: FIPS algo selftest failure"); - ok = 0; - } - if (wp_unlock(wp_get_cast_mutex()) != 1) { - ok = 0; - } - } -#endif + /* FIPS CAST tests are now run lazily per-algorithm via wp_init_cast() */ } if (ok) {