1#![doc = include_str!("../README.md")]
9
10#[cfg(feature = "multi-thread")]
11use rayon::prelude::*;
12#[cfg(feature = "multi-thread")]
13use std::sync::mpsc::channel;
14
15use blake2::digest::{FixedOutput, Mac};
16use chacha20::{
17 cipher::{generic_array::GenericArray, typenum, StreamCipher},
18 XChaCha20,
19};
20use chacha20poly1305::{aead::Aead, AeadCore, XChaCha20Poly1305};
21use ed25519_dalek::{Signer, Verifier};
22use x25519_dalek::{PublicKey, StaticSecret};
23
24const NONCE_LEN: usize = 24;
25const KEY_LEN: usize = 32;
26const SIGNATURE_LEN: usize = 64;
27
28pub fn generate_fingerprint() -> ([u8; 32], [u8; 32]) {
39 let fingerprint = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
40
41 (
42 fingerprint.to_bytes(),
43 fingerprint.verifying_key().to_bytes(),
44 )
45}
46
47#[inline]
49fn sign(fingerprint: &[u8; 32], content: &[u8]) -> [u8; 64] {
50 let fingerprint = ed25519_dalek::SigningKey::from_bytes(fingerprint);
51 let signature = fingerprint.sign(content);
52
53 signature.to_bytes()
54}
55
56#[inline]
58fn verify(signature: &[u8; 64], verifier: &[u8; 32], content: &[u8]) -> Result<(), &'static str> {
59 let signature = ed25519_dalek::Signature::from_bytes(signature);
60 let verifier = match ed25519_dalek::VerifyingKey::from_bytes(verifier) {
61 Ok(vk) => vk,
62 Err(_) => return Err("failed to convert verifying key"),
63 };
64
65 match verifier.verify(content, &signature) {
66 Ok(_) => Ok(()),
67 Err(_) => return Err("failed to verify signature"),
68 }
69}
70
71pub fn generate_dh_keys() -> ([u8; 32], [u8; 32]) {
82 let priv_key = x25519_dalek::StaticSecret::random_from_rng(rand_core::OsRng);
83 let pub_key = x25519_dalek::PublicKey::from(&priv_key);
84
85 (*priv_key.as_bytes(), *pub_key.as_bytes())
86}
87
88#[inline(always)]
89fn usize_to_bytes(num: usize) -> Vec<u8> {
90 match num {
91 0..=63 => vec![(0 << 6) | num as u8],
92 64..=318 => vec![((0 << 6) | 63), (num - 63) as u8],
93 319..=65_598 => {
94 let mut h = vec![(1 << 6) | 63];
95 h.extend_from_slice(&((num - 63) as u16).to_be_bytes());
96 h
97 }
98 65_599..=4_294_967_358 => {
99 let mut h = vec![(2 << 6) | 63];
100 h.extend_from_slice(&((num - 63) as u32).to_be_bytes());
101 h
102 }
103 _ => {
104 let mut h = vec![(3 << 6) | 63];
105 h.extend_from_slice(&((num - 63) as u64).to_be_bytes());
106 h
107 }
108 }
109}
110
111#[inline(always)]
112fn bytes_to_usize(bytes: &[u8]) -> (usize, usize) {
113 let num_size = bytes[0];
114
115 if num_size < 64 {
116 (1, num_size as usize)
117 } else {
118 match (num_size >> 6) & 0b11 {
119 0 => (2, bytes[1] as usize + 63),
120 1 => (
121 3,
122 u16::from_be_bytes(bytes[1..3].try_into().unwrap()) as usize + 63,
123 ),
124 2 => (
125 5,
126 u32::from_be_bytes(bytes[1..5].try_into().unwrap()) as usize + 63,
127 ),
128 3 => (
129 9,
130 u64::from_be_bytes(bytes[1..9].try_into().unwrap()) as usize + 63,
131 ),
132 _ => unreachable!(),
133 }
134 }
135}
136
137#[inline]
138fn encrypt_content(
139 fingerprint: [u8; 32],
140 nonce: &GenericArray<u8, typenum::U24>,
141 key: &GenericArray<u8, typenum::U32>,
142 content: &mut Vec<u8>,
143) -> Result<Vec<u8>, &'static str> {
144 use chacha20poly1305::KeyInit;
145
146 let signature = sign(&fingerprint, &content);
147 content.extend(signature);
148
149 let content_cipher = XChaCha20Poly1305::new(key);
150 match content_cipher.encrypt(nonce, content.as_ref()) {
151 Ok(encrypted_content) => Ok(encrypted_content),
152 Err(_) => Err("failed to encrypt content"),
153 }
154}
155
156#[inline]
157fn dh_encrypt_keys(
158 priv_key: [u8; KEY_LEN],
159 pub_keys: &Vec<[u8; KEY_LEN]>,
160 nonce: &GenericArray<u8, typenum::U24>,
161 content_key: &GenericArray<u8, typenum::U32>,
162) -> (Vec<u8>, Vec<u8>) {
163 use chacha20::cipher::KeyIvInit;
164
165 let keys_count = pub_keys.len();
166 let header = usize_to_bytes(keys_count);
167
168 let priv_key = StaticSecret::from(priv_key);
169
170 let mut keys = vec![0u8; KEY_LEN * keys_count];
171
172 #[cfg(feature = "multi-thread")]
173 let chunks = keys.par_chunks_mut(KEY_LEN);
174 #[cfg(not(feature = "multi-thread"))]
175 let chunks = keys.chunks_mut(KEY_LEN);
176
177 chunks.enumerate().for_each(|(i, chunk)| {
178 let shared_secret = priv_key
179 .diffie_hellman(&PublicKey::from(pub_keys[i]))
180 .to_bytes();
181
182 let mut key_cipher = XChaCha20::new(&shared_secret.into(), nonce);
183
184 let mut buf = content_key.clone();
185
186 key_cipher.apply_keystream(&mut buf);
187 chunk[0..KEY_LEN].copy_from_slice(&buf);
188 });
189
190 (header, keys)
191}
192
193pub enum Encrypt<'a> {
195 Dh([u8; KEY_LEN], &'a Vec<[u8; KEY_LEN]>),
198
199 Hmac([u8; KEY_LEN], [u8; KEY_LEN], usize),
202
203 Session([u8; KEY_LEN]),
205}
206
207pub fn encrypt(
281 fingerprint: [u8; 32],
282 mut content: Vec<u8>,
283 mode: Encrypt,
284) -> Result<(Vec<u8>, [u8; KEY_LEN]), &'static str> {
285 let nonce = XChaCha20Poly1305::generate_nonce(&mut rand_core::OsRng);
286 let mut out = nonce.to_vec();
287
288 match mode {
289 Encrypt::Session(key) => {
290 let encrypted_content =
291 encrypt_content(fingerprint, &nonce, &key.into(), &mut content)?;
292 out.extend(encrypted_content);
293
294 out.push(0);
295
296 Ok((out, key))
297 }
298 Encrypt::Hmac(hash_key, key, itr) => {
299 let key = blake2::Blake2sMac256::new_from_slice(&hash_key)
300 .unwrap()
301 .chain_update(&key)
302 .finalize_fixed();
303
304 let itr_as_bytes = usize_to_bytes(itr);
305 out.extend(itr_as_bytes);
306
307 let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content)?;
308 out.extend(encrypted_content);
309
310 out.push(1);
311
312 Ok((out, key.into()))
313 }
314 Encrypt::Dh(priv_key, pub_keys) => {
315 use chacha20poly1305::KeyInit;
316
317 let key = XChaCha20Poly1305::generate_key(&mut rand_core::OsRng);
318
319 #[cfg(feature = "multi-thread")]
320 let (sender, receiver) = channel();
321
322 #[cfg(feature = "multi-thread")]
323 rayon::spawn(move || {
324 let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content);
325 sender.send(encrypted_content).unwrap();
326 });
327
328 let (header, keys) = dh_encrypt_keys(priv_key, pub_keys, &nonce, &key);
329 out.extend(header);
330 out.extend(keys);
331
332 #[cfg(feature = "multi-thread")]
333 let encrypted_content = receiver.recv().unwrap()?;
334 #[cfg(not(feature = "multi-thread"))]
335 let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content)?;
336
337 out.extend(encrypted_content);
338
339 out.push(2);
340
341 Ok((out, key.into()))
342 }
343 }
344}
345
346pub enum Components {
348 Session,
349 Hmac(usize),
350 Dh([u8; KEY_LEN]),
351}
352
353#[inline(always)]
373pub fn extract_components(
374 position: usize,
375 mut encrypted_content: Vec<u8>,
376) -> (Components, Vec<u8>) {
377 let mode_meta = extract_components_mut(position, &mut encrypted_content);
378
379 (mode_meta, encrypted_content)
380}
381
382pub fn extract_components_mut(position: usize, encrypted_content: &mut Vec<u8>) -> Components {
400 let mode = encrypted_content.pop().expect("at least one element");
401
402 match mode {
403 2 => {
404 let (keys_count_size, keys_count) =
405 bytes_to_usize(&encrypted_content[NONCE_LEN..NONCE_LEN + 9]);
406
407 let keys_start = NONCE_LEN + keys_count_size;
408 let encrypted_key_start = keys_start + (position as usize * KEY_LEN);
409
410 let encrypted_content_start = keys_start + (keys_count * KEY_LEN);
411
412 let content_key: [u8; KEY_LEN] = encrypted_content
413 [encrypted_key_start..encrypted_key_start + KEY_LEN]
414 .try_into()
415 .unwrap();
416
417 encrypted_content.copy_within(encrypted_content_start.., NONCE_LEN);
418 encrypted_content
419 .truncate(encrypted_content.len() - keys_count_size - (keys_count * KEY_LEN));
420
421 Components::Dh(content_key)
422 }
423 1 => {
424 let (itr_size, itr) = bytes_to_usize(&encrypted_content[NONCE_LEN..NONCE_LEN + 9]);
425
426 encrypted_content.copy_within(NONCE_LEN + itr_size.., NONCE_LEN);
427 encrypted_content.truncate(encrypted_content.len() - itr_size);
428
429 Components::Hmac(itr)
430 }
431 _ => Components::Session,
432 }
433}
434
435pub enum Decrypt {
437 Dh([u8; KEY_LEN], [u8; KEY_LEN], [u8; KEY_LEN]),
439
440 Hmac([u8; KEY_LEN], [u8; KEY_LEN]),
443
444 Session([u8; KEY_LEN]),
446}
447
448pub fn decrypt(
494 verifier: Option<&[u8; 32]>,
495 encrypted_content: &[u8],
496 mode: Decrypt,
497) -> Result<(Vec<u8>, [u8; KEY_LEN]), &'static str> {
498 let nonce = &GenericArray::<u8, typenum::U24>::from_slice(&encrypted_content[0..NONCE_LEN]);
499
500 let (content_key, encrypted_content): (GenericArray<u8, typenum::U32>, &[u8]) = match mode {
501 Decrypt::Session(key) => (key.into(), &encrypted_content[NONCE_LEN..]),
502 Decrypt::Hmac(hash_key, key) => {
503 let key = blake2::Blake2sMac256::new_from_slice(&hash_key)
504 .unwrap()
505 .chain_update(&key)
506 .finalize_fixed();
507
508 (key, &encrypted_content[NONCE_LEN..])
509 }
510 Decrypt::Dh(mut content_key, pub_key, priv_key) => {
511 let priv_key = StaticSecret::from(priv_key);
512 let shared_secret = priv_key.diffie_hellman(&pub_key.into()).to_bytes();
513
514 let mut key_cipher = {
515 use chacha20::cipher::KeyIvInit;
516 XChaCha20::new(&shared_secret.into(), nonce)
517 };
518
519 key_cipher.apply_keystream(&mut content_key);
520
521 let content_key = GenericArray::from(content_key);
522
523 (content_key, &encrypted_content[NONCE_LEN..])
524 }
525 };
526
527 let content_cipher = {
528 use chacha20poly1305::KeyInit;
529 XChaCha20Poly1305::new(&content_key)
530 };
531
532 match content_cipher.decrypt(nonce, encrypted_content) {
533 Ok(mut content) => {
534 let signature = content.split_off(content.len() - SIGNATURE_LEN);
535
536 match verifier {
537 Some(verifier) => {
538 let signature_as_bytes: [u8; SIGNATURE_LEN] = match signature.try_into() {
539 Ok(v) => v,
540 Err(_) => return Err("failed to convert signature to bytes"),
541 };
542
543 verify(&signature_as_bytes, verifier, &content)?;
544 Ok((content, content_key.into()))
545 }
546 None => Ok((content, content_key.into())),
547 }
548 }
549 Err(_) => return Err("failed to decrypt content"),
550 }
551}