Expand description
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 andgit statusstays 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(featurewrap) — 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) andStandalonePq(age’s hybrid ML-KEM-768 + X25519 recipient, oneage1pq1…key per member device fromgunnar crypt keygen). -
pq(featurewrap) — that hybrid recipient and its identity, until an age release carries its own. -
diff(featurediff) — 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.
Re-exports§
pub use zeroize;
Modules§
- diff
- Line diffs of DECRYPTED content, computed where the key is: in the CLI
(
gunnar crypt diff) and in the browser console’s wasm. The server never sees plaintext, so it cannot diff an encrypted repository; there are no pre-made diffs either (they would only cost space). Both sides run this same function, so a diff reads the same in the terminal and in the browser. - envelope
- One encrypted file (blob). Two versions; this code writes v2 and reads both.
- keyring
- The repository’s data keys: one 64-byte AES-256-SIV key per EPOCH.
- members
.gunnar-crypt/members: who the key ring is wrapped to, by PUBLIC key.- paths
- Where the scheme’s own files live in a repository.
- policy
- What a server checks on a push to an ENCRYPTED repository.
- pq
- age’s hybrid post-quantum recipient,
mlkem768x25519(c2sp.org/age): anage1pq1…public key and itsAGE-SECRET-KEY-PQ-1…identity. - wrap
- The key ring, wrapped to every member key with age, and unwrapped with an SSH private key FILE or a post-quantum identity file.
Constants§
- DRIVER
- The name of the git filter, diff and merge drivers.
- GITATTRIBUTES_
LINES - The
.gitattributeslinesgunnar crypt initwrites.
Functions§
- diff_
blobs - The diff between two stored blobs of one path (
None= the path does not exist on that side), opened withring. Whatgunnar crypt diffprints and what the console shows for an encrypted repository. - open_
blob - Open a blob as text: an envelope is opened with
ring; anything else is returned as it is (history from beforegunnar crypt initis plaintext). - random_
bytes Nbytes from the OS random source (pairing tokens, keys).