Skip to content

Commit 4660f7a

Browse files
authored
Merge pull request #1915 from emilazy/push-qvyqmopsoltr
feat!: detect SHA‐1 collision attacks
2 parents cd96b64 + 4501086 commit 4660f7a

File tree

86 files changed

+800
-709
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+800
-709
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ jobs:
391391
- name: features of gix-features
392392
run: |
393393
set +x
394-
for feature in progress parallel io-pipe crc32 zlib zlib-rust-backend fast-sha1 rustsha1 cache-efficiency-debug; do
394+
for feature in progress parallel io-pipe crc32 zlib zlib-rust-backend cache-efficiency-debug; do
395395
(cd gix-features && cargo build --features "$feature" --target "$TARGET")
396396
done
397397
- name: crates with 'wasm' feature

.github/workflows/release.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,7 @@ jobs:
137137
os: windows-latest
138138
- target: aarch64-pc-windows-msvc
139139
os: windows-latest
140-
# on linux we build with musl which causes trouble with open-ssl. For now, just build max-pure there
141-
# even though we could also build with `--features max-control,http-client-reqwest,gitoxide-core-blocking-client,gix-features/fast-sha1` for fast hashing.
140+
# on linux we build with musl which causes trouble with open-ssl. For now, just build max-pure there.
142141
# It's a TODO.
143142
exclude:
144143
- target: x86_64-unknown-linux-musl

Cargo.lock

+6-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+8-12
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ max = ["max-control", "fast", "gitoxide-core-tools-query", "gitoxide-core-tools-
4545
## transports as it uses Rust's HTTP implementation.
4646
##
4747
## As fast as possible, with TUI progress, progress line rendering with auto-configuration, all transports available but less mature pure Rust HTTP implementation, all `ein` tools, CLI colors and local-time support, JSON output, regex support for rev-specs.
48-
max-pure = ["max-control", "gix-features/rustsha1", "gix-features/zlib-rust-backend", "http-client-reqwest", "gitoxide-core-blocking-client"]
48+
max-pure = ["max-control", "gix-features/zlib-rust-backend", "http-client-reqwest", "gitoxide-core-blocking-client"]
4949

5050
## Like `max`, but with more control for configuration. See the *Package Maintainers* headline for more information.
5151
max-control = ["tracing", "fast-safe", "pretty-cli", "gitoxide-core-tools", "prodash-render-line", "prodash-render-tui", "prodash/render-line-autoconfigure", "gix/revparse-regex"]
@@ -60,7 +60,7 @@ lean = ["fast", "tracing", "pretty-cli", "http-client-curl", "gitoxide-core-tool
6060
## This build is essentially limited to local operations without any fanciness.
6161
##
6262
## Optimized for size, no parallelism thus much slower, progress line rendering.
63-
small = ["pretty-cli", "gix-features/rustsha1", "gix-features/zlib-rust-backend", "prodash-render-line", "is-terminal"]
63+
small = ["pretty-cli", "gix-features/zlib-rust-backend", "prodash-render-line", "is-terminal"]
6464

6565
## Like lean, but uses Rusts async implementations for networking.
6666
##
@@ -74,7 +74,7 @@ small = ["pretty-cli", "gix-features/rustsha1", "gix-features/zlib-rust-backend"
7474
lean-async = ["fast", "tracing", "pretty-cli", "gitoxide-core-tools", "gitoxide-core-tools-query", "gitoxide-core-tools-corpus", "gitoxide-core-async-client", "prodash-render-line"]
7575

7676
#! ### Package Maintainers
77-
#! `*-control` features leave it to you to configure C libraries, involving choices for `zlib`, ! hashing and transport implementation.
77+
#! `*-control` features leave it to you to configure C libraries, involving choices for `zlib` and transport implementation.
7878
#!
7979
#! Additional features *can* be provided with `--features` and are handled by the [`gix-features` crate](https://docs.rs/gix-features/latest).
8080
#! If nothing else is specified, the Rust implementation is used. ! Note that only one feature of each section can be enabled at a time.
@@ -84,28 +84,25 @@ lean-async = ["fast", "tracing", "pretty-cli", "gitoxide-core-tools", "gitoxide-
8484
#! - `gix-features/zlib-ng-compat`
8585
#! - `gix-features/zlib-stock`
8686
#! - `gix-features/zlib-rust-backend` (*default if no choice is made*)
87-
#! * **sha1**
88-
#! - `gix-features/fast-sha1`
89-
#! - `gix-features/rustsha1` (*default if no choice is made*)
9087
#! * **HTTP** - see the *Building Blocks for mutually exclusive networking* headline
9188
#!
9289
#! #### Examples
9390
#!
9491
#! * `cargo build --release --no-default-features --features max-control,gix-features/zlib-stock,gitoxide-core-blocking-client,http-client-curl`
9592
#! - Create a build just like `max`, but using the stock `zlib` library instead of `zlib-ng`
96-
#! * `cargo build --release --no-default-features --features max-control,http-client-reqwest,gitoxide-core-blocking-client,gix-features/fast-sha1`
97-
#! - Create a build just like `max-pure`, but with faster hashing due to `fast-sha1`.
93+
#! * `cargo build --release --no-default-features --features max-control,http-client-reqwest,gitoxide-core-blocking-client,gix-features/zlib-ng`
94+
#! - Create a build just like `max-pure`, but with faster compression due to `zlib-ng`.
9895

9996
#! ### Building Blocks
10097
#! Typical combinations of features of our dependencies, some of which are referred to in the `gitoxide` crate's code for conditional compilation.
10198

10299
## Makes the crate execute as fast as possible by supporting parallel computation of otherwise long-running functions
103-
## as well as fast, hardware accelerated hashing, along with a faster zlib backend.
100+
## as well as a faster zlib backend.
104101
## If disabled, the binary will be visibly smaller.
105102
fast = ["gix/max-performance", "gix/comfort"]
106103

107104
## Makes the crate execute as fast as possible by supporting parallel computation of otherwise long-running functions
108-
## as well as fast, hardware accelerated hashing, along with a faster zlib backend.
105+
## as well as a faster zlib backend.
109106
## If disabled, the binary will be visibly smaller.
110107
fast-safe = ["gix/max-performance-safe", "gix/comfort"]
111108

@@ -205,8 +202,7 @@ gix-hash = { opt-level = 3 }
205202
gix-actor = { opt-level = 3 }
206203
gix-config = { opt-level = 3 }
207204
miniz_oxide = { opt-level = 3 }
208-
sha1 = { opt-level = 3 }
209-
sha1_smol = { opt-level = 3 }
205+
sha1-checked = { opt-level = 3 }
210206

211207
[profile.release]
212208
overflow-checks = false

SHORTCOMINGS.md

-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,3 @@ This file is for tracking features that are less well implemented or less powerf
3535
* **gix-url** _might_ be more restrictive than what git allows as for the most part, it uses a browser grade URL parser.
3636
* Thus far there is no proof for this, and as _potential remedy_ we could certainly re-implement exactly what git does
3737
to handle its URLs.
38-
39-
### `gix-features`
40-
41-
* **sha1** isn't hardened (i.e. doesn't have collision detection). Needs [to be contributed](https://github.com/GitoxideLabs/gitoxide/issues/585).

crate-status.md

-2
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,6 @@ See its [README.md](https://github.com/GitoxideLabs/gitoxide/blob/main/gix-lock/
894894
* `in_parallel`
895895
* `join`
896896
* _When off all functions execute serially_
897-
* **fast-sha1**
898-
* provides a faster SHA1 implementation using CPU intrinsics
899897
* [x] API documentation
900898

901899
### gix-tui

gitoxide-core/src/pack/explode.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ enum Error {
7878
},
7979
#[error("Object didn't verify after right after writing it")]
8080
Verify(#[from] objs::data::verify::Error),
81-
#[error("{kind} object {expected} wasn't re-encoded without change - new hash is {actual}")]
81+
#[error("{kind} object wasn't re-encoded without change")]
8282
ObjectEncodeMismatch {
83+
#[source]
84+
source: gix::hash::verify::Error,
8385
kind: object::Kind,
84-
actual: ObjectId,
85-
expected: ObjectId,
8686
},
8787
#[error("The recently written file for loose object {id} could not be found")]
8888
WrittenFileMissing { id: ObjectId },
@@ -201,17 +201,16 @@ pub fn pack_or_pack_index(
201201
kind: object_kind,
202202
id: index_entry.oid,
203203
})?;
204-
if written_id != index_entry.oid {
204+
if let Err(err) = written_id.verify(&index_entry.oid) {
205205
if let object::Kind::Tree = object_kind {
206206
progress.info(format!(
207207
"The tree in pack named {} was written as {} due to modes 100664 and 100640 rewritten as 100644.",
208208
index_entry.oid, written_id
209209
));
210210
} else {
211211
return Err(Error::ObjectEncodeMismatch {
212+
source: err,
212213
kind: object_kind,
213-
actual: index_entry.oid,
214-
expected: written_id,
215214
});
216215
}
217216
}

gix-commitgraph/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ doctest = false
2020
serde = ["dep:serde", "gix-hash/serde", "bstr/serde"]
2121

2222
[dependencies]
23-
gix-features = { version = "^0.40.0", path = "../gix-features", features = ["rustsha1"] }
2423
gix-hash = { version = "^0.16.0", path = "../gix-hash" }
2524
gix-chunk = { version = "^0.4.11", path = "../gix-chunk" }
2625

gix-commitgraph/src/file/verify.rs

+24-20
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@ pub enum Error<E: std::error::Error + 'static> {
2828
Filename(String),
2929
#[error("commit {id} has invalid generation {generation}")]
3030
Generation { generation: u32, id: gix_hash::ObjectId },
31-
#[error("checksum mismatch: expected {expected}, got {actual}")]
32-
Mismatch {
33-
actual: gix_hash::ObjectId,
34-
expected: gix_hash::ObjectId,
35-
},
31+
#[error(transparent)]
32+
Checksum(#[from] checksum::Error),
3633
#[error("{0}")]
3734
Processor(#[source] E),
3835
#[error("commit {id} has invalid root tree ID {root_tree_id}")]
@@ -42,6 +39,19 @@ pub enum Error<E: std::error::Error + 'static> {
4239
},
4340
}
4441

42+
///
43+
pub mod checksum {
44+
/// The error used in [`super::File::verify_checksum()`].
45+
#[derive(thiserror::Error, Debug)]
46+
#[allow(missing_docs)]
47+
pub enum Error {
48+
#[error("failed to hash commit graph file")]
49+
Hasher(#[from] gix_hash::hasher::Error),
50+
#[error(transparent)]
51+
Verify(#[from] gix_hash::verify::Error),
52+
}
53+
}
54+
4555
/// The positive result of [`File::traverse()`] providing some statistical information.
4656
#[derive(Clone, Debug, Eq, PartialEq)]
4757
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@@ -73,8 +83,7 @@ impl File {
7383
E: std::error::Error + 'static,
7484
Processor: FnMut(&file::Commit<'a>) -> Result<(), E>,
7585
{
76-
self.verify_checksum()
77-
.map_err(|(actual, expected)| Error::Mismatch { actual, expected })?;
86+
self.verify_checksum()?;
7887
verify_split_chain_filename_hash(&self.path, self.checksum()).map_err(Error::Filename)?;
7988

8089
let null_id = self.object_hash().null_ref();
@@ -138,23 +147,18 @@ impl File {
138147
/// Assure the [`checksum`][File::checksum()] matches the actual checksum over all content of this file, excluding the trailing
139148
/// checksum itself.
140149
///
141-
/// Return the actual checksum on success or `(actual checksum, expected checksum)` if there is a mismatch.
142-
pub fn verify_checksum(&self) -> Result<gix_hash::ObjectId, (gix_hash::ObjectId, gix_hash::ObjectId)> {
143-
// Even though we could use gix_features::hash::bytes_of_file(…), this would require using our own
144-
// Error type to support io::Error and Mismatch. As we only gain progress, there probably isn't much value
150+
/// Return the actual checksum on success or [`checksum::Error`] if there is a mismatch.
151+
pub fn verify_checksum(&self) -> Result<gix_hash::ObjectId, checksum::Error> {
152+
// Even though we could use gix_hash::bytes_of_file(…), this would require extending our
153+
// Error type to support io::Error. As we only gain progress, there probably isn't much value
145154
// as these files are usually small enough to process them in less than a second, even for the large ones.
146155
// But it's possible, once a progress instance is passed.
147156
let data_len_without_trailer = self.data.len() - self.hash_len;
148-
let mut hasher = gix_features::hash::hasher(self.object_hash());
157+
let mut hasher = gix_hash::hasher(self.object_hash());
149158
hasher.update(&self.data[..data_len_without_trailer]);
150-
let actual = gix_hash::ObjectId::from_bytes_or_panic(hasher.digest().as_ref());
151-
152-
let expected = self.checksum();
153-
if actual == expected {
154-
Ok(actual)
155-
} else {
156-
Err((actual, expected.into()))
157-
}
159+
let actual = hasher.try_finalize()?;
160+
actual.verify(self.checksum())?;
161+
Ok(actual)
158162
}
159163
}
160164

gix-commitgraph/src/verify.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,7 @@ impl Graph {
164164
file::verify::Error::RootTreeId { id, root_tree_id } => {
165165
file::verify::Error::RootTreeId { id, root_tree_id }
166166
}
167-
file::verify::Error::Mismatch { actual, expected } => {
168-
file::verify::Error::Mismatch { actual, expected }
169-
}
167+
file::verify::Error::Checksum(err) => file::verify::Error::Checksum(err),
170168
file::verify::Error::Generation { generation, id } => {
171169
file::verify::Error::Generation { generation, id }
172170
}

0 commit comments

Comments
 (0)