gix_hash/
hasher.rs

1/// The error returned by [`Hasher::try_finalize()`](crate::Hasher::try_finalize()).
2#[derive(Debug, thiserror::Error)]
3#[allow(missing_docs)]
4pub enum Error {
5    #[error("Detected SHA-1 collision attack with digest {digest}")]
6    CollisionAttack { digest: crate::ObjectId },
7}
8
9pub(super) mod _impl {
10    #[cfg(feature = "sha1")]
11    use sha1_checked::{CollisionResult, Digest};
12
13    #[cfg(all(not(feature = "sha1"), feature = "sha256"))]
14    use sha2::Digest;
15
16    use crate::hasher::Error;
17
18    /// Hash implementations that can be used once.
19    #[derive(Clone)]
20    pub enum Hasher {
21        /// An implementation of the SHA1 hash.
22        ///
23        /// We use [`sha1_checked`] to implement the same collision detection algorithm as Git.
24        #[cfg(feature = "sha1")]
25        Sha1(sha1_checked::Sha1),
26        /// An implementation of the SHA256 hash.
27        #[cfg(feature = "sha256")]
28        Sha256(sha2::Sha256),
29    }
30
31    impl Hasher {
32        /// Let's not make this public to force people to go through [`hasher()`].
33        #[cfg(feature = "sha1")]
34        fn new_sha1() -> Self {
35            // This matches the configuration used by Git, which only uses
36            // the collision detection to bail out, rather than computing
37            // alternate “safe hashes” for inputs where a collision attack
38            // was detected.
39            Self::Sha1(sha1_checked::Builder::default().safe_hash(false).build())
40        }
41
42        /// Let's not make this public to force people to go through [`hasher()`].
43        #[cfg(feature = "sha256")]
44        fn new_sha256() -> Self {
45            Self::Sha256(sha2::Sha256::new())
46        }
47    }
48
49    impl Hasher {
50        /// Digest the given `bytes`.
51        pub fn update(&mut self, bytes: &[u8]) {
52            match self {
53                #[cfg(feature = "sha1")]
54                Hasher::Sha1(sha1) => sha1.update(bytes),
55                #[cfg(feature = "sha256")]
56                Hasher::Sha256(sha256) => sha256.update(bytes),
57            }
58        }
59
60        /// Finalize the hash and produce an object id.
61        ///
62        /// Returns [`Error`] if a collision attack is detected.
63        // TODO: Since SHA-256 has an infallible `finalize`, it might be worth investigating
64        //       turning the return type into `Result<crate::ObjectId, Infallible>` when this crate is
65        //       compiled with SHA-256 support only.
66        #[inline]
67        pub fn try_finalize(self) -> Result<crate::ObjectId, Error> {
68            match self {
69                #[cfg(feature = "sha1")]
70                Hasher::Sha1(sha1) => match sha1.try_finalize() {
71                    CollisionResult::Ok(digest) => Ok(crate::ObjectId::Sha1(digest.into())),
72                    CollisionResult::Mitigated(_) => {
73                        // SAFETY: `CollisionResult::Mitigated` is only
74                        // returned when `safe_hash()` is on. `Hasher`’s field
75                        // is private, and we only construct the SHA-1 variant
76                        // via `Hasher::new_sha1()` (and thus through `hasher()`),
77                        // which configures the builder with `safe_hash(false)`.
78                        //
79                        // As of Rust 1.84.1, the compiler can’t figure out
80                        // this function cannot panic without this.
81                        #[allow(unsafe_code)]
82                        unsafe {
83                            std::hint::unreachable_unchecked()
84                        }
85                    }
86                    CollisionResult::Collision(digest) => Err(Error::CollisionAttack {
87                        digest: crate::ObjectId::Sha1(digest.into()),
88                    }),
89                },
90                #[cfg(feature = "sha256")]
91                Hasher::Sha256(sha256) => Ok(crate::ObjectId::Sha256(sha256.finalize().into())),
92            }
93        }
94    }
95
96    /// Produce a hasher suitable for the given `kind` of hash.
97    #[inline]
98    pub fn hasher(kind: crate::Kind) -> Hasher {
99        match kind {
100            #[cfg(feature = "sha1")]
101            crate::Kind::Sha1 => Hasher::new_sha1(),
102            #[cfg(feature = "sha256")]
103            crate::Kind::Sha256 => Hasher::new_sha256(),
104        }
105    }
106}