Skip to content

fix(keys): support passphrase-encrypted PEM/DER in createPrivateKey/createPublicKey#1050

Merged
boorad merged 3 commits into
mainfrom
fix/create-private-key-passphrase
May 21, 2026
Merged

fix(keys): support passphrase-encrypted PEM/DER in createPrivateKey/createPublicKey#1050
boorad merged 3 commits into
mainfrom
fix/create-private-key-passphrase

Conversation

@boorad
Copy link
Copy Markdown
Collaborator

@boorad boorad commented May 21, 2026

Summary

  • Plumbs the passphrase field through createPrivateKey / createPublicKey (TS layer was dropping it) and surfaces a clear "Passphrase required" error path on both encoding sides
  • Fixes a memory-ownership bug where ncrypto::DataPointer was constructed over JS-owned ArrayBuffer memory and then OPENSSL_clear_free'd it on destruction — corrupting the JS Buffer (the "passphrase as Buffer" reuse case) and causing latent heap damage on every passphrased call
  • Handles two ncrypto/OpenSSL 3.6 edge cases that the new tests exposed: the OpenSSL 3.6 NEED_PASSPHRASE error code reshuffle (ERR_R_INTERRUPTED_OR_CANCELLED on ERR_LIB_CRYPTO) and stale entries in OpenSSL's error queue tripping ncrypto's post-parse ERR_peek_error() check when multiple parse strategies are attempted

Changes

  • src/keys/index.ts + src/keys/classes.ts: forward passphrase through prepareAsymmetricKey and KeyObject.createKeyObject, always pass format/encoding/passphrase to handle.init
  • cpp/keys/KeyObjectData.cpp + cpp/keys/HybridKeyObjectHandle.cpp: replace ncrypto::DataPointer(ptr, len) with DataPointer::Copy(...) at all 7 sites so the wrapper owns memory it can safely free (mirrors Node.js's ByteSource::ToDataPointer)
  • cpp/keys/KeyObjectData.cpp: detect ERR_R_INTERRUPTED_OR_CANCELLED and translate to "Passphrase required" when no passphrase was supplied; ERR_clear_error() between SPKI/PKCS8 attempts in the no-type DER branch
  • example/src/tests/keys/create_keys.ts: new tests for PEM/DER passphrase round-trips, Buffer-typed passphrase, encrypted-without-passphrase error message, wrong-passphrase error, and createPublicKey extraction from a passphrase-encrypted DER private key

Test plan

  • bun ios, keys suite — all passphrase tests pass, no app crash
  • Existing key tests (createPrivateKey/createPublicKey PEM/DER/raw/JWK, round-trips, EC, Ed25519) still pass
  • bun android smoke-test of the keys suite

Fixes #1048

boorad added 3 commits May 21, 2026 10:24
The JS layer of createPrivateKey/createPublicKey advertised a
`passphrase` field on KeyInputObject and the native Nitro spec/C++
already supported it, but the TS plumbing dropped the value before
reaching handle.init(). Encrypted PEM keys surfaced OpenSSL's
"interrupted or cancelled" error and DER keys gave a generic
"Failed to read DER private key", matching the report in #1048.

- forward passphrase through prepareAsymmetricKey
- accept passphrase in KeyObject.createKeyObject and always pass
  format/encoding/passphrase to handle.init (previously the
  undefined-format branch dropped them)
- surface NEED_PASSPHRASE in the DER private-key path so users get
  "Passphrase required for encrypted key" instead of the generic error

Fixes #1048
ncrypto::DataPointer is an owning RAII wrapper whose destructor calls
OPENSSL_clear_free on the pointer it holds. The createPrivateKey /
createPublicKey / exportKey paths were constructing it directly from
the JS-owned ArrayBuffer's data pointer, so destruction zeroed and
freed memory it did not own. In the "passphrase as Buffer (PEM)" test
the same Buffer was reused across export and import — export cleared
the bytes, so import saw zeros and OpenSSL surfaced bad decrypt.

Use DataPointer::Copy at all 7 sites (KeyObjectData.cpp x6,
HybridKeyObjectHandle.cpp x1). This mirrors Node.js's
ByteSource::ToDataPointer helper (Alloc + memcpy), giving DataPointer
ownership of memory it can safely OPENSSL_clear_free.

Adds a DER variant of the createPublicKey passphrase-encrypted test
and tightens expected error messages on the throws-on-bad-input cases.
…n parse attempts

Two related issues exposed by the new createPrivateKey/createPublicKey
passphrase tests under OpenSSL 3.6:

1. Encrypted PEM without passphrase reported "interrupted or cancelled"
   instead of "Passphrase required". ncrypto's keyOrError only maps
   ERR_LIB_PEM/PEM_R_BAD_PASSWORD_READ to NEED_PASSPHRASE, but
   PEM_read_bio_PrivateKey in OpenSSL 3.6 surfaces a missing-passphrase
   callback as ERR_R_INTERRUPTED_OR_CANCELLED on ERR_LIB_CRYPTO. Detect
   that reason code in the rnqc wrapper when no passphrase was supplied.

2. createPublicKey on an encrypted PKCS8 DER buffer failed with
   "Failed to read DER asymmetric key" even though the parse succeeded.
   The no-type DER branch tries SPKI first; d2i_PUBKEY fails and leaves
   an error on OpenSSL's queue, then the follow-up TryParsePrivateKey
   parses the key correctly but ncrypto's keyOrError peeks the stale
   error and downgrades the result to FAILED. Clear the OpenSSL error
   queue between attempts.
@boorad boorad self-assigned this May 21, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-quick-crypto Ready Ready Preview, Comment May 21, 2026 5:09pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

🤖 End-to-End Test Results - Android

Status: ✅ Passed
Platform: Android
Run: 26241219921

📸 Final Test Screenshot

Maestro Test Results - android

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@github-actions
Copy link
Copy Markdown
Contributor

🤖 End-to-End Test Results - iOS

Status: ✅ Passed
Platform: iOS
Run: 26241219972

📸 Final Test Screenshot

Maestro Test Results - ios

Screenshot automatically captured from End-to-End tests and will expire in 30 days


This comment is automatically updated on each test run.

@boorad boorad merged commit 0ea34e5 into main May 21, 2026
10 checks passed
@boorad boorad deleted the fix/create-private-key-passphrase branch May 21, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 createPrivateKey does not handle keys encrypted with passphrases

1 participant