fix(core): zeroize the derived X25519 secret (#65)#91
Conversation
x25519_secret_from_seed derived the X25519 private scalar from the Ed25519 seed and returned it as a bare [u8; 32]. Because [u8; 32] is Copy with no Drop (and sha2's digest output likewise has no zeroize), the scalar, the SHA-512 digest, and the returned temporary were released without being scrubbed, leaving secret-derived material in freed memory. This is the same Copy-no-Drop class fixed for the raw seed in #41, on the derived secret. Return Zeroizing<[u8; 32]>, build the scalar directly into a zeroizing buffer so no bare secret local persists, and explicitly wipe the SHA-512 digest before it drops. Callers deref the result into crypto_box::SecretKey, which already scrubs its own copy. No behavior change: the derived scalar and all decryption output are byte-identical, covered by the existing ed25519_to_x25519_keypair_agrees and seal_open_round_trip_for_recipients tests.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesZeroize x25519_secret_from_seed intermediate scalar
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
jatmn
left a comment
There was a problem hiding this comment.
Thanks for the contribution. I do not see any actionable issues from my review.
@kevincodex1 LGTM
Re-lands the #65 fix that was reviewed and merged in #66 but never reached main.
#66 was stacked on #64 and targeted
fix/zeroize-seed-bytesas its base. #64 merged to main about a minute before #66 merged, so #66's commit landed on the already-retired stacked base and never propagated to main. The accessor signature on main still returns a bare[u8; 32], so issue #65 is still open. This PR puts the same reviewed change on main directly.What it does
x25519_secret_from_seedderived the X25519 private scalar and returned it as a bare[u8; 32]. Because that type isCopywith noDrop(andsha2's digest output has no zeroize), the scalar, the SHA-512 digest, and the returned temporary were released without being scrubbed, leaving secret-derived material in freed memory. SameCopy-no-Dropclass fixed for the raw seed in #41, one hop downstream on the derived secret.The fix returns
Zeroizing<[u8; 32]>, builds the scalar directly into a zeroizing buffer so no bare secret local persists, and explicitly wipes the SHA-512 digest before it drops. The two callers (open_bloband the crypto test) deref intocrypto_box::SecretKey, which already scrubs its own copy.Scope
Memory hygiene only. No change to the encryption scheme, key derivation, or wire format. The derived scalar and all decryption output are byte-identical, covered by the existing
seal_open_round_trip_for_recipientsanded25519_to_x25519_keypair_agreestests. Not remotely exploitable; defense-in-depth against core dumps, swap, and memory-disclosure bugs.Verification
cargo build -p gitlawb-core, clippy cleanencryptunit tests pass, including the two round-trip tests that would fail on any scalar divergenceZeroizingandGenericArray::as_mut_slice().zeroize()Closes #65.
Summary by CodeRabbit