Skip to content

Latest commit

Β 

History

History
113 lines (91 loc) Β· 4.47 KB

File metadata and controls

113 lines (91 loc) Β· 4.47 KB

Architecture

Visual map of how rsecure flows internally, from the CLI entry point down to the AES-256-GCM STREAM core. Diagrams are Mermaid 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 for the module layout and 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.

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"]

    %% --- --hide-name output naming (encrypt) ---
    encs -->|--hide-name| opq["opaque_enc_path<br/>random 32-hex .enc name"]

    %% --- 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 -->|--hide-name| np["prepend [u32 len][name]<br/>to plaintext Β· FLAG_ENCRYPTED_NAME"]
    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["drive_decrypt_loop<br/>AES-256-GCM STREAM (decrypt)<br/>requires terminal encrypt_last<br/>(truncation β†’ error)"]
    dv3 --> gcmD
    gcmD -->|has_encrypted_name| nsw["NameStrippingWriter<br/>peel name β†’ validate_embedded_name<br/>β†’ recovered_dest"]
Loading

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.

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)
        Note over R,G: stream must end on encrypt_last;<br/>EOF at a chunk boundary β†’ truncation error
        G-->>R: plaintext chunks (verify tag per chunk)
        opt FLAG_ENCRYPTED_NAME set
            Note over R: NameStrippingWriter peels [u32 len][name],<br/>validate_embedded_name, restore original name
        end
        R->>F: fs::rename tmp β†’ final ( -r removes .enc )
    end
Loading

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:

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