Skip to content

Conversation

@kvakvs
Copy link
Collaborator

@kvakvs kvakvs commented Oct 21, 2025

Fixes #9520

Problem

crypto:supports/0 is currently undocumented but i will add a doc string to it.
Crypto returns some algorithms which are indirectly forbidden under FIPS (i.e. listed as available but not working).

The Expectation

Expected: All algorithms returned by crypto:supports/0 will be available at least in one way.
Bonus points for returning forbidden algorithm names separately so that the users will not complain that something "has disappeared" and will see the reason.

Solution

The call to crypto:supports/0 under FIPS build with fips=enabled in crypto app settings, will filter the algorithms by their availability in a much more strict way (by actually trying to initialize each algorithm and recording the failures as a sign of unavailability) and will return an additional section in the results containing disabled algorithms:

[{hashs,[...
 {ciphers, ...
 {kems,[]},
 {public_keys, ...
 {macs, ...
 {curves, ...
 {rsa_opts, ...
 {fips_forbidden,[{hashs,[blake2s,blake2b,sm3,ripemd160,md5,
                          md4]},
                  {ciphers,[chacha20,sm4_ctr,sm4_ofb,sm4_cfb,sm4_ecb,sm4_cbc,
                            blowfish_ecb,blowfish_ofb64,blowfish_cfb64,blowfish_cbc,
                            des_ede3_cfb,des_ecb,des_cfb,des_cbc,rc4|...]},
                  {kems,[mlkem1024,mlkem768,mlkem512]},
                  {public_keys,[srp,eddh,eddsa,ecdh,ecdsa,ec_gf2m,dss]},
                  {macs,[hmac,poly1305]},
                  {curves,[secp256r1]},
                  {rsa_opts,[]}]}]

Note this will only appear if FIPS is enabled. And this will only appear if SSL API is 3.0 or later.
The behaviour with FIPS disabled or older SSL will remain unchanged.

TO DO

  • Update documentation for set_fips_mode (deprecated) that it is not thread safe
  • Review "fips=yes" usage in availability tests?
  • Ensure that cached flags for algorithm availability are used/recalculated correctly if fips is toggled in runtime
  • Try with normal (no FIPS)
    • SSL 3.5.4
    • Compare output with maint + SSL 3.5.4
    • SSL 1.1.1w
    • Compare output with maint + SSL 1.1.1w
    • SSL 0.9.x
    • Compare output with maint + SSL 0.9.8zh (compiling maint with 0.9.8zh failed)
  • Try with FIPS
    • SSL 3.5.4 FIPS
    • Compare output with maint + SSL 3.5.4 FIPS
    • SSL 1.1.1w FIPS
    • Compare output with maint + SSL 1.1.1w FIPS
    • SSL 0.9.x FIPS
    • Compare output with maint + SSL 0.9.8zh FIPS (compiling maint with 0.9.8zh failed)

@github-actions
Copy link
Contributor

github-actions bot commented Oct 21, 2025

CT Test Results

  2 files   14 suites   3m 55s ⏱️
190 tests 152 ✅  23 💤 15 ❌
478 runs  195 ✅ 241 💤 42 ❌

For more details on these failures, see this check.

Results for commit 01da622.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@kvakvs kvakvs added the team:PS Assigned to OTP team PS label Oct 21, 2025
@kvakvs kvakvs force-pushed the 9520-fips-hide-algorithms branch 3 times, most recently from 4fd908c to 65f3f3d Compare October 23, 2025 13:26
@kvakvs kvakvs changed the base branch from master to maint October 23, 2025 13:55
@kvakvs kvakvs force-pushed the 9520-fips-hide-algorithms branch from cd85ef1 to 0c6bd7f Compare October 23, 2025 13:57
fips_forbidden_* impl for digests
fips_forbidden_* impl for public keys
fips_forbidden_* impl for ciphers
fips_forbidden_* impl for MACs
fips_forbidden_* impl for KEM
fips_forbidden_* impl for curves
Use stdint/stdbool types already available
FIPS doc page updated
@kvakvs kvakvs force-pushed the 9520-fips-hide-algorithms branch from 0c6bd7f to aa72b5e Compare October 23, 2025 13:58
Copy link
Contributor

@sverker sverker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some testing, and it seems like
EVP_MD_fetch(NULL, p->str_v3, "fips=yes") // if supported with FIPS enabled
EVP_MD_fetch(NULL, p->str_v3, "fips=no") // if supported with FIPS disabled
EVP_MD_fetch(NULL, p->str_v3, "") // if supported in current mode set by EVP_default_properties_enable_fips()

Comment on lines 63 to 66
static bool IS_PUBKEY_FORBIDDEN_IN_FIPS(const struct pkey_availability_t* p) {
return p->flags == FIPS_FORBIDDEN_PKEY_ALL
|| p->flags == FIPS_PKEY_NOT_AVAIL;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between FIPS_PKEY_NOT_AVAIL and FIPS_FORBIDDEN_PKEY_ALL?

Comment on lines 277 to 285
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, str_v3, "fips=yes");
/* failed: algorithm not available, do not add */
if (ctx) {
if (EVP_PKEY_keygen_init(ctx) <= 0) { /* can't generate keys */
unavailable |= FIPS_FORBIDDEN_PKEY_KEYGEN;
}
EVP_PKEY_CTX_free(ctx);

ctx = EVP_PKEY_CTX_new_from_name(NULL, str_v3, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "fips=yes" in one place but not the other.

Comment on lines 480 to 486
static size_t curves_lazy_init(ErlNifEnv* env, const bool fips_enabled) {
size_t result = 0;
if (algo_curve.count >= 0) return algo_curve.count;

enif_mutex_lock(algo_curve.mtx_init_curve_types);
if (algo_curve.count < 0) {
init_curves(env, fips_enabled); /* also updates algo_curve.count[0] or [1] */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why pass fips_enabled to init_curves? We only have one array of curves initialized once here. And fips mode can be switched on/off in runtime with crypto:enable_fips_mode/1 (discouraged but used by our tests).

@kvakvs kvakvs marked this pull request as draft November 9, 2025 15:11
Comment on lines +65 to +68
enif_make_existing_atom(env, atom_name, &atom, ERL_NIF_UTF8);
if (!atom) {
atom = enif_make_atom(env, atom_name);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enif_make_atom already does this optimization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused. In erl_nif.c: enif_make_atom calls enif_make_atom_len, which calls enif_make_new_atom_len, which calls erts_atom_put. That doesn't look like its checking for existence.

Copy link
Contributor

@sverker sverker Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... which calls erts_atom_put_index, which takes the atom table read lock and does a lookup.

    atom_read_lock();
    aix = index_get(&erts_atom_table, (void*) &a);
    atom_read_unlock();
    if (aix >= 0) {
        /* Already in table no need to verify it */
        return aix;
    }

return; // failed to find the algorithm, do not add
}
#endif // FIPS_SUPPORT && HAS_3_0_API
return output.push_back(algo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function return type is void ?

Copy link
Collaborator Author

@kvakvs kvakvs Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew this is allowed, but now i dug into the actual C++ standard to prove it. Not allowed in C.

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just confusing.

Comment on lines 1024 to 1031
Do not use this function in your code, it is designed to only be used by the crypto
library or by Erlang self-tests. This function is called automatically on first load
of the crypto NIF with the value `fips_mode :: true | false` from crypto app environment.

This operation is not thread-safe, it should only be called once (by the Erlang crypto
library) and user code calling it, while there are SSL operations running, might get
undesired consequences, because the attached OpenSSL library structures will switch
on the fly. Unintended non-FIPS algorithms might become enabled in your FIPS-only code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can skip the fact that enable_fips_mode is called once by the crypto app itself. That's an irrelevant implementation detail.

Comment on lines 51 to 56
bool result = FIPS_mode_set(fips_mode) ? atom_true : atom_false;
if (result && previous_setting != fips_mode) {
/* Reinitialize the algorithms which may disappear or reappear when FIPS mode changes */
algorithms_reset_cache();
}
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return type is ERL_NIF_TERM which cannot be treated as a bool.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing for FIPS mode is not done yet. I now have tested only under non-FIPS, so those code blocks remain disabled with #ifdef, will find it out soon! Fixed this one, thanks for noticing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team:PS Assigned to OTP team PS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FIPS 140-3 vs supported algorithms in ssh

2 participants