|
| 1 | +/// The error returned by [`Hasher::try_finalize()`](crate::Hasher::try_finalize()). |
| 2 | +#[derive(Debug, thiserror::Error)] |
| 3 | +#[allow(missing_docs)] |
| 4 | +pub enum Error { |
| 5 | + #[error("Detected SHA-1 collision attack with digest {digest}")] |
| 6 | + CollisionAttack { digest: crate::ObjectId }, |
| 7 | +} |
| 8 | + |
| 9 | +pub(super) mod _impl { |
| 10 | + use crate::hasher::Error; |
| 11 | + use sha1_checked::{CollisionResult, Digest}; |
| 12 | + |
| 13 | + /// An implementation of the Sha1 hash, which can be used once. |
| 14 | + /// |
| 15 | + /// We use [`sha1_checked`] to implement the same collision detection |
| 16 | + /// algorithm as Git. |
| 17 | + #[derive(Clone)] |
| 18 | + pub struct Hasher(sha1_checked::Sha1); |
| 19 | + |
| 20 | + impl Hasher { |
| 21 | + /// Let's not provide a public default implementation to force people to go through [`hasher()`]. |
| 22 | + fn default() -> Self { |
| 23 | + // This matches the configuration used by Git, which only uses |
| 24 | + // the collision detection to bail out, rather than computing |
| 25 | + // alternate “safe hashes” for inputs where a collision attack |
| 26 | + // was detected. |
| 27 | + Self(sha1_checked::Builder::default().safe_hash(false).build()) |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + impl Hasher { |
| 32 | + /// Digest the given `bytes`. |
| 33 | + pub fn update(&mut self, bytes: &[u8]) { |
| 34 | + self.0.update(bytes); |
| 35 | + } |
| 36 | + |
| 37 | + /// Finalize the hash and produce an object ID. |
| 38 | + /// |
| 39 | + /// Returns [`Error`] if a collision attack is detected. |
| 40 | + #[inline] |
| 41 | + pub fn try_finalize(self) -> Result<crate::ObjectId, Error> { |
| 42 | + match self.0.try_finalize() { |
| 43 | + CollisionResult::Ok(digest) => Ok(crate::ObjectId::Sha1(digest.into())), |
| 44 | + CollisionResult::Mitigated(_) => { |
| 45 | + // SAFETY: `CollisionResult::Mitigated` is only |
| 46 | + // returned when `safe_hash()` is on. `Hasher`’s field |
| 47 | + // is private, and we only construct it in the |
| 48 | + // `Default` instance, which turns `safe_hash()` off. |
| 49 | + // |
| 50 | + // As of Rust 1.84.1, the compiler can’t figure out |
| 51 | + // this function cannot panic without this. |
| 52 | + #[allow(unsafe_code)] |
| 53 | + unsafe { |
| 54 | + std::hint::unreachable_unchecked() |
| 55 | + } |
| 56 | + } |
| 57 | + CollisionResult::Collision(digest) => Err(Error::CollisionAttack { |
| 58 | + digest: crate::ObjectId::Sha1(digest.into()), |
| 59 | + }), |
| 60 | + } |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + /// Produce a hasher suitable for the given `kind` of hash. |
| 65 | + #[inline] |
| 66 | + pub fn hasher(kind: crate::Kind) -> Hasher { |
| 67 | + match kind { |
| 68 | + crate::Kind::Sha1 => Hasher::default(), |
| 69 | + } |
| 70 | + } |
| 71 | +} |
0 commit comments