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
|
use sha1_checked::CollisionResult;
/// A hash-digest produced by a [`Hasher`] hash implementation.
pub type Digest = [u8; 20];
/// The error returned by [`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 },
}
/// A implementation of the Sha1 hash, which can be used once.
///
/// We use [`sha1_checked`] to implement the same collision detection
/// algorithm as Git.
#[derive(Clone)]
pub struct Hasher(sha1_checked::Sha1);
impl Default for Hasher {
#[inline]
fn default() -> 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_checked::Builder::default().safe_hash(false).build())
}
}
impl Hasher {
/// Digest the given `bytes`.
pub fn update(&mut self, bytes: &[u8]) {
use sha1_checked::Digest;
self.0.update(bytes);
}
/// Finalize the hash and produce an object ID.
///
/// Returns [`Error`] if a collision attack is detected.
#[inline]
pub fn try_finalize(self) -> Result<crate::ObjectId, Error> {
match self.0.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 it in the
// `Default` instance, which turns `safe_hash()` off.
//
// 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()),
}),
}
}
/// Finalize the hash and produce an object ID.
#[inline]
pub fn finalize(self) -> crate::ObjectId {
self.try_finalize().expect("Detected SHA-1 collision attack")
}
/// Finalize the hash and produce a digest.
#[inline]
pub fn digest(self) -> Digest {
self.finalize()
.as_slice()
.try_into()
.expect("SHA-1 object ID to be 20 bytes long")
}
}
/// Produce a hasher suitable for the given kind of hash.
#[inline]
pub fn hasher(kind: crate::Kind) -> Hasher {
match kind {
crate::Kind::Sha1 => Hasher::default(),
}
}
/// Hashing utilities for I/O operations.
pub mod io;
|