Skip to main content

aws_lc_rs/
digest.rs

1// Copyright 2015-2019 Brian Smith.
2// SPDX-License-Identifier: ISC
3// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0 OR ISC
5
6//! SHA-2, SHA-3 and the legacy SHA-1 digest algorithms.
7//!
8//! If all the data is available in a single contiguous slice then the `digest`
9//! function should be used. Otherwise, the digest can be calculated in
10//! multiple steps using `Context`.
11
12//! # Example
13//!
14//! ```
15//! use aws_lc_rs::digest;
16//!
17//! // Using `digest::digest`
18//! let one_shot = digest::digest(&digest::SHA384, b"hello, world");
19//!
20//! // Using `digest::Context`
21//! let mut ctx = digest::Context::new(&digest::SHA384);
22//! ctx.update(b"hello");
23//! ctx.update(b", ");
24//! ctx.update(b"world");
25//! let multi_part = ctx.finish();
26//!
27//! assert_eq!(&one_shot.as_ref(), &multi_part.as_ref());
28//! ```
29
30#![allow(non_snake_case)]
31use crate::fips::indicator_check;
32use crate::{debug, derive_debug_via_id};
33
34pub(crate) mod digest_ctx;
35// `pub(crate)` so `unstable::digest` and `test.rs` can reach Keccak-256; outside the
36// crate, only the items re-exported below are public.
37pub(crate) mod sha;
38#[cfg(all(not(feature = "fips"), feature = "unstable"))]
39use crate::aws_lc::EVP_keccak256;
40use crate::aws_lc::{
41    EVP_DigestFinal, EVP_DigestUpdate, EVP_sha1, EVP_sha224, EVP_sha256, EVP_sha384, EVP_sha3_256,
42    EVP_sha3_384, EVP_sha3_512, EVP_sha512, EVP_sha512_256, EVP_MD,
43};
44use crate::error::Unspecified;
45use crate::ptr::ConstPointer;
46use core::ffi::c_uint;
47use core::mem::MaybeUninit;
48use digest_ctx::DigestContext;
49// Keccak-256 is exposed through `crate::unstable::digest`, not from this module; this
50// re-export only lets the tests below name it like the stable algorithms.
51#[cfg(all(test, not(feature = "fips"), feature = "unstable"))]
52pub(crate) use sha::KECCAK256;
53pub use sha::{
54    SHA1_FOR_LEGACY_USE_ONLY, SHA1_OUTPUT_LEN, SHA224, SHA224_OUTPUT_LEN, SHA256,
55    SHA256_OUTPUT_LEN, SHA384, SHA384_OUTPUT_LEN, SHA3_256, SHA3_384, SHA3_512, SHA512, SHA512_256,
56    SHA512_256_OUTPUT_LEN, SHA512_OUTPUT_LEN,
57};
58
59/// A context for multi-step (Init-Update-Finish) digest calculations.
60//
61// # FIPS
62// Context must be used with one of the following algorithms:
63// * `SHA1_FOR_LEGACY_USE_ONLY`
64// * `SHA224`
65// * `SHA256`
66// * `SHA384`
67// * `SHA512`
68// * `SHA512_256`
69#[derive(Clone)]
70pub struct Context {
71    /// The context's algorithm.
72    pub(crate) algorithm: &'static Algorithm,
73    digest_ctx: DigestContext,
74    // The spec specifies that SHA-1 and SHA-256 support up to
75    // 2^64-1 bits of input. SHA-384 and SHA-512 support up to
76    // 2^128-1 bits.
77    // Implementations of `digest` only support up
78    // to 2^64-1 bits of input, which should be sufficient enough for
79    // practical use cases.
80    msg_len: u64,
81    max_input_reached: bool,
82}
83
84impl Context {
85    /// Constructs a new context.
86    ///
87    /// # Panics
88    ///
89    /// `new` panics if it fails to initialize an aws-lc digest context for the given
90    /// algorithm.
91    #[must_use]
92    pub fn new(algorithm: &'static Algorithm) -> Self {
93        Self {
94            algorithm,
95            digest_ctx: DigestContext::new(algorithm).unwrap(),
96            msg_len: 0u64,
97            max_input_reached: false,
98        }
99    }
100
101    /// Updates the message to digest with all the data in `data`.
102    ///
103    /// # Panics
104    /// Panics if update causes total input length to exceed maximum allowed (`u64::MAX`).
105    #[inline]
106    pub fn update(&mut self, data: &[u8]) {
107        Self::try_update(self, data).expect("digest update failed");
108    }
109
110    #[inline]
111    fn try_update(&mut self, data: &[u8]) -> Result<(), Unspecified> {
112        unsafe {
113            // Check if the message has reached the algorithm's maximum allowed input, or overflowed
114            // the msg_len counter.
115            let (msg_len, overflowed) = self.msg_len.overflowing_add(data.len() as u64);
116            if overflowed || msg_len > self.algorithm.max_input_len {
117                return Err(Unspecified);
118            }
119
120            self.msg_len = msg_len;
121            self.max_input_reached = self.msg_len == self.algorithm.max_input_len;
122
123            // Doesn't require boundary_check! guard
124            if 1 != EVP_DigestUpdate(
125                self.digest_ctx.as_mut_ptr(),
126                data.as_ptr().cast(),
127                data.len(),
128            ) {
129                return Err(Unspecified);
130            }
131            Ok(())
132        }
133    }
134
135    /// Finalizes the digest calculation and returns the digest value.
136    ///
137    /// `finish` consumes the context so it cannot be (mis-)used after `finish`
138    /// has been called.
139    ///
140    /// # Panics
141    /// Panics if the digest is unable to be finalized
142    #[inline]
143    #[must_use]
144    pub fn finish(self) -> Digest {
145        Self::try_finish(self).expect("EVP_DigestFinal failed")
146    }
147
148    #[inline]
149    fn try_finish(mut self) -> Result<Digest, Unspecified> {
150        let mut output = [0u8; MAX_OUTPUT_LEN];
151        let mut out_len = MaybeUninit::<c_uint>::uninit();
152        if 1 != indicator_check!(unsafe {
153            EVP_DigestFinal(
154                self.digest_ctx.as_mut_ptr(),
155                output.as_mut_ptr(),
156                out_len.as_mut_ptr(),
157            )
158        }) {
159            return Err(Unspecified);
160        }
161
162        Ok(Digest {
163            algorithm: self.algorithm,
164            message: output,
165            len: self.algorithm.output_len,
166        })
167    }
168
169    /// The algorithm that this context is using.
170    #[inline]
171    #[must_use]
172    pub fn algorithm(&self) -> &'static Algorithm {
173        self.algorithm
174    }
175}
176
177/// Returns the digest of `data` using the given digest algorithm.
178///
179// # FIPS
180// This function must only be used with one of the following algorithms:
181// * `SHA1_FOR_LEGACY_USE_ONLY`
182// * `SHA224`
183// * `SHA256`
184// * `SHA384`
185// * `SHA512`
186// * `SHA512_256`
187//
188/// # Examples:
189///
190/// ```
191/// # {
192/// use aws_lc_rs::{digest, test};
193/// let expected_hex = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b";
194/// let expected: Vec<u8> = test::from_hex(expected_hex).unwrap();
195/// let actual = digest::digest(&digest::SHA256, b"hello, world");
196///
197/// assert_eq!(&expected, &actual.as_ref());
198/// # }
199/// ```
200#[inline]
201#[must_use]
202pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Digest {
203    let mut output = [0u8; MAX_OUTPUT_LEN];
204    (algorithm.one_shot_hash)(data, &mut output);
205
206    Digest {
207        algorithm,
208        message: output,
209        len: algorithm.output_len,
210    }
211}
212
213/// A calculated digest value.
214///
215/// Use [`Self::as_ref`] to get the value as a `&[u8]`.
216#[derive(Clone, Copy)]
217pub struct Digest {
218    /// The trait `Copy` can't be implemented for dynamic arrays, so we set a
219    /// fixed array and the appropriate length.
220    message: [u8; MAX_OUTPUT_LEN],
221    len: usize,
222
223    algorithm: &'static Algorithm,
224}
225
226impl Digest {
227    /// Imports a digest value provide by an external source. This allows for the signing of
228    /// content that might not be directly accessible.
229    ///
230    /// WARNING: Ensure that the digest is provided by a trusted source.
231    /// When possible, prefer to directly compute the digest of content.
232    ///
233    /// # Errors
234    /// Returns `Unspecified` if the imported value is the wrong length for the specified algorithm.
235    pub fn import_less_safe(
236        digest: &[u8],
237        algorithm: &'static Algorithm,
238    ) -> Result<Self, Unspecified> {
239        if digest.len() != algorithm.output_len {
240            return Err(Unspecified);
241        }
242        let mut my_digest = [0u8; MAX_OUTPUT_LEN];
243        my_digest[0..digest.len()].copy_from_slice(&digest[0..digest.len()]);
244        Ok(Digest {
245            message: my_digest,
246            len: digest.len(),
247            algorithm,
248        })
249    }
250
251    /// The algorithm that was used to calculate the digest value.
252    #[inline]
253    #[must_use]
254    pub fn algorithm(&self) -> &'static Algorithm {
255        self.algorithm
256    }
257}
258
259impl AsRef<[u8]> for Digest {
260    #[inline]
261    fn as_ref(&self) -> &[u8] {
262        &self.message[..self.len]
263    }
264}
265
266impl core::fmt::Debug for Digest {
267    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
268        write!(fmt, "{:?}:", self.algorithm)?;
269        debug::write_hex_bytes(fmt, self.as_ref())
270    }
271}
272
273/// A digest algorithm.
274pub struct Algorithm {
275    /// The length of a finalized digest.
276    pub output_len: usize,
277
278    /// The size of the chaining value of the digest function, in bytes. For
279    /// non-truncated algorithms (SHA-1, SHA-256, SHA-512), this is equal to
280    /// `output_len`. For truncated algorithms (e.g. SHA-224, SHA-384, SHA-512/256),
281    /// this is equal to the length before truncation. This is mostly helpful
282    /// for determining the size of an HMAC key that is appropriate for the
283    /// digest algorithm.
284    ///
285    /// This function isn't actually used in *aws-lc-rs*, and is only
286    /// kept for compatibility with the original *ring* implementation.
287    #[deprecated(
288        since = "1.1.0",
289        note = "Unused by aws-lc-rs; retained for ring compatibility. Use `output_len()` to size an HMAC key."
290    )]
291    pub chaining_len: usize,
292
293    /// The internal block length.
294    pub block_len: usize,
295
296    // max_input_len is computed as u64 instead of usize to prevent overflowing on 32-bit machines.
297    max_input_len: u64,
298
299    one_shot_hash: fn(msg: &[u8], output: &mut [u8]),
300
301    pub(crate) id: AlgorithmID,
302}
303
304unsafe impl Send for Algorithm {}
305
306impl Algorithm {
307    /// The length of a finalized digest.
308    #[inline]
309    #[must_use]
310    pub fn output_len(&self) -> usize {
311        self.output_len
312    }
313
314    /// The size of the chaining value of the digest function, in bytes. For
315    /// non-truncated algorithms (SHA-1, SHA-256, SHA-512), this is equal to
316    /// `output_len`. For truncated algorithms (e.g. SHA-224, SHA-384, SHA-512/256),
317    /// this is equal to the length before truncation. This is mostly helpful
318    /// for determining the size of an HMAC key that is appropriate for the
319    /// digest algorithm.
320    ///
321    /// This function isn't actually used in *aws-lc-rs*, and is only
322    /// kept for compatibility with the original *ring* implementation.
323    #[deprecated(
324        since = "1.5.0",
325        note = "Unused by aws-lc-rs; retained for ring compatibility. Use `output_len()` to size an HMAC key."
326    )]
327    #[inline]
328    #[must_use]
329    pub fn chaining_len(&self) -> usize {
330        // clippy warns on deprecated functions accessing deprecated fields
331        #![allow(deprecated)]
332        self.chaining_len
333    }
334
335    /// The internal block length.
336    #[inline]
337    #[must_use]
338    pub fn block_len(&self) -> usize {
339        self.block_len
340    }
341}
342
343#[derive(Clone, Copy, Debug, Eq, PartialEq)]
344pub(crate) enum AlgorithmID {
345    SHA1,
346    SHA224,
347    SHA256,
348    SHA384,
349    SHA512,
350    SHA512_256,
351    SHA3_256,
352    SHA3_384,
353    SHA3_512,
354    #[cfg(all(not(feature = "fips"), feature = "unstable"))]
355    KECCAK256,
356}
357
358impl PartialEq for Algorithm {
359    fn eq(&self, other: &Self) -> bool {
360        self.id == other.id
361    }
362}
363
364impl Eq for Algorithm {}
365
366derive_debug_via_id!(Algorithm);
367
368/// The maximum block length ([`Algorithm::block_len`]) of all the algorithms
369/// in this module.
370pub const MAX_BLOCK_LEN: usize = 1024 / 8;
371
372/// The maximum output length ([`Algorithm::output_len`]) of all the
373/// algorithms in this module.
374pub const MAX_OUTPUT_LEN: usize = 512 / 8;
375
376/// The maximum chaining length ([`Algorithm::chaining_len`]) of all the
377/// algorithms in this module.
378pub const MAX_CHAINING_LEN: usize = MAX_OUTPUT_LEN;
379
380/// Match digest types for `EVP_MD` functions.
381pub(crate) fn match_digest_type(algorithm_id: &AlgorithmID) -> ConstPointer<'_, EVP_MD> {
382    unsafe {
383        ConstPointer::new_static(match algorithm_id {
384            AlgorithmID::SHA1 => EVP_sha1(),
385            AlgorithmID::SHA224 => EVP_sha224(),
386            AlgorithmID::SHA256 => EVP_sha256(),
387            AlgorithmID::SHA384 => EVP_sha384(),
388            AlgorithmID::SHA512 => EVP_sha512(),
389            AlgorithmID::SHA512_256 => EVP_sha512_256(),
390            AlgorithmID::SHA3_256 => EVP_sha3_256(),
391            AlgorithmID::SHA3_384 => EVP_sha3_384(),
392            AlgorithmID::SHA3_512 => EVP_sha3_512(),
393            #[cfg(all(not(feature = "fips"), feature = "unstable"))]
394            AlgorithmID::KECCAK256 => EVP_keccak256(),
395        })
396        .unwrap_or_else(|()| panic!("Digest algorithm not found: {algorithm_id:?}"))
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use crate::digest;
403    #[cfg(feature = "fips")]
404    mod fips;
405
406    mod max_input {
407        extern crate alloc;
408
409        use super::super::super::digest;
410        use crate::digest::digest_ctx::DigestContext;
411        use crate::digest::Digest;
412        use alloc::vec;
413
414        macro_rules! max_input_tests {
415            ( $algorithm_name:ident ) => {
416                mod $algorithm_name {
417                    use super::super::super::super::digest;
418
419                    #[test]
420                    fn max_input_test() {
421                        super::max_input_test(&digest::$algorithm_name);
422                    }
423                    #[test]
424                    #[should_panic(expected = "digest update failed")]
425                    fn too_long_input_test_block() {
426                        super::too_long_input_test_block(&digest::$algorithm_name);
427                    }
428
429                    #[test]
430                    #[should_panic(expected = "digest update failed")]
431                    fn too_long_input_test_byte() {
432                        super::too_long_input_test_byte(&digest::$algorithm_name);
433                    }
434                }
435            };
436        }
437
438        fn max_input_test(alg: &'static digest::Algorithm) {
439            let mut context = nearly_full_context(alg);
440            let next_input = vec![0u8; alg.block_len - 1];
441            context.update(&next_input);
442            let _: Digest = context.finish(); // no panic
443        }
444
445        fn too_long_input_test_block(alg: &'static digest::Algorithm) {
446            let mut context = nearly_full_context(alg);
447            let next_input = vec![0u8; alg.block_len];
448            context.update(&next_input);
449            let _: Digest = context.finish(); // should panic
450        }
451
452        fn too_long_input_test_byte(alg: &'static digest::Algorithm) {
453            let mut context = nearly_full_context(alg);
454            let next_input = vec![0u8; alg.block_len - 1];
455            context.update(&next_input); // no panic
456            context.update(&[0]);
457            let _: Digest = context.finish(); // should panic
458        }
459
460        fn nearly_full_context(alg: &'static digest::Algorithm) -> digest::Context {
461            // Implementations of `digest` only support up
462            // to 2^64-1 bits of input.
463            let block_len = alg.block_len as u64;
464            digest::Context {
465                algorithm: alg,
466                digest_ctx: DigestContext::new(alg).unwrap(),
467                msg_len: alg.max_input_len - block_len + 1,
468                max_input_reached: false,
469            }
470        }
471
472        max_input_tests!(SHA1_FOR_LEGACY_USE_ONLY);
473        max_input_tests!(SHA224);
474        max_input_tests!(SHA256);
475        max_input_tests!(SHA384);
476        max_input_tests!(SHA512);
477        max_input_tests!(SHA3_384);
478        max_input_tests!(SHA3_512);
479        #[cfg(all(not(feature = "fips"), feature = "unstable"))]
480        max_input_tests!(KECCAK256);
481    }
482
483    #[test]
484    fn digest_coverage() {
485        for alg in [
486            &digest::SHA1_FOR_LEGACY_USE_ONLY,
487            &digest::SHA224,
488            &digest::SHA256,
489            &digest::SHA384,
490            &digest::SHA512,
491            &digest::SHA3_384,
492            &digest::SHA3_512,
493            #[cfg(all(not(feature = "fips"), feature = "unstable"))]
494            &digest::KECCAK256,
495        ] {
496            // Clone after updating context with message, then check if the final Digest is the same.
497            let mut ctx = digest::Context::new(alg);
498            ctx.update(b"hello, world");
499            let ctx_clone = ctx.clone();
500            assert_eq!(ctx_clone.algorithm(), ctx.algorithm());
501
502            let orig_digest = ctx.finish();
503            let clone_digest = ctx_clone.finish();
504            assert_eq!(orig_digest.algorithm(), clone_digest.algorithm());
505            assert_eq!(orig_digest.as_ref(), clone_digest.as_ref());
506            assert_eq!(orig_digest.clone().as_ref(), clone_digest.as_ref());
507        }
508    }
509
510    #[test]
511    fn test_import_less_safe() {
512        let digest = digest::digest(&digest::SHA256, b"hello, world");
513        let digest_copy =
514            digest::Digest::import_less_safe(digest.as_ref(), &digest::SHA256).unwrap();
515
516        assert_eq!(digest.as_ref(), digest_copy.as_ref());
517        assert_eq!(digest.algorithm, digest_copy.algorithm);
518    }
519}