blob: 40be1897d7614041d5524b353cdcb5e64e4947e0 [file] [log] [blame]
Better Together Rust Devsf6f0ac82023-03-27 15:28:41 -07001// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/// Trait which defines hmac operations
16pub trait Hmac<const N: usize>: Sized {
17 /// Create a new hmac from a fixed size key
18 fn new_from_key(key: [u8; N]) -> Self;
19
20 /// Create new hmac value from variable size key.
21 fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength>;
22
23 /// Update state using the provided data
24 fn update(&mut self, data: &[u8]);
25
26 /// Obtain the hmac computation consuming the hmac instance
27 fn finalize(self) -> [u8; N];
28
29 /// Check that the tag value is correct for the processed input
30 fn verify_slice(self, tag: &[u8]) -> Result<(), MacError>;
31
32 /// Check that the tag value is correct for the processed input
33 fn verify(self, tag: [u8; N]) -> Result<(), MacError>;
34
35 /// Check truncated tag correctness using left side bytes of the calculated tag
36 fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError>;
37}
38
39/// Error type for when the output of the hmac operation
40/// is not equal to the expected value.
41#[derive(Debug)]
42pub struct MacError;
43
44/// Error output when the provided key material length is invalid
45#[derive(Debug)]
46pub struct InvalidLength;
Better Together Rust Devs9bfc9552023-04-07 11:13:13 -070047
48/// Test cases exported for testing specific hmac implementations
49#[cfg(feature = "testing")]
50pub mod testing {
51 use crate::hmac::Hmac;
52 use crate::rstest_reuse::template;
53 pub use crate::testing::prelude::*;
54 use crate::CryptoProvider;
55 use core::cmp::min;
56 use core::marker::PhantomData;
57 use wycheproof::TestResult;
58
59 /// Generates the test cases to validate the hmac implementation.
60 /// For example, to test `MyCryptoProvider`:
61 ///
62 /// ```
63 /// mod tests {
64 /// use std::marker::PhantomData;
65 /// use crypto_provider::testing::CryptoProviderTestCase;
66 /// #[apply(hmac_test_cases)]
67 /// fn hmac_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>){
68 /// testcase(PhantomData::<MyCryptoProvider>);
69 /// }
70 /// }
71 /// ```
72 #[template]
73 #[export]
74 #[rstest]
75 #[case::hmac_sha256_test_vectors(hmac_sha256_test_vectors)]
76 #[case::hmac_sha512_test_vectors(hmac_sha512_test_vectors)]
77 fn hmac_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
78
79 /// Run wycheproof hmac sha256 test vectors on provided CryptoProvider
80 pub fn hmac_sha256_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
81 run_hmac_test_vectors::<32, C::HmacSha256>(HashAlg::Sha256)
82 }
83
84 /// Run wycheproof hmac sha512 test vectors on provided CryptoProvider
85 pub fn hmac_sha512_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
86 run_hmac_test_vectors::<64, C::HmacSha512>(HashAlg::Sha512)
87 }
88
89 enum HashAlg {
90 Sha256,
91 Sha512,
92 }
93
94 // Tests vectors from Project Wycheproof:
95 // https://github.com/google/wycheproof
96 fn run_hmac_test_vectors<const N: usize, H: Hmac<N>>(hash: HashAlg) {
97 let test_name = match hash {
98 HashAlg::Sha256 => wycheproof::mac::TestName::HmacSha256,
99 HashAlg::Sha512 => wycheproof::mac::TestName::HmacSha512,
100 };
101 let test_set =
102 wycheproof::mac::TestSet::load(test_name).expect("should be able to load test set");
103
104 for test_group in test_set.test_groups {
105 for test in test_group.tests {
106 let key = test.key;
107 let msg = test.msg;
108 let tag = test.tag;
109 let tc_id = test.tc_id;
110 let valid = match test.result {
111 TestResult::Valid | TestResult::Acceptable => true,
112 TestResult::Invalid => false,
113 };
114
115 if let Some(desc) =
116 run_test::<N, H>(key.as_slice(), msg.as_slice(), tag.as_slice(), valid)
117 {
118 panic!(
119 "\n\
120 Failed test {tc_id}: {desc}\n\
121 key:\t{key:?}\n\
122 msg:\t{msg:?}\n\
123 tag:\t{tag:?}\n",
124 );
125 }
126 }
127 }
128 }
129
130 fn run_test<const N: usize, H: Hmac<N>>(
131 key: &[u8],
132 input: &[u8],
133 tag: &[u8],
134 valid_data: bool,
135 ) -> Option<&'static str> {
136 let mut mac = H::new_from_slice(key).unwrap();
137 mac.update(input);
138 let result = mac.finalize();
139 let n = tag.len();
140 let result_bytes = &result[..n];
141
142 if valid_data {
143 if result_bytes != tag {
144 return Some("whole message");
145 }
146 } else {
147 return if result_bytes == tag {
148 Some("invalid should not match")
149 } else {
150 None
151 };
152 }
153
154 // test reading different chunk sizes
155 for chunk_size in 1..min(64, input.len()) {
156 let mut mac = H::new_from_slice(key).unwrap();
157 for chunk in input.chunks(chunk_size) {
158 mac.update(chunk);
159 }
160 let res = mac.verify_truncated_left(tag);
161 if res.is_err() {
162 return Some("chunked message");
163 }
164 }
165
166 None
167 }
168}