Commit a7d819a6ce2 for nodejs

commit a7d819a6ce2556add080ca76e6b86db27e21a986
Author: Ulises Gascón <ulisesgascongonzalez@gmail.com>
Date:   Fri Oct 30 10:54:04 2026 +0100

    tls: test the SPKI blocklist with a fixture key

    Replace the test-only hooks on the crypto binding, and the process-wide
    list and mutex they required on every handshake, with a fixture CA
    whose public key is a permanent entry on the compiled-in blocklist. The
    test now exercises the real list through full handshakes, without
    --expose-internals, and the production path is a comparison against a
    constant array.

    The new fixtures provide two chains that contain the blocked key: a
    server certificate issued by blocked-ca, and one issued by ca1 that
    reuses the blocked key itself. The private key is checked in like every
    other fixture key, so blocking it costs nothing. It is pinned in the
    fixtures Makefile, since its digest is compiled in.

    Refs: https://github.com/nodejs/node/pull/66871#discussion_r4325672378
    Assisted-by: Claude, Codex
    Signed-off-by: Ulises Gascon <ulisesgascongonzalez@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66949
    Reviewed-By: Tim Perry <pimterry@gmail.com>

diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc
index 1c038641d12..7c3f9cbdf8d 100644
--- a/src/crypto/crypto_context.cc
+++ b/src/crypto/crypto_context.cc
@@ -10,7 +10,6 @@
 #include "ncrypto.h"
 #include "node.h"
 #include "node_buffer.h"
-#include "node_mutex.h"
 #include "node_options.h"
 #include "util.h"
 #include "v8.h"
@@ -32,7 +31,6 @@
 #endif

 #include <array>
-#include <cstring>
 #include <set>

 namespace node {
@@ -1428,29 +1426,18 @@ constexpr SpkiHash kBlockedSPKIs[] = {
     {0xe0, 0x25, 0x4c, 0x7f, 0xcd, 0xc2, 0x65, 0xe2, 0xf0, 0xf8, 0xa4,
      0x28, 0xda, 0xbb, 0x75, 0xab, 0x4a, 0xe0, 0x39, 0xb4, 0x87, 0x2d,
      0x62, 0xf5, 0xee, 0x57, 0xf0, 0x87, 0x7d, 0x5a, 0x8a, 0x41},
+    // Test fixture, not a real CA: test/fixtures/keys/blocked-ca-key.pem.
+    // Its private key is public, so blocking it costs nothing and lets
+    // test/parallel/test-tls-spki-blocklist.js exercise the real list. The
+    // key is pinned in the fixtures Makefile; rotating it means updating
+    // this entry too.
+    // a6cbb923961eada55c02f104681aeabe52ea1508ece2078aec11d00a5778ce71
+    // C=US, ST=CA, L=SF, O=Joyent, OU=Node.js, CN=blocked-ca
+    {0x41, 0xc2, 0xd1, 0xc4, 0x24, 0x68, 0x46, 0x6a, 0x54, 0x24, 0x83,
+     0x69, 0xf6, 0x65, 0x9a, 0x95, 0x52, 0xc5, 0xa4, 0x16, 0xf8, 0xd5,
+     0xfc, 0xd8, 0xdd, 0x55, 0xa8, 0x75, 0xd7, 0x20, 0xaa, 0xc2},
 };

-// Extra entries registered by the test-only hooks on the crypto binding.
-// The list is process-wide and TLS handshakes may run on worker threads, so
-// every access goes through the mutex.
-Mutex& BlockedSPKIsForTestingMutex() {
-  static Mutex mutex;
-  return mutex;
-}
-
-std::vector<SpkiHash>& BlockedSPKIsForTesting() {
-  static std::vector<SpkiHash> list;
-  return list;
-}
-
-bool IsBlockedSPKIForTesting(const SpkiHash& digest) {
-  Mutex::ScopedLock lock(BlockedSPKIsForTestingMutex());
-  for (const auto& blocked : BlockedSPKIsForTesting()) {
-    if (digest == blocked) return true;
-  }
-  return false;
-}
-
 enum class SpkiCheck { kAllowed, kBlocked, kError };

 // Hashes the DER-encoded SubjectPublicKeyInfo of `cert` and looks it up in
@@ -1474,7 +1461,6 @@ SpkiCheck CheckSPKI(X509* cert) {
   for (const auto& blocked : kBlockedSPKIs) {
     if (digest == blocked) return SpkiCheck::kBlocked;
   }
-  if (IsBlockedSPKIForTesting(digest)) return SpkiCheck::kBlocked;
   return SpkiCheck::kAllowed;
 }

@@ -1594,26 +1580,6 @@ void InstallCertVerifyCallback(SSL_CTX* ctx) {
   SSL_CTX_set_cert_verify_callback(ctx, CertVerifyCallback, nullptr);
 }

-void SecureContext::AddBlockedSPKIForTesting(
-    const FunctionCallbackInfo<Value>& args) {
-  Environment* env = Environment::GetCurrent(args);
-  THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "SPKI hash");
-  ArrayBufferOrViewContents<uint8_t> hash(args[0]);
-  if (hash.size() != kSpkiHashLength) {
-    return THROW_ERR_OUT_OF_RANGE(env, "SPKI hash must be 32 bytes");
-  }
-  SpkiHash entry;
-  memcpy(entry.data(), hash.data(), entry.size());
-  Mutex::ScopedLock lock(BlockedSPKIsForTestingMutex());
-  BlockedSPKIsForTesting().push_back(entry);
-}
-
-void SecureContext::ClearBlockedSPKIsForTesting(
-    const FunctionCallbackInfo<Value>& args) {
-  Mutex::ScopedLock lock(BlockedSPKIsForTestingMutex());
-  BlockedSPKIsForTesting().clear();
-}
-
 void SecureContext::Initialize(Environment* env, Local<Object> target) {
   Local<Context> context = env->context();
   SetConstructorFunction(context,
@@ -1641,12 +1607,6 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
                         target,
                         "getCertificateCompressionAlgorithms",
                         GetCertificateCompressionAlgorithms);
-  SetMethod(
-      context, target, "addBlockedSPKIForTesting", AddBlockedSPKIForTesting);
-  SetMethod(context,
-            target,
-            "clearBlockedSPKIsForTesting",
-            ClearBlockedSPKIsForTesting);
 }

 void SecureContext::RegisterExternalReferences(
@@ -1689,8 +1649,6 @@ void SecureContext::RegisterExternalReferences(
   registry->Register(GetUserRootCertificates);
   registry->Register(StartLoadingCertificatesOffThread);
   registry->Register(GetCertificateCompressionAlgorithms);
-  registry->Register(AddBlockedSPKIForTesting);
-  registry->Register(ClearBlockedSPKIsForTesting);
 }

 SecureContext* SecureContext::Create(Environment* env) {
diff --git a/src/crypto/crypto_context.h b/src/crypto/crypto_context.h
index 8912b26d657..1fecf228dec 100644
--- a/src/crypto/crypto_context.h
+++ b/src/crypto/crypto_context.h
@@ -153,15 +153,6 @@ class SecureContext final : public BaseObject {
   template <bool primary>
   static void GetCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);

-  // Test-only hooks for the SPKI blocklist that every SSL_CTX applies
-  // during certificate verification. Reachable through
-  // internalBinding('crypto') with --expose-internals. The argument is the
-  // SHA-256 of a DER-encoded SubjectPublicKeyInfo.
-  static void AddBlockedSPKIForTesting(
-      const v8::FunctionCallbackInfo<v8::Value>& args);
-  static void ClearBlockedSPKIsForTesting(
-      const v8::FunctionCallbackInfo<v8::Value>& args);
-
   static int TicketKeyCallback(SSL* ssl,
                                unsigned char* name,
                                unsigned char* iv,
diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile
index c7f444bf69e..480b111463f 100644
--- a/test/fixtures/keys/Makefile
+++ b/test/fixtures/keys/Makefile
@@ -12,6 +12,9 @@ all: \
   ca4-cert.pem \
   ca5-cert.pem \
   ca6-cert.pem \
+  blocked-ca-cert.pem \
+  blocked-ca-leaf-cert.pem \
+  blocked-key-leaf-cert.pem \
   cert-without-key-fips.pfx \
   agent1-cert.pem \
   agent1-fips.pfx \
@@ -225,6 +228,79 @@ ca3-cert.pem: ca3-csr.pem ca3-key.pem ca3.cnf ca1-cert.pem ca1-key.pem
 		-CAcreateserial \
 		-out ca3-cert.pem

+#
+# blocked-ca is a subordinate CA issued by ca1 whose public key is on the
+# compiled-in SPKI blocklist (src/crypto/crypto_context.cc). Any chain that
+# contains this key must be rejected, so it is used to test the blocklist.
+# The certificates can be regenerated freely; the key cannot (see below).
+# blocked-ca-leaf is a server cert issued by blocked-ca; blocked-key-leaf is
+# a server cert issued by ca1 that reuses the blocked key itself.
+# ('password' is used for the CA password.)
+#
+# The SHA-256 of this key's SubjectPublicKeyInfo is compiled into
+# src/crypto/crypto_context.cc, so the key is pinned: it is kept across
+# `make clean` and never regenerated here. Rotating it means updating the
+# digest and fingerprint in that file as well.
+blocked-ca-key.pem:
+	@echo "$@ is pinned (see src/crypto/crypto_context.cc); restore it with: git checkout -- $@" >&2
+	@exit 1
+
+blocked-ca-csr.pem: blocked-ca.cnf blocked-ca-key.pem
+	openssl req -new \
+		-extensions v3_ca \
+		-config blocked-ca.cnf \
+		-key blocked-ca-key.pem \
+		-out blocked-ca-csr.pem
+
+blocked-ca-cert.pem: blocked-ca-csr.pem blocked-ca-key.pem blocked-ca.cnf ca1-cert.pem ca1-key.pem
+	openssl x509 -req \
+		-extfile blocked-ca.cnf \
+		-extensions v3_ca \
+		-days 99999 \
+		-passin "pass:password" \
+		-in blocked-ca-csr.pem \
+		-CA ca1-cert.pem \
+		-CAkey ca1-key.pem \
+		-CAcreateserial \
+		-out blocked-ca-cert.pem
+
+blocked-ca-leaf-key.pem:
+	openssl genrsa -out blocked-ca-leaf-key.pem 2048
+
+blocked-ca-leaf-csr.pem: blocked-ca-leaf.cnf blocked-ca-leaf-key.pem
+	openssl req -new \
+		-config blocked-ca-leaf.cnf \
+		-key blocked-ca-leaf-key.pem \
+		-out blocked-ca-leaf-csr.pem
+
+blocked-ca-leaf-cert.pem: blocked-ca-leaf-csr.pem blocked-ca-cert.pem blocked-ca-key.pem
+	openssl x509 -req \
+		-days 99999 \
+		-in blocked-ca-leaf-csr.pem \
+		-CA blocked-ca-cert.pem \
+		-CAkey blocked-ca-key.pem \
+		-CAcreateserial \
+		-extfile blocked-ca-leaf.cnf \
+		-out blocked-ca-leaf-cert.pem
+	cat blocked-ca-cert.pem >> blocked-ca-leaf-cert.pem
+
+blocked-key-leaf-csr.pem: blocked-key-leaf.cnf blocked-ca-key.pem
+	openssl req -new \
+		-config blocked-key-leaf.cnf \
+		-key blocked-ca-key.pem \
+		-out blocked-key-leaf-csr.pem
+
+blocked-key-leaf-cert.pem: blocked-key-leaf-csr.pem ca1-cert.pem ca1-key.pem
+	openssl x509 -req \
+		-days 99999 \
+		-passin "pass:password" \
+		-in blocked-key-leaf-csr.pem \
+		-CA ca1-cert.pem \
+		-CAkey ca1-key.pem \
+		-CAcreateserial \
+		-extfile blocked-key-leaf.cnf \
+		-out blocked-key-leaf-cert.pem
+
 #
 # Create Subordinate Certificate Authority: ca4 issued by ca2
 # ('password' is used for the CA password.)
@@ -1312,7 +1388,7 @@ non-canonical-ip-dns-key.pem:
 	openssl ecparam -name prime256v1 -genkey -noout -out non-canonical-ip-dns-key.pem

 clean:
-	rm -f *.pfx *.pem *.srl ca2-database.txt ca2-crl-agent3-database.txt* ca2-crl-agent3.cnf ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
+	rm -f *.pfx $(filter-out blocked-ca-key.pem,$(wildcard *.pem)) *.srl ca2-database.txt ca2-crl-agent3-database.txt* ca2-crl-agent3.cnf ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
 	@> fake-startcom-root-database.txt

 test: agent1-verify agent2-verify agent3-verify agent4-verify agent5-verify agent6-verify agent7-verify agent8-verify agent10-verify ec10-verify
diff --git a/test/fixtures/keys/blocked-ca-cert.pem b/test/fixtures/keys/blocked-ca-cert.pem
new file mode 100644
index 00000000000..1d46cd5010f
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-cert.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEZjCCA06gAwIBAgIUFH02wcL3Qgben6tfIibXitsApCswDQYJKoZIhvcNAQEL
+BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
+A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
+BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTI2MTAyMzA5MzE1M1oY
+DzIzMDAwODA3MDkzMTUzWjCBhjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw
+CQYDVQQHDAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMRMw
+EQYDVQQDDApibG9ja2VkLWNhMSUwIwYJKoZIhvcNAQkBFhZibG9ja2VkLWNhQGV4
+YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3JSHZeuE
+OSZ77MDkjTgJRhQJtvLruDJdA2hVfT47aST8cXqfHAR+tQsotBcIc4b69l1QxIts
+SYkmiQbbnObIj1pAbmZ3blFy+13wNxfnLJR2Jh51vZyUTQMX+KIlrrzg9qpfOGi1
+D1zJvCSs5pyxpsGZ1GZxiFVTIGqZ4PZApyTjobqsivLV5FYiRgGESHXziRPJBeKY
+D9MGKslA8p1qY79iqyaoY580xkiFNXeXsmguy96+KdEuGlfdpT+mPsCRWEU7Rt9E
+wXsqUAPcZ0/22u62IJvBapMf3lvbTo3M6vraQmFEwsqlxeaWDhx6621VjHzsTHp9
+8Ko2VExaIYoSNQIDAQABo4HUMIHRMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFBO3
+GH448bBtSUSGJcz0UV8t0M2AMIGhBgNVHSMEgZkwgZahfqR8MHoxCzAJBgNVBAYT
+AlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVudDEQ
+MA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkBFhFy
+eUB0aW55Y2xvdWRzLm9yZ4IUSrFsjf1qfQ0t/KvfnEsOksatAikwDQYJKoZIhvcN
+AQELBQADggEBAKl9HrTXud8wHFRGTrrJBMOP5jZO50Sgf6/QSg3gVxLbTnraZDUq
+PD3MKON95r/4ksa139/SIcTPSU6Nr4uJw4iVtfMgXr5/cUOeJN+l6z9Ju0bN1Fiu
+xLXNgm7fHcNS2SkNxFKDCK7D11nhgkwUgs/JPQWD2DLmfoVScScQNXuFmgOYxUdI
+Lfis9fdnAlTwFvu87FxFhKNjTD+gbo16KUz+uhevOHz+b2MZNgqhNklePP/mlQhx
+kWzDz9B6ofhn3SD/q/idtQuEntVM7qV60ZXj27ELS58q7DnbwCJ/Kivl+cjKLmSJ
+3EbbetSB6+9PA5MJjAMgInyfSAHw+S1IFqk=
+-----END CERTIFICATE-----
diff --git a/test/fixtures/keys/blocked-ca-cert.srl b/test/fixtures/keys/blocked-ca-cert.srl
new file mode 100644
index 00000000000..419e72c54a6
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-cert.srl
@@ -0,0 +1 @@
+5A1AB9CDD00373643B19FADBFB08C51AE1EEFBC6
diff --git a/test/fixtures/keys/blocked-ca-csr.pem b/test/fixtures/keys/blocked-ca-csr.pem
new file mode 100644
index 00000000000..d2e32734904
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-csr.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC8TCCAdkCAQAwgYYxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE
+BwwCU0YxDzANBgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczETMBEGA1UE
+AwwKYmxvY2tlZC1jYTElMCMGCSqGSIb3DQEJARYWYmxvY2tlZC1jYUBleGFtcGxl
+Lm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANyUh2XrhDkme+zA
+5I04CUYUCbby67gyXQNoVX0+O2kk/HF6nxwEfrULKLQXCHOG+vZdUMSLbEmJJokG
+25zmyI9aQG5md25Rcvtd8DcX5yyUdiYedb2clE0DF/iiJa684PaqXzhotQ9cybwk
+rOacsabBmdRmcYhVUyBqmeD2QKck46G6rIry1eRWIkYBhEh184kTyQXimA/TBirJ
+QPKdamO/YqsmqGOfNMZIhTV3l7JoLsvevinRLhpX3aU/pj7AkVhFO0bfRMF7KlAD
+3GdP9trutiCbwWqTH95b206NzOr62kJhRMLKpcXmlg4ceuttVYx87Ex6ffCqNlRM
+WiGKEjUCAwEAAaAlMCMGCSqGSIb3DQEJBzEWDBRBIGNoYWxsZW5nZSBwYXNzd29y
+ZDANBgkqhkiG9w0BAQsFAAOCAQEAq+O48whfKxxS8PmRRoyrnNMpFAjwLW6QXNc0
+5VPTFU66mPxrG4zNTA0Jr0Z8P1SRAmMADFcWWIRW/qWg81nSRJAn1YbUyMj8Pq0O
+WUIItJsHMobCLEe/dDyIw30mPCjTqaYc7ZQ1joTnejQmhlza02FTzePKs1HApQlt
+NLkOheK4dx3C92YJFL5pd9pw1tsBBXeKt7X7hJ69ykvNPqmzN7gam4D+g+SA4dSL
+BR5fbW8cBTKzgydDRp9kV8SPyjE8Rt1LZFeRelRe5ow/iNlf0zZvO1/PfFv0gJZa
+Lp1gx0E5QaDhmlnGFToNxIgDtVAAqwWpFvBOwLWiOZBIBpm2Fw==
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/keys/blocked-ca-key.pem b/test/fixtures/keys/blocked-ca-key.pem
new file mode 100644
index 00000000000..69016e2784d
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDclIdl64Q5Jnvs
+wOSNOAlGFAm28uu4Ml0DaFV9PjtpJPxxep8cBH61Cyi0Fwhzhvr2XVDEi2xJiSaJ
+Btuc5siPWkBuZnduUXL7XfA3F+cslHYmHnW9nJRNAxf4oiWuvOD2ql84aLUPXMm8
+JKzmnLGmwZnUZnGIVVMgapng9kCnJOOhuqyK8tXkViJGAYRIdfOJE8kF4pgP0wYq
+yUDynWpjv2KrJqhjnzTGSIU1d5eyaC7L3r4p0S4aV92lP6Y+wJFYRTtG30TBeypQ
+A9xnT/ba7rYgm8Fqkx/eW9tOjczq+tpCYUTCyqXF5pYOHHrrbVWMfOxMen3wqjZU
+TFohihI1AgMBAAECggEAEafi4ZXVLtTlgPkcojAcz+rtLyNpGkSPvOTWSPDZBQzE
+lxm7UB0M/lVPFAEu+D5S6rLj8uN9DrLHbprpq7iuT4koFJ4ni6FB3JxzjJlhc+hL
+P7ha8MsudK6dddWWwhKUyDGxbvy0l270J9bgrxHWR3SCKWXsyv6XaYYW3JKhcK9l
+bVli1AVRk8JapNkkHfUxp0AxUJRbixlts760lXgArp74XLohleL0tw9lOoE+PWVY
+1dvO9X+sOu3a1t/EK3cz9gXYoU1GgR3sy59Y6lweMBnarbdHDqMhWojV/MQGyD7e
+WA+XS4Z/4kc4fGoCdFFLM9wznPevDfOEv4oOnS7vWQKBgQDt7vmYhVEUQ2QXtYaX
+MwN+UBoNinrcxXvzqbzTMD7A1ahxZm3e3aNjD7kILYI43EIfdUBYnkY3GhIMuz5B
++66BhsesGFAL9n9mDSbou/Ykr5sQ9VsqGeEjTGbXSujGVrQDGnSzIfKDRx71SlYA
+CnknFHn+DWqJQonrWNm46S29/QKBgQDtVDworobAkD8dmWi8sT4c60qLVp/7Pyp9
+W8fun4JXS1JlRZ/+N0C1qephD3/q/MVP4WDnUUR8BDvStWRxtSmJ8e2sTyB23IGx
+tt6JOS2nQxppcd6+PQ5FQHYxI20GE/IwpiPRr8mbyi9hDEUgRm+7xyjpYeE9U6Hv
+skRxpsV+mQKBgG6HENNVOvDazWhmHvul6v2GYFPh/GkKJCZwtxKz2rLhjboABxH8
+i6W87AtseI3JY74RTa7c92kpZzEa2jT0oX/yCFddg/HKtCvH+Ecs6Y7pNHOQRqUs
+GHW5TBy+45sfM8gtRgDgyDNLmon/2itysg0BahomSVMx4be+MoW/bExRAoGBAJZf
+2iJYuw8GFse1bVSp5VZDi58QCxwR4OU/I0ZCET8ofZOncwGLltfESrqnLIEDGi5L
++q92obMGp7URtAwMIr7B6LtWA5bsjszAtu4xqYiijvii6uSVIRamCn2GGc1RFBLZ
+Ijb5b//jMvQF50LU/nT3cye1GhdWh3Ot+4obhmV5AoGAG29zxE7brtbHx05P8iZ5
+tsQyPWx69uYZ6MU84eb5hdLJyo1jddTTolhedO0oIpyIPCna0HWkNmNXJzzof1He
+WfnKQZxbFQuiigP/u8/UlV8GF+rL9Vu5Wac156CsAqWdK0YokV5xOL/Dg0xzXigS
+/HOGgpgUjvkKU40z37qs0MI=
+-----END PRIVATE KEY-----
diff --git a/test/fixtures/keys/blocked-ca-leaf-cert.pem b/test/fixtures/keys/blocked-ca-leaf-cert.pem
new file mode 100644
index 00000000000..d1f1643e105
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-leaf-cert.pem
@@ -0,0 +1,48 @@
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn4CFFoauc3QA3NkOxn62/sIxRrh7vvGMA0GCSqGSIb3DQEBCwUAMIGG
+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQK
+DAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxEzARBgNVBAMMCmJsb2NrZWQtY2Ex
+JTAjBgkqhkiG9w0BCQEWFmJsb2NrZWQtY2FAZXhhbXBsZS5vcmcwIBcNMjYxMDIz
+MDkzMTU0WhgPMjMwMDA4MDcwOTMxNTRaMIGFMQswCQYDVQQGEwJVUzELMAkGA1UE
+CAwCQ0ExCzAJBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05v
+ZGUuanMxEjAQBgNVBAMMCWxvY2FsaG9zdDElMCMGCSqGSIb3DQEJARYWYmxvY2tl
+ZC1jYUBleGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANi/4BRZJCWbt6/0u6N1JAEHZtjQHJIkLsla2hIIqRzidIwZIvatkQYF2E2IAxjM
+E5fxVrZPCS29lyMnt+cgm/29OuLdHEf5wbZuXtXR01VrAY0Sd5YumFvpUDPxnB15
+NKKwClnx/op64ePglSbwgLqQpH94iiOsrp721n4aAGuxOwA1P4u64dT3ry4fclPw
+UIlq51EyQqNJ/1CtHXqGgdbXuEAUNy1iBb0EM9IVSAieDaPwlwuVG6k1e8cqGkXC
+gdtL29XLoX47rd58s9C9hI5u9e+BNLWtLIrJYtbGqQgF05BZbk+yH9pyeracQEXy
+ZyV1cJ0iVLpWByu7PNvjuisCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA1xqSl5FF
+23kKf+7gbPydD3/gIuZlsFAGPa9CaTrIs/hDZAX086KEadroSzWmNvlsGhNxCUug
+A5u5zDXir6SWHomnLggkA9S+TDBcF5IQp0DrGEZ3MpOAsbi6a+Qwofnc+jwwjBui
+FMYCJaVh5tQeY1MVxRbYL7hDiWe74k01WOczVCe726bvkUcryTa+jCR8h8jX1DRr
+xvgnU1KV7futG/afwM8if/XdyNKbPgtkP8HFgHEQLHXPMwju5ft73vW33TuGzIIW
+HNACy9z8Swqik8V2k7Mo0d0l5sEWtoablbAo53qDE1dW5wSVOxpTlDhOlxxrS0/T
+8nb7gMePTFNW+Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEZjCCA06gAwIBAgIUFH02wcL3Qgben6tfIibXitsApCswDQYJKoZIhvcNAQEL
+BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
+A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
+BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTI2MTAyMzA5MzE1M1oY
+DzIzMDAwODA3MDkzMTUzWjCBhjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw
+CQYDVQQHDAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMRMw
+EQYDVQQDDApibG9ja2VkLWNhMSUwIwYJKoZIhvcNAQkBFhZibG9ja2VkLWNhQGV4
+YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3JSHZeuE
+OSZ77MDkjTgJRhQJtvLruDJdA2hVfT47aST8cXqfHAR+tQsotBcIc4b69l1QxIts
+SYkmiQbbnObIj1pAbmZ3blFy+13wNxfnLJR2Jh51vZyUTQMX+KIlrrzg9qpfOGi1
+D1zJvCSs5pyxpsGZ1GZxiFVTIGqZ4PZApyTjobqsivLV5FYiRgGESHXziRPJBeKY
+D9MGKslA8p1qY79iqyaoY580xkiFNXeXsmguy96+KdEuGlfdpT+mPsCRWEU7Rt9E
+wXsqUAPcZ0/22u62IJvBapMf3lvbTo3M6vraQmFEwsqlxeaWDhx6621VjHzsTHp9
+8Ko2VExaIYoSNQIDAQABo4HUMIHRMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFBO3
+GH448bBtSUSGJcz0UV8t0M2AMIGhBgNVHSMEgZkwgZahfqR8MHoxCzAJBgNVBAYT
+AlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVudDEQ
+MA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkBFhFy
+eUB0aW55Y2xvdWRzLm9yZ4IUSrFsjf1qfQ0t/KvfnEsOksatAikwDQYJKoZIhvcN
+AQELBQADggEBAKl9HrTXud8wHFRGTrrJBMOP5jZO50Sgf6/QSg3gVxLbTnraZDUq
+PD3MKON95r/4ksa139/SIcTPSU6Nr4uJw4iVtfMgXr5/cUOeJN+l6z9Ju0bN1Fiu
+xLXNgm7fHcNS2SkNxFKDCK7D11nhgkwUgs/JPQWD2DLmfoVScScQNXuFmgOYxUdI
+Lfis9fdnAlTwFvu87FxFhKNjTD+gbo16KUz+uhevOHz+b2MZNgqhNklePP/mlQhx
+kWzDz9B6ofhn3SD/q/idtQuEntVM7qV60ZXj27ELS58q7DnbwCJ/Kivl+cjKLmSJ
+3EbbetSB6+9PA5MJjAMgInyfSAHw+S1IFqk=
+-----END CERTIFICATE-----
diff --git a/test/fixtures/keys/blocked-ca-leaf-csr.pem b/test/fixtures/keys/blocked-ca-leaf-csr.pem
new file mode 100644
index 00000000000..18bf31610af
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-leaf-csr.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC8DCCAdgCAQAwgYUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE
+BwwCU0YxDzANBgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczESMBAGA1UE
+AwwJbG9jYWxob3N0MSUwIwYJKoZIhvcNAQkBFhZibG9ja2VkLWNhQGV4YW1wbGUu
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2L/gFFkkJZu3r/S7
+o3UkAQdm2NAckiQuyVraEgipHOJ0jBki9q2RBgXYTYgDGMwTl/FWtk8JLb2XIye3
+5yCb/b064t0cR/nBtm5e1dHTVWsBjRJ3li6YW+lQM/GcHXk0orAKWfH+inrh4+CV
+JvCAupCkf3iKI6yunvbWfhoAa7E7ADU/i7rh1PevLh9yU/BQiWrnUTJCo0n/UK0d
+eoaB1te4QBQ3LWIFvQQz0hVICJ4No/CXC5UbqTV7xyoaRcKB20vb1cuhfjut3nyz
+0L2Ejm7174E0ta0sisli1sapCAXTkFluT7If2nJ6tpxARfJnJXVwnSJUulYHK7s8
+2+O6KwIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3Jk
+MA0GCSqGSIb3DQEBCwUAA4IBAQBEdD8hTSajIPZBRkE6me4vxHiGHsRZGGohdE5k
+UEzTU+TVH0V6c1E48Lh0nhLhzghvxdU9aBghoySDYfYWQTR4fN+l245yGWZCuRSp
+0fSlehzN+batpgtRqNuI/OUzV0iyLmHpTmjKBcYCCAPb33LsYEIUYOtECU7OMVVS
+Tz/AOsy+35TFqCV8OB8573en448Wcfiwh2f3IyG4zWAXaWyvGqxyCpbnGxDa8cNR
+/QPFLtqQMNUBDTxBSp2A6To8LM9YAJP0HEOaQnCgZOPH0h7bMY9NnLz9GT208Veh
+erTeSPFs2vazs34u78KwsFO9qVQQj9io3H1x4np14PhfYKQs
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/keys/blocked-ca-leaf-key.pem b/test/fixtures/keys/blocked-ca-leaf-key.pem
new file mode 100644
index 00000000000..b896545644b
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-leaf-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYv+AUWSQlm7ev
+9LujdSQBB2bY0BySJC7JWtoSCKkc4nSMGSL2rZEGBdhNiAMYzBOX8Va2TwktvZcj
+J7fnIJv9vTri3RxH+cG2bl7V0dNVawGNEneWLphb6VAz8ZwdeTSisApZ8f6KeuHj
+4JUm8IC6kKR/eIojrK6e9tZ+GgBrsTsANT+LuuHU968uH3JT8FCJaudRMkKjSf9Q
+rR16hoHW17hAFDctYgW9BDPSFUgIng2j8JcLlRupNXvHKhpFwoHbS9vVy6F+O63e
+fLPQvYSObvXvgTS1rSyKyWLWxqkIBdOQWW5Psh/acnq2nEBF8mcldXCdIlS6Vgcr
+uzzb47orAgMBAAECggEABLKv/DDscCY+ZoFy+bmFxMHw0D0+iUpgezvNYzut0rLz
+3L+Euv0LGirRQ+jxTD3MJOx1uTDgXY3w8zGeslNKHkdWOemZ/dNjh/0/b/oCzySp
+HXIrhmVPJxrZAq83d4oTgn03VDjDECRWthY9lWD8IU8WSa8Gq3xIMqjeAFC29p6Q
+FFxZxrkEHkmmzXchAvDqW9Dv5uudfRVUHZuC2rV3tNNbPfVf3vLERQDNJ0H6gtHB
+tEFJUn3CCc/LtRgfmPRHt2pmMwH5Eo7vkpkT5V5uqjlnm4Gnt/5RD0OAkzpy5wBI
+7q/cMhA/GU4oe65pi9qpVLClg+uSwasEooAtUUoULQKBgQD2viYNK4/3a1W0mGTW
+OHdtMlkJAdsK+hyh1Bzx23mQNkXvCnwOYtsTXc8/hQdNGLfaMZEI/3cLAui7cWUb
+oG0twJUl3rL79uwnJUYKWxewedV36F6zjxS9F1qfGaQEczH28n3hTxlD1akOresk
+79fBqihMdShw1n2ahZxkax1jXwKBgQDg4ae6ERbZmPtCvb/uLyERWBkJrZd7TAmO
+VlWEKK3k3naPkqf1jQxE3PyEjhbxVPQS0z4FNi8XH/oeKJCyZkdrxIxtzfm5hvLe
+2BgGpFrxEUDD4OGV2V2VpIfvIT4DNHeaHwV5AwpeIH59RhMx0MuWXr2miDJbVMrx
+jnSpf+6ItQKBgFx+RRdUyB/wbW1IXyxqeGJHqFaXCnxf65HCSKNlKXmkzqxf2sct
+yX/F/6WJAYamCG1TfVHJKd/H1GRHiqZDignHkmFizOdk1XuLHmlNYmD2Gs5bHfzj
+WtvTQFY5r2E3qXgvyH5IP2lNU5U8HtR20AM5/OdEV8eMsALXqRt/h8GzAoGACYPi
+ye+xLNHZR9Cpj1V1K7uzi4JJ15C3Esu6YJYsDXQQRjDpZG1saHmFQcKVW+7BSQ6h
+IC1TvsG+nooO3U2QlhHB8190wpHgMuen1JgdtPeK6v7X7LQ+zncgvVCFxDLzIJVR
+cPZc+PCdCYXuh/wSsTEfCy3WG6Ex2Riy/P0JxcUCgYAOqILp97U+ML5H1P+8d/Ct
+jNaODIFhiSpFKSXsFO7tcZe6IOYbUlE0E+ROuUiyLgp1IH4NwbYvaqI7VBYr5+oN
+WMrlC3wrACrENc8tFxpkzYYfWvM6g7IS7QjlFWz5Hw/O9nXlFiZrZ9Kcay3mx2YD
+bv5b3RsxMU1/cjdDGDNolA==
+-----END PRIVATE KEY-----
diff --git a/test/fixtures/keys/blocked-ca-leaf.cnf b/test/fixtures/keys/blocked-ca-leaf.cnf
new file mode 100644
index 00000000000..e2aef8f5c7d
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca-leaf.cnf
@@ -0,0 +1,18 @@
+[ req ]
+default_bits           = 2048
+days                   = 9999
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+prompt                 = no
+
+[ req_distinguished_name ]
+C                      = US
+ST                     = CA
+L                      = SF
+O                      = Joyent
+OU                     = Node.js
+CN                     = localhost
+emailAddress           = blocked-ca@example.org
+
+[ req_attributes ]
+challengePassword              = A challenge password
diff --git a/test/fixtures/keys/blocked-ca.cnf b/test/fixtures/keys/blocked-ca.cnf
new file mode 100644
index 00000000000..b989e3cc031
--- /dev/null
+++ b/test/fixtures/keys/blocked-ca.cnf
@@ -0,0 +1,23 @@
+[ req ]
+default_bits           = 2048
+days                   = 9999
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+prompt                 = no
+output_password        = password
+x509_extensions        = v3_ca
+
+[ req_distinguished_name ]
+C                      = US
+ST                     = CA
+L                      = SF
+O                      = Joyent
+OU                     = Node.js
+CN                     = blocked-ca
+emailAddress           = blocked-ca@example.org
+
+[ req_attributes ]
+challengePassword              = A challenge password
+
+[ v3_ca ]
+basicConstraints = CA:TRUE
diff --git a/test/fixtures/keys/blocked-key-leaf-cert.pem b/test/fixtures/keys/blocked-key-leaf-cert.pem
new file mode 100644
index 00000000000..37254c45231
--- /dev/null
+++ b/test/fixtures/keys/blocked-key-leaf-cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnECFBR9NsHC90IG3p+rXyIm14rbAKQsMA0GCSqGSIb3DQEBCwUAMHox
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoM
+BkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZI
+hvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzAgFw0yNjEwMjMwOTMxNTRaGA8yMzAw
+MDgwNzA5MzE1NFowgYUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE
+BwwCU0YxDzANBgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczESMBAGA1UE
+AwwJbG9jYWxob3N0MSUwIwYJKoZIhvcNAQkBFhZibG9ja2VkLWNhQGV4YW1wbGUu
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3JSHZeuEOSZ77MDk
+jTgJRhQJtvLruDJdA2hVfT47aST8cXqfHAR+tQsotBcIc4b69l1QxItsSYkmiQbb
+nObIj1pAbmZ3blFy+13wNxfnLJR2Jh51vZyUTQMX+KIlrrzg9qpfOGi1D1zJvCSs
+5pyxpsGZ1GZxiFVTIGqZ4PZApyTjobqsivLV5FYiRgGESHXziRPJBeKYD9MGKslA
+8p1qY79iqyaoY580xkiFNXeXsmguy96+KdEuGlfdpT+mPsCRWEU7Rt9EwXsqUAPc
+Z0/22u62IJvBapMf3lvbTo3M6vraQmFEwsqlxeaWDhx6621VjHzsTHp98Ko2VExa
+IYoSNQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCFpF2VptDf+Xbmm5MmFS/edORK
+lbZvL17Gw7bVJ3IlqvLc34BPmrLf4t/nh0tnA+OaMREh4YLJ5qZPXI2xpMt2hNef
+eMPk7a5OyV3ZdGfyUCRRvv16iLJB/N8jl4WWT6I0+qPtVPUeEdJVr9Hkrko5sX+H
+WtiA2EhOgCcmPDvwFbc+t8VVvfBJLKuAwimmMmKu9AnLc5rvnX4S0MFlJG4iCULj
+o+Kq+Xai2KtyoHcNbRgRSHl/HTN8nwjPo5KCN1rCFuwTGogJZkKTeiVmEs800uza
+gpFk85KpDPkMZrYBsFgz5sgVHaMCQCjMlpIUVoOJnXW7sX+IjWEwNCahuvuM
+-----END CERTIFICATE-----
diff --git a/test/fixtures/keys/blocked-key-leaf-csr.pem b/test/fixtures/keys/blocked-key-leaf-csr.pem
new file mode 100644
index 00000000000..86733c16e26
--- /dev/null
+++ b/test/fixtures/keys/blocked-key-leaf-csr.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC8DCCAdgCAQAwgYUxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UE
+BwwCU0YxDzANBgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczESMBAGA1UE
+AwwJbG9jYWxob3N0MSUwIwYJKoZIhvcNAQkBFhZibG9ja2VkLWNhQGV4YW1wbGUu
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3JSHZeuEOSZ77MDk
+jTgJRhQJtvLruDJdA2hVfT47aST8cXqfHAR+tQsotBcIc4b69l1QxItsSYkmiQbb
+nObIj1pAbmZ3blFy+13wNxfnLJR2Jh51vZyUTQMX+KIlrrzg9qpfOGi1D1zJvCSs
+5pyxpsGZ1GZxiFVTIGqZ4PZApyTjobqsivLV5FYiRgGESHXziRPJBeKYD9MGKslA
+8p1qY79iqyaoY580xkiFNXeXsmguy96+KdEuGlfdpT+mPsCRWEU7Rt9EwXsqUAPc
+Z0/22u62IJvBapMf3lvbTo3M6vraQmFEwsqlxeaWDhx6621VjHzsTHp98Ko2VExa
+IYoSNQIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3Jk
+MA0GCSqGSIb3DQEBCwUAA4IBAQDYknP02irc6qk8bmUVng5TwmGgqC+tyKtrZJI3
++o4R5wvB6m/UZBHgdhyTGeSsBYszJADHSmmWNzsoPiPw1keArwJ6bKH9dF+G8ve5
+oBdRGsYD4drsVkmOTSoILm4Rexg9/LnOehJ4zJCCsUdXXOLPAIpa/hZ3wWtVeYLi
+rblRu6bWa0lWdI6fFfz1ZmssdqI4kSmGhy22oUJViA4IMi37y5qTMtEGBJ6f5PNF
+FVu4FeOoNbmCocLGsWIGKymLrtm9jdCXfHp7I83vb76Xkaf3JzJH6GpaocKTDrpp
+lFc86ZfcR9/z9r+r+YM+2/DkPa0dGULOCaCIIi0o8hLrpj8i
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/keys/blocked-key-leaf.cnf b/test/fixtures/keys/blocked-key-leaf.cnf
new file mode 100644
index 00000000000..e2aef8f5c7d
--- /dev/null
+++ b/test/fixtures/keys/blocked-key-leaf.cnf
@@ -0,0 +1,18 @@
+[ req ]
+default_bits           = 2048
+days                   = 9999
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+prompt                 = no
+
+[ req_distinguished_name ]
+C                      = US
+ST                     = CA
+L                      = SF
+O                      = Joyent
+OU                     = Node.js
+CN                     = localhost
+emailAddress           = blocked-ca@example.org
+
+[ req_attributes ]
+challengePassword              = A challenge password
diff --git a/test/fixtures/keys/ca1-cert.srl b/test/fixtures/keys/ca1-cert.srl
index f4de097137c..973a463cf81 100644
--- a/test/fixtures/keys/ca1-cert.srl
+++ b/test/fixtures/keys/ca1-cert.srl
@@ -1 +1 @@
-147D36C1C2F74206DE9FAB5F2226D78ADB00A428
+147D36C1C2F74206DE9FAB5F2226D78ADB00A42C
diff --git a/test/parallel/test-tls-spki-blocklist.js b/test/parallel/test-tls-spki-blocklist.js
index a7d83e096a7..c822a9d818d 100644
--- a/test/parallel/test-tls-spki-blocklist.js
+++ b/test/parallel/test-tls-spki-blocklist.js
@@ -1,4 +1,3 @@
-// Flags: --expose-internals
 'use strict';
 const common = require('../common');
 if (!common.hasCrypto)
@@ -12,98 +11,81 @@ if (!common.hasCrypto)
 // is surfaced through socket.authorizationError and honours rejectUnauthorized
 // on both the client and the server side.
 //
-// The fixtures give a chain of agent6 -> ca3 (intermediate) -> ca1 (root);
-// agent6-cert.pem already bundles ca3. agent1 is signed directly by ca1 and
-// serves as an unaffected server certificate for the client-certificate
-// cases. The test blocks the SPKI of ca3 through the test-only hooks on the
-// crypto binding.
+// The public key of the blocked-ca fixture is a permanent entry on the
+// blocklist. The fixtures provide two chains that contain it:
+//   blocked-ca-leaf -> blocked-ca (intermediate) -> ca1 (root), and
+//   blocked-key-leaf -> ca1, where the leaf reuses the blocked key itself.
+// blocked-ca-leaf-cert.pem already bundles blocked-ca. The agent6 -> ca3 ->
+// ca1 chain and agent1 (signed directly by ca1) are unaffected.

 const assert = require('assert');
-const { createHash, X509Certificate } = require('crypto');
 const { once } = require('events');
 const { describe, it, beforeEach, afterEach } = require('node:test');
 const tls = require('tls');
 const fixtures = require('../common/fixtures');
-const { internalBinding } = require('internal/test/binding');
-
-const {
-  addBlockedSPKIForTesting,
-  clearBlockedSPKIsForTesting,
-} = internalBinding('crypto');
-
-function spkiSha256(pem) {
-  const spki = new X509Certificate(pem).publicKey.export({
-    type: 'spki',
-    format: 'der',
-  });
-  return createHash('sha256').update(spki).digest();
-}

 const rootCert = fixtures.readKey('ca1-cert.pem');
-const intermediateCert = fixtures.readKey('ca3-cert.pem');
-const leafKey = fixtures.readKey('agent6-key.pem');
-const leafChain = fixtures.readKey('agent6-cert.pem');
-const unaffectedKey = fixtures.readKey('agent1-key.pem');
-const unaffectedCert = fixtures.readKey('agent1-cert.pem');
-// ca2 is unrelated to the agent6 chain; appended to the server's certificate
-// it is sent to the peer but never becomes part of the verified chain.
-const unrelatedCert = fixtures.readKey('ca2-cert.pem');
-
-const intermediateSpkiHash = spkiSha256(intermediateCert);
-const leafSpkiHash = spkiSha256(leafChain);
-const unrelatedSpkiHash = spkiSha256(unrelatedCert);
+const blockedCaCert = fixtures.readKey('blocked-ca-cert.pem');
+const blockedCaLeafKey = fixtures.readKey('blocked-ca-leaf-key.pem');
+const blockedCaLeafChain = fixtures.readKey('blocked-ca-leaf-cert.pem');
+const blockedKey = fixtures.readKey('blocked-ca-key.pem');
+const blockedKeyLeafCert = fixtures.readKey('blocked-key-leaf-cert.pem');
+const unaffectedKey = fixtures.readKey('agent6-key.pem');
+const unaffectedChain = fixtures.readKey('agent6-cert.pem');
+const serverKey = fixtures.readKey('agent1-key.pem');
+const serverCert = fixtures.readKey('agent1-cert.pem');

 describe('SPKI blocklist on the server certificate', function() {
   let server;
   let client;
-  let opts;

-  beforeEach(async function() {
-    server = tls.createServer({
-      key: leafKey,
-      cert: leafChain,
-    }, (socket) => socket.resume());
+  function listen(serverOpts) {
+    server = tls.createServer(serverOpts, (socket) => socket.resume());
     server.listen(0);
-    await once(server, 'listening');
+    return once(server, 'listening');
+  }

-    opts = {
+  function connect(clientOpts) {
+    client = tls.connect({
       port: server.address().port,
       ca: rootCert,
       checkServerIdentity() {},
-    };
-  });
+      ...clientOpts,
+    });
+    return client;
+  }

-  afterEach(async function() {
-    clearBlockedSPKIsForTesting();
+  afterEach(function() {
     client?.destroy();
     server?.close();
   });

-  it('accepts the chain when nothing is blocked', async function() {
-    client = tls.connect(opts);
+  it('accepts a chain that contains no blocked key', async function() {
+    await listen({ key: unaffectedKey, cert: unaffectedChain });
+    connect();
     await once(client, 'secureConnect');
     assert.strictEqual(client.authorized, true);
   });

   it('rejects a chain through a blocked intermediate', async function() {
-    addBlockedSPKIForTesting(intermediateSpkiHash);
+    await listen({ key: blockedCaLeafKey, cert: blockedCaLeafChain });
     await assert.rejects(async () => {
-      client = tls.connect(opts);
+      connect();
       await once(client, 'secureConnect');
     }, { code: 'CERT_REVOKED' });
   });

   it('rejects a chain whose leaf key is blocked', async function() {
-    addBlockedSPKIForTesting(leafSpkiHash);
+    await listen({ key: blockedKey, cert: blockedKeyLeafCert });
     await assert.rejects(async () => {
-      client = tls.connect(opts);
+      connect();
       await once(client, 'secureConnect');
     }, { code: 'CERT_REVOKED' });
   });

   it('reports CERT_REVOKED without rejectUnauthorized', async function() {
-    addBlockedSPKIForTesting(intermediateSpkiHash);
-    client = tls.connect({ ...opts, rejectUnauthorized: false });
+    await listen({ key: blockedCaLeafKey, cert: blockedCaLeafChain });
+    connect({ rejectUnauthorized: false });
     await once(client, 'secureConnect');
     assert.strictEqual(client.authorized, false);
     assert.strictEqual(client.authorizationError, 'CERT_REVOKED');
@@ -113,52 +95,36 @@ describe('SPKI blocklist on the server certificate', function() {
     // With an empty trust store the chain cannot reach a root, which would
     // normally be reported as UNABLE_TO_GET_ISSUER_CERT_LOCALLY. The
     // blocked key must win over that error.
-    addBlockedSPKIForTesting(intermediateSpkiHash);
+    await listen({ key: blockedCaLeafKey, cert: blockedCaLeafChain });
     await assert.rejects(async () => {
-      client = tls.connect({ ...opts, ca: [] });
+      connect({ ca: [] });
       await once(client, 'secureConnect');
     }, { code: 'CERT_REVOKED' });
   });
-
-  it('accepts the chain again once the blocklist is cleared', async function() {
-    addBlockedSPKIForTesting(intermediateSpkiHash);
-    clearBlockedSPKIsForTesting();
-    client = tls.connect(opts);
-    await once(client, 'secureConnect');
-    assert.strictEqual(client.authorized, true);
-  });
-
-  it('validates the hook arguments', function() {
-    assert.throws(() => addBlockedSPKIForTesting('not a buffer'), {
-      code: 'ERR_INVALID_ARG_TYPE',
-    });
-    assert.throws(() => addBlockedSPKIForTesting(Buffer.alloc(31)), {
-      code: 'ERR_OUT_OF_RANGE',
-    });
-  });
 });

 describe('SPKI blocklist on certificates outside the verified chain', function() {
   let server;
   let client;

+  // blocked-ca is unrelated to the agent6 chain; appended to the server's
+  // certificate it is sent to the peer but never becomes part of the
+  // verified chain.
   beforeEach(async function() {
     server = tls.createServer({
-      key: leafKey,
-      cert: `${leafChain}\n${unrelatedCert}`,
+      key: unaffectedKey,
+      cert: `${unaffectedChain}\n${blockedCaCert}`,
     }, (socket) => socket.resume());
     server.listen(0);
     await once(server, 'listening');
   });

-  afterEach(async function() {
-    clearBlockedSPKIsForTesting();
+  afterEach(function() {
     client?.destroy();
     server?.close();
   });

   it('ignores an unused peer-sent certificate when the chain verifies', async function() {
-    addBlockedSPKIForTesting(unrelatedSpkiHash);
     client = tls.connect({
       port: server.address().port,
       ca: rootCert,
@@ -171,7 +137,6 @@ describe('SPKI blocklist on certificates outside the verified chain', function()
   it('checks every peer-sent certificate when the chain does not verify', async function() {
     // With an empty trust store the chain is incomplete, so the fallback
     // scan of everything the peer sent applies and finds the blocked key.
-    addBlockedSPKIForTesting(unrelatedSpkiHash);
     await assert.rejects(async () => {
       client = tls.connect({
         port: server.address().port,
@@ -189,8 +154,8 @@ describe('SPKI blocklist on the client certificate', function() {

   function listen(serverOpts) {
     server = tls.createServer({
-      key: unaffectedKey,
-      cert: unaffectedCert,
+      key: serverKey,
+      cert: serverCert,
       ca: rootCert,
       requestCert: true,
       ...serverOpts,
@@ -199,50 +164,47 @@ describe('SPKI blocklist on the client certificate', function() {
     return once(server, 'listening');
   }

-  function connect() {
+  function connect(clientOpts) {
     client = tls.connect({
       port: server.address().port,
-      key: leafKey,
-      cert: leafChain,
       ca: rootCert,
       checkServerIdentity() {},
+      ...clientOpts,
     });
     return client;
   }

-  afterEach(async function() {
-    clearBlockedSPKIsForTesting();
+  afterEach(function() {
     client?.destroy();
     server?.close();
   });

-  it('accepts the client chain when nothing is blocked', async function() {
+  it('accepts a client chain that contains no blocked key', async function() {
     await listen({ rejectUnauthorized: true });
     const connection = once(server, 'secureConnection');
-    connect();
+    connect({ key: unaffectedKey, cert: unaffectedChain });
     const [socket] = await connection;
     assert.strictEqual(socket.authorized, true);
   });

   it('reports CERT_REVOKED without rejectUnauthorized', async function() {
-    addBlockedSPKIForTesting(intermediateSpkiHash);
     await listen({ rejectUnauthorized: false });
     const connection = once(server, 'secureConnection');
-    connect();
+    connect({ key: blockedCaLeafKey, cert: blockedCaLeafChain });
     const [socket] = await connection;
     assert.strictEqual(socket.authorized, false);
     assert.strictEqual(socket.authorizationError, 'CERT_REVOKED');
   });

   it('rejects the client chain with rejectUnauthorized', async function() {
-    addBlockedSPKIForTesting(intermediateSpkiHash);
     await listen({ rejectUnauthorized: true });
     server.on('secureConnection', common.mustNotCall());
     const clientError = once(server, 'tlsClientError');
     // The client may see either a clean close or a reset depending on
     // timing, so accept both and only wait for it to go away.
     const clientClosed = new Promise((resolve) => {
-      connect().on('error', () => {}).once('close', resolve);
+      connect({ key: blockedCaLeafKey, cert: blockedCaLeafChain })
+        .on('error', () => {}).once('close', resolve);
     });
     // The server destroys the socket during 'secure', which surfaces as a
     // reset rather than a handshake failure; the reason is kept on the socket.