Commit 18c2b337af7 for nodejs

commit 18c2b337af78c15112ffd522088904a38bef76f6
Author: Filip Skokan <panva.ip@gmail.com>
Date:   Wed Sep 23 10:50:56 2026 +0200

    crypto: use current FIPS state for availability

    Refresh conditional algorithm registration on FIPS changes and use the
    current state for parameter restrictions.

    Use per-algorithm availability checks in normalization and native named
    key generation. Remove getPqcKeyTypes().

    Enable supports() tests under FIPS and cover state transitions in warmed
    workers.

    Signed-off-by: Filip Skokan <panva.ip@gmail.com>
    Assisted-by: Codex
    PR-URL: https://github.com/nodejs/node/pull/66160
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>

diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index 4d415049b05..83b3eb9d674 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -3101,12 +3101,6 @@ const KeyAlgorithm* KeyAlgorithm::FromName(const char* name) {
   return nullptr;
 }

-void KeyAlgorithm::ForEachPqc(Callback callback) {
-  for (const auto* algorithm : kKeyAlgorithms) {
-    if (algorithm->isPqc() && algorithm->isAvailable()) callback(*algorithm);
-  }
-}
-
 bool KeyAlgorithm::isRsa() const {
   return this == &RSA || this == &RSA_PSS;
 }
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index 13444e226b6..4649c514372 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -1093,8 +1093,6 @@ class KeyAlgorithm final {
   // Look up a canonical name case-insensitively, including unavailable
   // algorithms.
   static const KeyAlgorithm* FromName(const char* name);
-  using Callback = std::function<void(const KeyAlgorithm&)>;
-  static void ForEachPqc(Callback callback);

   const char* name() const { return name_; }
   const char* keyTypeName() const {
diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js
index 4ac2fdee2df..f49165074f7 100644
--- a/lib/internal/crypto/keygen.js
+++ b/lib/internal/crypto/keygen.js
@@ -4,7 +4,6 @@ const {
   FunctionPrototypeCall,
   ObjectDefineProperty,
   SafeArrayIterator,
-  StringPrototypeToLowerCase,
 } = primordials;

 const {
@@ -12,7 +11,6 @@ const {
   DsaKeyPairGenJob,
   EcKeyPairGenJob,
   NamedKeyPairGenJob,
-  getPqcKeyTypes,
   RsaKeyPairGenJob,
   SecretKeyGenJob,
   kCryptoJobAsync,
@@ -160,17 +158,6 @@ function parseKeyEncoding(keyType, options = kEmptyObject) {
   ];
 }

-const namedKeyPairs = {
-  '__proto__': null,
-  'ed25519': 'Ed25519',
-  'ed448': 'Ed448',
-  'x25519': 'X25519',
-  'x448': 'X448',
-};
-for (const name of new SafeArrayIterator(getPqcKeyTypes())) {
-  namedKeyPairs[StringPrototypeToLowerCase(name)] = name;
-}
-
 function createJob(mode, type, options) {
   validateString(type, 'type');

@@ -337,12 +324,8 @@ function createJob(mode, type, options) {
         generator == null ? 2 : generator,
         ...encoding);
     }
-    default: {
-      if (namedKeyPairs[type] === undefined) {
-        throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
-      }
-      return new NamedKeyPairGenJob(mode, namedKeyPairs[type], ...encoding);
-    }
+    default:
+      return new NamedKeyPairGenJob(mode, type, ...encoding);
   }
 }

diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index 22b51f72074..8ac5046b740 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -47,10 +47,19 @@ const {
   getFipsCrypto,
   getFipsCryptoGeneration,
   KmacJob,
-  getPqcKeyTypes,
+  isKeyAlgorithmAvailable,
 } = internalBinding('crypto');

-const isFips = getFipsCrypto() === 1;
+let fips;
+let fipsGeneration;
+function isFips() {
+  const generation = getFipsCryptoGeneration();
+  if (fipsGeneration !== generation) {
+    fips = getFipsCrypto() === 1;
+    fipsGeneration = generation;
+  }
+  return fips;
+}

 const { getOptionValue } = require('internal/options');

@@ -119,6 +128,9 @@ let _hashCache;
 let _macCache;
 if (isBuildingSnapshot()) {
   addSerializeCallback(() => {
+    fips = undefined;
+    fipsGeneration = undefined;
+    supportedAlgorithmsGeneration = undefined;
     _hashCache = undefined;
     _macCache = undefined;
   });
@@ -495,53 +507,69 @@ const kAlgorithmDefinitions = {
   },
 };

-// Conditionally supported algorithms
-const pqcKeyTypes = getPqcKeyTypes();
-
-const conditionalAlgorithms = {
-  'AES-OCB': !!hasAesOcbMode,
-  'Argon2d': !!Argon2Job,
-  'Argon2i': !!Argon2Job,
-  'Argon2id': !!Argon2Job,
-  'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
-  'cSHAKE128': !process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getHashes(), 'shake128'),
-  'cSHAKE256': !process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getHashes(), 'shake256'),
-  'Ed448': !process.features.openssl_is_boringssl,
-  'KMAC128': !!KmacJob,
-  'KMAC256': !!KmacJob,
-  'KT128': !isFips,
-  'KT256': !isFips,
-  'ML-DSA-44': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-44'),
-  'ML-DSA-65': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-65'),
-  'ML-DSA-87': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-87'),
-  'ML-KEM-512': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-512'),
-  'ML-KEM-768': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768'),
-  'ML-KEM-1024': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024'),
-  'MLKEM768-P256': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
-    (!process.features.openssl_is_boringssl ||
-      (ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
-       ArrayPrototypeIncludes(getHashes(), 'shake256'))),
-  'MLKEM768-X25519': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
-    (!process.features.openssl_is_boringssl ||
-      (ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
-       ArrayPrototypeIncludes(getHashes(), 'shake256'))),
-  'MLKEM1024-P384': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024') &&
-    (!process.features.openssl_is_boringssl ||
-      (ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
-       ArrayPrototypeIncludes(getHashes(), 'shake256'))),
-  'SHA3-256': !process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
-  'SHA3-384': !process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
-  'SHA3-512': !process.features.openssl_is_boringssl ||
-    ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
-  'TurboSHAKE128': !isFips,
-  'TurboSHAKE256': !isFips,
-  'X448': !process.features.openssl_is_boringssl,
-};
+function has(algorithms, name) {
+  return ArrayPrototypeIncludes(algorithms, name);
+}
+
+// Re-evaluated when the active FIPS state changes.
+function getConditionalAlgorithms() {
+  const mlKem768 = isKeyAlgorithmAvailable('ML-KEM-768');
+  const mlKem1024 = isKeyAlgorithmAvailable('ML-KEM-1024');
+  const ciphers = getCiphers();
+  const hashes = getHashes();
+  const macs = getMacs();
+
+  const fips = isFips();
+
+  return {
+    'AES-OCB': !!hasAesOcbMode &&
+      (has(ciphers, 'aes-128-ocb') ||
+       has(ciphers, 'aes-192-ocb') ||
+       has(ciphers, 'aes-256-ocb')),
+    'Argon2d': !!Argon2Job && !fips,
+    'Argon2i': !!Argon2Job && !fips,
+    'Argon2id': !!Argon2Job && !fips,
+    'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
+      has(ciphers, 'chacha20-poly1305'),
+    'cSHAKE128': has(hashes, 'shake128'),
+    'cSHAKE256': has(hashes, 'shake256'),
+    'Ed25519': isKeyAlgorithmAvailable('Ed25519'),
+    'Ed448': isKeyAlgorithmAvailable('Ed448'),
+    'KMAC128': !!KmacJob && has(macs, 'kmac128'),
+    'KMAC256': !!KmacJob && has(macs, 'kmac256'),
+    'KT128': !fips,
+    'KT256': !fips,
+    'ML-DSA-44': isKeyAlgorithmAvailable('ML-DSA-44'),
+    'ML-DSA-65': isKeyAlgorithmAvailable('ML-DSA-65'),
+    'ML-DSA-87': isKeyAlgorithmAvailable('ML-DSA-87'),
+    'ML-KEM-512': isKeyAlgorithmAvailable('ML-KEM-512'),
+    'ML-KEM-768': mlKem768,
+    'ML-KEM-1024': mlKem1024,
+    'MLKEM768-P256': !fips && mlKem768 &&
+      (!process.features.openssl_is_boringssl ||
+        (has(hashes, 'sha3-256') &&
+         has(hashes, 'shake256'))),
+    'MLKEM768-X25519': !fips && mlKem768 &&
+      (!process.features.openssl_is_boringssl ||
+        (has(hashes, 'sha3-256') &&
+         has(hashes, 'shake256'))),
+    'MLKEM1024-P384': !fips && mlKem1024 &&
+      (!process.features.openssl_is_boringssl ||
+        (has(hashes, 'sha3-256') &&
+         has(hashes, 'shake256'))),
+    'SHA-1': has(hashes, 'sha1'),
+    'SHA-256': has(hashes, 'sha256'),
+    'SHA-384': has(hashes, 'sha384'),
+    'SHA-512': has(hashes, 'sha512'),
+    'SHA3-256': has(hashes, 'sha3-256'),
+    'SHA3-384': has(hashes, 'sha3-384'),
+    'SHA3-512': has(hashes, 'sha3-512'),
+    'TurboSHAKE128': !fips,
+    'TurboSHAKE256': !fips,
+    'X25519': isKeyAlgorithmAvailable('X25519'),
+    'X448': isKeyAlgorithmAvailable('X448'),
+  };
+}

 // Experimental algorithms
 const experimentalAlgorithms = [
@@ -578,6 +606,7 @@ const experimentalAlgorithms = [
 // Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
 // for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
 function createSupportedAlgorithms(algorithmDefs) {
+  const conditionalAlgorithms = getConditionalAlgorithms();
   // Detached below rather than declared `__proto__: null`: V8 puts that
   // literal form in dictionary mode, slowing every registry lookup.
   const result = {};
@@ -625,8 +654,16 @@ function createSupportedAlgorithms(algorithmDefs) {
   return { algorithms: result, nameMap };
 }

-const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
-  createSupportedAlgorithms(kAlgorithmDefinitions);
+let supportedAlgorithms;
+let supportedAlgorithmsGeneration;
+function getSupportedAlgorithms() {
+  const generation = getFipsCryptoGeneration();
+  if (supportedAlgorithmsGeneration !== generation) {
+    supportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
+    supportedAlgorithmsGeneration = generation;
+  }
+  return supportedAlgorithms;
+}

 const simpleAlgorithmDictionaries = {
   AesCbcParams: { iv: 'BufferSource' },
@@ -688,7 +725,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
 }

 function validateKmacKeyLength(length) {
-  if ((length < 32 || length % 8) && isFips)
+  if ((length < 32 || length % 8) && isFips())
     throw lazyDOMException('Invalid key length', 'NotSupportedError');
 }

@@ -751,16 +788,16 @@ function normalizeAlgorithm(algorithm, op) {

   webidl ??= require('internal/crypto/webidl');

-  // 1.
-  const registeredAlgorithms = kSupportedAlgorithms[op];
   // 2. 3.
   const initialAlg = webidl.converters.Algorithm(algorithm,
                                                  kNormalizeAlgorithmOpts);
+  const { algorithms, nameMap } = getSupportedAlgorithms();
+  const registeredAlgorithms = algorithms[op];
   // 4.
   let algName = initialAlg.name;

   // 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
-  const canonicalName = kAlgorithmNameMap[op]?.get(
+  const canonicalName = nameMap[op]?.get(
     StringPrototypeToUpperCase(algName));
   if (canonicalName === undefined)
     throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
@@ -1197,7 +1234,9 @@ module.exports = {
   toBuf,

   kNamedCurveAliases,
-  kSupportedAlgorithms,
+  get kSupportedAlgorithms() {
+    return getSupportedAlgorithms().algorithms;
+  },
   isFips,
   normalizeAlgorithm,
   normalizeHashName,
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index e61ff3d47dd..caeca416836 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -1757,7 +1757,6 @@ class SubtleCrypto {
   }

   // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
-  // TODO(panva): Make supports() account for the active FIPS state.
   static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
     emitExperimentalWarning('The supports Web Crypto API method');
     if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');
diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js
index 5ea062dcaf7..73b3af6a7f5 100644
--- a/lib/internal/crypto/webidl.js
+++ b/lib/internal/crypto/webidl.js
@@ -43,8 +43,6 @@ const {
   type,
 } = require('internal/webidl');

-const kRsaKeyGenMinimumModulusLength = isFips ? 2048 : 512;
-
 function validateByteLength(buf, name, target) {
   if (getBufferSourceByteLength(buf) !== target) {
     throw lazyDOMException(
@@ -157,6 +155,7 @@ const dictRsaKeyGenParams = [
     converter: (V, opts) =>
       converters['unsigned long'](V, enforceRangeOptions(opts)),
     validator: (modulusLength) => {
+      const kRsaKeyGenMinimumModulusLength = isFips() ? 2048 : 512;
       if (modulusLength < kRsaKeyGenMinimumModulusLength) {
         throw lazyDOMException(
           `algorithm.modulusLength must be at least ${kRsaKeyGenMinimumModulusLength}`,
@@ -285,7 +284,7 @@ function validateCShakeFunctionName(V) {
   const length = getBufferSourceByteLength(V);
   if (length === 0) return;

-  if (!isFips) {
+  if (!isFips()) {
     const bytes = getBufferSourceBytes(V);
     for (let i = 0; i < kCShakeFunctionNames.length; i++) {
       const functionName = kCShakeFunctionNames[i];
@@ -305,7 +304,7 @@ function validateCShakeFunctionName(V) {
 }

 function validateCShakeCustomization(V) {
-  if (isFips && getBufferSourceByteLength(V) !== 0)
+  if (isFips() && getBufferSourceByteLength(V) !== 0)
     throw lazyDOMException(
       'Unsupported CShakeParams customization',
       'NotSupportedError');
@@ -782,7 +781,7 @@ converters.KmacParams = createDictionaryConverter(
         converter: (V, opts) =>
           converters['unsigned long'](V, enforceRangeOptions(opts)),
         validator: (V) => {
-          if ((V === 0 || V % 8) && isFips)
+          if ((V === 0 || V % 8) && isFips())
             throw lazyDOMException(
               'Invalid KmacParams outputLength',
               'NotSupportedError');
diff --git a/node.gyp b/node.gyp
index 8692f42c897..7ad2d95ef9e 100644
--- a/node.gyp
+++ b/node.gyp
@@ -410,7 +410,6 @@
       'src/crypto/crypto_context.cc',
       'src/crypto/crypto_tls_certificates.cc',
       'src/crypto/crypto_ec.cc',
-      'src/crypto/crypto_pqc.cc',
       'src/crypto/crypto_kem.cc',
       'src/crypto/crypto_hmac.cc',
       'src/crypto/crypto_kmac.cc',
@@ -451,7 +450,6 @@
       'src/crypto/crypto_context.h',
       'src/crypto/crypto_tls_certificates.h',
       'src/crypto/crypto_ec.h',
-      'src/crypto/crypto_pqc.h',
       'src/crypto/crypto_hkdf.h',
       'src/crypto/crypto_pbkdf2.h',
       'src/crypto/crypto_sig.h',
diff --git a/src/crypto/README.md b/src/crypto/README.md
index de4883c424c..1655fc5ea0f 100644
--- a/src/crypto/README.md
+++ b/src/crypto/README.md
@@ -46,7 +46,6 @@ following table:
 | `crypto_keys`     | Utilities for using and generating secret, private, and public keys. |
 | `crypto_mac`      | Provider-generic MAC implementations.                                |
 | `crypto_pbkdf2`   | PBKDF2 key / bit generation implementation.                          |
-| `crypto_pqc`      | Post-quantum algorithm enumeration.                                  |
 | `crypto_rsa`      | RSA Key Generation functions.                                        |
 | `crypto_scrypt`   | Scrypt key / bit generation implementation.                          |
 | `crypto_sig`      | General digital signature and verification utilities.                |
@@ -205,10 +204,11 @@ Public input validation remains specific to each API: PQC JWK `alg` values use
 exact canonical names such as `ML-DSA-44`, while raw imports require exact public
 `asymmetricKeyType` values such as `ml-dsa-44`.

-The internal JavaScript binding exposes `getPqcKeyTypes()` for the available
-known PQC algorithm names in their canonical spelling. Named key generation
-passes algorithm names to `NamedKeyPairGenJob`, which resolves the name to a static
-`KeyAlgorithm` descriptor. Asymmetric key IDs are not exposed to JavaScript.
+The internal JavaScript binding exposes `isKeyAlgorithmAvailable()` to check
+whether a known key algorithm is available from the current backend. Named key
+generation passes algorithm names to `NamedKeyPairGenJob`, which resolves the
+name to a static `KeyAlgorithm` descriptor. Asymmetric key IDs are not exposed to
+JavaScript.

 Real EC curve and ASN.1/OID NIDs still have their own uses. The EC generation
 path keeps Ed/X algorithm descriptors separate from curve NIDs while preserving
diff --git a/src/crypto/crypto_keygen.cc b/src/crypto/crypto_keygen.cc
index 7e273eb57a5..1da910690b1 100644
--- a/src/crypto/crypto_keygen.cc
+++ b/src/crypto/crypto_keygen.cc
@@ -9,6 +9,7 @@
 #include "v8.h"

 #include <cmath>
+#include <string_view>

 namespace node {

@@ -19,6 +20,7 @@ using v8::JustVoid;
 using v8::Local;
 using v8::Maybe;
 using v8::MaybeLocal;
+using v8::Nothing;
 using v8::Object;
 using v8::Uint32;
 using v8::Value;
@@ -40,8 +42,20 @@ Maybe<void> NamedKeyPairGenTraits::AdditionalConfig(
     NamedKeyPairGenConfig* params) {
   CHECK(args[*offset]->IsString());
   Utf8Value name(args.GetIsolate(), args[*offset]);
-  params->params.algorithm = ncrypto::KeyAlgorithm::FromName(*name);
-  CHECK_NOT_NULL(params->params.algorithm);
+  const auto* algorithm = ncrypto::KeyAlgorithm::FromName(*name);
+  // Traditional key generation accepts lowercase key types; Web Crypto passes
+  // normalized algorithm names and checks availability during normalization.
+  if (algorithm == nullptr || (!algorithm->isOkp() && !algorithm->isPqc()) ||
+      (mode != kCryptoJobWebCrypto &&
+       (std::string_view(*name, name.length()) != algorithm->keyTypeName() ||
+        !algorithm->isAvailable()))) {
+    THROW_ERR_INVALID_ARG_VALUE(
+        Environment::GetCurrent(args),
+        "The argument 'type' must be a supported key type. Received '%s'",
+        *name);
+    return Nothing<void>();
+  }
+  params->params.algorithm = algorithm;

   *offset += 1;

diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc
index 3b22175862a..f787c97eb7f 100644
--- a/src/crypto/crypto_keys.cc
+++ b/src/crypto/crypto_keys.cc
@@ -50,6 +50,13 @@ using v8::Value;

 namespace crypto {
 namespace {
+void IsKeyAlgorithmAvailable(const FunctionCallbackInfo<Value>& args) {
+  CHECK(args[0]->IsString());
+  const Utf8Value name(args.GetIsolate(), args[0]);
+  const auto* algorithm = KeyAlgorithm::FromName(*name);
+  args.GetReturnValue().Set(algorithm != nullptr && algorithm->isAvailable());
+}
+
 Maybe<EVPKeyPointer::AsymmetricKeyEncodingConfig> GetKeyFormatAndTypeFromJs(
     const FunctionCallbackInfo<Value>& args,
     unsigned int* offset,
@@ -2188,7 +2195,8 @@ void Initialize(Environment* env, Local<Object> target) {
   NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8);
   NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI);
   NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
-  SetMethod(context, target, "getPqcKeyTypes", GetPqcKeyTypes);
+  SetMethodNoSideEffect(
+      context, target, "isKeyAlgorithmAvailable", IsKeyAlgorithmAvailable);
   NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
   NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
   NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
@@ -2209,7 +2217,7 @@ void Initialize(Environment* env, Local<Object> target) {

 void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
   KeyObjectHandle::RegisterExternalReferences(registry);
-  registry->Register(GetPqcKeyTypes);
+  registry->Register(IsKeyAlgorithmAvailable);
 }
 }  // namespace Keys

diff --git a/src/crypto/crypto_pqc.cc b/src/crypto/crypto_pqc.cc
deleted file mode 100644
index 17d6855ada7..00000000000
--- a/src/crypto/crypto_pqc.cc
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "crypto/crypto_pqc.h"
-#include "ncrypto.h"
-#include "util-inl.h"
-#include "v8.h"
-
-namespace node {
-
-using v8::Value;
-
-namespace crypto {
-
-void GetPqcKeyTypes(const v8::FunctionCallbackInfo<Value>& args) {
-  v8::LocalVector<Value> names(args.GetIsolate());
-  ncrypto::KeyAlgorithm::ForEachPqc(
-      [&](const ncrypto::KeyAlgorithm& algorithm) {
-        names.push_back(OneByteString(args.GetIsolate(), algorithm.name()));
-      });
-  args.GetReturnValue().Set(
-      v8::Array::New(args.GetIsolate(), names.data(), names.size()));
-}
-}  // namespace crypto
-}  // namespace node
diff --git a/src/crypto/crypto_pqc.h b/src/crypto/crypto_pqc.h
deleted file mode 100644
index 23cdcdc0512..00000000000
--- a/src/crypto/crypto_pqc.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef SRC_CRYPTO_CRYPTO_PQC_H_
-#define SRC_CRYPTO_CRYPTO_PQC_H_
-
-#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-
-#include "v8.h"
-
-namespace node {
-namespace crypto {
-void GetPqcKeyTypes(const v8::FunctionCallbackInfo<v8::Value>& args);
-}  // namespace crypto
-}  // namespace node
-
-#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-#endif  // SRC_CRYPTO_CRYPTO_PQC_H_
diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc
index dffa807d183..1e003e9e51b 100644
--- a/src/crypto/crypto_sig.cc
+++ b/src/crypto/crypto_sig.cc
@@ -3,7 +3,6 @@
 #include "base_object-inl.h"
 #include "crypto/crypto_ec.h"
 #include "crypto/crypto_keys.h"
-#include "crypto/crypto_pqc.h"
 #include "crypto/crypto_util.h"
 #include "env-inl.h"
 #include "memory_tracker-inl.h"
diff --git a/src/node_crypto.h b/src/node_crypto.h
index e15e5958808..92d35b844f9 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -51,7 +51,6 @@
 #include "crypto/crypto_mac.h"
 #include "crypto/crypto_pbkdf2.h"
 #include "crypto/crypto_pkcs12.h"
-#include "crypto/crypto_pqc.h"
 #include "crypto/crypto_random.h"
 #include "crypto/crypto_rsa.h"
 #include "crypto/crypto_scrypt.h"
diff --git a/test/fixtures/webcrypto/supports-level-2.mjs b/test/fixtures/webcrypto/supports-level-2.mjs
index b07ce9097bb..dacaa907002 100644
--- a/test/fixtures/webcrypto/supports-level-2.mjs
+++ b/test/fixtures/webcrypto/supports-level-2.mjs
@@ -1,4 +1,4 @@
-import { getFips } from 'node:crypto';
+import { generateKeyPairSync, getFips } from 'node:crypto';

 const { subtle } = globalThis.crypto;
 const RSA_MINIMUM_MODULUS_LENGTH = getFips() === 1 ? 2048 : 512;
@@ -8,10 +8,28 @@ const RSA_KEY_GEN = {
   publicExponent: new Uint8Array([1, 0, 1])
 };

-const [ECDH, X25519] = await Promise.all([
-  subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']),
-  subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']),
-]);
+// Determine availability through traditional crypto, independently of supports().
+export function generateNamedKeyPair(name) {
+  let pair;
+  try {
+    pair = generateKeyPairSync(name.toLowerCase());
+  } catch (err) {
+    if (err.code !== 'ERR_INVALID_ARG_VALUE') throw err;
+    return;
+  }
+  const derive = name.startsWith('X');
+  return {
+    publicKey: pair.publicKey.toCryptoKey(name, true, derive ? [] : ['verify']),
+    privateKey: pair.privateKey.toCryptoKey(name, false, derive ? ['deriveBits', 'deriveKey'] : ['sign']),
+  };
+}
+
+export const ECDH = await subtle.generateKey(
+  { name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']);
+export const X25519 = generateNamedKeyPair('X25519');
+export const Ed25519 = generateNamedKeyPair('Ed25519');
+const hasX25519 = X25519 !== undefined;
+const hasEd25519 = Ed25519 !== undefined;

 export const vectors = {
   'encrypt': [
@@ -32,7 +50,7 @@ export const vectors = {
     [false, 'Invalid'],
     [false, 'SHA-1'],

-    [true, 'Ed25519'],
+    [hasEd25519, 'Ed25519'],

     [true, 'RSASSA-PKCS1-v1_5'],

@@ -59,8 +77,8 @@ export const vectors = {
     [false, 'Invalid'],
     [false, 'HKDF'],
     [false, 'PBKDF2'],
-    [true, 'X25519'],
-    [true, 'Ed25519'],
+    [hasX25519, 'X25519'],
+    [hasEd25519, 'Ed25519'],
     [true, { name: 'HMAC', hash: 'SHA-256' }],
     [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [true, { name: 'HMAC', hash: 'SHA-256', length: 25 }],
@@ -128,20 +146,20 @@ export const vectors = {
     [false,
      { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 },
      'HKDF'],
-    [true,
-     { name: 'X25519', public: X25519.publicKey },
+    [hasX25519,
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'AES-CBC', length: 128 }],
     [false,
-     { name: 'X25519', public: X25519.publicKey },
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'HMAC', hash: 'SHA-256' }],
-    [true,
-     { name: 'X25519', public: X25519.publicKey },
+    [hasX25519,
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [false,
-     { name: 'X25519', public: X25519.publicKey },
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'HMAC', hash: 'SHA-256', length: 257 }],
-    [true,
-     { name: 'X25519', public: X25519.publicKey },
+    [hasX25519,
+     { name: 'X25519', public: X25519?.publicKey },
      'HKDF'],
     [true,
      { name: 'ECDH', public: ECDH.publicKey },
@@ -165,10 +183,10 @@ export const vectors = {
      { name: 'ECDH', public: ECDH.publicKey },
      'HKDF'],
     [false,
-      { name: 'X25519', public: X25519.publicKey },
+      { name: 'X25519', public: X25519?.publicKey },
       'SHA-256'],
     [false,
-      { name: 'X25519', public: X25519.publicKey },
+      { name: 'X25519', public: X25519?.publicKey },
       'AES-CBC'],
   ],
   'deriveBits': [
@@ -201,17 +219,17 @@ export const vectors = {
     [false, { name: 'ECDH', public: ECDH.privateKey }],
     [false, 'ECDH'],

-    [true, { name: 'X25519', public: X25519.publicKey }],
-    [true, { name: 'X25519', public: X25519.publicKey }, 256],
-    [false, { name: 'X25519', public: X25519.publicKey }, 257],
-    [false, { name: 'X25519', public: X25519.privateKey }],
+    [hasX25519, { name: 'X25519', public: X25519?.publicKey }],
+    [hasX25519, { name: 'X25519', public: X25519?.publicKey }, 256],
+    [false, { name: 'X25519', public: X25519?.publicKey }, 257],
+    [false, { name: 'X25519', public: X25519?.privateKey }],
     [false, 'X25519'],
   ],
   'importKey': [
     [false, 'SHA-1'],
     [false, 'Invalid'],
-    [true, 'X25519'],
-    [true, 'Ed25519'],
+    [hasX25519, 'X25519'],
+    [hasEd25519, 'Ed25519'],
     [true, { name: 'HMAC', hash: 'SHA-256' }],
     [true, { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [true, { name: 'HMAC', hash: 'SHA-256', length: 25 }],
@@ -246,8 +264,8 @@ export const vectors = {
     [true, 'AES-CBC'],
     [true, 'AES-GCM'],
     [true, 'AES-KW'],
-    [true, 'Ed25519'],
-    [true, 'X25519'],
+    [hasEd25519, 'Ed25519'],
+    [hasX25519, 'X25519'],
   ],
   'wrapKey': [
     [false, 'AES-KW'],
diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs
index 67fad15f7f3..b01ebce3e5a 100644
--- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs
+++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs
@@ -1,63 +1,65 @@
 import * as crypto from 'node:crypto'

 import { hasOpenSSL, isBoringSSL } from '../../common/crypto.js'
+import { X25519, Ed25519 } from './supports-level-2.mjs'
+import { X448, Ed448 } from './supports-secure-curves.mjs'

 const boringSSL = isBoringSSL;
+const fips = crypto.getFips() === 1;
+const chacha = boringSSL || crypto.getCiphers().includes('chacha20-poly1305');
 const pqc = hasOpenSSL(3, 5) || boringSSL;
-const argon2 = hasOpenSSL(3, 2);
+const argon2 = hasOpenSSL(3, 2) && !fips;
 const shake128 = crypto.getHashes().includes('shake128');
 const shake256 = crypto.getHashes().includes('shake256');
 const sha3 = crypto.getHashes().includes('sha3-256');
-const ocb = hasOpenSSL(3);
-const kmac = hasOpenSSL(3);
-const hybridKems = pqc && (!boringSSL || (sha3 && shake256));
+const ocb = hasOpenSSL(3) && crypto.getCiphers().includes('aes-128-ocb');
+const kmac = hasOpenSSL(3) && crypto.getMacs().includes('kmac128');
+const hybridKems = !fips && pqc && (!boringSSL || (sha3 && shake256));

-const { subtle } = globalThis.crypto;
-const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']);

 export const vectors = {
   'digest': [
     [false, 'cSHAKE128'],
     [shake128, { name: 'cSHAKE128', outputLength: 128 }],
     [shake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }],
-    [shake128 && kmac, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }],
+    [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }],
     [false, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('SHAKE') }],
-    [shake128 && kmac, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }],
+    [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }],
     [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(513) }],
     [shake128, { name: 'cSHAKE128', outputLength: 127 }],
     [false, 'cSHAKE256'],
     [shake256, { name: 'cSHAKE256', outputLength: 256 }],
     [shake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }],
-    [shake256 && kmac, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }],
+    [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }],
     [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('SHAKE') }],
-    [shake256 && kmac, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }],
+    [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }],
     [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(513) }],
     [shake256, { name: 'cSHAKE256', outputLength: 255 }],
     [false, 'TurboSHAKE128'],
-    [true, { name: 'TurboSHAKE128', outputLength: 128 }],
-    [true, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }],
+    [!fips, { name: 'TurboSHAKE128', outputLength: 128 }],
+    [!fips, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }],
     [false, { name: 'TurboSHAKE128', outputLength: 0 }],
     [false, { name: 'TurboSHAKE128', outputLength: 127 }],
     [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x00 }],
     [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x80 }],
     [false, 'TurboSHAKE256'],
-    [true, { name: 'TurboSHAKE256', outputLength: 256 }],
-    [true, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }],
+    [!fips, { name: 'TurboSHAKE256', outputLength: 256 }],
+    [!fips, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }],
     [false, { name: 'TurboSHAKE256', outputLength: 0 }],
     [false, { name: 'TurboSHAKE256', outputLength: 255 }],
     [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x00 }],
     [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x80 }],
     [false, 'KT128'],
-    [true, { name: 'KT128', outputLength: 128 }],
-    [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }],
-    [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(512) }],
+    [!fips, { name: 'KT128', outputLength: 128 }],
+    [!fips, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }],
+    [!fips, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(512) }],
     [false, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(513) }],
     [false, { name: 'KT128', outputLength: 0 }],
     [false, { name: 'KT128', outputLength: 127 }],
     [false, 'KT256'],
-    [true, { name: 'KT256', outputLength: 256 }],
-    [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }],
-    [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(512) }],
+    [!fips, { name: 'KT256', outputLength: 256 }],
+    [!fips, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }],
+    [!fips, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(512) }],
     [false, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(513) }],
     [false, { name: 'KT256', outputLength: 0 }],
     [false, { name: 'KT256', outputLength: 255 }],
@@ -84,9 +86,9 @@ export const vectors = {
     [false, 'KMAC128'],
     [false, 'KMAC256'],
     [kmac, { name: 'KMAC128', outputLength: 256 }],
-    [kmac, { name: 'KMAC128', outputLength: 255 }],
+    [kmac && !fips, { name: 'KMAC128', outputLength: 255 }],
     [kmac, { name: 'KMAC256', outputLength: 256 }],
-    [kmac, { name: 'KMAC256', outputLength: 255 }],
+    [kmac && !fips, { name: 'KMAC256', outputLength: 255 }],
   ],
   'generateKey': [
     [pqc, 'ML-DSA-44'],
@@ -95,7 +97,7 @@ export const vectors = {
     [pqc && !boringSSL, 'ML-KEM-512'],
     [pqc, 'ML-KEM-768'],
     [pqc, 'ML-KEM-1024'],
-    [true, 'ChaCha20-Poly1305'],
+    [chacha, 'ChaCha20-Poly1305'],
     [hybridKems, 'MLKEM768-P256'],
     [hybridKems, 'MLKEM768-X25519'],
     [hybridKems, 'MLKEM1024-P384'],
@@ -107,10 +109,10 @@ export const vectors = {
     [kmac, 'KMAC256'],
     [kmac, { name: 'KMAC128', length: 256 }],
     [kmac, { name: 'KMAC256', length: 128 }],
-    [kmac, { name: 'KMAC128', length: 0 }],
-    [kmac, { name: 'KMAC256', length: 0 }],
-    [kmac, { name: 'KMAC128', length: 1 }],
-    [kmac, { name: 'KMAC256', length: 1 }],
+    [kmac && !fips, { name: 'KMAC128', length: 0 }],
+    [kmac && !fips, { name: 'KMAC256', length: 0 }],
+    [kmac && !fips, { name: 'KMAC128', length: 1 }],
+    [kmac && !fips, { name: 'KMAC256', length: 1 }],
   ],
   'importKey': [
     [pqc, 'ML-DSA-44'],
@@ -119,7 +121,7 @@ export const vectors = {
     [pqc && !boringSSL, 'ML-KEM-512'],
     [pqc, 'ML-KEM-768'],
     [pqc, 'ML-KEM-1024'],
-    [true, 'ChaCha20-Poly1305'],
+    [chacha, 'ChaCha20-Poly1305'],
     [hybridKems, 'MLKEM768-P256'],
     [hybridKems, 'MLKEM768-X25519'],
     [hybridKems, 'MLKEM1024-P384'],
@@ -131,10 +133,10 @@ export const vectors = {
     [kmac, 'KMAC256'],
     [kmac, { name: 'KMAC128', length: 256 }],
     [kmac, { name: 'KMAC256', length: 128 }],
-    [kmac, { name: 'KMAC128', length: 0 }],
-    [kmac, { name: 'KMAC256', length: 0 }],
-    [kmac, { name: 'KMAC128', length: 1 }],
-    [kmac, { name: 'KMAC256', length: 1 }],
+    [kmac && !fips, { name: 'KMAC128', length: 0 }],
+    [kmac && !fips, { name: 'KMAC256', length: 0 }],
+    [kmac && !fips, { name: 'KMAC128', length: 1 }],
+    [kmac && !fips, { name: 'KMAC256', length: 1 }],
   ],
   'exportKey': [
     [pqc, 'ML-DSA-44'],
@@ -143,7 +145,7 @@ export const vectors = {
     [pqc && !boringSSL, 'ML-KEM-512'],
     [pqc, 'ML-KEM-768'],
     [pqc, 'ML-KEM-1024'],
-    [true, 'ChaCha20-Poly1305'],
+    [chacha, 'ChaCha20-Poly1305'],
     [hybridKems, 'MLKEM768-P256'],
     [hybridKems, 'MLKEM768-X25519'],
     [hybridKems, 'MLKEM1024-P384'],
@@ -158,10 +160,10 @@ export const vectors = {
     [true, 'RSA-OAEP'],
     [true, 'RSA-PSS'],
     [true, 'RSASSA-PKCS1-v1_5'],
-    [true, 'X25519'],
-    [!boringSSL, 'X448'],
-    [true, 'Ed25519'],
-    [!boringSSL, 'Ed448'],
+    [!!X25519, 'X25519'],
+    [!!X448, 'X448'],
+    [!!Ed25519, 'Ed25519'],
+    [!!Ed448, 'Ed448'],
     [true, 'ECDH'],
     [true, 'ECDSA'],
     [pqc, 'ML-DSA-44'],
@@ -186,14 +188,14 @@ export const vectors = {
     [false, 'KMAC256'],
   ],
   'deriveKey': [
-    [argon2,
-     { name: 'X25519', public: X25519.publicKey },
+    [argon2 && !!X25519,
+     { name: 'X25519', public: X25519?.publicKey },
      'Argon2d'],
-    [argon2,
-     { name: 'X25519', public: X25519.publicKey },
+    [argon2 && !!X25519,
+     { name: 'X25519', public: X25519?.publicKey },
      'Argon2i'],
-    [argon2,
-     { name: 'X25519', public: X25519.publicKey },
+    [argon2 && !!X25519,
+     { name: 'X25519', public: X25519?.publicKey },
      'Argon2id'],
   ],
   'deriveBits': [
@@ -215,9 +217,9 @@ export const vectors = {
     [false, { name: 'Argon2d', nonce: Buffer.alloc(8), parallelism: 16777215, memory: 8, passes: 1 }, 32],
   ],
   'encrypt': [
-    [true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }],
+    [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12) }],
     [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(16) }],
-    [true, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }],
+    [chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }],
     [false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }],
     [false, 'ChaCha20-Poly1305'],
     [ocb, { name: 'AES-OCB', iv: Buffer.alloc(15) }],
@@ -242,14 +244,14 @@ export const vectors = {
     [pqc, 'ML-KEM-768', 'AES-GCM'],
     [pqc, 'ML-KEM-768', 'AES-CTR'],
     [pqc, 'ML-KEM-768', 'AES-CBC'],
-    [pqc, 'ML-KEM-768', 'ChaCha20-Poly1305'],
+    [pqc && chacha, 'ML-KEM-768', 'ChaCha20-Poly1305'],
     [pqc, 'ML-KEM-768', 'HKDF'],
     [pqc, 'ML-KEM-768', 'PBKDF2'],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256' }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }],
-    [pqc && kmac, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
+    [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
     [hybridKems, 'MLKEM768-P256', 'HKDF'],
     [hybridKems, 'MLKEM768-X25519', 'HKDF'],
     [hybridKems, 'MLKEM1024-P384', 'HKDF'],
@@ -274,14 +276,14 @@ export const vectors = {
     [pqc, 'ML-KEM-768', 'AES-GCM'],
     [pqc, 'ML-KEM-768', 'AES-CTR'],
     [pqc, 'ML-KEM-768', 'AES-CBC'],
-    [pqc, 'ML-KEM-768', 'ChaCha20-Poly1305'],
+    [pqc && chacha, 'ML-KEM-768', 'ChaCha20-Poly1305'],
     [pqc, 'ML-KEM-768', 'HKDF'],
     [pqc, 'ML-KEM-768', 'PBKDF2'],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256' }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }],
     [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }],
-    [pqc && kmac, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
+    [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }],
     [hybridKems, 'MLKEM768-P256', 'HKDF'],
     [hybridKems, 'MLKEM768-X25519', 'HKDF'],
     [hybridKems, 'MLKEM1024-P384', 'HKDF'],
diff --git a/test/fixtures/webcrypto/supports-secure-curves.mjs b/test/fixtures/webcrypto/supports-secure-curves.mjs
index fa3f1d36521..52dd1c95995 100644
--- a/test/fixtures/webcrypto/supports-secure-curves.mjs
+++ b/test/fixtures/webcrypto/supports-secure-curves.mjs
@@ -1,62 +1,55 @@
-import { hasOpenSSL, isBoringSSL } from '../../common/crypto.js'
+import { hasOpenSSL } from '../../common/crypto.js';
+import { generateNamedKeyPair, X25519 } from './supports-level-2.mjs';

 const supportsContext = hasOpenSSL(3, 2);
-
-const { subtle } = globalThis.crypto;
-
-const boringSSL = isBoringSSL;
-
-const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']);
-let X448;
-let Ed448;
-if (!boringSSL) {
-  X448 = await subtle.generateKey('X448', false, ['deriveBits', 'deriveKey'])
-  Ed448 = await subtle.generateKey('Ed448', false, ['sign', 'verify'])
-}
+export const X448 = generateNamedKeyPair('X448');
+export const Ed448 = generateNamedKeyPair('Ed448');
+const hasX448 = X448 !== undefined;
+const hasEd448 = Ed448 !== undefined;

 export const vectors = {
   'sign': [
-    [!boringSSL, 'Ed448'],
-    [!boringSSL, { name: 'Ed448', context: Buffer.alloc(0) }],
-    [!boringSSL && supportsContext, { name: 'Ed448', context: Buffer.alloc(32) }],
-    [!boringSSL && supportsContext, { name: 'Ed448', context: Buffer.alloc(255) }],
+    [hasEd448, 'Ed448'],
+    [hasEd448, { name: 'Ed448', context: Buffer.alloc(0) }],
+    [hasEd448 && supportsContext, { name: 'Ed448', context: Buffer.alloc(32) }],
+    [hasEd448 && supportsContext, { name: 'Ed448', context: Buffer.alloc(255) }],
     [false, { name: 'Ed448', context: Buffer.alloc(256) }],
   ],
   'generateKey': [
-    [!boringSSL, 'X448'],
-    [!boringSSL, 'Ed448'],
+    [hasX448, 'X448'],
+    [hasEd448, 'Ed448'],
   ],
   'deriveKey': [
-    [!boringSSL,
+    [hasX448,
      { name: 'X448', public: X448?.publicKey },
      { name: 'AES-CBC', length: 128 }],
     [false,
      { name: 'X448', public: X448?.publicKey },
      { name: 'HMAC', hash: 'SHA-256' }],
-    [!boringSSL,
+    [hasX448,
      { name: 'X448', public: X448?.publicKey },
      { name: 'HMAC', hash: 'SHA-256', length: 448 }],
     [false,
      { name: 'X448', public: X448?.publicKey },
      { name: 'HMAC', hash: 'SHA-256', length: 449 }],
-    [!boringSSL,
+    [hasX448,
      { name: 'X448', public: X448?.publicKey },
      'HKDF'],
   ],
   'deriveBits': [
-    [!boringSSL, { name: 'X448', public: X448?.publicKey }],
-    [!boringSSL, { name: 'X448', public: X448?.publicKey }, 448],
+    [hasX448, { name: 'X448', public: X448?.publicKey }],
+    [hasX448, { name: 'X448', public: X448?.publicKey }, 448],
     [false, { name: 'X448', public: X448?.publicKey }, 449],
-    [false, { name: 'X448', public: X25519.publicKey }],
+    [false, { name: 'X448', public: X25519?.publicKey }],
     [false, { name: 'X448', public: X448?.privateKey }],
     [false, 'X448'],
   ],
   'importKey': [
-    [!boringSSL, 'X448'],
-    [!boringSSL, 'Ed448'],
+    [hasX448, 'X448'],
+    [hasEd448, 'Ed448'],
   ],
   'exportKey': [
-    [!boringSSL, 'Ed448'],
-    [!boringSSL, 'X448'],
+    [hasEd448, 'Ed448'],
+    [hasX448, 'X448'],
   ],
 };
diff --git a/test/fixtures/webcrypto/supports-sha3.mjs b/test/fixtures/webcrypto/supports-sha3.mjs
index fe256dcfb16..699b72bb11b 100644
--- a/test/fixtures/webcrypto/supports-sha3.mjs
+++ b/test/fixtures/webcrypto/supports-sha3.mjs
@@ -1,33 +1,28 @@
-import { isBoringSSL } from '../../common/crypto.js'
+import { getHashes } from 'node:crypto';
+import { ECDH, X25519 } from './supports-level-2.mjs';

-const { subtle } = globalThis.crypto;
-
-const boringSSL = isBoringSSL;
+const hashes = getHashes();
+const hasSha3 = hashes.includes('sha3-256');

 const RSA_KEY_GEN = {
   modulusLength: 2048,
   publicExponent: new Uint8Array([1, 0, 1])
 };

-const [ECDH, X25519] = await Promise.all([
-  subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveBits', 'deriveKey']),
-  subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']),
-]);
-
 export const vectors = {
   'digest': [
-    [!boringSSL, 'SHA3-256'],
-    [!boringSSL, 'SHA3-384'],
-    [!boringSSL, 'SHA3-512'],
+    [hasSha3, 'SHA3-256'],
+    [hashes.includes('sha3-384'), 'SHA3-384'],
+    [hashes.includes('sha3-512'), 'SHA3-512'],
   ],
   'generateKey': [
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
-    [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
+    [hasSha3, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
     [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }],

     // This interaction is not defined for now.
@@ -35,28 +30,28 @@ export const vectors = {
     [false, { name: 'HMAC', hash: 'SHA3-256' }],
   ],
   'deriveKey': [
-    [!boringSSL,
+    [hasSha3,
      { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) },
      { name: 'AES-CBC', length: 128 }],
-    [!boringSSL,
+    [hasSha3,
      { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) },
      { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
     [false,
      { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) },
      'HKDF'],
-    [!boringSSL,
+    [hasSha3,
      { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 },
      { name: 'AES-CBC', length: 128 }],
-    [!boringSSL,
+    [hasSha3,
      { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 },
      { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
     [false,
      { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 },
      'HKDF'],
-    [!boringSSL,
-     { name: 'X25519', public: X25519.publicKey },
+    [hasSha3 && X25519 !== undefined,
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
-    [!boringSSL,
+    [hasSha3,
      { name: 'ECDH', public: ECDH.publicKey },
      { name: 'HMAC', hash: 'SHA3-256', length: 256 }],

@@ -69,34 +64,34 @@ export const vectors = {
      { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 },
      { name: 'HMAC', hash: 'SHA3-256' }],
     [false,
-     { name: 'X25519', public: X25519.publicKey },
+     { name: 'X25519', public: X25519?.publicKey },
      { name: 'HMAC', hash: 'SHA3-256' }],
     [false,
      { name: 'ECDH', public: ECDH.publicKey },
      { name: 'HMAC', hash: 'SHA3-256' }],
   ],
   'deriveBits': [
-    [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8],
-    [!boringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0],
+    [hasSha3, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8],
+    [hasSha3, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0],
     [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null],
     [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7],

-    [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8],
-    [!boringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0],
+    [hasSha3, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8],
+    [hasSha3, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0],
     [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 0 }, 8],
     [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, null],
     [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 7],
   ],
   'importKey': [
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
-    [!boringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256' }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
-    [!boringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256' }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
+    [hasSha3, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256' }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }],
+    [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }],
     [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }],
   ],
   'get key length': [
diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js
index f699ec5462c..ba41eb64eb2 100644
--- a/test/parallel/test-crypto-key-objects-to-crypto-key.js
+++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js
@@ -257,7 +257,7 @@ function ecVectors(name, usagesByType) {
 function cfrgVectors(name, usagesByType) {
   if (rejectsXCurves && name.startsWith('X')) {
     assert.throws(() => generateKeyPairSync(name.toLowerCase()), {
-      code: 'ERR_OSSL_EVP_UNSUPPORTED',
+      code: 'ERR_INVALID_ARG_VALUE',
     });
     return [];
   }
@@ -313,9 +313,12 @@ function invalidAsymmetricKeyType(name, invalidAlgorithm) {
   const { publicKey } = generateKeyPairSync(name.toLowerCase());
   assert.throws(() => {
     publicKey.toCryptoKey(invalidAlgorithm, true, []);
-  }, {
+  }, invalidAlgorithm in kSupportedAlgorithms.importKey ? {
     name: 'DataError',
     message: 'Invalid key type'
+  } : {
+    name: 'NotSupportedError',
+    message: 'Unrecognized algorithm name',
   });
 }

diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js
index 6dfdada9430..97810af5d9c 100644
--- a/test/parallel/test-crypto-key-objects.js
+++ b/test/parallel/test-crypto-key-objects.js
@@ -1216,7 +1216,7 @@ if (!isBoringSSL) {
   const first = generateKeyPairSync('ed25519');
   if (rejectsXCurves) {
     assert.throws(() => generateKeyPairSync('x25519'), {
-      code: 'ERR_OSSL_EVP_UNSUPPORTED',
+      code: 'ERR_INVALID_ARG_VALUE',
     });
   } else {
     const second = generateKeyPairSync('x25519');
diff --git a/test/parallel/test-crypto-key-store.js b/test/parallel/test-crypto-key-store.js
index 58a23192b28..d5636f350dc 100644
--- a/test/parallel/test-crypto-key-store.js
+++ b/test/parallel/test-crypto-key-store.js
@@ -86,7 +86,7 @@ const data = Buffer.from('hello store');
 {
   if (hasFIPS(3, 5)) {
     assert.throws(() => generateKeyPairSync('x25519'), {
-      code: 'ERR_OSSL_EVP_UNSUPPORTED',
+      code: 'ERR_INVALID_ARG_VALUE',
     });
   } else {
     const alice = generateKeyPairSync('x25519');
diff --git a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js
index 9985631b409..bd097f04eca 100644
--- a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js
+++ b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js
@@ -18,19 +18,21 @@ const rejectsXCurves = hasFIPS(3, 5);
       common.printSkipMessage(`Skipping unsupported ${type} test case`);
       continue;
     }
-    generateKeyPair(type, {
+    const options = {
       publicKeyEncoding: {
         format: 'jwk'
       },
       privateKeyEncoding: {
         format: 'jwk'
       }
-    }, common.mustCall((err, publicKey, privateKey) => {
-      if (rejectsXCurves && type.startsWith('x')) {
-        assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED');
-        return;
-      }
-      assert.ifError(err);
+    };
+    if (rejectsXCurves && type.startsWith('x')) {
+      assert.throws(() => generateKeyPair(type, options, common.mustNotCall()), {
+        code: 'ERR_INVALID_ARG_VALUE',
+      });
+      continue;
+    }
+    generateKeyPair(type, options, common.mustSucceed((publicKey, privateKey) => {
       assert.strictEqual(typeof publicKey, 'object');
       assert.strictEqual(typeof privateKey, 'object');
       assert.strictEqual(publicKey.x, privateKey.x);
diff --git a/test/parallel/test-crypto-keygen-eddsa.js b/test/parallel/test-crypto-keygen-eddsa.js
index 0f9f8421ce7..a9faf39d0ab 100644
--- a/test/parallel/test-crypto-keygen-eddsa.js
+++ b/test/parallel/test-crypto-keygen-eddsa.js
@@ -7,23 +7,40 @@ if (!common.hasCrypto)
 const assert = require('assert');
 const {
   generateKeyPair,
+  generateKeyPairSync,
 } = require('crypto');
 const { hasFIPS, isBoringSSL } = require('../common/crypto');
 const rejectsXCurves = hasFIPS(3, 5);

+// Named key generation accepts only supported, lowercase public key types.
+for (const type of [
+  'toString', 'constructor', 'sm2', 'ml-kem-999',
+  'Ed25519', 'X25519', 'ML-KEM-768', 'ML-DSA-44', 'SLH-DSA-SHA2-128f',
+]) {
+  const error = {
+    name: 'TypeError',
+    code: 'ERR_INVALID_ARG_VALUE',
+    message: `The argument 'type' must be a supported key type. Received '${type}'`,
+  };
+  assert.throws(() => generateKeyPairSync(type), error);
+  assert.throws(() => generateKeyPair(type, common.mustNotCall()), error);
+}
+
 // Test EdDSA key generation.
 {
   for (const keyType of ['ed25519', 'ed448', 'x25519', 'x448']) {
-    if (isBoringSSL && keyType.endsWith('448')) {
-      common.printSkipMessage(`Skipping unsupported ${keyType} test case`);
+    if ((isBoringSSL && keyType.endsWith('448')) ||
+        (rejectsXCurves && keyType.startsWith('x'))) {
+      const error = {
+        name: 'TypeError',
+        code: 'ERR_INVALID_ARG_VALUE',
+        message: `The argument 'type' must be a supported key type. Received '${keyType}'`,
+      };
+      assert.throws(() => generateKeyPairSync(keyType), error);
+      assert.throws(() => generateKeyPair(keyType, common.mustNotCall()), error);
       continue;
     }
-    generateKeyPair(keyType, common.mustCall((err, publicKey, privateKey) => {
-      if (rejectsXCurves && keyType.startsWith('x')) {
-        assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED');
-        return;
-      }
-      assert.ifError(err);
+    generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => {
       assert.strictEqual(publicKey.type, 'public');
       assert.strictEqual(publicKey.asymmetricKeyType, keyType);
       assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {});
diff --git a/test/parallel/test-crypto-keygen-raw.js b/test/parallel/test-crypto-keygen-raw.js
index e748830757f..9c6b85a1fc2 100644
--- a/test/parallel/test-crypto-keygen-raw.js
+++ b/test/parallel/test-crypto-keygen-raw.js
@@ -17,18 +17,18 @@ const rejectsXCurves = hasFIPS(3, 5);

 // Test generateKeyPairSync with raw encoding for EdDSA/ECDH key types.
 {
-  const types = ['ed25519', 'x25519'];
-  if (!isBoringSSL) {
-    types.push('ed448', 'x448');
-  }
+  const types = ['ed25519', 'x25519', 'ed448', 'x448'];
   for (const type of types) {
     const options = {
       publicKeyEncoding: { format: 'raw-public' },
       privateKeyEncoding: { format: 'raw-private' },
     };
-    if (rejectsXCurves && type.startsWith('x')) {
+    if ((isBoringSSL && type.endsWith('448')) ||
+        (rejectsXCurves && type.startsWith('x'))) {
       assert.throws(() => generateKeyPairSync(type, options), {
-        code: 'ERR_OSSL_EVP_UNSUPPORTED',
+        name: 'TypeError',
+        code: 'ERR_INVALID_ARG_VALUE',
+        message: `The argument 'type' must be a supported key type. Received '${type}'`,
       });
       continue;
     }
@@ -58,22 +58,23 @@ const rejectsXCurves = hasFIPS(3, 5);

 // Test async generateKeyPair with raw encoding for EdDSA/ECDH key types.
 {
-  const types = ['ed25519', 'x25519'];
-  if (!isBoringSSL) {
-    types.push('ed448', 'x448');
-  }
+  const types = ['ed25519', 'x25519', 'ed448', 'x448'];
   for (const type of types) {
     const options = {
       publicKeyEncoding: { format: 'raw-public' },
       privateKeyEncoding: { format: 'raw-private' },
     };
+    if ((isBoringSSL && type.endsWith('448')) ||
+        (rejectsXCurves && type.startsWith('x'))) {
+      assert.throws(() => generateKeyPair(type, options, common.mustNotCall()), {
+        name: 'TypeError',
+        code: 'ERR_INVALID_ARG_VALUE',
+        message: `The argument 'type' must be a supported key type. Received '${type}'`,
+      });
+      continue;
+    }
     generateKeyPair(type, options,
-                    common.mustCall((err, publicKey, privateKey) => {
-                      if (rejectsXCurves && type.startsWith('x')) {
-                        assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED');
-                        return;
-                      }
-                      assert.ifError(err);
+                    common.mustSucceed((publicKey, privateKey) => {
                       assert(Buffer.isBuffer(publicKey));
                       assert(Buffer.isBuffer(privateKey));
                     }));
diff --git a/test/parallel/test-crypto-provider-cache.js b/test/parallel/test-crypto-provider-cache.js
index 4767d47dc52..bbfee76b615 100644
--- a/test/parallel/test-crypto-provider-cache.js
+++ b/test/parallel/test-crypto-provider-cache.js
@@ -93,7 +93,26 @@ function checkMacAliases() {
   assert.strictEqual(getAliasId(after, 'kmac-128'), id);
 }

+function checkNamedKeygen() {
+  for (const name of ['ML-KEM-768', 'Ed25519', 'Ed448', 'X25519', 'X448']) {
+    const type = name.toLowerCase();
+    if (binding.isKeyAlgorithmAvailable(name)) {
+      const { publicKey, privateKey } = crypto.generateKeyPairSync(type);
+      assert.strictEqual(publicKey.asymmetricKeyType, type);
+      assert.strictEqual(privateKey.asymmetricKeyType, type);
+    } else {
+      const error = {
+        name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE',
+        message: `The argument 'type' must be a supported key type. Received '${type}'`,
+      };
+      assert.throws(() => crypto.generateKeyPairSync(type), error);
+      assert.throws(() => crypto.generateKeyPair(type, common.mustNotCall()), error);
+    }
+  }
+}
+
 function createFixtures(lists) {
+  checkNamedKeygen();
   const fixtures = {};
   if (lists.getCiphers.includes(cipherAlgorithm)) {
     const info = crypto.getCipherInfo(cipherAlgorithm);
@@ -128,6 +147,7 @@ function createFixtures(lists) {
 }

 function checkEnabled(fixtures, available) {
+  checkNamedKeygen();
   // Availability comes from a fresh environment. Exercise cached handles before
   // refreshing the warmed JavaScript lists.
   assert(!available.getCiphers.includes(cipherAlgorithm));
@@ -165,6 +185,7 @@ function checkEnabled(fixtures, available) {
 }

 function checkDisabled(fixtures) {
+  checkNamedKeygen();
   if (fixtures.cipher !== undefined) {
     assert(crypto.getCipherInfo(cipherAlgorithm));
     const cipher = crypto.createCipheriv(cipherAlgorithm, cipherKey, iv);
diff --git a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js
index 5960c46f8aa..53db81496b2 100644
--- a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js
+++ b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js
@@ -15,8 +15,13 @@ async function test(
   keyLength,
   ivLength,
   format = 'raw',
-  causeCode,
 ) {
+  if (fips3 && algorithmName === 'AES-OCB') {
+    await assert.rejects(
+      subtle.importKey(format, new Uint8Array(keyLength), algorithmName, false, ['decrypt']),
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
+    return;
+  }
   const key = await subtle.importKey(
     format,
     new Uint8Array(keyLength),
@@ -28,13 +33,9 @@ async function test(
   const data = new Uint8Array(32);
   data.buffer.transfer();

-  const expected = causeCode === undefined ?
-    { name: 'OperationError' } :
-    (err) => err.name === 'OperationError' &&
-             err.cause?.code === causeCode;
   await assert.rejects(
     subtle.decrypt({ name: algorithmName, iv: new Uint8Array(ivLength) }, key, data),
-    expected,
+    { name: 'OperationError' },
   );
 }

@@ -60,8 +61,7 @@ if (hasOpenSSL(3)) {
     'AES-OCB',
     32,
     12,
-    'raw-secret',
-    fips3 ? 'ERR_OSSL_EVP_UNSUPPORTED' : undefined));
+    'raw-secret'));
 }

 Promise.all(tests).then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-deduplicate-usages.js b/test/parallel/test-webcrypto-deduplicate-usages.js
index 1d7247644ab..4ef6cbd8d9b 100644
--- a/test/parallel/test-webcrypto-deduplicate-usages.js
+++ b/test/parallel/test-webcrypto-deduplicate-usages.js
@@ -71,7 +71,14 @@ function assertSameSet(actual, expected, msg) {

   for (const { algorithm, usages, expected } of symmetric) {
     tests.push((async () => {
-      const key = await subtle.generateKey(algorithm, true, usages);
+      const generated = subtle.generateKey(algorithm, true, usages);
+      if (hasFIPS(3) && algorithm.name === 'AES-OCB') {
+        await assert.rejects(generated, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        });
+        return;
+      }
+      const key = await generated;
       assertSameSet(key.usages, expected,
                     `generateKey ${algorithm.name}`);
       assert.strictEqual(key.usages.length, expected.length,
@@ -228,12 +235,19 @@ function assertSameSet(actual, expected, msg) {
   // Argon2 only supports raw-secret import.
   if (hasOpenSSL(3, 2)) {
     tests.push((async () => {
-      const key = await subtle.importKey(
+      const imported = subtle.importKey(
         'raw-secret',
         new Uint8Array(16),
         'Argon2id',
         false,
         ['deriveBits', 'deriveBits']);
+      if (hasFIPS(3)) {
+        await assert.rejects(imported, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        });
+        return;
+      }
+      const key = await imported;
       assertSameSet(key.usages, ['deriveBits'],
                     'importKey raw-secret Argon2id');
       assert.strictEqual(key.usages.length, 1);
@@ -362,12 +376,19 @@ function assertSameSet(actual, expected, msg) {
   // AES-OCB raw-secret import.
   if (hasOpenSSL(3)) {
     tests.push((async () => {
-      const key = await subtle.importKey(
+      const imported = subtle.importKey(
         'raw-secret',
         new Uint8Array(16),
         { name: 'AES-OCB' },
         true,
         ['decrypt', 'encrypt', 'decrypt', 'encrypt']);
+      if (hasFIPS(3)) {
+        await assert.rejects(imported, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        });
+        return;
+      }
+      const key = await imported;
       assertSameSet(key.usages, ['encrypt', 'decrypt']);
       assert.strictEqual(key.usages.length, 2);
     })());
@@ -465,7 +486,14 @@ function assertSameSet(actual, expected, msg) {

   for (const { algorithm, usages, expected } of jwkVectors) {
     tests.push((async () => {
-      const key = await subtle.generateKey(algorithm, true, usages);
+      const generated = subtle.generateKey(algorithm, true, usages);
+      if (hasFIPS(3) && algorithm.name === 'AES-OCB') {
+        await assert.rejects(generated, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        });
+        return;
+      }
+      const key = await generated;
       const jwk = await subtle.exportKey('jwk', key);
       assertSameSet(jwk.key_ops, expected,
                     `jwk key_ops for ${algorithm.name}`);
diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js
index 134360c6275..faacfb6922a 100644
--- a/test/parallel/test-webcrypto-derivebits-cfrg.js
+++ b/test/parallel/test-webcrypto-derivebits-cfrg.js
@@ -45,10 +45,7 @@ async function prepareKeys() {
   const keys = {};
   await Promise.all(
     kTests.map(async ({ name, size, pkcs8, spki, result }) => {
-      const [
-        privateKey,
-        publicKey,
-      ] = await Promise.all([
+      const imported = [
         subtle.importKey(
           'pkcs8',
           Buffer.from(pkcs8, 'hex'),
@@ -61,7 +58,14 @@ async function prepareKeys() {
           { name },
           true,
           []),
-      ]);
+      ];
+      if (rejectsXCurves) {
+        await Promise.all(imported.map((promise) => assert.rejects(promise, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        })));
+        return;
+      }
+      const [privateKey, publicKey] = await Promise.all(imported);
       keys[name] = {
         privateKey,
         publicKey,
@@ -74,20 +78,12 @@ async function prepareKeys() {

 (async function() {
   const keys = await prepareKeys();
+  if (rejectsXCurves) return;

   await Promise.all(
     Object.keys(keys).map(async (name) => {
       const { size, result, privateKey, publicKey } = keys[name];

-      if (rejectsXCurves) {
-        await assert.rejects(
-          subtle.deriveBits({ name, public: publicKey }, privateKey, 8 * size),
-          (err) => err.name === 'OperationError' &&
-                   err.cause?.code ===
-                     'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE');
-        return;
-      }
-
       {
         // Good parameters
         const bits = await subtle.deriveBits({
diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js
index fafd6a7afa5..4fb372fd3a8 100644
--- a/test/parallel/test-webcrypto-derivebits-hkdf.js
+++ b/test/parallel/test-webcrypto-derivebits-hkdf.js
@@ -6,7 +6,7 @@ if (!common.hasCrypto)
   common.skip('missing crypto');

 const assert = require('assert');
-const { hasOpenSSL, isBoringSSL } = require('../common/crypto');
+const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../common/crypto');
 const { subtle } = globalThis.crypto;

 function getDeriveKeyInfo(name, length, hash, ...usages) {
@@ -606,6 +606,12 @@ async function testWrongKeyType(

             kDerivedKeyTypes.forEach((keyType) => {
               const keyArgs = getDeriveKeyInfo(...keyType);
+              if (hasFIPS() && keyType[0] === 'AES-OCB') {
+                variations.push(assert.rejects(testDeriveKey(...args, ...keyArgs), {
+                  name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+                }));
+                return;
+              }
               variations.push(testDeriveKey(...args, ...keyArgs));
               variations.push(testDeriveKeyBadHash(...args, ...keyArgs));
               variations.push(testDeriveKeyBadUsage(
diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js
index e7deee0eeae..99de1e6ba6d 100644
--- a/test/parallel/test-webcrypto-derivebits.js
+++ b/test/parallel/test-webcrypto-derivebits.js
@@ -168,8 +168,7 @@ const rejectsXCurves = hasFIPS(3, 5);
     for (const name of ['X25519', 'X448']) {
       assert.rejects(
         test(name),
-        (err) => err.name === 'OperationError' &&
-                 err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED')
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' })
         .then(common.mustCall());
     }
   } else {
diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js
index c9a9b60d091..130e2ad4cc8 100644
--- a/test/parallel/test-webcrypto-derivekey-cfrg.js
+++ b/test/parallel/test-webcrypto-derivekey-cfrg.js
@@ -44,10 +44,7 @@ async function prepareKeys() {
   const keys = {};
   await Promise.all(
     kTests.map(async ({ name, size, pkcs8, spki, result }) => {
-      const [
-        privateKey,
-        publicKey,
-      ] = await Promise.all([
+      const imported = [
         subtle.importKey(
           'pkcs8',
           Buffer.from(pkcs8, 'hex'),
@@ -60,7 +57,14 @@ async function prepareKeys() {
           { name },
           true,
           []),
-      ]);
+      ];
+      if (rejectsXCurves) {
+        await Promise.all(imported.map((promise) => assert.rejects(promise, {
+          name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+        })));
+        return;
+      }
+      const [privateKey, publicKey] = await Promise.all(imported);
       keys[name] = {
         privateKey,
         publicKey,
@@ -73,6 +77,7 @@ async function prepareKeys() {

 (async function() {
   const keys = await prepareKeys();
+  if (rejectsXCurves) return;
   const otherArgs = [
     { name: 'HMAC', hash: 'SHA-256', length: 256 },
     true,
@@ -82,15 +87,6 @@ async function prepareKeys() {
     Object.keys(keys).map(async (name) => {
       const { result, privateKey, publicKey } = keys[name];

-      if (rejectsXCurves) {
-        await assert.rejects(
-          subtle.deriveKey({ name, public: publicKey }, privateKey, ...otherArgs),
-          (err) => err.name === 'OperationError' &&
-                   err.cause?.code ===
-                     'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE');
-        return;
-      }
-
       {
         // Good parameters
         const key = await subtle.deriveKey({
diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js
index 516838aaf0b..e1b663fa30f 100644
--- a/test/parallel/test-webcrypto-derivekey.js
+++ b/test/parallel/test-webcrypto-derivekey.js
@@ -366,8 +366,7 @@ if (hasOpenSSL(3) && !hasFIPS()) {
     for (const name of ['X25519', 'X448']) {
       assert.rejects(
         test(name),
-        (err) => err.name === 'OperationError' &&
-                 err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED')
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' })
         .then(common.mustCall());
     }
   } else {
diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js
index 00c294839bf..bdf1a1d6666 100644
--- a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js
+++ b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js
@@ -241,8 +241,7 @@ if (hasOpenSSL(3)) {
     if (getFips() === 1) {
       await assert.rejects(
         testEncrypt(passing[0]),
-        (err) => err.name === 'OperationError' &&
-                 err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
       return;
     }

diff --git a/test/parallel/test-webcrypto-encrypt-decrypt.js b/test/parallel/test-webcrypto-encrypt-decrypt.js
index 9a10b214d7a..0f3341db64b 100644
--- a/test/parallel/test-webcrypto-encrypt-decrypt.js
+++ b/test/parallel/test-webcrypto-encrypt-decrypt.js
@@ -211,8 +211,7 @@ if (hasOpenSSL(3)) {
   if (getFips() === 1) {
     assert.rejects(
       test(),
-      (err) => err.name === 'OperationError' &&
-               err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED')
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' })
       .then(common.mustCall());
   } else {
     test().then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js
index 1cc1cd478ac..4747a477804 100644
--- a/test/parallel/test-webcrypto-export-import-cfrg.js
+++ b/test/parallel/test-webcrypto-export-import-cfrg.js
@@ -413,18 +413,18 @@ async function testImportRaw({ name, publicUsages }) {
   const tests = [];
   for (const vector of testVectors) {
     for (const extractable of [true, false]) {
-      tests.push(testImportSpki(vector, extractable));
-      tests.push(testImportPkcs8(vector, extractable));
-      if (rejectsXCurves && vector.name.startsWith('X')) {
-        tests.push(assert.rejects(
-          testImportJwk(vector, extractable),
-          { name: 'DataError' }));
-      } else {
-        tests.push(testImportJwk(vector, extractable));
+      for (const test of [testImportSpki, testImportPkcs8, testImportJwk]) {
+        const imported = test(vector, extractable);
+        tests.push(rejectsXCurves && vector.name.startsWith('X') ?
+          assert.rejects(imported, {
+            name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+          }) : imported);
       }
     }
     if (rejectsXCurves && vector.name.startsWith('X')) {
-      tests.push(assert.rejects(testImportRaw(vector), { name: 'DataError' }));
+      tests.push(assert.rejects(testImportRaw(vector), {
+        name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+      }));
     } else {
       tests.push(testImportRaw(vector));
     }
@@ -453,7 +453,9 @@ async function testImportRaw({ name, publicUsages }) {
         { name },
         true,
         [invalidUsage]),
-      { name: 'SyntaxError', message: /Unsupported key usage/ });
+      rejectsXCurves && isKeyAgreement ?
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' } :
+        { name: 'SyntaxError', message: /Unsupported key usage/ });

     const validUsage = privateUsages[0];
     await assert.rejects(
@@ -463,7 +465,9 @@ async function testImportRaw({ name, publicUsages }) {
         { name },
         true,
         [validUsage]),
-      { name: 'DataError', message: 'Duplicate key operation' });
+      rejectsXCurves && isKeyAgreement ?
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' } :
+        { name: 'DataError', message: 'Duplicate key operation' });
   }
 })().then(common.mustCall());

@@ -477,15 +481,18 @@ async function testImportRaw({ name, publicUsages }) {
     ['Ed25519', ['verify'], ['sign']],
     ['X25519', [], ['deriveBits']],
   ]) {
+    const error = rejectsXCurves && name.startsWith('X') ?
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' } :
+      { message: /Invalid key type/ };
     assert.rejects(subtle.importKey(
       'spki',
       rsaPublic.export({ format: 'der', type: 'spki' }),
       { name },
-      true, publicUsages), { message: /Invalid key type/ }).then(common.mustCall());
+      true, publicUsages), error).then(common.mustCall());
     assert.rejects(subtle.importKey(
       'pkcs8',
       rsaPrivate.export({ format: 'der', type: 'pkcs8' }),
       { name },
-      true, privateUsages), { message: /Invalid key type/ }).then(common.mustCall());
+      true, privateUsages), error).then(common.mustCall());
   }
 }
diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js
index e63ea8dfc7d..b1fc8f41b2a 100644
--- a/test/parallel/test-webcrypto-keygen.js
+++ b/test/parallel/test-webcrypto-keygen.js
@@ -251,18 +251,17 @@ if (hasOpenSSL(3, 5) || isBoringSSL) {
 // Test bad usages
 {
   async function test(name) {
-    if (fips3 && name === 'ChaCha20-Poly1305') {
+    if (fips3 && (name === 'ChaCha20-Poly1305' || name === 'AES-OCB')) {
       await assert.rejects(
         subtle.generateKey({ name }, true, []),
-        { name: 'NotSupportedError' });
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
       return;
     }

     if (fips35 && (name === 'X25519' || name === 'X448')) {
       await assert.rejects(
         subtle.generateKey({ name }, true, ['deriveBits']),
-        (err) => err.name === 'OperationError' &&
-                 err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
+        { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
       return;
     }

@@ -757,8 +756,7 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' });
   async function testFipsUnsupported(name) {
     await assert.rejects(
       subtle.generateKey({ name }, true, ['deriveKey', 'deriveBits']),
-      (err) => err.name === 'OperationError' &&
-               err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
   }

   async function test(
diff --git a/test/parallel/test-webcrypto-raw-format-aliases.js b/test/parallel/test-webcrypto-raw-format-aliases.js
index c9a0e8a4cf4..49a09713f49 100644
--- a/test/parallel/test-webcrypto-raw-format-aliases.js
+++ b/test/parallel/test-webcrypto-raw-format-aliases.js
@@ -72,8 +72,7 @@ const tests = [
 if (rejectsXCurves) {
   tests.push(assert.rejects(
     subtle.generateKey('X25519', true, ['deriveBits']),
-    (err) => err.name === 'OperationError' &&
-             err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'));
+    { name: 'NotSupportedError', message: 'Unrecognized algorithm name' }));
 } else {
   tests.push(assertPublicKeyDoesNotAcceptRawSecret(
     'X25519',
diff --git a/test/parallel/test-webcrypto-supports-fips.js b/test/parallel/test-webcrypto-supports-fips.js
new file mode 100644
index 00000000000..ae5d1d4b32d
--- /dev/null
+++ b/test/parallel/test-webcrypto-supports-fips.js
@@ -0,0 +1,135 @@
+// Flags: --expose-internals --no-warnings
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto) common.skip('missing crypto');
+const { hasOpenSSL, isBoringSSL } = require('../common/crypto');
+if (!hasOpenSSL(3) || isBoringSSL) common.skip('requires OpenSSL 3');
+
+const assert = require('node:assert');
+const crypto = require('node:crypto');
+const { once } = require('node:events');
+const { isMainThread, parentPort, Worker, workerData } = require('node:worker_threads');
+const { normalizeAlgorithm } = require('internal/crypto/util');
+const { subtle } = globalThis.crypto;
+
+if (!isMainThread && !workerData?.supportsFips)
+  common.skip('crypto.setFips() is not supported in workers');
+
+async function check() {
+  const fips = crypto.getFips() === 1;
+  const turbo = { name: 'TurboSHAKE128', outputLength: 128 };
+  if (fips) {
+    const error = { name: 'NotSupportedError', message: 'Unrecognized algorithm name' };
+    assert.throws(() => normalizeAlgorithm(turbo, 'digest'), error);
+    await assert.rejects(subtle.digest(turbo, new Uint8Array()), error);
+  } else {
+    assert.strictEqual(normalizeAlgorithm(turbo, 'digest').name, turbo.name);
+    assert.strictEqual((await subtle.digest(turbo, new Uint8Array())).byteLength, 16);
+  }
+  assert.strictEqual(SubtleCrypto.supports('digest', turbo), !fips);
+  for (const [algorithm, usages] of [
+    [{ name: 'AES-OCB', length: 128 }, ['encrypt']],
+    [{ name: 'X25519' }, ['deriveBits']],
+  ]) {
+    const supported = SubtleCrypto.supports('generateKey', algorithm);
+    const generated = subtle.generateKey(algorithm, true, usages);
+    if (supported) {
+      await generated;
+    } else {
+      await assert.rejects(generated, {
+        name: 'NotSupportedError', message: 'Unrecognized algorithm name',
+      });
+    }
+  }
+  for (const name of ['ECDH', 'ECDSA']) {
+    for (const namedCurve of ['P-256', 'P-384', 'P-521']) {
+      const algorithm = { name, namedCurve };
+      for (const operation of ['generateKey', 'importKey']) {
+        assert.strictEqual(SubtleCrypto.supports(operation, algorithm), true);
+      }
+    }
+  }
+  const rsa = {
+    name: 'RSA-PSS', modulusLength: 1024,
+    publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256',
+  };
+  assert.strictEqual(SubtleCrypto.supports('generateKey', rsa), !fips);
+  if (fips) {
+    assert.throws(() => normalizeAlgorithm(rsa, 'generateKey'), {
+      name: 'OperationError', message: 'algorithm.modulusLength must be at least 2048',
+    });
+  } else {
+    assert.strictEqual(normalizeAlgorithm(rsa, 'generateKey').modulusLength, 1024);
+  }
+  const hashes = crypto.getHashes();
+  const salt = new Uint8Array(16);
+  const hashError = { name: 'NotSupportedError', message: 'Unrecognized algorithm name' };
+  for (const [name, alias] of [
+    ['SHA-1', 'sha1'], ['SHA-256', 'sha256'], ['SHA-384', 'sha384'], ['SHA-512', 'sha512'],
+    ['SHA3-256', 'sha3-256'], ['SHA3-384', 'sha3-384'], ['SHA3-512', 'sha3-512'],
+  ]) {
+    const available = hashes.includes(alias);
+    for (const hash of [name, { name }]) {
+      const hmac = { name: 'HMAC', hash, length: 256 };
+      const hkdf = { name: 'HKDF', hash, salt, info: new Uint8Array() };
+      for (const [operations, algorithm] of [
+        [['digest'], hash],
+        [['generateKey', 'importKey'], { ...rsa, modulusLength: 2048, hash }],
+        [['generateKey', 'importKey'], hmac],
+        [['sign', 'verify'], { name: 'ECDSA', hash }],
+        [['deriveBits'], hkdf],
+        [['deriveBits'], { name: 'PBKDF2', hash, salt, iterations: 1 }],
+      ]) {
+        for (const operation of operations) {
+          const length = operation === 'deriveBits' ? 128 : undefined;
+          assert.strictEqual(SubtleCrypto.supports(operation, algorithm, length), available);
+          if (!available)
+            assert.throws(() => normalizeAlgorithm(algorithm, operation), hashError);
+        }
+      }
+      assert.strictEqual(SubtleCrypto.supports('deriveKey', hkdf, hmac), available);
+      if (!available)
+        assert.throws(() => normalizeAlgorithm(hmac, 'get key length'), hashError);
+    }
+  }
+}
+
+if (!isMainThread) {
+  parentPort.on('message', async () => {
+    await check();
+    parentPort.postMessage(crypto.getFips());
+  });
+  parentPort.postMessage('ready');
+} else {
+  (async () => {
+    const originalFips = crypto.getFips();
+    await check();
+    try {
+      crypto.setFips(0);
+    } catch (err) {
+      if (err.code !== 'ERR_CRYPTO_FIPS_FORCED') throw err;
+      common.printSkipMessage('FIPS mode cannot be disabled');
+      return;
+    }
+    let worker;
+    try {
+      // Start the worker before enabling FIPS: isolate initialization can need
+      // OpenSSL entropy, which is unavailable without an active FIPS provider.
+      worker = new Worker(__filename, { workerData: { supportsFips: true } });
+      worker.on('error', common.mustNotCall());
+      await once(worker, 'message');
+      for (const fips of [0, 1, 0]) {
+        crypto.setFips(fips);
+        await check();
+        const response = once(worker, 'message');
+        worker.postMessage('check');
+        const [actual] = await response;
+        assert.strictEqual(actual, fips);
+      }
+    } finally {
+      if (worker !== undefined) await worker.terminate();
+      crypto.setFips(originalFips);
+    }
+  })().then(common.mustCall());
+}
diff --git a/test/parallel/test-webcrypto-supports.mjs b/test/parallel/test-webcrypto-supports.mjs
index 15874102a22..7b98cf13c0d 100644
--- a/test/parallel/test-webcrypto-supports.mjs
+++ b/test/parallel/test-webcrypto-supports.mjs
@@ -4,10 +4,6 @@ if (!common.hasCrypto)
   common.skip('missing crypto');

 import * as assert from 'node:assert';
-import { hasFIPS } from '../common/crypto.js';
-
-if (hasFIPS(3))
-  common.skip('SubtleCrypto.supports() does not reflect FIPS provider availability');

 const { SubtleCrypto } = globalThis;

diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js
index 60ceffd710c..b899e4f712d 100644
--- a/test/parallel/test-webcrypto-wrap-unwrap.js
+++ b/test/parallel/test-webcrypto-wrap-unwrap.js
@@ -390,13 +390,13 @@ function testWrapping(name, keys) {
       for (const name of ['X25519', 'X448']) {
         await assert.rejects(
           subtle.generateKey({ name }, true, ['deriveBits']),
-          (err) => err.name === 'OperationError' &&
-                   err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
+          { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
       }
     }

-    const wrappingKey = await subtle.generateKey(
-      { name: 'AES-OCB', length: 128 }, true, ['wrapKey']);
+    await assert.rejects(
+      subtle.generateKey({ name: 'AES-OCB', length: 128 }, true, ['wrapKey']),
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
     const key = await subtle.generateKey(
       { name: 'HMAC', hash: 'SHA-256', length: 256 },
       true,
@@ -405,10 +405,9 @@ function testWrapping(name, keys) {
       subtle.wrapKey(
         'raw',
         key,
-        wrappingKey,
+        key,
         { name: 'AES-OCB', iv: new Uint8Array(15), tagLength: 128 }),
-      (err) => err.name === 'OperationError' &&
-               err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
+      { name: 'NotSupportedError', message: 'Unrecognized algorithm name' });
   }

   await generateWrappingKeys();
diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs
index eb85fa94e63..ff618016f3d 100644
--- a/test/wpt/status/WebCryptoAPI.cjs
+++ b/test/wpt/status/WebCryptoAPI.cjs
@@ -113,9 +113,13 @@ if (hasFIPS(3)) {
   skip(
     'encrypt_decrypt/aes_ocb.tentative.https.any.js',
     'encrypt_decrypt/chacha20_poly1305.tentative.https.any.js',
+    'generateKey/failures_AES-OCB.tentative.https.any.js',
     'generateKey/failures_chacha20_poly1305.tentative.https.any.js',
+    'generateKey/successes_AES-OCB.tentative.https.any.js',
     'generateKey/successes_chacha20_poly1305.tentative.https.any.js',
+    'import_export/AES-OCB_importKey.tentative.https.any.js',
     'import_export/ChaCha20-Poly1305_importKey.tentative.https.any.js',
+    'serialization/aes-ocb.tentative.https.any.js',
     'serialization/chacha20-poly1305.tentative.https.any.js');

   skipSubtests(
@@ -153,6 +157,8 @@ if (hasFIPS(3, 5)) {
     'derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js',
     'derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js',
     'generateKey/failures_Hybrid-KEM.tentative.https.any.js',
+    'generateKey/failures_X25519.https.any.js',
+    'generateKey/failures_X448.tentative.https.any.js',
     'generateKey/successes_Hybrid-KEM.tentative.https.any.js',
     'generateKey/successes_X25519.https.any.js',
     'generateKey/successes_X448.tentative.https.any.js',
diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts
index b31bd413e0b..306e5738302 100644
--- a/typings/internalBinding/crypto.d.ts
+++ b/typings/internalBinding/crypto.d.ts
@@ -831,7 +831,6 @@ export interface CryptoBinding {
   KEMEncapsulateJob?: InternalCryptoBinding.KEMEncapsulateJobConstructor;
   KangarooTwelveJob: InternalCryptoBinding.KangarooTwelveJobConstructor;
   KmacJob: InternalCryptoBinding.KmacJobConstructor;
-  getPqcKeyTypes(): string[];
   NamedKeyPairGenJob: InternalCryptoBinding.NamedKeyPairGenJobConstructor;
   PBKDF2Job: InternalCryptoBinding.PBKDF2JobConstructor;
   RandomBytesJob: InternalCryptoBinding.RandomBytesJobConstructor;
@@ -987,6 +986,7 @@ export interface CryptoBinding {
   getHashes(): string[];
   getMacs(): string[];
   isCryptoKey(key: unknown): boolean;
+  isKeyAlgorithmAvailable(name: string): boolean;
   isKeyObject(key: unknown): boolean;
   isX509Certificate(value: unknown): boolean;
   getKeyObjectSlots(key: object): InternalCryptoBinding.KeyObjectSlots;