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
/// The error returned by [`Hasher::try_finalize()`](crate::Hasher::try_finalize()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Detected SHA-1 collision attack with digest {digest}")]
CollisionAttack { digest: crate::ObjectId },
}
pub(super) mod _impl {
#[cfg(feature = "sha1")]
use sha1_checked::{CollisionResult, Digest};
#[cfg(all(not(feature = "sha1"), feature = "sha256"))]
use sha2::Digest;
use crate::hasher::Error;
/// Hash implementations that can be used once.
#[derive(Clone)]
pub enum Hasher {
/// An implementation of the SHA1 hash.
///
/// We use [`sha1_checked`] to implement the same collision detection algorithm as Git.
#[cfg(feature = "sha1")]
Sha1(sha1_checked::Sha1),
/// An implementation of the SHA256 hash.
#[cfg(feature = "sha256")]
Sha256(sha2::Sha256),
}
impl Hasher {
/// Let's not make this public to force people to go through [`hasher()`].
#[cfg(feature = "sha1")]
fn new_sha1() -> Self {
// This matches the configuration used by Git, which only uses
// the collision detection to bail out, rather than computing
// alternate “safe hashes” for inputs where a collision attack
// was detected.
Self::Sha1(sha1_checked::Builder::default().safe_hash(false).build())
}
/// Let's not make this public to force people to go through [`hasher()`].
#[cfg(feature = "sha256")]
fn new_sha256() -> Self {
Self::Sha256(sha2::Sha256::new())
}
}
impl Hasher {
/// Digest the given `bytes`.
pub fn update(&mut self, bytes: &[u8]) {
match self {
#[cfg(feature = "sha1")]
Hasher::Sha1(sha1) => sha1.update(bytes),
#[cfg(feature = "sha256")]
Hasher::Sha256(sha256) => sha256.update(bytes),
}
}
/// Finalize the hash and produce an object id.
///
/// Returns [`Error`] if a collision attack is detected.
// TODO: Since SHA-256 has an infallible `finalize`, it might be worth investigating
// turning the return type into `Result<crate::ObjectId, Infallible>` when this crate is
// compiled with SHA-256 support only.
#[inline]
pub fn try_finalize(self) -> Result<crate::ObjectId, Error> {
match self {
#[cfg(feature = "sha1")]
Hasher::Sha1(sha1) => match sha1.try_finalize() {
CollisionResult::Ok(digest) => Ok(crate::ObjectId::Sha1(digest.into())),
CollisionResult::Mitigated(_) => {
// SAFETY: `CollisionResult::Mitigated` is only
// returned when `safe_hash()` is on. `Hasher`’s field
// is private, and we only construct the SHA-1 variant
// via `Hasher::new_sha1()` (and thus through `hasher()`),
// which configures the builder with `safe_hash(false)`.
//
// As of Rust 1.84.1, the compiler can’t figure out
// this function cannot panic without this.
#[allow(unsafe_code)]
unsafe {
std::hint::unreachable_unchecked()
}
}
CollisionResult::Collision(digest) => Err(Error::CollisionAttack {
digest: crate::ObjectId::Sha1(digest.into()),
}),
},
#[cfg(feature = "sha256")]
Hasher::Sha256(sha256) => Ok(crate::ObjectId::Sha256(sha256.finalize().into())),
}
}
}
/// Produce a hasher suitable for the given `kind` of hash.
#[inline]
pub fn hasher(kind: crate::Kind) -> Hasher {
match kind {
#[cfg(feature = "sha1")]
crate::Kind::Sha1 => Hasher::new_sha1(),
#[cfg(feature = "sha256")]
crate::Kind::Sha256 => Hasher::new_sha256(),
}
}
}