Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 201 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ path = "src/bin/git-remote-e2ee.rs"
[dependencies]
anyhow = "1.0"
base64 = "0.22"
chacha20poly1305 = { version = "0.10", features = ["stream"] }
aead-stream = "0.6"
chacha20poly1305 = "0.11"
clap = { version = "4.5", features = ["derive"] }
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
fs2 = "0.4"
Expand All @@ -33,7 +34,7 @@ rand_09 = { package = "rand", version = "0.9", features = ["os_rng", "std_rng"],
rand_chacha = "0.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10"
sha2 = { version = "0.10", features = ["asm"] }
subtle = "2.6"
thiserror = "2.0"
tempfile = "3.14"
Expand Down
372 changes: 366 additions & 6 deletions docs/benchmarks.md

Large diffs are not rendered by default.

44 changes: 37 additions & 7 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ administrative CAS is never automatically rebased.
- New data is generated with `git pack-objects --stdout --revs`.
- Pack output is encrypted as 1 MiB authenticated segments directly into a
backend-owned stage; it is never collected into a whole-pack Rust buffer.
- Pack ciphertext hashing uses `sha2`'s runtime-selected hardware assembly
when available, with its portable fallback otherwise. The ChaCha20 backend
selects NEON automatically on supported AArch64 targets, including for
`cargo install --git` builds. The STREAM implementation is provided by
RustCrypto's `aead-stream` crate; switching to it preserves the BE32
construction. These code-generation choices do not change the stream format
or affect Git configuration in the user's inner repository.
`GIT_REMOTE_E2EE_TRACE=1` reports the compiled ChaCha20 backend.
- Existing remote tips present locally are pack exclusions.
- Packs are decrypted segment by segment directly into `git index-pack`
oldest-first.
Expand Down Expand Up @@ -192,13 +200,35 @@ and membership changes are encrypted; object-size patterns can still suggest
that a membership transition occurred. Inner Git metadata remains encrypted.

Each helper process fetches the carrier branch into a persistent bare object
cache, then creates a disposable checkout that borrows the cache's objects.
Fetches transfer only objects missing from the cache. The cache is keyed by the
SHA-256 of a normalized remote URL; a file lock serializes updates across helper
processes, and automatic garbage collection is disabled so borrowed objects
remain available. The carrier branch is fetched again immediately before each
CAS, and the CAS remains a normal fast-forward push. A stale cache therefore
cannot authorize a write against an outdated branch tip.
cache, then creates a disposable Git directory with an alternate pointing at
the cache. It seeds a process-local index from the base commit with `git
read-tree`, without checking out or copying the historical `e2ee/` files.
Carrier object reads stream blobs through `git cat-file --batch`; publication
stages only the new chunk paths and `HEAD` with a NUL-delimited path list, so
Git does not scan the complete carrier tree. The temporary index keeps
concurrent helper processes from sharing mutable checkout state.

The cache is keyed by the SHA-256 of a normalized remote URL; a file lock
serializes cache updates and publications across helper processes. Automatic
garbage collection is disabled so borrowed objects remain available. During a
publication, Git writes new objects and the commit into the locked cache object
database. Only after the normal fast-forward push wins does the cache's carrier
ref advance to the new commit. A lost CAS can leave unreachable ciphertext in
the cache, but it does not move the ref. The carrier branch is fetched again
immediately before each CAS, so a stale cache cannot authorize a write against
an outdated branch tip.

Carrier Git repositories touched by the helper set `core.compression=0`,
`core.looseCompression=0`, `pack.compression=0`, `pack.window=0`,
`pack.depth=0`, and `core.bigFileThreshold=1m`. The committed root
`.gitattributes` marks `e2ee/**` as `-delta`. These settings avoid spending CPU
compressing or deltifying ciphertext, which is already encrypted and
incompressible. They are performance settings, not integrity assumptions;
normal object IDs and the signed manifest checks remain in force. A Git server
must consult that attribute tree for its own `upload-pack` delta search to
avoid server-side deltification. The benchmark's local bare receiver sets
`attr.tree=refs/heads/git-remote-e2ee`; hosted servers control this setting and
may have different server-side pack behavior.

Recovery publishes a fresh cache generation instead of replacing objects that
an in-flight temporary checkout may still borrow. Obsolete generations are
Expand Down
Loading
Loading