1use std::fs::File;
4use std::io::Read;
5
6use blake2::{Blake2b512, Blake2s256};
7use digest::{Digest, DynDigest};
8use hmac::{Hmac, Mac};
9use sha2::{Sha256, Sha384, Sha512};
10use sha3::{Sha3_256, Sha3_384, Sha3_512};
11use subtle::ConstantTimeEq;
12
13use crate::error::{Error, Result};
14
15macro_rules! hmac_with {
16 ($key:expr, $data:expr, $alg:ty) => {{
17 let mut mac = Hmac::<$alg>::new_from_slice($key)
18 .map_err(|_| Error::InvalidKey("HMAC key length invalid".to_string()))?;
19 mac.update($data);
20 Ok(hex::encode(mac.finalize().into_bytes()))
21 }};
22}
23
24#[derive(Clone, Copy, Debug)]
27enum Algorithm {
28 Sha256,
29 Sha384,
30 Sha512,
31 Sha3_256,
32 Sha3_384,
33 Sha3_512,
34 Blake2b,
35 Blake2s,
36}
37
38impl Algorithm {
39 fn parse(name: &str) -> Result<Self> {
40 match name.to_lowercase().as_str() {
41 "sha-256" | "sha256" => Ok(Algorithm::Sha256),
42 "sha-384" | "sha384" => Ok(Algorithm::Sha384),
43 "sha-512" | "sha512" => Ok(Algorithm::Sha512),
44 "sha3-256" | "sha3_256" => Ok(Algorithm::Sha3_256),
45 "sha3-384" | "sha3_384" => Ok(Algorithm::Sha3_384),
46 "sha3-512" | "sha3_512" => Ok(Algorithm::Sha3_512),
47 "blake2b" => Ok(Algorithm::Blake2b),
48 "blake2s" => Ok(Algorithm::Blake2s),
49 _ => Err(Error::UnsupportedAlgorithm(name.to_string())),
50 }
51 }
52
53 fn new_digest(&self) -> Box<dyn DynDigest> {
54 match self {
55 Algorithm::Sha256 => Box::new(Sha256::new()),
56 Algorithm::Sha384 => Box::new(Sha384::new()),
57 Algorithm::Sha512 => Box::new(Sha512::new()),
58 Algorithm::Sha3_256 => Box::new(Sha3_256::new()),
59 Algorithm::Sha3_384 => Box::new(Sha3_384::new()),
60 Algorithm::Sha3_512 => Box::new(Sha3_512::new()),
61 Algorithm::Blake2b => Box::new(Blake2b512::new()),
62 Algorithm::Blake2s => Box::new(Blake2s256::new()),
63 }
64 }
65
66 fn hmac(&self, key: &[u8], data: &[u8]) -> Result<String> {
67 match self {
68 Algorithm::Sha256 => hmac_with!(key, data, Sha256),
69 Algorithm::Sha384 => hmac_with!(key, data, Sha384),
70 Algorithm::Sha512 => hmac_with!(key, data, Sha512),
71 Algorithm::Sha3_256 => hmac_with!(key, data, Sha3_256),
72 Algorithm::Sha3_384 => hmac_with!(key, data, Sha3_384),
73 Algorithm::Sha3_512 => hmac_with!(key, data, Sha3_512),
74 Algorithm::Blake2b | Algorithm::Blake2s => {
76 Err(Error::UnsupportedAlgorithm("blake2 hmac".to_string()))
77 }
78 }
79 }
80}
81
82pub fn string(data: &str, algorithm: &str) -> Result<String> {
84 let mut hasher = Algorithm::parse(algorithm)?.new_digest();
85 hasher.update(data.as_bytes());
86 Ok(hex::encode(hasher.finalize()))
87}
88
89pub fn file(path: &str, algorithm: &str) -> Result<String> {
91 let mut hasher = Algorithm::parse(algorithm)?.new_digest();
92 let mut file = File::open(path).map_err(|e| Error::Internal(e.to_string()))?;
93
94 let mut buf = [0u8; 8192];
95 loop {
96 let n = file.read(&mut buf).map_err(|e| Error::Internal(e.to_string()))?;
97 if n == 0 {
98 break;
99 }
100 hasher.update(&buf[..n]);
101 }
102 Ok(hex::encode(hasher.finalize()))
103}
104
105pub fn hmac(
110 key: impl AsRef<[u8]>,
111 data: impl AsRef<[u8]>,
112 algorithm: &str,
113) -> Result<String> {
114 Algorithm::parse(algorithm)?.hmac(key.as_ref(), data.as_ref())
115}
116
117pub fn verify_hmac(
119 mac: &str,
120 key: impl AsRef<[u8]>,
121 data: impl AsRef<[u8]>,
122 algorithm: &str,
123) -> Result<bool> {
124 let expected = hmac(key, data, algorithm)?;
125 let a = hex::decode(mac).map_err(|e| Error::Hex(e.to_string()))?;
126 let b = hex::decode(expected).map_err(|e| Error::Hex(e.to_string()))?;
127 if a.len() != b.len() {
128 return Ok(false);
129 }
130 Ok(a.as_slice().ct_eq(b.as_slice()).into())
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_sha256() {
139 let got = string("hello world", "sha-256").unwrap();
140 assert_eq!(got.len(), 64);
141 }
142
143 #[test]
144 fn test_hmac() {
145 let mac = hmac("key", "message", "sha-256").unwrap();
146 assert!(verify_hmac(&mac, "key", "message", "sha-256").unwrap());
147 assert!(!verify_hmac(&mac, "key", "tampered", "sha-256").unwrap());
148 }
149
150 #[test]
151 fn test_hmac_binary_inputs() {
152 let key = [0x00u8, 0xff, 0x10];
153 let data = [0xdeu8, 0xad, 0xbe, 0xef];
154 let mac = hmac(key, data, "sha-256").unwrap();
155 assert!(verify_hmac(&mac, key, data, "sha-256").unwrap());
156 assert!(!verify_hmac(&mac, key, b"other", "sha-256").unwrap());
157 }
158
159 #[test]
160 fn test_rejects_weak() {
161 assert!(string("test", "md5").is_err());
162 assert!(string("test", "sha-1").is_err());
163 assert!(string("test", "sha1").is_err());
164 assert!(file("nonexistent", "md5").is_err());
165 assert!(hmac("k", "d", "sha-1").is_err());
166 assert!(hmac("k", "d", "blake2b").is_err());
167 }
168}