Skip to content

Add defensive key material zeroization to prevent memory leaks#4

Merged
brody-0125 merged 4 commits intomainfrom
claude/fix-key-memory-security-6vE4S
Apr 16, 2026
Merged

Add defensive key material zeroization to prevent memory leaks#4
brody-0125 merged 4 commits intomainfrom
claude/fix-key-memory-security-6vE4S

Conversation

@brody-0125
Copy link
Copy Markdown
Owner

Summary

This PR introduces defensive zeroization of sensitive cryptographic key material to prevent recovery via heap dumps, core dumps, or cold-boot attacks. A new KeyWipe utility class provides safe helpers for clearing byte arrays and destroying Destroyable key objects, which are now integrated throughout the credential signing code paths.

Key Changes

  • New KeyWipe utility class: Provides two static helper methods:

    • zero(byte[]): Safely overwrites byte arrays with zeros (null-safe)
    • tryDestroy(Destroyable): Safely destroys Destroyable objects, skipping already-destroyed instances and swallowing DestroyFailedException (null-safe)
  • SelectiveDisclosure.createBaseProof():

    • Extracts ECPrivateKey once at the start and reuses it for all signatures (performance optimization)
    • Wraps the entire method in try-finally to zero the HMAC key and destroy the EC private key after use
    • Added clarifying comment about CBOR encoder's synchronous copying of HMAC key
  • SelectiveDisclosure.replaceBlankNodesWithHmac():

    • Extracts SecretKeySpec creation outside the try block for proper cleanup
    • Added finally block to destroy the key spec after use
  • SelectiveDisclosure.signEcdsaP256():

    • Changed from instance method to static method
    • Simplified to accept pre-extracted ECPrivateKey instead of ECKey (removes redundant key extraction)
    • Removed exception wrapping for JOSEException
  • CredentialSigner.signEd25519Raw():

    • Added try-finally block to zero the seed bytes and destroy the private key after use
  • CredentialSigner.signEcdsaP256Raw():

    • Added try-finally block to destroy the EC private key after use

Implementation Details

All zeroization is performed in finally blocks to ensure cleanup even when exceptions occur. The KeyWipe.tryDestroy() method gracefully handles JDK implementations that don't fully support the Destroyable interface by catching and ignoring DestroyFailedException.

Comprehensive unit tests verify that KeyWipe correctly handles null inputs, overwrites all bytes, respects already-destroyed state, and properly swallows exceptions.

https://claude.ai/code/session_01Tjyxx8f3ZhfXdNEFMP1avf

claude added 4 commits April 15, 2026 09:28
Raw Ed25519 seed bytes and the ecdsa-sd-2023 HMAC key were held in
byte[] arrays and JCA PrivateKey / SecretKeySpec objects that were
never cleared, leaving recoverable key material on the heap after GC
and exposing it to heap dumps, core dumps, and swap files.

Add a package-private KeyWipe helper exposing zero(byte[]) and
tryDestroy(Destroyable), and wrap the affected signing paths in
try-finally so sensitive buffers and key objects are wiped on both
normal return and exception:

- CredentialSigner.signEd25519Raw: zero the decoded seed, destroy the
  Ed25519 PrivateKey.
- CredentialSigner.signEcdsaP256Raw: destroy the extracted ECPrivateKey.
- SelectiveDisclosure.createBaseProof: zero the HMAC-SHA256 key after
  it has been copied into the CBOR proof value.
- SelectiveDisclosure.replaceBlankNodesWithHmac: destroy the
  SecretKeySpec after the Mac loop.
- SelectiveDisclosure.signEcdsaP256: destroy the extracted ECPrivateKey.

Add KeyWipeTest covering null-safety, byte zeroing, already-destroyed
skip, and DestroyFailedException swallowing.

https://claude.ai/code/session_01Tjyxx8f3ZhfXdNEFMP1avf
Review feedback:
- SelectiveDisclosure.createBaseProof extracts the JCA ECPrivateKey once
  before the loop and reuses it for both the per-quad signatures and the
  base signature, instead of re-running toECPrivateKey() (which invokes
  KeyFactory.generatePrivate internally) on every iteration. Destruction
  is hoisted to the outer finally along with hmacKey zeroization.
- signEcdsaP256 now takes the pre-extracted ECPrivateKey and no longer
  owns the destroy lifecycle.
- KeyWipe.tryDestroy narrows the catch to DestroyFailedException so real
  runtime bugs are not silently swallowed.
- Drop the stale "Re-initialize for next use" comment in
  replaceBlankNodesWithHmac and tighten the CBOR finally-safety note.
- KeyWipeTest uses assertFalse instead of assertTrue(!x).

https://claude.ai/code/session_01Tjyxx8f3ZhfXdNEFMP1avf
Conflicts in CredentialSigner.signEcdsaP256Raw and
SelectiveDisclosure.signEcdsaP256 came from PR #3 (low-S canonicalization)
and PR #5 (ecdsa-sd-2023 derivation) landing on main alongside this
branch's key-zeroization rewrite of the same methods.

Resolution:
- CredentialSigner.signEcdsaP256Raw: keep ecPrivateKey hoisted out of
  the inner try (needed by our finally{tryDestroy}) and add the byte[]
  p1363 staging variable from main so normalizeToLowS can run on the
  result before return.
- SelectiveDisclosure.signEcdsaP256: keep our pre-extracted ECPrivateKey
  parameter shape (avoids re-running toECPrivateKey per quad) and adopt
  main's algorithm-name constants (CredentialSigner.ALGO_ECDSA_*),
  P256_COMPONENT_LEN, and final normalizeToLowS step. The outer JOSE
  try/catch is dropped because the JOSE call is now in the caller.

Drive-by: EcdsaInternalsTest.selectiveDisclosureBaseSignatureIsAlwaysLowS
was broken on plain main — PR #5 changed createBaseProof to W3C-required
multibase-base64url-no-pad ('u' prefix) and updated the CBOR base-proof
tag low byte from 0x02 to 0x00, but PR #3's test still called
decodeBase58Btc and asserted 0x02. Switch the test to
decodeBase64UrlNoPad and the 0x00 tag so this branch's CI is green.

https://claude.ai/code/session_01Tjyxx8f3ZhfXdNEFMP1avf
@brody-0125 brody-0125 merged commit 335a4e5 into main Apr 16, 2026
2 checks passed
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.

2 participants