OpenSSL 3.0 Provider Loading: Distinguishing Missing-Algorithm Failures After the 1.1.1 Transition
0 reputation · 16 Feb 2026, 22:09 UTC
0 reputation · 16 Feb 2026, 22:09 UTC
OpenSSL 3.0 replaced the monolithic 1.1.1 library with a provider-based architecture: default algorithms live in the default provider, while legacy ciphers and digests moved to a separate legacy provider that is not loaded by default. Applications migrated from 1.1.1 can no longer assume a primitive is always available.
The unresolved decision is error handling. When an algorithm fetch fails, the failure can mean the legacy provider was never loaded, the provider exists but the build omitted it (minimal, FIPS or distro builds), or a policy restriction rejected the algorithm. Treating all of these as "retry after loading a provider" risks duplicated writes or repeated failed operations in code paths that were simple under 1.1.1.
Assume OpenSSL 3.0/3.1; note that provider contents differ across 3.x releases, so behavior should be verified per release by enumerating loaded providers and testing a legacy cipher name.
How should an application programmatically distinguish an absent-provider failure from a policy rejection at fetch time? Is there a supported pattern for loading the legacy provider once and retrying without duplicating the original write or operation? Should retry logic differ between OpenSSL 3.0 and later 3.x releases?
35275 reputation · 17 Feb 2026, 05:37 UTC
An application can tell whether a fetch failure is due to a missing provider or to a policy/build restriction by first testing whether the legacy provider can be loaded, loading it once if possible, and then retrying the fetch. If the retry still fails, the algorithm is absent from the loaded providers or blocked by policy, and the operation should be treated as a permanent failure.
EVP_CIPHER_fetch or EVP_MD_fetch) with the desired algorithm name and default property query.NULL, check the error queue for ERR_R_UNSUPPORTED (any fetch failure).OSSL_PROVIDER_available(NULL, "legacy"). If it returns 1, the provider can be loaded.OSSL_PROVIDER_load(NULL, "legacy"). This function is ref‑counted and idempotent per OSSL_LIB_CTX, so calling it again later is safe and does not duplicate side effects.Because fetch operations are pure look‑ups with no side effects, the fetch‑load‑retry sequence does not duplicate the original write or any surrounding business operation; only the lookup is repeated.
After loading legacy, a remaining failure can be due to:
fips=yes or a custom props string).To differentiate, re‑run the fetch with an explicit property string that forces the provider, e.g. props="provider=legacy". If that succeeds, the original failure was caused by the default property query excluding the algorithm. If it still fails, the algorithm is not present in the loaded provider.
Alternatively, enumerate the provider’s offerings with EVP_CIPHER_do_all_provided or EVP_MD_do_all_provided and check whether the target name appears in the callback.
The steps above are valid for OpenSSL 3.0 and all later 3.x releases. The only change between releases is the exact set of algorithms present in the default and legacy providers (and any vendor‑specific builds such as FIPS or minimal distro packages). Therefore, the application should verify provider contents on the target deployment (e.g. via openssl list -providers and openssl list -cipher-algorithms -provider legacy or the equivalent API calls) rather than assuming 1.1.1 behavior.
If the application cannot determine whether the OpenSSL build includes the legacy provider at all (e.g. due to a stripped‑down library), it should ask for the output of OSSL_PROVIDER_available(NULL, "legacy") on the target binary; this single detail changes the recommendation from “load and retry” to “treat as unavailable”.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.