vss_client/util/
key_obfuscator.rs1use std::io::{Error, ErrorKind};
2
3use base64::prelude::BASE64_STANDARD_NO_PAD;
4use base64::Engine;
5use bitcoin_hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine};
6
7use crate::crypto::chacha20poly1305::ChaCha20Poly1305;
8
9pub struct KeyObfuscator {
14 obfuscation_key: [u8; 32],
15 hashing_key: [u8; 32],
16}
17
18impl KeyObfuscator {
19 pub fn new(obfuscation_master_key: [u8; 32]) -> KeyObfuscator {
21 let (obfuscation_key, hashing_key) =
22 Self::derive_obfuscation_and_hashing_keys(&obfuscation_master_key);
23 Self { obfuscation_key, hashing_key }
24 }
25}
26
27const TAG_LENGTH: usize = 16;
28const NONCE_LENGTH: usize = 12;
29
30impl KeyObfuscator {
31 pub fn obfuscate(&self, key: &str) -> String {
33 let key_bytes = key.as_bytes();
34 let mut ciphertext =
35 Vec::with_capacity(key_bytes.len() + TAG_LENGTH + NONCE_LENGTH + TAG_LENGTH);
36 ciphertext.extend_from_slice(&key_bytes);
37
38 let (mut nonce, tag) = self.encrypt(&mut ciphertext, key.as_bytes());
40
41 let (_, nonce_tag) = self.encrypt(&mut nonce, &ciphertext);
43
44 debug_assert_eq!(tag.len(), TAG_LENGTH);
45 ciphertext.extend_from_slice(&tag);
46 debug_assert_eq!(nonce.len(), NONCE_LENGTH);
47 ciphertext.extend_from_slice(&nonce);
48 debug_assert_eq!(nonce_tag.len(), TAG_LENGTH);
49 ciphertext.extend_from_slice(&nonce_tag);
50 BASE64_STANDARD_NO_PAD.encode(ciphertext)
51 }
52
53 pub fn deobfuscate(&self, obfuscated_key: &str) -> Result<String, Error> {
55 let obfuscated_key_bytes = BASE64_STANDARD_NO_PAD.decode(obfuscated_key).map_err(|e| {
56 let msg = format!(
57 "Failed to decode base64 while deobfuscating key: {}, Error: {}",
58 obfuscated_key, e
59 );
60 Error::new(ErrorKind::InvalidData, msg)
61 })?;
62
63 if obfuscated_key_bytes.len() < TAG_LENGTH + NONCE_LENGTH + TAG_LENGTH {
64 let msg = format!(
65 "Failed to deobfuscate, obfuscated_key was of invalid length. \
66 Obfuscated key should at least have {} bytes, found: {}. Key: {}.",
67 (TAG_LENGTH + NONCE_LENGTH + TAG_LENGTH),
68 obfuscated_key_bytes.len(),
69 obfuscated_key
70 );
71 return Err(Error::new(ErrorKind::InvalidData, msg));
72 }
73
74 let (ciphertext, remaining) = obfuscated_key_bytes
76 .split_at(obfuscated_key_bytes.len() - TAG_LENGTH - NONCE_LENGTH - TAG_LENGTH);
77 let (tag, remaining) = remaining.split_at(TAG_LENGTH);
78 let (wrapped_nonce_bytes, wrapped_nonce_tag) = remaining.split_at(NONCE_LENGTH);
79 debug_assert_eq!(wrapped_nonce_tag.len(), TAG_LENGTH);
80
81 let mut wrapped_nonce = [0u8; NONCE_LENGTH];
83 wrapped_nonce.clone_from_slice(&wrapped_nonce_bytes);
84 self.decrypt(&mut wrapped_nonce, ciphertext, wrapped_nonce_tag).map_err(|_| {
85 let msg = format!(
86 "Failed to decrypt wrapped nonce, for key: {}, Invalid Tag.",
87 obfuscated_key
88 );
89 Error::new(ErrorKind::InvalidData, msg)
90 })?;
91
92 let mut cipher = ChaCha20Poly1305::new(&self.obfuscation_key, &wrapped_nonce, &[]);
94 let mut ciphertext = ciphertext.to_vec();
95 cipher.decrypt_inplace(&mut ciphertext, tag).map_err(|_| {
96 let msg = format!("Failed to decrypt key: {}, Invalid Tag.", obfuscated_key);
97 Error::new(ErrorKind::InvalidData, msg)
98 })?;
99
100 let original_key = String::from_utf8(ciphertext).map_err(|e| {
101 let msg = format!(
102 "Input was not valid utf8 while deobfuscating key: {}, Error: {}",
103 obfuscated_key, e
104 );
105 Error::new(ErrorKind::InvalidData, msg)
106 })?;
107 Ok(original_key)
108 }
109
110 fn encrypt(
112 &self, mut plaintext: &mut [u8], initial_nonce_material: &[u8],
113 ) -> ([u8; 12], [u8; 16]) {
114 let nonce = self.generate_synthetic_nonce(initial_nonce_material);
115 let mut cipher = ChaCha20Poly1305::new(&self.obfuscation_key, &nonce, &[]);
116 let mut tag = [0u8; TAG_LENGTH];
117 cipher.encrypt_inplace(&mut plaintext, &mut tag);
118 (nonce, tag)
119 }
120
121 fn decrypt(
123 &self, mut ciphertext: &mut [u8], initial_nonce_material: &[u8], tag: &[u8],
124 ) -> Result<(), ()> {
125 let nonce = self.generate_synthetic_nonce(initial_nonce_material);
126 let mut cipher = ChaCha20Poly1305::new(&self.obfuscation_key, &nonce, &[]);
127 cipher.decrypt_inplace(&mut ciphertext, tag)
128 }
129
130 fn generate_synthetic_nonce(&self, initial_nonce_material: &[u8]) -> [u8; 12] {
132 let hmac = Self::hkdf(&self.hashing_key, initial_nonce_material);
133 let mut nonce = [0u8; NONCE_LENGTH];
134 nonce[4..].copy_from_slice(&hmac[..8]);
135 nonce
136 }
137
138 fn derive_obfuscation_and_hashing_keys(
140 obfuscation_master_key: &[u8; 32],
141 ) -> ([u8; 32], [u8; 32]) {
142 let prk = Self::hkdf(obfuscation_master_key, "pseudo_random_key".as_bytes());
143 let k1 = Self::hkdf(&prk, "obfuscation_key".as_bytes());
144 let k2 = Self::hkdf(&prk, &[&k1[..], "hashing_key".as_bytes()].concat());
145 (k1, k2)
146 }
147 fn hkdf(initial_key_material: &[u8], salt: &[u8]) -> [u8; 32] {
148 let mut engine = HmacEngine::<sha256::Hash>::new(salt);
149 engine.input(initial_key_material);
150 Hmac::from_engine(engine).to_byte_array()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::util::key_obfuscator::KeyObfuscator;
157
158 #[test]
159 fn obfuscate_deobfuscate_deterministic() {
160 let obfuscation_master_key = [42u8; 32];
161 let key_obfuscator = KeyObfuscator::new(obfuscation_master_key);
162 let expected_key = "a_semi_secret_key";
163 let obfuscated_key = key_obfuscator.obfuscate(expected_key);
164
165 let actual_key = key_obfuscator.deobfuscate(obfuscated_key.as_str()).unwrap();
166 assert_eq!(actual_key, expected_key);
167 assert_eq!(
168 obfuscated_key,
169 "cMoet5WTvl0nYds+VW7JPCtXUq24DtMG2dR9apAi/T5jy8eNIEyDrUAJBS4geeUuX+XGXPqlizIByOip2g"
170 );
171 }
172
173 use proptest::prelude::*;
174
175 proptest! {
176 #[test]
177 fn obfuscate_deobfuscate_proptest(expected_key in "[a-zA-Z0-9_!@#,;:%\\s\\*\\$\\^&\\(\\)\\[\\]\\{\\}\\.]*", obfuscation_master_key in any::<[u8; 32]>()) {
178 let key_obfuscator = KeyObfuscator::new(obfuscation_master_key);
179 let obfuscated_key = key_obfuscator.obfuscate(&expected_key);
180 let actual_key = key_obfuscator.deobfuscate(obfuscated_key.as_str()).unwrap();
181 assert_eq!(actual_key, expected_key);
182 }
183 }
184}