Skip to content

Harden x25519_secret_from_seed: zeroize the derived X25519 secret (follow-up to #41) #65

@beardthelion

Description

@beardthelion

Background

#41 removed Keypair::seed_bytes() and routed Ed25519 seed access through the zeroizing to_seed(). While closing that, the review surfaced a sibling leak one hop downstream that #41 deliberately left out of scope to keep that PR to a single hardening slice.

Current behavior

x25519_secret_from_seed (crates/gitlawb-core/src/encrypt.rs) derives the X25519 private scalar from the Ed25519 seed and returns it as a bare [u8; 32]:

fn x25519_secret_from_seed(seed: &[u8; 32]) -> [u8; 32] {
    let h = Sha512::digest(seed);     // 64 bytes; lower 32 are the secret
    let mut s = [0u8; 32];
    s.copy_from_slice(&h[..32]);
    // clamp ...
    s                                  // [u8; 32]: Copy, no Drop
}

The returned value is the actual X25519 private key (fed into crypto_box::SecretKey in open_blob to unwrap content keys). Because [u8; 32] is Copy with no Drop, and sha2's digest output (h) likewise has no zeroize, the secret-bearing bytes in s, h, and the returned temporary are released without being scrubbed. This is the same Copy-no-Drop memory-hygiene class fixed in #41, on the derived secret rather than the raw seed. It sits in the open_blob path, which runs on every withheld-blob read.

crypto_box::SecretKey already zeroizes its own internal copy on drop; this issue is only about the intermediate copies that precede it.

Proposal

  • Return Zeroizing<[u8; 32]> from x25519_secret_from_seed and explicitly zeroize the h digest before it drops.
  • Migrate the two callers (open_blob and the crypto test) accordingly.
  • One wrinkle to settle in the fix: crypto_box::SecretKey::from takes [u8; 32] by value, so feeding it from a Zeroizing still materializes one transient copy. That's acceptable (the SecretKey scrubs its own copy), but the fix should confirm no extra bare copy is left behind.

Scope

Memory hygiene only. No change to the encryption scheme, key derivation math, or wire format; behavior is byte-identical and covered by the existing seal_open_round_trip_for_recipients / ed25519_to_x25519_keypair_agrees tests. Not remotely exploitable; defense-in-depth against core dumps, swap, and memory-disclosure bugs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions