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