Skip to content

Secure In-Memory Master Key using XOR-Splitting (Keychain + Memguard Enclave) #65

Description

@imdeepmind

Objective

Eliminate the security risk of storing the naked, raw Master Key inside a standard Go struct on the managed heap long-term (Session.masterKey, internal/features/auth/service.go:36). Prevent memory dump exploits and operating system swap space leaks while maintaining a seamless user session.

Why now

Today the master key is held as a plain []byte in the in-memory Session for the whole signed-in lifetime, and Logout() merely drops the reference — the bytes stay on the heap until GC reuses them. This issue is the concrete hardening for the "no idle timeout / master key handling" gap in #63.

Technical Requirements

  1. Integrate the memguard library into the application.

  2. Immediately after deriving the Master Key at login/registration, split it into two components via an XOR loop (A ⊕ B = C).

  3. Store Piece A in memory inside an unswappable, encrypted memguard.Enclave.

  4. Store Piece B securely outside the app heap inside the macOS Keychain (or the equivalent OS secret store on other platforms, e.g. via the existing internal/platform/keyring wrapper).

  5. Immediately overwrite the temporary, raw Master Key bytes with zeros (0x00) to scrub them from the Go garbage collector's scope.

  6. Implement a dynamic reconstruction function: whenever a file operation occurs,

    • read Piece B from Keychain,
    • pull Piece A from the enclave,
    • XOR them back together inside a temporary memguard.LockedBuffer,
    • execute the AES operation,
    • immediately invoke .Destroy().

Acceptance Criteria

  • The raw plaintext Master Key never stays resident in a standard Go struct or global variable.
  • Memory profiling shows no persistent plaintext keys sitting on the Go heap.
  • File decryption successfully reconstructs the key on-demand via XOR without asking the user to re-enter their password.
  • Temporary buffers holding reconstructed keys are explicitly zeroed/destroyed immediately after execution.

Logout / session expiry handling

  • On Logout (Service.Logout, service.go:492): destroy Piece A in the enclave, wipe the locked buffer, and zero any transient reconstruction buffers. Decide whether Piece B is also deleted from the Keychain at logout or kept for the next login — it should be kept (it is useless without Piece A and is derived from the session), so the app must also handle the case where a Keychain Piece B exists but no enclave Piece A does (e.g. after a crash/restart) and clean it up.
  • On idle timeout / auto-lock (see Session never expires: add inactivity timeout / auto-lock for signed-in desktop session #63): destroy the enclave piece and zero buffers so the master key is fully out of memory while the app stays open; reconstruct on unlock would require the password again — document that trade-off explicitly.
  • On session expiry / app restart: the session is in-memory only, so both pieces are already gone; ensure no orphaned Keychain entries accumulate (see the crash case above).

Scope

  • internal/shared/crypto — XOR split/combine helpers (tested).
  • internal/features/auth/service.go — store/derive/teardown integration.
  • internal/features/settings + upload/download — swap MasterKey() consumers to the on-demand reconstruction API.
  • Deps: add github.com/awnumar/memguard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    authenticationAuthentication related issuesenhancementNew feature or requestgo-internalGo backend related changes

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions