Skip to content

crypto,tls: do not ignore BN_get_word error#63895

Merged
nodejs-github-bot merged 1 commit into
nodejs:mainfrom
tniessen:tls-fix-wrong-exponent
Jun 19, 2026
Merged

crypto,tls: do not ignore BN_get_word error#63895
nodejs-github-bot merged 1 commit into
nodejs:mainfrom
tniessen:tls-fix-wrong-exponent

Conversation

@tniessen

Copy link
Copy Markdown
Member

This changes BignumPointer::GetWord such that it does not hide errors from the caller. In the context of RSA keys within X.509 certificates, we should eventually compute the public exponent correctly regardless of its size. This patch, however, is designed to be a minimal change that prevents callers from using erroneous return values of BN_get_word.

@tniessen tniessen added tls Issues and PRs related to the tls subsystem. crypto Issues and PRs related to the crypto subsystem. labels Jun 13, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

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

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Jun 13, 2026
This changes `BignumPointer::GetWord` such that it does not hide errors
from the caller. In the context of RSA keys within X.509 certificates,
we should eventually compute the public exponent correctly regardless of
its size. This patch, however, is designed to be a minimal change that
prevents callers from using erroneous return values of `BN_get_word`.

Signed-off-by: Tobias Nießen <tniessen@tnie.de>
@tniessen tniessen force-pushed the tls-fix-wrong-exponent branch from 27df418 to 3c157c5 Compare June 13, 2026 19:44
@tniessen tniessen changed the title ncrypto,tls: do not ignore BN_get_word error crypto,tls: do not ignore BN_get_word error Jun 13, 2026
@panva panva added request-ci Add this label to start a Jenkins CI on a PR. author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Jun 15, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jun 15, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@panva panva added the commit-queue Add this label to land a pull request using GitHub Actions. label Jun 19, 2026
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Jun 19, 2026
@nodejs-github-bot nodejs-github-bot merged commit 21310cb into nodejs:main Jun 19, 2026
83 of 86 checks passed
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Landed in 21310cb

aduh95 pushed a commit that referenced this pull request Jun 20, 2026
This changes `BignumPointer::GetWord` such that it does not hide errors
from the caller. In the context of RSA keys within X.509 certificates,
we should eventually compute the public exponent correctly regardless of
its size. This patch, however, is designed to be a minimal change that
prevents callers from using erroneous return values of `BN_get_word`.

Signed-off-by: Tobias Nießen <tniessen@tnie.de>
PR-URL: #63895
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
luanmuniz pushed a commit to luanmuniz/node that referenced this pull request Jun 25, 2026
This changes `BignumPointer::GetWord` such that it does not hide errors
from the caller. In the context of RSA keys within X.509 certificates,
we should eventually compute the public exponent correctly regardless of
its size. This patch, however, is designed to be a minimal change that
prevents callers from using erroneous return values of `BN_get_word`.

Signed-off-by: Tobias Nießen <tniessen@tnie.de>
PR-URL: nodejs#63895
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
aduh95 pushed a commit that referenced this pull request Jun 25, 2026
This changes `BignumPointer::GetWord` such that it does not hide errors
from the caller. In the context of RSA keys within X.509 certificates,
we should eventually compute the public exponent correctly regardless of
its size. This patch, however, is designed to be a minimal change that
prevents callers from using erroneous return values of `BN_get_word`.

Signed-off-by: Tobias Nießen <tniessen@tnie.de>
PR-URL: #63895
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
nodejs-github-bot pushed a commit that referenced this pull request Jun 25, 2026
If the exponent does not fit into a single word, previous versions of
Node.js would report an incorrect value or, recently, return `null`.
Change `GetExponentString()` to handle arbitrarily large RSA public
exponents properly.

Signed-off-by: Tobias Nießen <tniessen@tnie.de>
PR-URL: #64093
Refs: #63895
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
dylan-conway added a commit to oven-sh/bun that referenced this pull request Jul 1, 2026
Bun's `src/jsc/bindings/ncrypto.{cpp,h}` is a port of
[nodejs/ncrypto@134ac40](nodejs/ncrypto@134ac40)
(February 2025), which maps to Node's `deps/ncrypto` from late January
2025. This ports the upstream correctness fixes made since then that
apply to code Bun already has, plus one Bun-local typo found while
auditing the drift.

### Upstream fixes

| Change | Upstream |
| --- | --- |
| `DataPointer::resize()`: handle `OPENSSL_realloc` failure instead of
returning a `DataPointer` with a null pointer and a nonzero length. Also
frees the old block on failure. Upstream's version calls a `free()`
helper after `release()` has already nulled `data_`, which is a no-op,
so the old block leaks; this keeps the intent without that. |
[nodejs/ncrypto#37](nodejs/ncrypto#37) |
| `CipherCtxPointer::{setIvLength, setAeadTag, setAeadTagLength,
getAeadTag}`: compare the `EVP_CIPHER_CTX_ctrl` return against `> 0`.
The raw `int` can be negative, which converts to `true`. BoringSSL
normalizes `-1` to `0`, so this is masked in Bun's build today, but it
is still the wrong check. |
[nodejs/ncrypto#36](nodejs/ncrypto#36) |
| `BignumPointer::{GetWord, getWord}` return `std::optional` so a
`BN_get_word` overflow (reported as the all-ones word) is
distinguishable from the real all-ones value. The optional wraps
`BN_ULONG`, not upstream's `unsigned long`: BoringSSL's `BN_ULONG` is
`uint64_t` on every 64-bit target, so `unsigned long` would still
silently truncate 33-to-64-bit values on LLP64 (Windows x64). |
[nodejs/node#63895](nodejs/node#63895) |
| `BIOPointer::New(method)`: return an empty pointer for a null method.
| [nodejs/node#61788](nodejs/node#61788) |
| `EVPKeyCtxPointer::publicCheck()`: remove an unconditional return that
made the `EVP_PKEY_public_check_quick` branch unreachable. The whole
block is non-BoringSSL, so it is dead in Bun's build. |
[nodejs/node#59471](nodejs/node#59471) |
| `X509Name::Iterator::operator*()`: free the buffer allocated by
`ASN1_STRING_to_UTF8`, and return early on a negative size (the
out-pointer is never written on failure). Dead code today:
`JSX509Certificate.cpp` has its own correct loop. |
[nodejs/node#60609](nodejs/node#60609) |

### `GetWord` callers updated

- `JSDiffieHellmanConstructor.cpp`: the generator-below-2 check becomes
`has_value() && *word < 2`. This is the one spot that needed care:
`std::optional<T> < 2` compiles and evaluates `nullopt < 2` as `true`,
so a naive translation would have started rejecting any generator wider
than a word.
- `JSX509Certificate.cpp` (both legacy-object sites): `exponent` is
reported as `null` when the RSA public exponent does not fit in a word,
matching Node. This branch is unreachable under BoringSSL, which rejects
RSA public exponents wider than 33 bits at SPKI parse time.

### Bun-local typo

`SSLCtxPointer::setCipherSuites` passed `ciphers.length()` where
`SSL_CTX_set_ciphersuites` expects the string. It is in the
non-BoringSSL `#ifndef` branch so it never compiles in Bun's build;
introduced by the original `WTF::StringView` port of the file.

### Testing

No test here can fail against the unmodified source on a Linux machine,
because nothing in this diff changes observable behavior on Linux. I
checked each one: `DataPointer::resize` only diverges under
`OPENSSL_realloc` failure, the `EVP_CIPHER_CTX_ctrl` returns are already
normalized to `0` or `1` by BoringSSL, `publicCheck` and
`setCipherSuites` are inside non-BoringSSL `#ifndef` branches,
`BIOPointer::New(method)` and `X509Name::Iterator` have no callers, and
`BN_ULONG` and `unsigned long` are the same 64-bit type on LP64. The one
real before-and-after is the `BN_ULONG` truncation, and it only exists
on Windows x64 (LLP64).

So the new tests pin invariants rather than prove a failure.
`node-crypto.test.js` gains three `DiffieHellman` cases:

- A 72-bit buffer generator is accepted. This is what a naive `optional
< 2` translation would have broken on every platform.
- A 33-bit (5-byte) buffer generator is accepted. Equivalent on
platforms with a 64-bit `unsigned long`; on Windows x64 the `unsigned
long` version of this change truncates it to `0` and wrongly rejects it
as below 2, so this is the real regression test for the `BN_ULONG`
return type on that platform.
- Generators below 2 are still rejected and exactly 2 is still accepted.

Node v26 agrees on all three.

<details>
<summary>Local verification against the debug build</summary>

-
`test/js/node/crypto/{crypto,node-crypto,crypto.key-objects,crypto-rsa,x509-subclass}.test.*`:
677 pass, 0 fail
-
`test/js/node/test/parallel/test-crypto-{x509,dh,dh-errors,dh-constructor,dh-odd-key,dh-generate-keys,dh-shared,dh-padding,cipheriv-decipheriv,gcm-explicit-short-tag,gcm-implicit-short-tag,aes-wrap,padding-aes256,getcipherinfo,rsa-dsa}.js`,
`test-webcrypto-encrypt-decrypt-aes.js`,
`test-crypto-webcrypto-aes-decrypt-tag-too-small.js`: all pass

</details>

### Deliberately not included

- `Cipher::MAX_AUTH_TAG_LENGTH`
([nodejs/node#57803](nodejs/node#57803)): of the
three macros upstream's `static_assert` uses, BoringSSL only defines
`EVP_GCM_TLS_TAG_LEN`, so the assert degenerates to `16 <= 16`.
- `ECKeyPointer::setPublicKeyRaw`
([nodejs/node#62396](nodejs/node#62396)): the
expensive check that rewrite avoids (the `order * Q == infinity` step of
`EC_KEY_check_key`) does not exist in BoringSSL's `EC_KEY_check_key`, so
there is no perf win for Bun.
- The BoringSSL build guards the standalone repo added
(`NCRYPTO_NO_DSA_KEYGEN`, `NCRYPTO_NO_EVP_DH`, rejecting DSA in
`NewFromID`, ...): those exist for vanilla BoringSSL CI, and Bun's DSA
and DH paths work today against its BoringSSL.

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. crypto Issues and PRs related to the crypto subsystem. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. tls Issues and PRs related to the tls subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants