devolutions_crypto/
utils.rs1use base64::{
4 alphabet,
5 engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
6 Engine as _,
7};
8use hmac::Hmac;
9use pbkdf2::pbkdf2;
10use rand::TryRng;
11use sha2::Sha256;
12use subtle::ConstantTimeEq as _;
13
14use crate::online_ciphertext::OnlineCiphertextHeader;
15
16use super::Argon2Parameters;
17use super::DataType;
18use super::Error;
19use super::Header;
20use super::Result;
21
22pub fn generate_key(length: usize) -> Result<Vec<u8>> {
34 let mut key = vec![0u8; length];
35 rand::rngs::SysRng
36 .try_fill_bytes(&mut key)
37 .map_err(|_| Error::RandomError)?;
38 Ok(key)
39}
40
41pub fn derive_key_pbkdf2(key: &[u8], salt: &[u8], iterations: u32, length: usize) -> Vec<u8> {
61 let mut new_key = vec![0u8; length];
62 let _ = pbkdf2::<Hmac<Sha256>>(key, salt, iterations, &mut new_key);
63 new_key
64}
65
66pub fn derive_key_argon2(key: &[u8], parameters: &Argon2Parameters) -> Result<Vec<u8>> {
82 parameters.compute(key)
83}
84
85pub fn validate_header(data: &[u8], data_type: DataType) -> bool {
103 use super::ciphertext::Ciphertext;
104 use super::derive_encrypt::KdfEncryptedData;
105 use super::key::{PrivateKey, PublicKey};
106 use super::password_hash::PasswordHash;
107 use super::secret_sharing::Share;
108 use super::signature::Signature;
109 use super::signing_key::{SigningKeyPair, SigningPublicKey};
110
111 if data.len() < Header::len() {
112 return false;
113 }
114
115 match data_type {
116 DataType::None => false,
117 DataType::Ciphertext => Header::<Ciphertext>::try_from(&data[0..Header::len()]).is_ok(),
118 DataType::PasswordHash => Header::<PasswordHash>::try_from(&data[0..Header::len()]).is_ok(),
119 DataType::Key => {
120 Header::<PrivateKey>::try_from(&data[0..Header::len()]).is_ok()
121 || Header::<PublicKey>::try_from(&data[0..Header::len()]).is_ok()
122 }
123 DataType::SigningKey => {
124 Header::<SigningKeyPair>::try_from(&data[0..Header::len()]).is_ok()
125 || Header::<SigningPublicKey>::try_from(&data[0..Header::len()]).is_ok()
126 }
127 DataType::Share => Header::<Share>::try_from(&data[0..Header::len()]).is_ok(),
128 DataType::Signature => Header::<Signature>::try_from(&data[0..Header::len()]).is_ok(),
129 DataType::OnlineCiphertext => {
130 Header::<OnlineCiphertextHeader>::try_from(&data[0..Header::len()]).is_ok()
131 }
132 DataType::KeyDerivation => {
133 use super::key_derivation::DerivationParameters;
134 Header::<DerivationParameters>::try_from(&data[0..Header::len()]).is_ok()
135 }
136 DataType::KdfEncryptedData => {
137 Header::<KdfEncryptedData>::try_from(&data[0..Header::len()]).is_ok()
138 }
139 }
140}
141
142pub fn scrypt_simple(password: &[u8], salt: &[u8], log_n: u8, r: u32, p: u32) -> String {
148 use byteorder::{ByteOrder, LittleEndian};
149
150 let params = scrypt::Params::new(log_n, r, p).expect("params should be valid");
151
152 let mut dk = [0u8; 32];
154
155 scrypt::scrypt(password, salt, ¶ms, &mut dk)
156 .expect("32 bytes always satisfy output length requirements");
157
158 let mut result = String::with_capacity(128);
160 result.push_str("$rscrypt$");
161 if r < 256 && p < 256 {
162 result.push_str("0$");
163 let mut tmp = [0u8; 3];
164 tmp[0] = log_n;
165 tmp[1] = r as u8;
166 tmp[2] = p as u8;
167 result.push_str(&DEVO_BASE64.encode(tmp));
168 } else {
169 result.push_str("1$");
170 let mut tmp = [0u8; 9];
171 tmp[0] = log_n;
172 LittleEndian::write_u32(&mut tmp[1..5], r);
173 LittleEndian::write_u32(&mut tmp[5..9], p);
174 result.push_str(&DEVO_BASE64.encode(tmp));
175 }
176 result.push('$');
177 result.push_str(&DEVO_BASE64.encode(salt));
178 result.push('$');
179 result.push_str(&DEVO_BASE64.encode(dk));
180 result.push('$');
181
182 result
183}
184
185pub fn base64_encode(data: &[u8]) -> String {
186 DEVO_BASE64.encode(data)
187}
188
189pub fn base64_encode_url(data: &[u8]) -> String {
190 DEVO_BASE64_URLSAFE_NOPAD.encode(data)
191}
192
193pub fn base64_decode(data: &str) -> Result<Vec<u8>> {
194 match DEVO_BASE64.decode(data) {
195 Ok(d) => Ok(d),
196 _ => Err(Error::InvalidData),
197 }
198}
199
200pub fn base64_decode_url(data: &str) -> Result<Vec<u8>> {
201 match DEVO_BASE64_URLSAFE_NOPAD.decode(data) {
202 Ok(d) => Ok(d),
203 _ => Err(Error::InvalidData),
204 }
205}
206
207pub fn constant_time_equals(x: &[u8], y: &[u8]) -> bool {
208 x.ct_eq(y).into()
209}
210
211const BASE64_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
212 .with_encode_padding(true)
213 .with_decode_padding_mode(DecodePaddingMode::Indifferent)
214 .with_decode_allow_trailing_bits(true);
215
216const BASE64_CONFIG_NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
217 .with_encode_padding(false)
218 .with_decode_padding_mode(DecodePaddingMode::Indifferent)
219 .with_decode_allow_trailing_bits(true);
220
221const DEVO_BASE64: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, BASE64_CONFIG);
222const DEVO_BASE64_URLSAFE_NOPAD: GeneralPurpose =
223 GeneralPurpose::new(&alphabet::URL_SAFE, BASE64_CONFIG_NO_PAD);
224
225#[test]
226fn test_constant_time_equals() {
227 let x: [u8; 3] = [0, 1, 2];
228 let y: [u8; 3] = [4, 5, 6];
229 let z: [u8; 4] = [0, 1, 2, 3];
230
231 assert!(constant_time_equals(&x, &x));
232 assert!(!constant_time_equals(&x, &y));
233 assert!(!constant_time_equals(&x, &z));
234 assert!(!constant_time_equals(&y, &x));
235 assert!(constant_time_equals(&y, &y));
236 assert!(!constant_time_equals(&y, &z));
237 assert!(!constant_time_equals(&z, &x));
238 assert!(!constant_time_equals(&z, &y));
239 assert!(constant_time_equals(&z, &z));
240}
241
242#[test]
243fn test_generate_key() {
244 let size = 32;
245 let key = generate_key(size).unwrap();
246
247 assert_eq!(size, key.len());
248 assert_ne!(vec![0u8; size], key);
249}
250
251#[test]
252fn test_derive_key_pbkdf2() {
253 let salt = b"salt";
254 let key = b"key";
255 let iterations = 100;
256 let size = 32;
257
258 let derived = derive_key_pbkdf2(key, salt, iterations, size);
259
260 assert_eq!(size, derived.len());
261 assert_ne!(vec![0u8; size], derived);
262}
263
264#[test]
265fn test_validate_header() {
266 use base64::engine::general_purpose::STANDARD;
267
268 let valid_ciphertext = STANDARD.decode("DQwCAAAAAQA=").unwrap();
269 let valid_password_hash = STANDARD.decode("DQwDAAAAAQA=").unwrap();
270 let valid_share = STANDARD.decode("DQwEAAAAAQA=").unwrap();
271 let valid_private_key = STANDARD.decode("DQwBAAEAAQA=").unwrap();
272 let valid_public_key = STANDARD.decode("DQwBAAEAAQA=").unwrap();
273
274 assert!(validate_header(&valid_ciphertext, DataType::Ciphertext));
275 assert!(validate_header(
276 &valid_password_hash,
277 DataType::PasswordHash
278 ));
279 assert!(validate_header(&valid_share, DataType::Share));
280 assert!(validate_header(&valid_private_key, DataType::Key));
281 assert!(validate_header(&valid_public_key, DataType::Key));
282
283 assert!(!validate_header(&valid_ciphertext, DataType::PasswordHash));
284
285 let invalid_signature = STANDARD.decode("DAwBAAEAAQA=").unwrap();
286 let invalid_type = STANDARD.decode("DQwIAAEAAQA=").unwrap();
287 let invalid_subtype = STANDARD.decode("DQwBAAgAAQA=").unwrap();
288 let invalid_version = STANDARD.decode("DQwBAAEACAA=").unwrap();
289
290 assert!(!validate_header(&invalid_signature, DataType::Key));
291 assert!(!validate_header(&invalid_type, DataType::Key));
292 assert!(!validate_header(&invalid_subtype, DataType::Key));
293 assert!(!validate_header(&invalid_version, DataType::Key));
294
295 let not_long_enough = STANDARD.decode("DQwBAAEAAQ==").unwrap();
296
297 assert!(!validate_header(¬_long_enough, DataType::Key));
298}
299
300#[test]
301fn test_scrypt_simple() {
302 let hash = scrypt_simple(b"password", b"devolutions-salt", 8, 8, 1);
306
307 assert_eq!(
308 hash,
309 "$rscrypt$0$CAgB$ZGV2b2x1dGlvbnMtc2FsdA==$hIK4WZ1ct+CgcxYxMoi+lkk5RztS/EJBxxm0zmsNVY0=$"
310 );
311}