Skip to content

node:tls: honor the crl option when building a SecureContext - #33629

Closed
robobun wants to merge 8 commits into
mainfrom
farm/d34af88a/tls-crl-option
Closed

node:tls: honor the crl option when building a SecureContext#33629
robobun wants to merge 8 commits into
mainfrom
farm/d34af88a/tls-crl-option

Conversation

@robobun

@robobun robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

The crl option on tls.createServer / tls.connect / tls.createSecureContext was accepted without error and then dropped before reaching BoringSSL. A client certificate revoked by the supplied CRL was fully authorized (authorized === true, authorizationError unset) under requestCert: true + rejectUnauthorized: true.

Cause

SSLConfig (the native TLSOptions mirror) had no crl field, so the option never reached us_bun_socket_context_options_t or the SSL_CTX's X509_STORE, and X509_V_FLAG_CRL_CHECK was never set.

Fix

Plumb crl (string | Buffer | BunFile | array) end to end:

  • SSLConfig.bindv2.ts / jsc/generated.rs / runtime/socket/SSLConfig.rs: read the option from JS.
  • http/ssl_config.rs: store it on SSLConfig (owned, freed in deinit, part of the content hash and equality check).
  • uws_sys/SocketContext.rs / libusockets.h / App.h: add crl / crl_count to the C ABI options struct and its digest.
  • openssl.c: in us_ssl_ctx_build_raw, parse each PEM CRL with PEM_read_bio_X509_CRL, X509_STORE_add_crl, then set X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL on the context's store (matching Node's SecureContext::AddCRL). The store is cloned first if it is the process-wide shared default so the CRL flags do not leak across contexts.
  • An unparseable CRL now throws ERR_CRYPTO_OPERATION_FAILED ("Failed to parse CRL") at context creation instead of being ignored.
  • tls.ts: validate options.crl the same way ca/cert/key are validated, and forward it from the Server options to the native listener.

Verification

Using the Node test fixtures (agent4 is signed by ca2 and revoked by ca2-crl; agent3 is not revoked):

$ bun bd test test/js/node/tls/node-tls-server.test.ts -t 'crl option'
(pass) tls.createServer crl option > rejects a client certificate listed in the CRL
(pass) tls.createServer crl option > accepts a client certificate not listed in the CRL
(pass) tls.createServer crl option > accepts crl as a PEM string
(pass) tls.createServer crl option > accepts crl as an array
(pass) tls.createServer crl option > rejectUnauthorized:true refuses a revoked client certificate
(pass) tls.createServer crl option > rejects an unparseable crl at secure-context creation
(pass) tls.createServer crl option > validates the crl option type

All of the CERT_REVOKED cases fail against the released binary (the revoked client is authorized).

The crl option on tls.createServer / tls.connect / tls.createSecureContext
was silently dropped before reaching BoringSSL, so a revoked client or
server certificate was treated as authorized. Plumb the option through
SSLConfig into us_bun_socket_context_options_t and load each CRL into the
context's X509_STORE with X509_V_FLAG_CRL_CHECK|CRL_CHECK_ALL, matching
Node's SecureContext::AddCRL. An unparseable CRL now throws
ERR_CRYPTO_OPERATION_FAILED at context creation instead of being ignored.
@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:09 AM PT - Jul 7th, 2026

@robobun, your commit b8ce22c has some failures in Build #69777 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 33629

That installs a local version of the PR into your bun-33629 executable, so you can run:

bun-33629 --bun

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. node:tls: sync the test suite to Node v26.3.0 and fix the gaps it surfaces (+24 tests, 155→179 of 221 upstream passing) #32630 - Also implements the crl option for tls.createSecureContext (PEM_read_bio_X509_CRLX509_STORE_add_crl + X509_V_FLAG_CRL_CHECK) as part of a larger Node v26.3.0 TLS test-suite sync

🤖 Generated with Claude Code

@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

The overlap with #32630 is real: that PR also wires crl through SSLConfig into the X509_STORE as one piece of a much larger Node v26.3.0 TLS test-suite sync (88 files, +24 upstream tests, still iterating on CI).

This PR is the focused security subset only: plumb crl, enable X509_V_FLAG_CRL_CHECK|CRL_CHECK_ALL, throw on an unparseable CRL, plus seven regression tests. 13 files / +225 lines, no other behavior changes.

Happy to close this if #32630 is about to land; otherwise it can merge independently so the silent-CRL-bypass is fixed without waiting on the broader sync. Rebase on whichever lands first should be trivial since both touch the same set of struct fields the same way.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85c003e2-a16d-4696-82ce-5e0c302d4476

📥 Commits

Reviewing files that changed from the base of the PR and between 65c6be9 and b8ce22c.

📒 Files selected for processing (1)
  • packages/bun-types/bun.d.ts

Walkthrough

This PR adds TLS CRL support across native OpenSSL handling, Rust and JS option propagation, JS error mapping, Node tls wiring, and coverage for revocation and invalid-input cases.

Changes

TLS CRL support end-to-end

Layer / File(s) Summary
Native CRL parsing and SSL context wiring
packages/bun-usockets/src/crypto/openssl.c, packages/bun-usockets/src/libusockets.h, packages/bun-uws/src/App.h
Adds CRL parsing into X509_STORE, attaches CRLs during SSL_CTX construction, and extends public socket options and error codes with CRL fields.
Rust uws_sys bindings for CRL options and errors
src/uws_sys/SocketContext.rs, src/uws_sys/lib.rs
Adds CRL fields to BunSocketContextOptions, updates defaulting and cache/memory helpers, and adds the invalid_crl error variant and message mapping.
SSLConfig field lifecycle
src/http/ssl_config.rs, src/jsc/generated.rs, src/runtime/socket/SSLConfig.bindv2.ts, src/runtime/socket/SSLConfig.rs
Adds crl to SSLConfig and generated FFI types, updates zeroing, conversion, equality, hashing, cloning, and parsing to carry CRL data through runtime socket config.
CRL error to JS exception mapping
src/runtime/socket/uws_jsc.rs, src/sql_jsc/jsc.rs
Maps the native invalid_crl error to ERR_CRYPTO_OPERATION_FAILED with a CRL parse failure message.
Node tls.js crl option support
src/js/node/tls.ts
Validates crl in secure context setup, stores it on Server, and forwards it to the native TLS upgrade path.
CRL test coverage
test/js/node/tls/node-tls-crl.test.ts
Adds tests for revocation handling, supported CRL input forms, rejectUnauthorized behavior, parse failures, and invalid argument types.

Possibly related PRs

  • oven-sh/bun#31339: Also modifies us_ssl_ctx_build_raw in packages/bun-usockets/src/crypto/openssl.c, changing certificate store selection/isolation logic that this PR extends with CRL handling.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: honoring the TLS crl option when building a SecureContext.
Description check ✅ Passed The description covers the PR purpose, root cause, fix, and verification results, matching the template content well enough.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bun-usockets/src/crypto/openssl.c`:
- Around line 768-793: The CRL loader in add_crl_to_ctx_store currently treats
X509_STORE_add_crl failures and partial parsing as success, so a bad bundle can
still return a nonzero count. Update add_crl_to_ctx_store to fail fast on any
parse/store error: check the return value of X509_STORE_add_crl, stop processing
and return 0 if it fails, and avoid incrementing count unless the CRL was
actually added. Keep the existing success path in place so the caller’s
CREATE_BUN_SOCKET_ERROR_INVALID_CRL mapping still triggers correctly when any
CRL block is invalid.

In `@test/js/node/tls/node-tls-server.test.ts`:
- Around line 1338-1371: The test in node-tls-server.test.ts has a race because
the client "close" wait is attached only after awaiting the server result, so
the event may already have fired by then. Update the rejectUnauthorized:true
revoked-certificate test to register the client close listener before awaiting
result.promise, using the existing client and result promise flow, so the close
event cannot be missed if tlsClientError resolves first.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6c72e3d1-32ee-4f36-ae74-8b782347ef49

📥 Commits

Reviewing files that changed from the base of the PR and between 2bffc92 and 2ca2884.

📒 Files selected for processing (13)
  • packages/bun-usockets/src/crypto/openssl.c
  • packages/bun-usockets/src/libusockets.h
  • packages/bun-uws/src/App.h
  • src/http/ssl_config.rs
  • src/js/node/tls.ts
  • src/jsc/generated.rs
  • src/runtime/socket/SSLConfig.bindv2.ts
  • src/runtime/socket/SSLConfig.rs
  • src/runtime/socket/uws_jsc.rs
  • src/sql_jsc/jsc.rs
  • src/uws_sys/SocketContext.rs
  • src/uws_sys/lib.rs
  • test/js/node/tls/node-tls-server.test.ts

Comment thread packages/bun-usockets/src/crypto/openssl.c
Comment thread test/js/node/tls/node-tls-server.test.ts Outdated
Comment thread packages/bun-usockets/src/crypto/openssl.c
robobun and others added 3 commits July 7, 2026 08:17
…ext's store as user-owned

add_crl_to_ctx_store now checks X509_STORE_add_crl's return and fails when
the PEM loop terminates on anything other than PEM_R_NO_START_LINE, so a
bundle with one valid CRL followed by garbage is rejected.

The CRL block in us_ssl_ctx_build_raw now seeds a fresh default-roots store
when the context's store is empty and sets us_ctx_user_ca_ex_idx, so the
per-socket client attach in us_internal_ssl_attach does not override the
verify store with the shared default roots and drop the CRL flags for a
tls.connect({ crl }) without an explicit ca.

Also register the client 'close' listener before awaiting the server result
in the rejectUnauthorized:true test to avoid a listener-registration race.
node-tls-server.test.ts has a pre-existing unrelated failure on main
("SNICallback runs even when the requested servername matches the bind
hostname" fails with ECONNREFUSED). Keep the CRL coverage in
node-tls-crl.test.ts so it is isolated from that.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/tls/node-tls-crl.test.ts`:
- Around line 52-132: This test suite only covers the `tls.createServer` CRL
path, so add sibling coverage for the `tls.connect` path and the BunFile input
variant. Extend the `handshake`/`connect`-based coverage to verify that passing
`crl` into `tls.connect()` correctly accepts or rejects the server certificate
based on revocation status, and add a case that uses a BunFile-backed CRL input
(for example via `Bun.file(...)`) to ensure that input type is honored. Keep the
new tests alongside the existing `tls.createServer crl option` cases and reuse
the existing fixtures/helpers like `handshake`, `serverBase`, and `ca2Crl` where
possible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b92662d8-4022-4d7f-b25c-59f025f5606d

📥 Commits

Reviewing files that changed from the base of the PR and between 26d4c6c and b12b749.

📒 Files selected for processing (1)
  • test/js/node/tls/node-tls-crl.test.ts

Comment thread test/js/node/tls/node-tls-crl.test.ts
robobun added 2 commits July 7, 2026 08:36
…he extern buffer

Appending keeps the existing field offsets in ExternSSLConfig unchanged so
the hand-written #[repr(C)] mirror only grows at the tail. Zero-initialising
the MaybeUninit buffer before bindgenConvertJSToSSLConfig writes into it
means a stale generated header that under-writes the struct leaves the
trailing field as its None variant instead of garbage; every field's
all-zeros representation is valid.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/tls/node-tls-crl.test.ts`:
- Around line 78-85: The TLSOptions.crl type is missing BunFile support, which
is why the test has to cast to never. Update the TLS type definition in bun.d.ts
so crl matches the existing ca and cert patterns by accepting both BunFile and
BunFile[] alongside the current string/Buffer forms, and ensure the
handshake-related types still work without any cast in the crl test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a50e7151-109e-43b9-b5f8-71cdcc711341

📥 Commits

Reviewing files that changed from the base of the PR and between b12b749 and 65c6be9.

📒 Files selected for processing (3)
  • src/jsc/generated.rs
  • src/runtime/socket/SSLConfig.bindv2.ts
  • test/js/node/tls/node-tls-crl.test.ts

Comment thread test/js/node/tls/node-tls-crl.test.ts
@robobun
robobun requested a review from alii as a code owner July 7, 2026 09:00
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Status: diff is ready for review.

  • test/js/node/tls/node-tls-crl.test.ts passes on all CI lanes in build 69777; the annotation failures there (fetch-leak, zlib/leak, solc, napi, bun-install, bake, webview-chrome, postgres, etc.) are in areas the diff does not touch and are a different set from the previous run's failures at build 69769.
  • All 8 CRL tests pass locally under bun bd test (debug+ASAN) and the release gate build.
  • All review threads are resolved.

The internal fail-before gate's ASAN step has been bouncing with a debug_assert_eq!(tag, 1) panic (left=255) in ExternOptional::get() when it rebuilds after git stash pop; that is an ExternSSLConfig layout mismatch from stale bindgenv2 codegen in build/debug/codegen/GeneratedSSLConfig.h not regenerating across the stash cycle, not a defect in this diff. 65c6be9 appends crl at the end of the dictionary and zero-inits the extern buffer so an under-writing header reads the trailing field as None rather than garbage.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

The crl option landed on main through #32630 (the node:tls suite sync). SSLConfig now reads crl like the other PEM options, usockets adds each CRL to the context's own certificate store and turns on X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL (packages/bun-usockets/src/crypto/openssl.c), an unparseable CRL fails secure context creation, and node:tls validates and forwards options.crl.

Checked by running this PR's test/js/node/tls/node-tls-crl.test.ts unmodified against a debug build of current main (05dd45e): 2 runs, 8 pass / 0 fail each time.

The only part of this PR that is not on main is the crl entry in the Bun.TLSOptions type declaration. The runtime already accepts it, so that is a small types-only change and will be handled on its own. Closing this PR.

@robobun robobun closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant