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
//! **Encrypted repositories**: the operator of a gunnar server cannot read a
//! repository's content; only holders of a member's SSH private key can.
//!
//! The design, the threat model and what still leaks are in
//! `.nornir/encrypted-repos.md`. This crate is the part every door shares:
//!
//! * [`envelope`] — the format of one encrypted file (blob): a 13-byte header
//! naming the key epoch, then AES-256-SIV over the content. Deterministic on
//! purpose: the same content under the same key gives the same blob, so git's
//! clean filter is stable and `git status` stays quiet, and the blob id is a
//! KEYED id (it depends on the secret key), so nobody without the key can
//! confirm that a known file is in the repository.
//! * [`keyring`] — the repository's data keys, one per epoch. A rotation adds
//! an epoch; old epochs stay so members can still read old content.
//! * [`members`] — the plaintext list of members' PUBLIC keys
//! (`.gunnar-crypt/members`). The key ring is wrapped to every key in it.
//! * [`policy`] — what a server checks on a push to an encrypted repository:
//! every blob is an envelope, except the three files the scheme itself needs.
//! It reads formats, never keys.
//! * `wrap` (feature `wrap`) — the age wrap of the key ring, behind one
//! trait with two schemes chosen per repository by its level: `SshClassic`
//! (members' SSH public keys, `ssh-ed25519`/`ssh-rsa`; unwrapped with an SSH
//! private key FILE — ssh-agent cannot help, its protocol only signs) and
//! `StandalonePq` (age's hybrid ML-KEM-768 + X25519 recipient, one
//! `age1pq1…` key per member device from `gunnar crypt keygen`).
//! * `pq` (feature `wrap`) — that hybrid recipient and its identity, until an
//! age release carries its own.
//!
//! * [`diff`] (feature `diff`) — line diffs of decrypted content. The server
//! cannot diff ciphertext and keeps no pre-made diffs; the client does it.
//!
//! **One crate, two targets.** The CLI and the browser console run this same
//! code, native and wasm32: fetch ciphertext → unwrap the key ring → open and
//! diff locally → seal before push. The only difference is where the SSH
//! private key lives: the CLI reads `~/.ssh` itself; the browser asks the
//! user's own `gunnar unlock` agent (the same CLI, on 127.0.0.1) to unwrap the
//! ring, and is never given the SSH key. `tests/vectors.rs` runs the same
//! vectors on both targets.
//!
//! Who links what: gunnar-server takes `default-features = false` (policy and
//! format only; no cipher, no key), the browser console takes `cipher` and
//! `diff`, and the CLI takes everything.
/// zeroize, for callers holding key material.
pub use zeroize;
/// `N` bytes from the OS random source (pairing tokens, keys).
/// Open a blob as text: an envelope is opened with `ring`; anything else is
/// returned as it is (history from before `gunnar crypt init` is plaintext).
/// The diff between two stored blobs of one path (`None` = the path does not
/// exist on that side), opened with `ring`. What `gunnar crypt diff` prints
/// and what the console shows for an encrypted repository.
/// Where the scheme's own files live in a repository.
/// The name of the git filter, diff and merge drivers.
pub const DRIVER: &str = "gunnar-crypt";
/// The `.gitattributes` lines `gunnar crypt init` writes.
///
/// Everything goes through the filter except the scheme's own files, which
/// must stay readable before a clone is unlocked.
pub const GITATTRIBUTES_LINES: & = &;