crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

crypto: support provider-only SM4 cipher modes - #65399

Closed
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated
Closed

crypto: support provider-only SM4 cipher modes#65399
PickBas wants to merge 1 commit into
nodejs:mainfrom
JaneaSystems:issue-64866-updated

Conversation

@PickBas

Copy link
Copy Markdown
Contributor

crypto: support provider-only SM4 cipher modes

sm4-gcm, sm4-ccm and sm4-xts exist only as fetchable provider algorithms in OpenSSL 3. There is no legacy EVP_CIPHER for them, so EVP_get_cipherbyname() / EVP_get_cipherbynid() cannot resolve them and EVP_CIPHER_do_all_sorted(), which walks the legacy name table, never reports them. createCipheriv() threw Unknown cipher, getCipherInfo() returned undefined, and getCiphers() omitted them.

Changes

  • Cipher::FromName() / Cipher::FromNid() fall back to EVP_CIPHER_fetch().
  • Cipher::getNid() recovers the nid from the algorithm name, since a fetched cipher inherits its nid from the legacy implementation it does not have. Without this, getCipherInfo(name) reports no nid and cannot round-trip through getCipherInfo(nid).
  • Cipher::ForEach() probes for the provider-only modes so getCiphers() lists them.
  • Fetched instances are reference counted while Cipher is a non-owning wrapper, so they are cached for the process lifetime. The cache key includes the library context's default property state, because crypto.setFips() changes it at runtime and a cipher fetched beforehand must not stay usable afterwards.

Tests

  • test-crypto-sm4-aead.js - RFC 8998 A.1/A.2 known-answer vectors for GCM and CCM, XTS round-trip, tag tampering, getCipherInfo name/nid round-trip, case-insensitive lookup, and negative cases for unknown names and nids.
  • test-crypto-sm4-fips.js - enabling FIPS at runtime must invalidate an already fetched SM4 cipher. Skipped unless a FIPS provider is available.

Fixes: #64866

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-botnodejs-github-bot added dependencies PRs that add, update, or configure Node.js dependencies. needs-ci PRs that need a full CI run. labels Aug 19, 2026
Fixes: nodejs#64866
Co-authored-by: StefanStojanovic <stefan.stojanovic@janeasystems.com>
Signed-off-by: Kirill Saied <sayed.kirill@gmail.com>
@PickBas
PickBasforce-pushed the issue-64866-updated branch from 6cb1a8a to 491dc6dCompareAugust 19, 2026 11:27
@codecov

codecovBot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 90.11%. Comparing base (de333e8) to head (491dc6d).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65399 +/- ##
==========================================
- Coverage 90.30% 90.11% -0.20% 
==========================================
Files 759 752 -7 Lines 247648 251864 +4216 Branches 46696 47355 +659 ==========================================
+ Hits 223644 226970 +3326 - Misses 15466 16230 +764 - Partials 8538 8664 +126 

see 203 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell
jasnell requested review from panva and tniessenAugust 20, 2026 02:29

@panvapanva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to wait for and be rebased on #63411 before landing. Both PRs modify the fetched-cipher lookup, ownership, metadata, and provider-only enumeration paths. #63411 now provides owned fetched handles and the authenticated-mode handling required for AES-SIV and AES-GCM-SIV.

The unrestricted fallback here currently makes every provider-fetchable cipher appear supported. On this head, AES-SIV resolves through createCipheriv() even though setAAD() and getAuthTag() fail, and provider algorithms without an OBJ NID can be reported as name: 'undef', nid: 0.

Please retain #63411's ownership and metadata handling, then extend its explicit eligibility and enumeration paths for SM4-GCM, SM4-CCM, and SM4-XTS. The process-lifetime fetched-cipher cache should not be carried over.

The FIPS test also needs correction: getCiphers() is memoized before the FIPS transition, and the test can run with OpenSSL 3.0 even though SM4-GCM requires OpenSSL 3.1.

if (auto it = fetched_ciphers.find(key); it != fetched_ciphers.end()) {
return Cipher(it->second);
}
if (const EVP_CIPHER* fetched = EVP_CIPHER_fetch(nullptr, name, nullptr)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This cannot be an unrestricted fallback. It makes every fetchable provider cipher a Node-supported cipher before CipherBase has implemented that mode's contract. With this head on OpenSSL 3.0, createCipheriv('aes-128-siv', ...) succeeds, while setAAD() and getAuthTag() both throw ERR_CRYPTO_INVALID_STATE; the cipher is also absent from getCiphers().

Please rebase on #63411 and only return a fetched cipher when its mode or name is explicitly supported by Node. The SM4 additions should extend that eligibility rather than accepting every successful EVP_CIPHER_fetch().

#if NCRYPTO_USE_OPENSSL3_PROVIDER
// EVP_CIPHER_do_all_sorted() walks the legacy name table, so provider-only
// algorithms have to be probed for by name.
static constexpr const char* kProviderOnlyCiphers[] = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#63411 adds provider-only AES-SIV and AES-GCM-SIV probes in this same region. Please extend its existing probe helper and preserve both sets of names when rebasing. Taking only this side drops SIV/GCM-SIV from getCiphers(), while taking only #63411's side drops these SM4 modes.

return EVP_CIPHER_nid(cipher_);
int nid = EVP_CIPHER_nid(cipher_);
#if NCRYPTO_USE_OPENSSL3_PROVIDER
if (nid == NID_undef) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This recovery only works when the provider algorithm's name is registered in the OBJ database. The generic fallback above also exposes algorithms without an OBJ NID: with OpenSSL 3.0, getCipherInfo('aes-128-cbc-cts') resolves but reports name: 'undef' and nid: 0.

Please preserve #63411's behavior of using EVP_CIPHER_get0_name() when no NID exists and omitting nid from the JavaScript result when it remains NID_undef.

// A fetch is resolved against the library context's default properties,
// which setFipsEnabled() changes at runtime. Key on that state as well so
// that a cipher fetched before the switch cannot outlive it.
std::string key(EVP_default_properties_is_fips_enabled(nullptr) ? "fips:"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reading the FIPS state and performing the fetch are not synchronized with setFipsCrypto(). This code uses fetched_mutex, while FIPS transitions use a separate fips_mutex.

A worker can read the state as enabled, the main thread can disable FIPS, and the worker can then fetch a non-FIPS implementation and store it under the fips: key. If FIPS is enabled again, that cached implementation is reused.

Please retain #63411's owned, per-Cipher fetched handles when rebasing instead of introducing this process-lifetime property cache.

const iv = Buffer.alloc(12);

// Populate the cache while FIPS is still disabled.
assert(crypto.getCiphers().includes('sm4-gcm'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

test-crypto-sm4-aead.js correctly gates these modes on OpenSSL 3.1, but this test only checks for sm4-cbc. A FIPS-capable OpenSSL 3.0 build has SM4-CBC but not SM4-GCM, so it reaches this assertion and fails before exercising the FIPS transition.

Please import hasOpenSSL from ../common/crypto and add the same hasOpenSSL(3, 1) skip used by the AEAD test.

crypto.setFips(true);
assert.strictEqual(crypto.getFips(), 1);

assert(!crypto.getCiphers().includes('sm4-gcm'));

@panvapanvaAug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This assertion cannot observe the transition as written. crypto.getCiphers() is wrapped in cachedResult() in lib/internal/crypto/util.js, and the calls above populate that JavaScript cache before setFips(true). This call therefore returns the same pre-FIPS list.

Please either make the public cipher-list cache FIPS-state-aware, or remove the dynamic getCiphers() assertions and retain the direct getCipherInfo() / createCipheriv() checks for the in-process transition.

@panvapanva added the blocked PRs that are blocked by other issues or PRs. label Aug 20, 2026
@panva

Copy link
Copy Markdown
Member

Blocked by #63411

@panvapanva removed the blocked PRs that are blocked by other issues or PRs. label Aug 22, 2026
@panva

panva commented Aug 22, 2026

Copy link
Copy Markdown
Member

I believe this is now superseded by a broader refactor of the cipher discovery in #65484

@panvapanva closed this Aug 23, 2026
@PickBas

Copy link
Copy Markdown
ContributorAuthor

@panva Thank you for looking into this and for the feedback!

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

Labels

dependenciesPRs that add, update, or configure Node.js dependencies.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crypto: SM4-GCM and SM4-CCM not available despite OpenSSL 3.5 bundling the implementation

3 participants

@PickBas@nodejs-github-bot@panva