rsecure 0.8.0

A simple file encryption and decryption tool using AES-GCM.
# Architecture

Visual map of how `rsecure` flows internally, from the CLI entry point down to the
AES-256-GCM STREAM core. Diagrams are [Mermaid](https://mermaid.js.org/) so GitHub
renders them inline and they version alongside the code.

> Keep these in sync with the source. They were derived from the call graph in
> `src/` — see [`AGENTS.md`]../AGENTS.md for the module layout and
> [`SECURITY.md`]../SECURITY.md for the cryptographic details and on-disk format.

## Call flow

High-level map of the calls: entry point → CLI parsing → subcommand dispatch → key
resolution (keyfile vs Argon2id passphrase) → per-file HKDF subkey → AES-GCM STREAM.

```mermaid
flowchart TD
    main["main()<br/>src/main.rs"] --> parse["RsecureCliArgs::parse() — clap"]
    parse --> cmd{"match Commands"}

    cmd -->|CreateKey| ck["create_key::run<br/>write 32-byte AES-256 key"]
    cmd -->|Encrypt| enc["encrypt_file::run"]
    cmd -->|Decrypt| dec["decrypt_file::run"]

    %% --- master key resolution (shared) ---
    subgraph KEY["Master key resolution"]
      opk["open_private_key<br/>(-p keyfile)"]
      pp["prompt_passphrase<br/>(--passphrase)"] --> argon["derive_master_key_argon2<br/>Argon2id · src/crypto.rs"]
    end
    enc --> KEY
    dec --> KEY

    %% --- file / directory fan-out (rayon) ---
    enc --> fe{"is_file / is_dir"}
    dec --> fd{"is_file / is_dir"}
    fe --> encs["encrypt_file_stream"] --> etp["encrypt_to_path"]
    fd --> decs["decrypt_file_stream"] --> dtp["decrypt_to_path"]

    %% --- decrypt: read header, pick subkey version ---
    dtp --> ph["parse_header<br/>src/format.rs"] --> ver{"version?"}
    ver -->|v2| dv2["derive_subkey_v2 (HKDF)"]
    ver -->|v3| dv3["derive_subkey_v3 (HKDF)"]

    %% --- encrypt: always v3 ---
    etp --> dv3e["derive_subkey_v3<br/>HKDF-SHA256 · per-file subkey"]

    %% --- AES-GCM STREAM core ---
    dv3e --> gcmE["AES-256-GCM STREAM<br/>EncryptorBE32 · 128 KiB chunks<br/>header bound as AAD"]
    dv2 --> gcmD["AES-256-GCM STREAM (decrypt)"]
    dv3 --> gcmD
```

## Decrypt sequence

Temporal ordering of a single-file decrypt. The header is read first because it
carries the format version, the keyfile/passphrase flag, the HKDF salt, and (in
passphrase mode) the Argon2 parameters and salt needed to reconstruct the master key.

```mermaid
sequenceDiagram
    autonumber
    actor U as User
    participant M as main / clap
    participant R as decrypt_file::run
    participant F as file_ops
    participant H as format::parse_header
    participant K as crypto (Argon2id + HKDF)
    participant G as AES-256-GCM STREAM

    U->>M: rsecure decrypt -s file.enc [-p key | --passphrase]
    M->>R: dispatch Commands::Decrypt
    R->>F: is_file / is_dir (fan-out targets)

    loop each .enc file
        R->>H: parse_header(file)
        H-->>R: version, flags, hkdf_salt, argon2 params/salt
        Note over R,H: AAD = magic+version+flags+chunk_size+salt<br/>tampering fails the first GCM tag

        alt keyfile mode (-p)
            R->>F: open_private_key → master key
        else passphrase mode
            R->>F: prompt_passphrase
            F->>K: derive_master_key_argon2(pass, salt, params)
            K-->>R: master key (zeroized)
        end

        R->>K: derive_subkey_v2/v3(master_key, hkdf_salt)
        K-->>R: per-file AES-256 subkey
        R->>G: drive_decrypt_loop (128 KiB chunks)
        G-->>R: plaintext chunks (verify tag per chunk)
        R->>F: fs::rename tmp → final ( -r removes .enc )
    end
```

## Regenerating these diagrams

The call structure is indexed in `codebase-memory`. To refresh after code changes,
re-index the repo and re-derive the flow with `trace_path` / `search_graph` rather
than editing the Mermaid by hand:

```text
index_repository(repo_path=".")
trace_path(function_name="main", direction="outbound", depth=4)
```