rc_crypto/
hawk_crypto.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::{digest, hmac, rand};
6use hawk::crypto as hc;
7
8impl From<crate::Error> for hc::CryptoError {
9    // Our errors impl `Fail`, so we can do this.
10    fn from(e: crate::Error) -> Self {
11        hc::CryptoError::Other(e.into())
12    }
13}
14
15pub(crate) struct RcCryptoCryptographer;
16
17impl hc::HmacKey for crate::hmac::SigningKey {
18    fn sign(&self, data: &[u8]) -> Result<Vec<u8>, hc::CryptoError> {
19        let digest = hmac::sign(self, data)?;
20        Ok(digest.as_ref().into())
21    }
22}
23
24// I don't really see a reason to bother doing incremental hashing here. A
25// one-shot is going to be faster in many cases anyway, and the higher memory
26// usage probably doesn't matter given our usage.
27struct NssHasher {
28    buffer: Vec<u8>,
29    algorithm: &'static digest::Algorithm,
30}
31
32impl hc::Hasher for NssHasher {
33    fn update(&mut self, data: &[u8]) -> Result<(), hc::CryptoError> {
34        self.buffer.extend_from_slice(data);
35        Ok(())
36    }
37
38    fn finish(&mut self) -> Result<Vec<u8>, hc::CryptoError> {
39        let digest = digest::digest(self.algorithm, &self.buffer)?;
40        let bytes: &[u8] = digest.as_ref();
41        Ok(bytes.to_owned())
42    }
43}
44
45impl hc::Cryptographer for RcCryptoCryptographer {
46    fn rand_bytes(&self, output: &mut [u8]) -> Result<(), hc::CryptoError> {
47        rand::fill(output)?;
48        Ok(())
49    }
50
51    fn new_key(
52        &self,
53        algorithm: hawk::DigestAlgorithm,
54        key: &[u8],
55    ) -> Result<Box<dyn hc::HmacKey>, hc::CryptoError> {
56        let k = hmac::SigningKey::new(to_rc_crypto_algorithm(algorithm)?, key);
57        Ok(Box::new(k))
58    }
59
60    fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
61        crate::constant_time::verify_slices_are_equal(a, b).is_ok()
62    }
63
64    fn new_hasher(
65        &self,
66        algorithm: hawk::DigestAlgorithm,
67    ) -> Result<Box<dyn hc::Hasher>, hc::CryptoError> {
68        Ok(Box::new(NssHasher {
69            algorithm: to_rc_crypto_algorithm(algorithm)?,
70            buffer: vec![],
71        }))
72    }
73}
74
75fn to_rc_crypto_algorithm(
76    algorithm: hawk::DigestAlgorithm,
77) -> Result<&'static digest::Algorithm, hc::CryptoError> {
78    match algorithm {
79        hawk::DigestAlgorithm::Sha256 => Ok(&digest::SHA256),
80        algo => Err(hc::CryptoError::UnsupportedDigest(algo)),
81    }
82}
83
84// Note: this doesn't initialize NSS!
85pub(crate) fn init() {
86    hawk::crypto::set_cryptographer(&crate::hawk_crypto::RcCryptoCryptographer)
87        .expect("Failed to initialize `hawk` cryptographer!")
88}
89
90#[cfg(test)]
91mod test {
92
93    // Based on rust-hawk's hash_consistency. This fails if we've messed up the hashing.
94    #[test]
95    fn test_hawk_hashing() {
96        nss::ensure_initialized();
97        crate::ensure_initialized();
98
99        let mut hasher1 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap();
100        hasher1.update("pày").unwrap();
101        hasher1.update("load").unwrap();
102        let hash1 = hasher1.finish().unwrap();
103
104        let mut hasher2 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap();
105        hasher2.update("pàyload").unwrap();
106        let hash2 = hasher2.finish().unwrap();
107
108        let hash3 = hawk::PayloadHasher::hash("text/plain", hawk::SHA256, "pàyload").unwrap();
109
110        let hash4 = // "pàyload" as utf-8 bytes
111            hawk::PayloadHasher::hash("text/plain", hawk::SHA256, [112, 195, 160, 121, 108, 111, 97, 100]).unwrap();
112
113        assert_eq!(
114            hash1,
115            &[
116                228, 238, 241, 224, 235, 114, 158, 112, 211, 254, 118, 89, 25, 236, 87, 176, 181,
117                54, 61, 135, 42, 223, 188, 103, 194, 59, 83, 36, 136, 31, 198, 50
118            ]
119        );
120        assert_eq!(hash2, hash1);
121        assert_eq!(hash3, hash1);
122        assert_eq!(hash4, hash1);
123    }
124
125    // Based on rust-hawk's test_make_mac. This fails if we've messed up the signing.
126    #[test]
127    fn test_hawk_signing() {
128        nss::ensure_initialized();
129        crate::ensure_initialized();
130
131        let key = hawk::Key::new(
132            [
133                11u8, 19, 228, 209, 79, 189, 200, 59, 166, 47, 86, 254, 235, 184, 120, 197, 75,
134                152, 201, 79, 115, 61, 111, 242, 219, 187, 173, 14, 227, 108, 60, 232,
135            ],
136            hawk::SHA256,
137        )
138        .unwrap();
139
140        let mac = hawk::mac::Mac::new(
141            hawk::mac::MacType::Header,
142            &key,
143            std::time::UNIX_EPOCH + std::time::Duration::new(1000, 100),
144            "nonny",
145            "POST",
146            "mysite.com",
147            443,
148            "/v1/api",
149            None,
150            None,
151        )
152        .unwrap();
153        assert_eq!(
154            mac.as_ref(),
155            &[
156                192, 227, 235, 121, 157, 185, 197, 79, 189, 214, 235, 139, 9, 232, 99, 55, 67, 30,
157                68, 0, 150, 187, 192, 238, 21, 200, 209, 107, 245, 159, 243, 178
158            ]
159        );
160    }
161}