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
//! **Encrypted repositories**: the operator of a gunnar server cannot read a
//! repository's content; only its members can, each with their own
//! encryption key, opened by their SSH key or their security key.
//!
//! 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.
//! * `userkey` (feature `user-key`) — the USER's encryption key: one key
//! pair per user (age X25519, or the hybrid `age1pq1…` for quantum-safe
//! repositories), its private half stored on the server only as age files,
//! one per unlock method: an SSH key file (CLI), a security key's WebAuthn
//! PRF / FIDO2 hmac-secret output through HKDF-SHA256 (browser and CLI), an
//! `age1pq1…` key file, or a printed recovery code (age's scrypt).
//! * `wrap` (feature `user-key`) — the age wrap of the key ring to every
//! member's user public key. A repository never names an SSH key or a
//! security key; adding or removing an unlock method touches no repository.
//! Feature `wrap` adds the SSH unlock method and the quantum-safe user key.
//! * `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 → open the user key with an
//! unlock method → unwrap the key ring → open and diff locally → seal before
//! push. The CLI opens the user key with the SSH key file in `~/.ssh` (or a
//! security key over FIDO2 hmac-secret); the browser opens it with a security
//! key (WebAuthn PRF), in the tab, with no helper. `tests/vectors.rs` and
//! `tests/userkey.rs` run 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`,
//! `diff` and `user-key`, and the CLI takes everything.
/// zeroize, for callers holding key material.
pub use zeroize;
/// `N` bytes from the OS random source (keys, recovery codes).
/// 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: & = &;