Repository navigation
Fix CVE-2026-85393: reject non-canonical DigestInfo in RSA PKCS#1 v1.5 verification - #1159
Conversation
…026-85393) RSASSA-PKCS#1 v1.5 signature verification accepted DigestInfo structures that were not canonically DER-encoded. asn1.validate() confirms the overall shape but, by design, tolerates extra elements in the outer DigestInfo or the inner DigestAlgorithm SEQUENCE, a NULL parameters field carrying content bytes, and non-minimal (long form) length octets. Any of these leave attacker-controlled slack bytes inside an otherwise valid DigestInfo, which is enough to mount a Bleichenbacher-style cube-root forgery for low public exponent keys (e.g. e=3). The 1.4.0 fix for CVE-2026-33894 rejected extra elements only in the outer DigestInfo SEQUENCE, so the inner SEQUENCE and the encoding-level slack above remained exploitable (an element-count check on the inner SEQUENCE would still miss the two-element-garbage, NULL-with-content, and long-form length cases). Instead of enumerating individual malformed shapes, verification now re-encodes the expected DigestInfo from the parsed algorithm OID and digest and requires the decoded bytes to match. Both the with-parameters (NULL) and parameter-less forms are accepted, preserving interop with forge- and OpenSSL-generated signatures (verified against OpenSSL for SHA-256/384/512, e=65537 and e=3). Adds regression tests for the inner-SEQUENCE garbage element, a non-NULL second element, a NULL carrying content, and long-form length octets, plus a positive test that a valid parameter-less DigestInfo still verifies. Fixes: CVE-2026-85393 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
When will this fix be released? |
|
I independently validated the RSA-only change at Results on Node 24.19.0:
The harness is available here: installed-dependency regressions. It would be useful to retain the non-minimal inner-length regression alongside the existing outer-length case. Counting nested elements alone does not address content inside NULL or non-minimal encodings. Limits: the malformed signatures are constructed with a private key to exercise parser/verifier acceptance, rather than demonstrating a private-key-free forgery. I checked the Node implementation, not regenerated browser bundles. This validation is not a claim that every RSA attack has been excluded. We kept the original package name/version and applied a pnpm patch; registry/Dependency Review advisories still flag 1.4.0. A maintainer-reviewed fixed release remains necessary for downstream consumers to clear those checks without exceptions. Thank you for proposing the canonical-encoding fix. |
|
Thanks for taking the time to do this, and especially for exercising it through the On your suggestion: both regressions you call out are already in this PR.
alongside the extra-inner-element and non-NULL-second-element cases and a positive test Your independent run is a useful confirmation of why the fix re-encodes rather than Agreed that a local patch doesn't clear the registry / Dependency Review advisories, |
The mobile client now uses Eternal's web navigation and visual language for Feed, Notifications, Search, story creation and Music Hub. Both Feed and Create open the same story editor, and music search uses live provider results instead of a demo fallback. - Add native authentication, the Home / Reels / Chats / Music / Profile dock, notification states and user/content search. - Add story text, image, drawing, music and voice layers, filters, backgrounds, privacy and viewer interactions. Extend the shared API and web viewer for persistent story appearance and independent sticker uploads; convert native HEIC uploads to JPEG and reject unsupported HEIC before storage. - Add Music Hub discovery, search, library, playlists/folders, player and queue controls, Jam, lyrics/karaoke and Game Mode. Game Mode keeps its own playlist controls and removes the normal application shell while suspending nonessential activity. - Integrate current main security fixes and keep Expo SDK 52 dependencies and React 18 native types compatible with the React 19 web workspace. - Backport the Node RSA canonical DigestInfo verifier from upstream forge PR #1159 at `db05494352dd3c9a5a07c5270333dcc78866cad2` through an exact-version pnpm patch. Preserve the original package identity, add regression and real Expo certificate/manifest tests, and run them after dependency install/cache restore in CI. Validation: - Mobile: 136 tests passed; iOS, Android and web JavaScript exports passed; Expo dependency check passed. - Backend: 61 focused story/music tests passed, affected-file ESLint passed with zero warnings, and production build passed. - Web: 24 focused story tests and 27 query-policy/music-store tests passed, affected-file ESLint passed with zero warnings, and production build / 32-page SEO prerender passed. - Design documentation/inventory, LF and Kubernetes policy checks passed. Local PostgreSQL reports all 26 migrations applied. - Security backport: 30 regression/integration tests passed; the old implementation failed 10 malformed-encoding assertions. The pinned upstream Node suite passed with 833 passing / 4 pending. Frozen offline pnpm install and post-backport iOS/Android/web exports passed. See [backport provenance and limitations](docs/security/NODE_FORGE_BACKPORT.md). - Final CI on 5059f91 completed: 36 successful checks, one neutral summary and two intentional skips. Both Docker build/scan jobs, unit/E2E tests, affected lint/typecheck/build, design regression and the mandatory 30-case RSA regression step passed. Dependency Review still flags the original node-forge version; the aggregate gate fails solely because of that check. All review threads are resolved; required reviewer approval remains outstanding. Rollout: apply the additive nullable `20261005000000_story_appearance` migration before publishing clients that save story appearance. Physical-device execution and native screen-reader checks remain separate from JavaScript exports. Provider availability and playback depend on the configured production integrations. CI and required reviewer approval are tracked on this PR. Security dependency blocker: the Node implementation of `node-forge@1.4.0` used by Expo is locally patched, but there is no published maintainer-approved release for [GHSA-86w9-cpqp-85rv](GHSA-86w9-cpqp-85rv). Version-based Dependency Review therefore still fails; this is not claimed to be a green or merge-ready PR. The upstream patch is unmerged, and browser bundles are outside this backport's scope. A concrete [upstream validation report](digitalbazaar/forge#1159 (comment)) was submitted. `braces` <=3.0.3 also has no published fix for [GHSA-vfj7-8cjw-p6xm](GHSA-vfj7-8cjw-p6xm). The shell-quote/compression overrides remove the fixable critical/high findings. No security checks or advisory allowlists were weakened. Remaining warnings: Dependency Review reports 55 OpenSSF Scorecard warnings for upstream package maintenance practices, including Expo/React Native transitive dependencies. GitHub's Ubuntu 26 runner migration annotations are notices. Existing local build/install tooling also reports the web circular-chunk warning and dependency/Knip configuration warnings. These are documented rather than suppressed; the PR is not claimed to be warning-free or merge-ready.
Element-count checks alone do not close CVE-2026-85393: forge's parser accepts a NULL parameters field that carries content bytes and non-minimal long-form length octets, so a count-valid DigestInfo can still hold attacker-chosen slack bytes usable for the same low-exponent (e=3) forgery. Identified by Matthew Conroy in review of this PR and confirmed independently by subvincdebian in digitalbazaar#1159. Replace the element-count checks with a single canonical-encoding comparison: re-encode the expected DigestInfo from the parsed OID and digest (_encodeDigestInfo) and require the decoded bytes to match. Any encoding slack -- extra elements in either SEQUENCE, NULL parameters with content, non-minimal length octets -- changes the re-encoded bytes, so one comparison rejects all variants. Both DigestAlgorithm forms that occur in practice (with NULL parameters, and without them) remain accepted. Add regression tests for the count-preserving variants: NULL parameters with content bytes, non-minimal long-form length octets, and a garbage element in the parameters slot. Fixes CVE-2026-85393 (GHSA-86w9-cpqp-85rv). Signed-off-by: Shaswata Das <shaswata56@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1159 +/- ##
==========================================
- Coverage 80.25% 80.24% -0.02%
==========================================
Files 42 42
Lines 9597 9607 +10
==========================================
+ Hits 7702 7709 +7
- Misses 1895 1898 +3
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
- [rsa] Require a canonical DER DigestInfo in RSASSA PKCS#1 v1.5 signature verification by re-encoding the expected DigestInfo and comparing bytes, as in RFC 8017 8.2.2. Rejects extra DigestAlgorithm elements, NULL parameters with content, non-minimal lengths, and non-canonical OIDs. - Address GHSA-2g46-22mc-6gwp (CVE-2026-85393): - GHSA-2g46-22mc-6gwp - Address GHSA-6qrw-m8cv-hcxf: - GHSA-6qrw-m8cv-hcxf - Based on work in PRs #1159 (@matthewscottconroy), #1160 (@shaswata56), #1161 (@nitrog7), and #1162 (@RoastFried-RF). - Add PoC from PR #1160. - Add tests for canonical and non-canonical DigestInfo encodings, and for every supported digest algorithm with and without NULL parameters. Co-authored-by: Matthew Conroy <matthew.conroy@ibm.com> Co-authored-by: Shaswata Das <shaswata56@gmail.com>
|
Thank you. This PR's encode-and-compare approach is the basis of the fix in node-forge 1.4.1 (GHSA-2g46-22mc-6gwp, GHSA-6qrw-m8cv-hcxf). The commit lists you as co-author, and you're credited in the advisory. Closing, as the fix landed in a combined security release. |
Fixes #1149 / CVE-2026-85393.
Context and relationship to existing work
Thank you for your time on this. I know #1149 is one of many demands on it and that several contributors have already put work in here, so I've tried to make this quick to evaluate and to be clear about where it fits.
This builds directly on the analysis in #1149 and on the fixes already proposed there: #1152 (which #1151 was closed in favor of) and #1158. I'm grateful for that prior work. While validating those approaches I found that the element-count strategy they share does not fully close the vulnerability, so this PR takes a different approach rather than re-proposing the same one.
All of the existing PRs require the inner
DigestAlgorithmSEQUENCE to contain only the OID plus an optional NULL, verified by element count. But forge's parser does not require a NULL to be empty, so an attacker can keep the inner SEQUENCE at exactly two elements and smuggle arbitrary bytes inside the NULL parameters field. I verified that #1152's exact check accepts aDigestInfowhose NULL carries 100 attacker-controlled bytes, which is ample slack for the low-exponent (e=3) forgery. Non-minimal (long form) length octets slip through for the same reason. Those vectors stay open under a count-only check at any nesting level.The fix
Instead of enumerating malformed shapes, verification re-encodes the expected
DigestInfocanonically from the parsed algorithm OID and digest and requires the decoded bytes to match. Any slack — an extra element in either SEQUENCE, a NULL carrying content, non-minimal length octets, trailing bytes — changes the encoding and is rejected in a single comparison.Both the with-parameters (NULL) and parameter-less forms are accepted, preserving the optional-parameters behavior established in #968. I verified interop against OpenSSL-generated signatures for SHA-256/384/512 with both e=65537 and e=3.
Tests
DigestInfostill verifies.npm run test-node: 833 passing, 0 failing (4 pending).npx mocha tests/unit/rsa.js: 105 passing.CHANGELOG
Adds a
1.4.1Security entry. The GHSA ID is left as a placeholder for you to fill in.Disclosure
In the interest of transparency: I used an AI assistant (Claude) to analyze the vulnerability, review the existing issue and PRs, implement this change, and write its tests. I have reviewed and verified all of it myself, including running the full suite and the OpenSSL interop checks, but I want to be upfront about how it was produced so you can weigh it accordingly.
I'm happy to adapt this — align it with #1152's structure if you prefer a smaller diff, or close it in favor of another PR if you'd rather fix the remaining gaps there. Either way, the NULL-with-content and long-form length cases are the ones I'd suggest making sure are covered in whatever lands.
🤖 Generated with Claude Code