age-vault 0.1.0

A secure vault for managing age-encrypted accounts and data.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
# AGE-VAULT TECHNICAL WIKI

## HOME / OVERVIEW

Age Vault is a Rust library (crate) that provides a secure, file-based vault for
managing identities and encrypting data using the age encryption format. It
wraps lower-level age-crypto, age-setup, and neuxdb crates to deliver a
batteries-included solution for applications needing encrypted account
management.

Key features:

- File-based vault: single database file, encrypted with a master password.
- Account management: create, list, remove accounts with unique names and roles.
- Age keypairs: each account owns an age public/secret keypair generated on
  creation. The secret key is encrypted with a Key Encryption Key (KEK) derived
  from the master password and a random salt.
- Multi-recipient encryption: encrypt arbitrary data for one or more accounts
  using age's native multi-recipient support.
- Decryption: decrypt data using an account's secret key, which is
  transparently decrypted by the vault's KEK.
- Strong cryptographic primitives: Argon2id for key derivation, age for
  authenticated encryption, secret zeroization for sensitive material.
- Error handling: unified error type covering I/O, database, cryptographic,
  serialization, and vault-specific failures.

The crate is intended for developers who need to embed a secure,
identity-oriented encryption layer in their Rust applications.

Quick Start:

    Command: cargo add age-vault

Then in your code:

    Code: Rust
    use age_vault::{Vault, Role};
    use std::path::PathBuf;

    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let vault_path = PathBuf::from("my_vault.age");
        let master_password = "strong-master-password";

        // Create a new vault
        let mut vault = Vault::create(&vault_path, master_password)?;

        // Add accounts
        let alice = vault.add_account("alice", Role::Admin)?;
        let bob = vault.add_account("bob", Role::User)?;

        // Encrypt a message for multiple recipients
        let ciphertext = vault.encrypt_for(
            &["alice", "bob"],
            b"confidential data"
        )?;

        // Decrypt using Alice's identity
        let plaintext = vault.decrypt_with("alice", &ciphertext)?;
        assert_eq!(plaintext, b"confidential data");

        Ok(())
    }

# API REFERENCE

This section documents all public types, functions, and methods exposed by the
crate. Everything is re-exported from the crate root for convenience.

## Module: age_vault (crate root)

Public re-exports:

    * age_vault::Vault      (vault::Vault)
    * age_vault::Account    (account::Account)
    * age_vault::Role        (account::Role)
    * age_vault::Error       (error::Error)
    * age_vault::Result      (error::Result)

## Type: Role (enum)

Defined in account.rs. Represents the authorization role assigned to an
account.

Variants:

    * Admin         - Full privileges (default administrative role).
    * User          - Standard user.
    * Custom(String) - Arbitrary role defined by a string.

What: A simple tagged enum to categorize accounts. Currently the vault itself
does not enforce role-based access control (RBAC) on operations; this is left
to the consuming application. However, the enum supports serialization so roles
can be persisted and used in application-level authorization.

How: Construct directly:

    Code: Rust
    let admin = Role::Admin;
    let user = Role::User;
    let manager = Role::Custom("manager".into());

Why: Providing built-in roles and a custom variant gives flexibility for future
extensions without breaking the data model.

## Type: Account (struct)

Defined in account.rs. Represents a named identity with an age keypair stored
securely in the vault.

Fields (all public):

    Name                 Type     Description
    ----                 ----     -----------
    id                   String   Unique identifier (UUID v4).
    name                 String   Human-readable account name, must be unique
                                  within a single vault.
    role                 Role     Role assigned to the account.
    public_key           String   Age public key (e.g., "age1...") for
                                  encryption.
    encrypted_secret_key Vec<u8>  Age secret key encrypted with the vault's
                                  Key Encryption Key (KEK). Never stored in
                                  plaintext.
    enabled              bool     Whether the account is active. Disabled
                                  accounts are still stored but may be ignored
                                  by application logic.

What: An Account is the core identity object. It encapsulates everything needed
to encrypt data for this identity (via the public key) and, after KEK
decryption, decrypt data intended for it.

How: Usually created via Vault::add_account. Manual construction is possible
but requires generating a proper age keypair and encrypting the secret key
with the correct KEK.

Why: Separating the public key (plaintext) and encrypted secret key allows the
vault to list accounts and encrypt for them without exposing the master
password or KEK after opening. The secret key is only decrypted when needed for
a decryption operation, minimizing exposure time.

## Type: Vault (struct)

The primary entry point. A Vault instance holds a connection to an encrypted
database (neuxdb) and the derived Key Encryption Key (KEK) in memory.

Fields (internal):
_ db: neuxdb::Database - Embedded database file, encrypted with master pw.
_ kek: secrecy::SecretString - Key Encryption Key derived from master
password and salt. Zeroized on drop. \* salt: Vec<u8> - Random 16-byte salt for KEK derivation, also stored in DB.

Methods:

Method: Vault::create

```

Signature:
    pub fn create(
        db_path: impl Into<PathBuf>,
        master_password: &str
    ) -> Result<Self>

What: Creates a new vault file at db_path. The database is initialized with two
tables (accounts, _metadata), a random 16-byte salt is generated and stored in
_metadata, and the KEK is derived via Argon2id(password, salt). The accounts
table is created empty.

Parameters:
    Name               Type        Description
    ----               ----        -----------
    db_path            PathBuf     Path to the database file to create.
    master_password    &str        Master password; must be strong.
                                   Used to encrypt the database and derive KEK.

Returns: A Vault instance ready for account operations.

Errors: Error::Db (database creation), Error::Kdf (key derivation failure),
Error::Io.

Example:

    Code: Rust
    use age_vault::Vault;
    let vault = Vault::create("./vault.db", "secure_password")?;

Why: The design deliberately uses a random salt per vault so that even if the
same master password is reused across vaults, the derived KEK is different.
The KEK is used to protect account secret keys, while the database encryption
is handled by neuxdb's own password-based encryption (which may also use the
same master password). This provides defense-in-depth: an attacker who
compromises the database layer would still need the KEK (or master password) to
decrypt individual secret keys.

Method: Vault::open
~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn open(
        db_path: impl Into<PathBuf>,
        master_password: &str
    ) -> Result<Self>

What: Opens an existing vault file. Reads the salt from the _metadata table,
re-derives the KEK from the master password and salt, and prepares the accounts
table for use.

Parameters:
    Name               Type     Description
    ----               ----     -----------
    db_path            PathBuf  Path to existing vault database file.
    master_password    &str     Master password (must match creation password).

Returns: Vault instance if password and salt are correct.

Errors: Error::Db (database open failure), Error::DecryptionFailed (salt not
found or base64 decode failure), Error::Kdf (key derivation failure),
Error::InvalidMasterPassword (indirectly, when later operations fail).

Example:

    Code: Rust
    use age_vault::Vault;
    let vault = Vault::open("./vault.db", "secure_password")?;

Why: The KEK is re-derived every time the vault is opened. No stored hash of
the master password is compared; correctness is validated implicitly when
decrypting account secret keys later. If the password is wrong, decryption of
any secret key will fail with Error::DecryptionFailed.

Method: Vault::add_account
```

Signature:
pub fn add_account(
&mut self,
name: &str,
role: Role
) -> Result<Account>

What: Generates a new age keypair, encrypts the secret key with the vault's
KEK, and stores the resulting Account in the database. Returns the newly
created account.

Parameters:
Name Type Description
---- ---- -----------
name &str Desired account name. Must be unique within the vault.
role Role Role to assign (e.g., Role::Admin, Role::User).

Returns: The created Account (with public_key, etc.).

Errors: Error::AccountExists if name is already in use. Error::KeyGen if age
keypair generation fails. Error::Crypto if secret key encryption fails.
Error::Db or serialization errors from store.

Example:

    Code: Rust
    let alice = vault.add_account("alice", Role::Admin)?;

Why: The secret key is encrypted immediately with the KEK and never stored in
plaintext. The KEK is held only in memory as a SecretString and is zeroized
on vault drop. This reduces the risk of secret key exposure.

Method: Vault::remove_account

```

Signature:
    pub fn remove_account(&mut self, name: &str) -> Result<()>

What: Deletes the account identified by name from the database.

Parameters:
    Name    Type    Description
    ----    ----    -----------
    name    &str    Name of the account to remove.

Returns: Ok(()) on success.

Errors: Error::AccountNotFound if name does not exist. Error::Db on deletion or
commit failure.

Example:

    Code: Rust
    vault.remove_account("bob")?;

Why: The method uses the account's unique ID (derived from the name lookup)
to delete the database row, ensuring that even if multiple accounts had the same
name (which is prevented by uniqueness), the correct one is removed.

Method: Vault::list_accounts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn list_accounts(&self) -> Result<Vec<Account>>

What: Returns a vector of all accounts stored in the vault. The returned
accounts contain encrypted secret keys (still encrypted with KEK) and public
keys.

Parameters: None.

Returns: Vec<Account> of all accounts.

Errors: Error::Db, Error::Serde (deserialization), Error::DecryptionFailed
(base64 decoding issues).

Example:

    Code: Rust
    for acc in vault.list_accounts()? {
        println!("{}: {}", acc.name, acc.public_key);
    }

Why: The list is useful for displaying available recipients to a user. Note
that the encrypted secret keys are included in the returned structs; they
cannot be decrypted without the KEK, which is not exposed via this API.

Method: Vault::encrypt_for
~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn encrypt_for(
        &self,
        account_names: &[&str],
        plaintext: &[u8]
    ) -> Result<Vec<u8>>

What: Encrypts arbitrary bytes for one or more accounts using age's
multi-recipient encryption. Each named account's public key is fetched and used
as a recipient.

Parameters:
    Name             Type          Description
    ----             ----          -----------
    account_names    &[&str]       Slice of account names that will be able
                                   to decrypt.
    plaintext        &[u8]         Data to encrypt.

Returns: Ciphertext as Vec<u8>.

Errors: Error::AccountNotFound if any name does not exist. Error::Crypto if
age encryption fails.

Example:

    Code: Rust
    let cipher = vault.encrypt_for(
        &["alice", "bob"],
        b"top secret"
    )?;

Why: This abstracts away the complexity of mapping human-readable names to
public keys and calling age's multi-recipient encrypt. It ensures that the
ciphertext header contains all necessary recipient information.

Method: Vault::decrypt_with
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn decrypt_with(
        &self,
        account_name: &str,
        ciphertext: &[u8]
    ) -> Result<Vec<u8>>

What: Decrypts ciphertext that was encrypted for the named account. The
account's encrypted secret key is first decrypted using the KEK, then the
resulting age identity is used to decrypt the ciphertext.

Parameters:
    Name             Type    Description
    ----             ----    -----------
    account_name     &str    Name of the account whose secret key will be used.
    ciphertext       &[u8]   Ciphertext produced by encrypt_for or any
                             age-compatible tool.

Returns: Plaintext as Vec<u8>.

Errors: Error::AccountNotFound, Error::DecryptionFailed (if secret key
decryption fails due to wrong password/corruption, or age decryption fails).

Example:

    Code: Rust
    let plain = vault.decrypt_with("alice", &ciphertext)?;

Why: The KEK is only used temporarily to decrypt the secret key; the plain
secret key is then immediately used for decryption and dropped. This minimizes
the window during which the secret key is in plaintext memory.

Module: crypto (public functions)
---------------------------------

The crypto module provides lower-level key derivation and key-wrapping
utilities. They are public for advanced usage but are typically used internally
by Vault.

Function: derive_kek
~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn derive_kek(
        master_password: &str,
        salt_bytes: &[u8]
    ) -> Result<SecretString>

What: Derives a Key Encryption Key (KEK) from a master password and salt using
Argon2id with default parameters (memory, iterations as per argon2 crate
defaults). The output is a SecretString that zeroizes on drop.

Parameters:
    Name               Type    Description
    ----               ----    -----------
    master_password    &str    Master password.
    salt_bytes         &[u8]   Salt (recommended 16 bytes).

Returns: SecretString containing the derived KEK.

Errors: Error::Kdf if salt encoding or Argon2 hashing fails.

Function: encrypt_secret_key
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn encrypt_secret_key(
        secret_key: &str,
        kek: &str
    ) -> Result<Vec<u8>>

What: Encrypts an age secret key (in its text representation) with a KEK using
age's passphrase encryption (age_crypto::encrypt_with_passphrase). The output
is a binary ciphertext that can be stored.

Parameters:
    Name          Type    Description
    ----          ----    -----------
    secret_key    &str    Plaintext age secret key (e.g., "AGE-SECRET-KEY-...").
    kek           &str    Key encryption key (derived from derive_kek).

Returns: Encrypted bytes.

Errors: Error::Crypto if age encryption fails.

Function: decrypt_secret_key
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature:
    pub fn decrypt_secret_key(
        encrypted: &[u8],
        kek: &str
    ) -> Result<String>

What: Reverse of encrypt_secret_key. Decrypts using the KEK and returns the
UTF-8 secret key string.

Parameters:
    Name        Type    Description
    ----        ----    -----------
    encrypted   &[u8]   Encrypted secret key bytes.
    kek         &str    Key encryption key.

Returns: Decrypted age secret key string.

Errors: Error::Crypto if decryption fails (wrong KEK, corruption);
Error::DecryptionFailed if output is not valid UTF-8.

Module: store (public functions)
--------------------------------

The store module provides direct access to the underlying neuxdb database
operations. These are public but intended primarily for internal use by Vault.

Function: init_accounts_table
```

Signature: pub fn init_accounts_table(db: &mut Database) -> Result<()>
Creates the "accounts" table with columns (id, name, role, public_key,
encrypted_secret_key, enabled) if it doesn't exist. Idempotent.

Function: init_metadata_table

```

Signature: pub fn init_metadata_table(db: &mut Database) -> Result<()>
Creates the "_metadata" key-value table. Idempotent.

Function: save_account
~~~~~~~~~~~~~~~~~~~~~~

Signature: pub fn save_account(db: &mut Database, account: &Account) -> Result<()>
Serializes an account and inserts it into the accounts table. Role is stored as
JSON, encrypted secret key as base64.

Function: load_accounts
~~~~~~~~~~~~~~~~~~~~~~~

Signature: pub fn load_accounts(db: &Database) -> Result<Vec<Account>>
Reads all rows from accounts table, deserializing each.

Function: save_metadata_salt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature: pub fn save_metadata_salt(db: &mut Database, salt: &[u8]) -> Result<()>
Stores the salt bytes (base64-encoded) under key "salt" in _metadata.

Function: load_metadata_salt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Signature: pub fn load_metadata_salt(db: &Database) -> Result<Vec<u8>>
Retrieves and decodes the salt from _metadata.

Error Type: Error (enum)
-------------------------

Defined in error.rs. All operations return Result<T, Error>. The Error enum
provides both automatic conversion from dependency errors (via #[from]) and
explicit vault-specific variants.

Variants:

    * Io(std::io::Error)               - I/O error.
    * Db(neuxdb::Error)                - Database error.
    * Config(neuxcfg::NeuxcfgError)    - Configuration error.
    * Crypto(age_crypto::Error)        - Cryptographic error.
    * KeyGen(age_setup::Error)         - Key generation error.
    * Kdf(String)                      - Argon2 key derivation error.
    * Serde(serde_json::Error)         - Serialization error.
    * AccountNotFound(String)          - Account name missing.
    * AccountExists(String)            - Account name already present.
    * InvalidMasterPassword            - Master password invalid.
    * DecryptionFailed(String)         - Decryption failure.
    * EncryptionFailed(String)         - Encryption failure.

The error type implements std::error::Error and Display via thiserror. It is
Send + Sync, making it suitable for use in async contexts.

Result type alias:

    type Result<T> = std::result::Result<T, Error>;

ARCHITECTURE & INTERNALS
========================

This section explains the system design, data flow, and rationale behind key
decisions. The vault is a layered architecture where the Vault struct
orchestrates storage, cryptographic, and identity modules.

High-level component interaction diagram (text flow):

    User Application
        |
        | calls Vault::create, open, add_account, encrypt_for, decrypt_with
        |
    [Vault] -- holds Database, KEK, salt
        |  |
        |  | uses crypto::derive_kek, encrypt_secret_key, decrypt_secret_key
        |  |
        |  +---> [crypto module] -- argon2, age_crypto (passphrase encrypt)
        |  |
        |  +---> [store module] -- neuxdb (embedded database)
        |
        +-------> [account] struct and Role enum

Data Flow:

1. Vault Creation:
   - Generate 16 random bytes (salt).
   - Derive KEK = Argon2id(password, salt).
   - Create encrypted database file with the master password (neuxdb).
   - Init tables (accounts, _metadata).
   - Store salt in _metadata.
   - Commit.

2. Adding an Account:
   - Check for duplicate name.
   - Generate age keypair (age_setup::build_keypair).
   - Encrypt secret key: ciphertext = age_crypto::encrypt_with_passphrase(secret_key, KEK).
   - Build Account struct (id=UUIDv4, name, role, public_key, encrypted_secret_key, enabled=true).
   - Store via store::save_account (role -> JSON, enc_key -> base64, insert into neuxdb).
   - Commit.

3. Opening a Vault:
   - Open encrypted database with master password (neuxdb).
   - Load salt from _metadata.
   - Derive KEK = Argon2id(password, salt).
   - Accounts table is available for queries.

4. Encrypting Data for Recipients:
   - For each recipient name, fetch Account from DB (store::load_accounts, filter).
   - Collect public keys.
   - age_crypto::encrypt(plaintext, &public_keys) -> ciphertext.

5. Decrypting Data with an Account:
   - Fetch Account by name.
   - Decrypt secret key: age_crypto::decrypt_with_passphrase(encrypted_secret_key, KEK).
   - Use decrypted secret key to age_crypto::decrypt(ciphertext, &secret_key) -> plaintext.

Design Decisions:

* Defense-in-depth encryption: The vault uses two encryption layers: the neuxdb
  file is encrypted with the master password, and the account secret keys are
  encrypted with a separate KEK derived from the same password plus a unique
  salt. This means that even if the database encryption is bypassed (e.g., via
  a vulnerability in neuxdb), the attacker still needs to derive the KEK to
  recover the secret keys. The salt is stored in the _metadata table, so an
  offline attacker would need both the database file and the master password
  to access secret keys.

* Why Argon2id: Argon2id is the recommended password-hashing algorithm,
  combining resistance to side-channel and GPU attacks. Default parameters are
  used; future versions may expose tuning knobs for memory/iterations.

* Why base64 for encrypted secret keys and salt: Neuxdb stores text columns; to
  safely store arbitrary binary (ciphertext), base64 encoding ensures
  compatibility.

* Why a separate KEK: The master password is used for both the database
  encryption (neuxdb's internal mechanism) and the KEK derivation. Separating
  the KEK derivation with a salt means the key material used to protect the
  most sensitive items (age secret keys) is not directly the same as the
  database encryption key. This provides a security boundary even if the
  database's encryption key is extracted at runtime.

* The crate re-exports important types for ergonomics: users don't need to
  know the internal module structure.

GUIDES & TUTORIALS
==================

Basic Usage: Setting Up a Vault and Managing Accounts

Step 1: Create a new vault.

    Code: Rust
    use age_vault::Vault;
    let mut vault = Vault::create("identities.age", "my_master_pw")?;

Step 2: Add accounts for your users or devices.

    Code: Rust
    use age_vault::Role;
    let admin_acc = vault.add_account("admin", Role::Admin)?;
    let user_acc = vault.add_account("laptop", Role::User)?;

Step 3: Encrypt a confidential document for both admin and laptop.

    Code: Rust
    let doc = b"Quarterly financial report";
    let ciphertext = vault.encrypt_for(&["admin", "laptop"], doc)?;

Step 4: Later, decrypt the document using the admin identity.

    Code: Rust
    let recovered = vault.decrypt_with("admin", &ciphertext)?;
    assert_eq!(recovered, doc);

Step 5: Remove an account when no longer needed.

    Code: Rust
    vault.remove_account("laptop")?;

Step 6: List all current accounts.

    Code: Rust
    for acc in vault.list_accounts()? {
        println!("{} - {}", acc.name, acc.public_key);
    }

Configuration Reference
-----------------------

All configuration is currently done via compile-time feature flags or
dependency versions. The argon2 parameters are fixed to the crate's defaults;
if stronger parameters are needed, you might need to fork or propose a feature
to expose settings.

The vault database is a single neuxdb file; its path is determined at creation
time. You may want to place it in a suitable application data directory.

Troubleshooting FAQ
-------------------

Q: I get "Invalid master password" when opening a vault.
A: The vault does not store a password hash; it will only report
InvalidMasterPassword indirectly. Most likely you will receive a
DecryptionFailed error when trying to decrypt a secret key. Double-check the
password and ensure the vault file is not corrupted.

Q: An account name already exists. How can I handle it?
A: The add_account method returns Error::AccountExists. You can either remove
the existing account first or choose a different name.

Q: Can I rename an account?
A: Not directly. You can remove it and re-add with a new name, but note that
the new account will have a different keypair, so previously encrypted data for
the old account will be inaccessible.

Q: How do I change the master password?
A: Currently not supported natively. You would need to create a new vault,
iterate over all accounts, decrypt each secret key with the old KEK, re-encrypt
with the new KEK, and save them in the new vault. Future versions may add a
password change function.

CONTRIBUTING & DEVELOPMENT
==========================

This section is for developers who want to modify the crate itself.

Setup Instructions
------------------

1. Clone the repository (see repository URL in Cargo.toml).

2. Ensure you have a recent Rust toolchain (edition 2024, Rust >= 1.70 likely).

3. Build: Command: cargo build

4. Run tests: Command: cargo test

5. Generate local documentation: Command: cargo doc --open

Coding Standards
----------------

* Follow standard Rust conventions (rustfmt, clippy).
* All public items must have documentation comments (enforced by
  #![warn(missing_docs)] in lib.rs or Cargo.toml lints).
* Unsafe code is warned against; if absolutely necessary, document safety
  invariants.
* Use thiserror for error types, secrecy for sensitive strings, and keep the
  crate no_std-friendly (currently uses std, but might be considered).
* Lints: unsafe_code = "warn", missing_docs = "warn", unused = "warn",
  deprecated = "warn" (see Cargo.toml).

Testing Strategy
----------------

* Unit tests are located in each module (e.g., vault.rs) but not shown here;
  they likely use the test vault paths as seen in doc examples. The dev
  dependency `criterion` suggests benchmarks exist under benches/.
* Integration tests may exist under tests/ (excluded by package metadata).
* Run all tests: cargo test --all-features

Pull Request Process
--------------------

* Fork the repository.
* Create a feature branch: git checkout -b feat/your-feature
* Ensure all tests pass: cargo test
* If adding public API, update documentation accordingly.
* Submit a PR with a clear description.

License
=======

This crate is licensed under the MIT license. See the LICENSE file in the
repository root.

Repository: https://github.com/neuxdotdev/age-vault
Documentation: https://docs.rs/age-vault
Authors: neuxdotdev <neuxdev1@gmail.com>