Skip to main content

spideroak_crypto/
hash.rs

1//! Cryptographic hash functions.
2
3#![forbid(unsafe_code)]
4
5use core::{
6    fmt::{self, Debug},
7    ops::{Deref, DerefMut},
8};
9
10use generic_array::{ArrayLength, GenericArray, IntoArrayLength};
11use sha3_utils::{encode_string, right_encode_bytes};
12use subtle::{Choice, ConstantTimeEq};
13use typenum::{
14    generic_const_mappings::Const,
15    type_operators::{IsGreaterOrEqual, IsLess},
16    Unsigned, U32, U65536,
17};
18
19/// A cryptographic hash function.
20///
21/// # Requirements
22///
23/// The function must:
24///
25/// * Have pre-image resistance
26/// * Be collision resistant (and thus second pre-image
27///   resistance)
28///
29/// The function does not need to be resistant to
30/// length-extension attacks.
31///
32/// Examples of cryptographic hash functions that fulfill
33/// these requirements include SHA-256, SHA-512, and SHA3-512.
34pub trait Hash: Clone {
35    /// The size in octets of a digest used by this [`Hash`].
36    ///
37    /// Must be at least 32 octets and less than 2¹⁶ octets.
38    type DigestSize: ArrayLength + IsGreaterOrEqual<U32> + IsLess<U65536> + 'static;
39    /// Shorthand for [`DigestSize`][Self::DigestSize].
40    const DIGEST_SIZE: usize = Self::DigestSize::USIZE;
41
42    /// Creates a new [`Hash`].
43    fn new() -> Self;
44
45    /// Adds `data` to the running hash.
46    fn update(&mut self, data: &[u8]);
47
48    /// Returns the current digest.
49    fn digest(self) -> Digest<Self::DigestSize>;
50
51    /// Returns the digest of `data`.
52    ///
53    /// While this function is provided by default,
54    /// implementations of [`Hash`] are encouraged to provide
55    /// optimized "single-shot" implementations.
56    fn hash(data: &[u8]) -> Digest<Self::DigestSize>
57    where
58        Self: Sized,
59    {
60        let mut h = Self::new();
61        h.update(data);
62        h.digest()
63    }
64}
65
66/// The output of a [`Hash`].
67#[derive(Clone, Default)]
68#[repr(transparent)]
69pub struct Digest<N: ArrayLength>(GenericArray<u8, N>);
70
71impl<N: ArrayLength> Digest<N> {
72    /// Creates a new hash digest.
73    #[inline]
74    pub const fn new(digest: GenericArray<u8, N>) -> Self {
75        Self(digest)
76    }
77
78    /// Creates a new hash digest from an array.
79    #[inline]
80    pub const fn from_array<const U: usize>(digest: [u8; U]) -> Self
81    where
82        Const<U>: IntoArrayLength<ArrayLength = N>,
83    {
84        Self::new(GenericArray::from_array(digest))
85    }
86
87    /// Returns the length of the hash digest.
88    #[inline]
89    #[allow(clippy::len_without_is_empty)]
90    pub const fn len(&self) -> usize {
91        N::USIZE
92    }
93
94    /// Returns the hash digest as a byte slice.
95    #[inline]
96    pub const fn as_bytes(&self) -> &[u8] {
97        self.0.as_slice()
98    }
99
100    /// Returns the hash digest as a byte slice.
101    #[inline]
102    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
103        &mut self.0
104    }
105
106    /// Converts itself to an array.
107    #[inline]
108    pub fn into_array(self) -> GenericArray<u8, N> {
109        self.0
110    }
111}
112
113impl<N: ArrayLength> Copy for Digest<N> where N::ArrayType<u8>: Copy {}
114
115impl<N: ArrayLength> Deref for Digest<N> {
116    type Target = [u8];
117
118    #[inline]
119    fn deref(&self) -> &Self::Target {
120        &self.0
121    }
122}
123
124impl<N: ArrayLength> DerefMut for Digest<N> {
125    #[inline]
126    fn deref_mut(&mut self) -> &mut Self::Target {
127        &mut self.0
128    }
129}
130
131impl<N: ArrayLength> Debug for Digest<N> {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_tuple("Digest").field(&self.0).finish()
134    }
135}
136
137// Gated for safety purposes; see the comment inside
138// `PartialEq::Eq`.
139#[cfg(any(test, feature = "test_util"))]
140impl<N: ArrayLength> Eq for Digest<N> {}
141
142// Gated for safety purposes; see the comment inside
143// `PartialEq::Eq`.
144#[cfg(any(test, feature = "test_util"))]
145impl<N: ArrayLength> PartialEq for Digest<N> {
146    fn eq(&self, other: &Self) -> bool {
147        // While it's generally fine to compare digests with ==
148        // (non-constant time), it has the potential to be
149        // a footgun. For example, MACs must be compared in
150        // constant time, but some MACs are simply hash digests
151        // (see HMAC, etc.). A naive implementation of HMAC could
152        // return `Digest` directly, opening it up to side
153        // channel attacks.
154        //
155        // To protect against this, we only allow comparisons
156        // with == while testing. Out of paranoia, we also use
157        // a constant time comparison for the equality check.
158        bool::from(ConstantTimeEq::ct_eq(self, other))
159    }
160}
161
162impl<N: ArrayLength> ConstantTimeEq for Digest<N> {
163    #[inline]
164    fn ct_eq(&self, other: &Self) -> Choice {
165        self.as_bytes().ct_eq(other.as_bytes())
166    }
167}
168
169/// A cryptographic hash over a set of strings such that each
170/// element is unambiguously encoded per [NIST SP 800-185].
171///
172/// In short, this means that for some tuple hash `H`, `H("abc",
173/// "d")` creates a different hash value from `H("abcd")`,
174/// `H("a", "bcd")`, etc.
175///
176/// Note that this means that zero-length strings are
177/// significant. For example, `H("", "a")` is distinct from
178/// `H("a")`.
179///
180/// [NIST SP 800-185]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf
181pub fn tuple_hash<H, I>(s: I) -> Digest<H::DigestSize>
182where
183    H: Hash,
184    I: IntoIterator<Item: AsRef<[u8]>>,
185{
186    // TupleHash(X, L, S)
187    // 1. z = "".
188    // 2. n = the number of input strings in the tuple X.
189    // 3. for i = 1 to n:
190    //        z = z || encode_string(X[i]).
191    // 4. newX = z || right_encode(L).
192    let mut h = H::new();
193    s.into_iter().for_each(|v| {
194        for part in &encode_string(v.as_ref()) {
195            h.update(part);
196        }
197    });
198    h.update(right_encode_bytes(H::DIGEST_SIZE).as_bytes());
199    h.digest()
200}
201
202#[cfg(test)]
203mod test {
204    use super::*;
205
206    macro_rules! test_tuple_hash {
207        ($name:ident, $hash:ty) => {
208            #[test]
209            fn $name() {
210                assert_eq!(
211                    tuple_hash::<$hash, _>(["abc"].iter()),
212                    tuple_hash::<$hash, _>(["abc"])
213                );
214                assert_eq!(tuple_hash::<$hash, _>([""]), tuple_hash::<$hash, _>([""]));
215
216                assert_ne!(
217                    tuple_hash::<$hash, _>(["a", "b", "c"]),
218                    tuple_hash::<$hash, _>(["abc"])
219                );
220                assert_ne!(
221                    tuple_hash::<$hash, _>(["a", ""]),
222                    tuple_hash::<$hash, _>(["a"])
223                );
224            }
225        };
226    }
227    macro_rules! tuple_hash_tests {
228        () => {
229            use super::*;
230            test_tuple_hash!(test_sha256, Sha256);
231            test_tuple_hash!(test_sha384, Sha384);
232            test_tuple_hash!(test_sha512, Sha512);
233        };
234    }
235
236    #[cfg(feature = "bearssl")]
237    mod bearssl {
238        use crate::bearssl::{Sha256, Sha384, Sha512};
239        tuple_hash_tests!();
240    }
241
242    mod rust {
243        use crate::rust::{Sha256, Sha384, Sha512};
244        tuple_hash_tests!();
245    }
246}