Switching to async-psec
This commit is contained in:
parent
8adcef8852
commit
275c2972df
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
Cargo.lock
|
||||
target
|
||||
/target
|
||||
local
|
2754
Cargo.lock
generated
Normal file
2754
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
Cargo.toml
39
Cargo.toml
@ -5,39 +5,36 @@ authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rand-8 = {package = "rand", version = "0.8.3"}
|
||||
rand = "0.8"
|
||||
rand-7 = { package = "rand", version = "0.7.3" }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "io-util"] }
|
||||
async-trait = "0.1.5"
|
||||
async-psec = { version = "0.1", git = "https://forge.chapril.org/hardcoresushi/async-psec", features = ["split"] }
|
||||
lazy_static = "1.4"
|
||||
socket2 = "0.4.0"
|
||||
socket2 = "0.4"
|
||||
rusqlite = { version = "0.25.1", features = ["bundled"] }
|
||||
ed25519-dalek = "1" #for singing
|
||||
x25519-dalek = "1.1" #for shared secret
|
||||
sha2 = "0.9.3"
|
||||
hkdf = "0.11.0"
|
||||
aes-gcm = "0.9.0" #PSEC
|
||||
aes-gcm-siv = "0.10.0" #Database
|
||||
hmac = "0.11.0"
|
||||
hex = "0.4.3"
|
||||
strum_macros = "0.20.1" #display enums
|
||||
sha2 = "0.9"
|
||||
aes-gcm = "0.9"
|
||||
aes-gcm-siv = "0.10" #Database
|
||||
hkdf = "0.11"
|
||||
hex = "0.4"
|
||||
actix-web = "3"
|
||||
actix-multipart = "0.3"
|
||||
time = "0.2.25" #needed for actix cookies
|
||||
time = "0.2" #needed for actix cookies
|
||||
futures = "0.3"
|
||||
tungstenite = "0.13.0" #websocket
|
||||
serde = "1.0.124" #serialization
|
||||
html-escape = "0.2.7"
|
||||
tungstenite = "0.13" #websocket
|
||||
serde = "1.0" #serialization
|
||||
html-escape = "0.2"
|
||||
sanitize-filename = "0.3"
|
||||
platform-dirs = "0.3.0"
|
||||
platform-dirs = "0.3"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
webbrowser = "0.5.5"
|
||||
webbrowser = "0.5"
|
||||
libmdns = "0.6" #mDNS advertiser
|
||||
multicast_dns = "0.5" #mDNS browser
|
||||
if-addrs = "0.6"
|
||||
base64 = "0.13.0"
|
||||
scrypt = "0.7.0"
|
||||
zeroize = "1.2.0"
|
||||
base64 = "0.13"
|
||||
scrypt = "0.7"
|
||||
zeroize = "1.2"
|
||||
|
||||
[build-dependencies]
|
||||
html-minifier = "3.0.11"
|
||||
html-minifier = "3.0"
|
||||
|
146
src/crypto.rs
146
src/crypto.rs
@ -1,148 +1,18 @@
|
||||
use std::convert::TryInto;
|
||||
use std::{convert::TryInto, fmt::Display};
|
||||
use hkdf::Hkdf;
|
||||
use sha2::Sha384;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use scrypt::{scrypt, Params};
|
||||
use rand_8::{RngCore, rngs::OsRng};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use aes_gcm::{aead::Aead, NewAead, Nonce};
|
||||
use aes_gcm_siv::Aes256GcmSiv;
|
||||
use zeroize::Zeroize;
|
||||
use strum_macros::Display;
|
||||
use crate::utils::*;
|
||||
|
||||
pub const HASH_OUTPUT_LEN: usize = 48; //SHA384
|
||||
const KEY_LEN: usize = 16;
|
||||
pub const IV_LEN: usize = 12;
|
||||
pub const AES_TAG_LEN: usize = 16;
|
||||
pub const SALT_LEN: usize = 32;
|
||||
const PASSWORD_HASH_LEN: usize = 32;
|
||||
pub const MASTER_KEY_LEN: usize = 32;
|
||||
|
||||
pub fn iv_to_nonce(iv: &[u8], counter: &mut usize) -> Vec<u8> {
|
||||
let mut counter_bytes = vec![0; 4];
|
||||
counter_bytes.extend_from_slice(&counter.to_be_bytes());
|
||||
let r: Vec<u8> = iv.iter().zip(counter_bytes.iter()).map(|(a, b)| a^b).collect();
|
||||
*counter += 1;
|
||||
r
|
||||
}
|
||||
|
||||
fn hkdf_expand_label(key: &[u8], label: &str, context: Option<&[u8]>, okm: &mut [u8]) {
|
||||
let hkdf = Hkdf::<Sha384>::from_prk(key).unwrap();
|
||||
//can't set info conditionnally because of different array size
|
||||
match context {
|
||||
Some(context) => {
|
||||
let info = [&(label.len() as u32).to_be_bytes(), label.as_bytes(), &(context.len() as u32).to_be_bytes(), context];
|
||||
hkdf.expand_multi_info(&info, okm).unwrap();
|
||||
}
|
||||
None => {
|
||||
let info = [&(label.len() as u32).to_be_bytes(), label.as_bytes()];
|
||||
hkdf.expand_multi_info(&info, okm).unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct HandshakeKeys {
|
||||
pub local_key: [u8; KEY_LEN],
|
||||
pub local_iv: [u8; IV_LEN],
|
||||
pub local_handshake_traffic_secret: [u8; HASH_OUTPUT_LEN],
|
||||
pub peer_key: [u8; KEY_LEN],
|
||||
pub peer_iv: [u8; IV_LEN],
|
||||
pub peer_handshake_traffic_secret: [u8; HASH_OUTPUT_LEN],
|
||||
pub handshake_secret: [u8; HASH_OUTPUT_LEN],
|
||||
}
|
||||
|
||||
impl HandshakeKeys {
|
||||
pub fn derive_keys(shared_secret: [u8; 32], handshake_hash: [u8; HASH_OUTPUT_LEN], i_am_bob: bool) -> HandshakeKeys {
|
||||
let (handshake_secret, _) = Hkdf::<Sha384>::extract(None, &shared_secret);
|
||||
|
||||
let local_label = "handshake".to_owned() + if i_am_bob {"i_am_bob"} else {"i_am_alice"};
|
||||
let mut local_handshake_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(handshake_secret.as_slice(), &local_label, Some(&handshake_hash), &mut local_handshake_traffic_secret);
|
||||
|
||||
let peer_label = "handshake".to_owned() + if i_am_bob {"i_am_alice"} else {"i_am_bob"};
|
||||
let mut peer_handshake_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(handshake_secret.as_slice(), &peer_label, Some(&handshake_hash), &mut peer_handshake_traffic_secret);
|
||||
|
||||
let mut local_handshake_key = [0; KEY_LEN];
|
||||
hkdf_expand_label(&local_handshake_traffic_secret, "key", None, &mut local_handshake_key);
|
||||
let mut local_handshake_iv = [0; IV_LEN];
|
||||
hkdf_expand_label(&local_handshake_traffic_secret, "iv", None, &mut local_handshake_iv);
|
||||
|
||||
let mut peer_handshake_key = [0; KEY_LEN];
|
||||
hkdf_expand_label(&peer_handshake_traffic_secret, "key", None, &mut peer_handshake_key);
|
||||
let mut peer_handshake_iv = [0; IV_LEN];
|
||||
hkdf_expand_label(&peer_handshake_traffic_secret,"iv", None, &mut peer_handshake_iv);
|
||||
|
||||
HandshakeKeys {
|
||||
local_key: local_handshake_key,
|
||||
local_iv: local_handshake_iv,
|
||||
local_handshake_traffic_secret: local_handshake_traffic_secret,
|
||||
peer_key: peer_handshake_key,
|
||||
peer_iv: peer_handshake_iv,
|
||||
peer_handshake_traffic_secret: peer_handshake_traffic_secret,
|
||||
handshake_secret: to_array_48(handshake_secret.as_slice())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApplicationKeys {
|
||||
pub local_key: [u8; KEY_LEN],
|
||||
pub local_iv: [u8; IV_LEN],
|
||||
pub peer_key: [u8; KEY_LEN],
|
||||
pub peer_iv: [u8; IV_LEN],
|
||||
}
|
||||
|
||||
impl ApplicationKeys {
|
||||
pub fn derive_keys(handshake_secret: [u8; HASH_OUTPUT_LEN], handshake_hash: [u8; HASH_OUTPUT_LEN], i_am_bob: bool) -> ApplicationKeys {
|
||||
let mut derived_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&handshake_secret, "derived", None, &mut derived_secret);
|
||||
let (master_secret, _) = Hkdf::<Sha384>::extract(Some(&derived_secret), b"");
|
||||
|
||||
let local_label = "application".to_owned() + if i_am_bob {"i_am_bob"} else {"i_am_alice"};
|
||||
let mut local_application_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&master_secret, &local_label, Some(&handshake_hash), &mut local_application_traffic_secret);
|
||||
|
||||
let peer_label = "application".to_owned() + if i_am_bob {"i_am_alice"} else {"i_am_bob"};
|
||||
let mut peer_application_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&master_secret, &peer_label, Some(&handshake_hash), &mut peer_application_traffic_secret);
|
||||
|
||||
let mut local_application_key = [0; KEY_LEN];
|
||||
hkdf_expand_label(&local_application_traffic_secret, "key", None, &mut local_application_key);
|
||||
let mut local_application_iv = [0; IV_LEN];
|
||||
hkdf_expand_label(&local_application_traffic_secret, "iv", None, &mut local_application_iv);
|
||||
|
||||
let mut peer_application_key = [0; KEY_LEN];
|
||||
hkdf_expand_label(&peer_application_traffic_secret, "key", None, &mut peer_application_key);
|
||||
let mut peer_application_iv = [0; IV_LEN];
|
||||
hkdf_expand_label(&peer_application_traffic_secret,"iv", None, &mut peer_application_iv);
|
||||
|
||||
ApplicationKeys {
|
||||
local_key: local_application_key,
|
||||
local_iv: local_application_iv,
|
||||
peer_key: peer_application_key,
|
||||
peer_iv: peer_application_iv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_handshake_finished(local_handshake_traffic_secret: [u8; HASH_OUTPUT_LEN], handshake_hash: [u8; HASH_OUTPUT_LEN]) -> [u8; HASH_OUTPUT_LEN] {
|
||||
let mut finished_key = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&local_handshake_traffic_secret, "finished", None, &mut finished_key);
|
||||
let mut hmac = Hmac::<Sha384>::new_from_slice(&finished_key).unwrap();
|
||||
hmac.update(&handshake_hash);
|
||||
hmac.finalize().into_bytes().as_slice().try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn verify_handshake_finished(peer_handshake_finished: [u8; HASH_OUTPUT_LEN], peer_handshake_traffic_secret: [u8; HASH_OUTPUT_LEN], handshake_hash: [u8; HASH_OUTPUT_LEN]) -> bool {
|
||||
let mut peer_finished_key = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&peer_handshake_traffic_secret, "finished", None, &mut peer_finished_key);
|
||||
let mut hmac = Hmac::<Sha384>::new_from_slice(&peer_finished_key).unwrap();
|
||||
hmac.update(&handshake_hash);
|
||||
hmac.verify(&peer_handshake_finished).is_ok()
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn generate_fingerprint(public_key: &[u8]) -> String {
|
||||
let mut raw_fingerprint = [0; 16];
|
||||
Hkdf::<Sha384>::new(None, public_key).expand(&[], &mut raw_fingerprint).unwrap();
|
||||
@ -150,7 +20,6 @@ pub fn generate_fingerprint(public_key: &[u8]) -> String {
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn generate_master_key() -> [u8; MASTER_KEY_LEN] {
|
||||
let mut master_key = [0; MASTER_KEY_LEN];
|
||||
OsRng.fill_bytes(&mut master_key);
|
||||
@ -169,12 +38,21 @@ pub fn encrypt_data(data: &[u8], master_key: &[u8]) -> Result<Vec<u8>, CryptoErr
|
||||
Ok(cipher_text)
|
||||
}
|
||||
|
||||
#[derive(Display, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CryptoError {
|
||||
DecryptionFailed,
|
||||
InvalidLength
|
||||
}
|
||||
|
||||
impl Display for CryptoError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
CryptoError::DecryptionFailed => "Decryption failed",
|
||||
CryptoError::InvalidLength => "Invalid length",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_data(data: &[u8], master_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
if data.len() <= IV_LEN || master_key.len() != MASTER_KEY_LEN {
|
||||
return Err(CryptoError::InvalidLength);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::convert::TryInto;
|
||||
use crypto::CryptoError;
|
||||
use ed25519_dalek::{Keypair, Signer, SIGNATURE_LENGTH, PUBLIC_KEY_LENGTH};
|
||||
use ed25519_dalek::{Keypair, PUBLIC_KEY_LENGTH};
|
||||
use rusqlite::{Connection, params};
|
||||
use platform_dirs::AppDirs;
|
||||
use utils::to_uuid_bytes;
|
||||
@ -59,17 +59,13 @@ pub struct Contact {
|
||||
|
||||
pub struct Identity {
|
||||
pub name: String,
|
||||
keypair: Keypair,
|
||||
pub keypair: Keypair,
|
||||
pub master_key: [u8; crypto::MASTER_KEY_LEN],
|
||||
pub use_padding: bool,
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
|
||||
pub fn sign(&self, input: &[u8]) -> [u8; SIGNATURE_LENGTH] {
|
||||
self.keypair.sign(input).to_bytes()
|
||||
}
|
||||
|
||||
pub fn get_public_key(&self) -> [u8; PUBLIC_KEY_LENGTH] {
|
||||
self.keypair.public.to_bytes()
|
||||
}
|
||||
|
53
src/main.rs
53
src/main.rs
@ -2,6 +2,7 @@ mod key_value_table;
|
||||
mod identity;
|
||||
mod crypto;
|
||||
mod session_manager;
|
||||
mod protocol;
|
||||
mod utils;
|
||||
mod ui_interface;
|
||||
mod constants;
|
||||
@ -13,14 +14,14 @@ use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{
|
||||
use actix_multipart::Multipart;
|
||||
use tungstenite::Message;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use rand_8::{RngCore, rngs::OsRng};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use platform_dirs::AppDirs;
|
||||
use zeroize::Zeroize;
|
||||
use utils::escape_double_quote;
|
||||
use identity::Identity;
|
||||
use session_manager::{SessionManager, SessionCommand, protocol};
|
||||
use session_manager::{SessionManager, SessionCommand};
|
||||
use ui_interface::UiConnection;
|
||||
|
||||
async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
||||
@ -159,11 +160,10 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||
"send" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string());
|
||||
match session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
if session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: buffer.clone()
|
||||
}).await {
|
||||
Ok(_) => session_manager.store_msg(&session_id, true, buffer),
|
||||
Err(e) => print_error!(e)
|
||||
session_manager.store_msg(&session_id, true, buffer);
|
||||
}
|
||||
}
|
||||
"large_files" => {
|
||||
@ -172,33 +172,25 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||
for n in (2..args.len()).step_by(2) {
|
||||
file_info.push((args[n].parse::<u64>().unwrap(), base64::decode(args[n+1]).unwrap()));
|
||||
}
|
||||
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::ask_large_files(file_info)
|
||||
}).await {
|
||||
print_error!(e);
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
"download" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: vec![protocol::Headers::ACCEPT_LARGE_FILES]
|
||||
}).await {
|
||||
print_error!(e);
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
"abort" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: vec![protocol::Headers::ABORT_FILES_TRANSFER]
|
||||
}).await {
|
||||
print_error!(e);
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
"sending_ended" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::SendingEnded).await {
|
||||
print_error!(e);
|
||||
}
|
||||
session_manager.send_command(&session_id, SessionCommand::SendingEnded).await;
|
||||
}
|
||||
"load_msgs" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
@ -234,11 +226,9 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||
}
|
||||
"ask_name" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::ask_name()
|
||||
}).await {
|
||||
print_error!(e);
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
"set_use_padding" => {
|
||||
let use_padding: bool = args[1].parse().unwrap();
|
||||
@ -357,10 +347,9 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||
while let Some(Ok(chunk)) = field.next().await {
|
||||
buffer.extend(chunk);
|
||||
}
|
||||
match global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::file(filename, &buffer)
|
||||
}).await {
|
||||
Ok(_) => {
|
||||
match global_vars_read.session_manager.store_file(&session_id, &buffer) {
|
||||
Ok(file_uuid) => {
|
||||
let msg = [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat();
|
||||
@ -370,8 +359,6 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
} else {
|
||||
let (ack_sender, mut ack_receiver) = mpsc::channel(1);
|
||||
let mut pending_buffer = Vec::new();
|
||||
@ -394,22 +381,20 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Err(e) = global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||
if !global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||
plain_text: chunk_buffer.clone()
|
||||
}).await {
|
||||
print_error!(e);
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
if !match ack_receiver.recv().await {
|
||||
Some(should_continue) => {
|
||||
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
||||
if let Err(e) = global_vars_read.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||
if global_vars_read.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||
ack_sender: ack_sender.clone()
|
||||
}).await {
|
||||
print_error!(e);
|
||||
false
|
||||
} else {
|
||||
should_continue
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
None => false
|
||||
|
@ -1,27 +1,11 @@
|
||||
mod session;
|
||||
pub mod protocol;
|
||||
|
||||
use std::{collections::HashMap, fs::OpenOptions, io::{self, Write}, net::{IpAddr, SocketAddr}, path::PathBuf, str::from_utf8, sync::{Mutex, RwLock, Arc}};
|
||||
use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{self, Sender, Receiver}};
|
||||
use libmdns::Service;
|
||||
use strum_macros::Display;
|
||||
use session::Session;
|
||||
use ed25519_dalek::PUBLIC_KEY_LENGTH;
|
||||
use uuid::Uuid;
|
||||
use platform_dirs::UserDirs;
|
||||
use crate::{constants, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}};
|
||||
use async_psec::{PUBLIC_KEY_LENGTH, Session, SessionWriteHalf, PsecWriter, PsecReader, PsecError};
|
||||
use crate::{constants, protocol, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}};
|
||||
use crate::ui_interface::UiConnection;
|
||||
use self::session::{SessionWrite, PSECWriter};
|
||||
|
||||
#[derive(Display, Debug, PartialEq, Eq)]
|
||||
pub enum SessionError {
|
||||
ConnectionReset,
|
||||
BrokenPipe,
|
||||
TransmissionCorrupted,
|
||||
BufferTooLarge,
|
||||
InvalidSessionId,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub enum SessionCommand {
|
||||
Send {
|
||||
@ -88,7 +72,7 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn encrypt_and_send<T: PSECWriter>(&self, writer: &mut T, buff: &[u8]) -> Result<(), SessionError> {
|
||||
async fn encrypt_and_send<T: PsecWriter>(&self, writer: &mut T, buff: &[u8]) -> Result<(), PsecError> {
|
||||
let use_padding = self.identity.read().unwrap().as_ref().unwrap().use_padding;
|
||||
writer.encrypt_and_send(buff, use_padding).await
|
||||
}
|
||||
@ -124,17 +108,17 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> Result<(), SessionError> {
|
||||
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> bool {
|
||||
if let Some(sender) = self.get_session_sender(session_id) {
|
||||
match sender.send(session_command).await {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
Err(SessionError::BrokenPipe)
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(SessionError::InvalidSessionId)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +131,7 @@ impl SessionManager {
|
||||
self.not_seen.write().unwrap().retain(|x| x != session_id);
|
||||
}
|
||||
|
||||
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWrite, buff: &[u8], is_sending: &mut bool, file_ack_sender: &mut Option<Sender<bool>>) -> Result<(), SessionError> {
|
||||
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: &[u8], is_sending: &mut bool, file_ack_sender: &mut Option<Sender<bool>>) -> Result<(), PsecError> {
|
||||
self.encrypt_and_send(session_write, &buff).await?;
|
||||
if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true;
|
||||
@ -177,15 +161,16 @@ impl SessionManager {
|
||||
let mut msg_queue = Vec::new();
|
||||
let mut is_sending = false;
|
||||
|
||||
let (session_read, mut session_write) = session.into_spit().unwrap();
|
||||
let receiving = session_read.receive_and_decrypt();
|
||||
let (session_read, mut session_write) = session.into_split().unwrap();
|
||||
let receiving = session_read.into_receive_and_decrypt();
|
||||
tokio::pin!(receiving);
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = &mut receiving => {
|
||||
match result {
|
||||
Ok((session_read, buffer)) => {
|
||||
receiving.set(session_read.receive_and_decrypt());
|
||||
match result.0 {
|
||||
Ok(buffer) => {
|
||||
let session_read = result.1;
|
||||
receiving.set(session_read.into_receive_and_decrypt());
|
||||
match buffer[0] {
|
||||
protocol::Headers::ASK_NAME => {
|
||||
let name = {
|
||||
@ -378,7 +363,7 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e != SessionError::BrokenPipe && e != SessionError::ConnectionReset && e != SessionError::BufferTooLarge {
|
||||
if e != PsecError::BrokenPipe && e != PsecError::ConnectionReset && e != PsecError::BufferTooLarge {
|
||||
print_error!(e);
|
||||
}
|
||||
break;
|
||||
@ -444,7 +429,7 @@ impl SessionManager {
|
||||
};
|
||||
match identity {
|
||||
Some(identity) => {
|
||||
match session.do_handshake(&identity).await {
|
||||
match session.do_handshake(&identity.keypair).await {
|
||||
Ok(_) => {
|
||||
peer_public_key = session.peer_public_key.unwrap();
|
||||
if identity.get_public_key() != peer_public_key {
|
||||
@ -463,7 +448,7 @@ impl SessionManager {
|
||||
}
|
||||
};
|
||||
if let Some(mut session) = session {
|
||||
let ip = session.get_ip();
|
||||
let ip = session.get_addr().unwrap().ip();
|
||||
let mut is_contact = false;
|
||||
let session_data = {
|
||||
let mut sessions = session_manager.sessions.write().unwrap();
|
@ -1,344 +0,0 @@
|
||||
use std::{convert::TryInto, io::ErrorKind, net::IpAddr};
|
||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}};
|
||||
use async_trait::async_trait;
|
||||
use ed25519_dalek;
|
||||
use ed25519_dalek::{ed25519::signature::Signature, Verifier, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use x25519_dalek;
|
||||
use rand_7::{RngCore, rngs::OsRng};
|
||||
use sha2::{Sha384, Digest};
|
||||
use aes_gcm::{Aes128Gcm, aead::Aead, NewAead, aead::Payload, Nonce};
|
||||
use crate::utils::*;
|
||||
use crate::crypto::*;
|
||||
use crate::identity::Identity;
|
||||
use crate::session_manager::SessionError;
|
||||
use crate::print_error;
|
||||
|
||||
const RANDOM_LEN: usize = 64;
|
||||
const MESSAGE_LEN_LEN: usize = 4;
|
||||
type MessageLenType = u32;
|
||||
|
||||
async fn receive<T: AsyncReadExt + Unpin>(reader: &mut T, buff: &mut [u8]) -> Result<usize, SessionError> {
|
||||
match reader.read(buff).await {
|
||||
Ok(read) => {
|
||||
if read > 0 {
|
||||
Ok(read)
|
||||
} else {
|
||||
Err(SessionError::BrokenPipe)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ErrorKind::ConnectionReset => Err(SessionError::ConnectionReset),
|
||||
_ => {
|
||||
print_error!("Receive error ({:?}): {}", e.kind(), e);
|
||||
Err(SessionError::Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send<T: AsyncWriteExt + Unpin>(writer: &mut T, buff: &[u8]) -> Result<(), SessionError> {
|
||||
match writer.write_all(buff).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(match e.kind() {
|
||||
ErrorKind::BrokenPipe => SessionError::BrokenPipe,
|
||||
ErrorKind::ConnectionReset => SessionError::ConnectionReset,
|
||||
_ => {
|
||||
print_error!("Send error ({:?}): {}", e.kind(), e);
|
||||
SessionError::Unknown
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn pad(plain_text: &[u8], use_padding: bool) -> Vec<u8> {
|
||||
let encoded_msg_len = (plain_text.len() as MessageLenType).to_be_bytes();
|
||||
let msg_len = plain_text.len()+encoded_msg_len.len();
|
||||
let mut output = Vec::from(encoded_msg_len);
|
||||
if use_padding {
|
||||
let mut len = 1000;
|
||||
while len < msg_len {
|
||||
len *= 2;
|
||||
}
|
||||
output.reserve(len);
|
||||
output.extend(plain_text);
|
||||
output.resize(len, 0);
|
||||
OsRng.fill_bytes(&mut output[msg_len..]);
|
||||
} else {
|
||||
output.extend(plain_text);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn unpad(input: Vec<u8>) -> Vec<u8> {
|
||||
let msg_len = MessageLenType::from_be_bytes(input[0..MESSAGE_LEN_LEN].try_into().unwrap()) as usize;
|
||||
Vec::from(&input[MESSAGE_LEN_LEN..MESSAGE_LEN_LEN+msg_len])
|
||||
}
|
||||
|
||||
fn encrypt(local_cipher: &Aes128Gcm, local_iv: &[u8], local_counter: &mut usize, plain_text: &[u8], use_padding: bool) -> Vec<u8> {
|
||||
let padded_msg = pad(plain_text, use_padding);
|
||||
let cipher_len = (padded_msg.len() as MessageLenType).to_be_bytes();
|
||||
let payload = Payload {
|
||||
msg: &padded_msg,
|
||||
aad: &cipher_len
|
||||
};
|
||||
let nonce = iv_to_nonce(local_iv, local_counter);
|
||||
let cipher_text = local_cipher.encrypt(Nonce::from_slice(&nonce), payload).unwrap();
|
||||
[&cipher_len, cipher_text.as_slice()].concat()
|
||||
}
|
||||
|
||||
pub async fn encrypt_and_send<T: AsyncWriteExt + Unpin>(writer: &mut T, local_cipher: &Aes128Gcm, local_iv: &[u8], local_counter: &mut usize, plain_text: &[u8], use_padding: bool) -> Result<(), SessionError> {
|
||||
let cipher_text = encrypt(local_cipher, local_iv, local_counter, plain_text, use_padding);
|
||||
send(writer, &cipher_text).await
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait PSECWriter {
|
||||
async fn encrypt_and_send(&mut self, plain_text: &[u8], use_padding: bool) -> Result<(), SessionError>;
|
||||
fn encrypt(&mut self, plain_text: &[u8], use_padding: bool) -> Vec<u8>;
|
||||
async fn send(&mut self, cipher_text: &[u8]) -> Result<(), SessionError>;
|
||||
}
|
||||
|
||||
pub struct SessionRead {
|
||||
read_half: OwnedReadHalf,
|
||||
peer_cipher: Aes128Gcm,
|
||||
peer_iv: [u8; IV_LEN],
|
||||
peer_counter: usize,
|
||||
}
|
||||
|
||||
impl SessionRead {
|
||||
async fn receive(&mut self, buff: &mut [u8]) -> Result<usize, SessionError> {
|
||||
receive(&mut self.read_half, buff).await
|
||||
}
|
||||
|
||||
pub async fn receive_and_decrypt(mut self) -> Result<(SessionRead, Vec<u8>), SessionError> {
|
||||
let mut message_len = [0; MESSAGE_LEN_LEN];
|
||||
self.receive(&mut message_len).await?;
|
||||
let recv_len = MessageLenType::from_be_bytes(message_len) as usize + AES_TAG_LEN;
|
||||
if recv_len <= Session::MAX_RECV_SIZE {
|
||||
let mut cipher_text = vec![0; recv_len];
|
||||
let mut read = 0;
|
||||
while read < recv_len {
|
||||
read += self.receive(&mut cipher_text[read..]).await?;
|
||||
}
|
||||
let peer_nonce = iv_to_nonce(&self.peer_iv, &mut self.peer_counter);
|
||||
let payload = Payload {
|
||||
msg: &cipher_text,
|
||||
aad: &message_len
|
||||
};
|
||||
match self.peer_cipher.decrypt(Nonce::from_slice(&peer_nonce), payload) {
|
||||
Ok(plain_text) => Ok((self, unpad(plain_text))),
|
||||
Err(_) => Err(SessionError::TransmissionCorrupted)
|
||||
}
|
||||
} else {
|
||||
print_error!("Buffer too large: {} B", recv_len);
|
||||
Err(SessionError::BufferTooLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionWrite {
|
||||
write_half: OwnedWriteHalf,
|
||||
local_cipher: Aes128Gcm,
|
||||
local_iv: [u8; IV_LEN],
|
||||
local_counter: usize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PSECWriter for SessionWrite {
|
||||
async fn encrypt_and_send(&mut self, plain_text: &[u8], use_padding: bool) -> Result<(), SessionError> {
|
||||
encrypt_and_send(&mut self.write_half, &self.local_cipher, &self.local_iv, &mut self.local_counter, plain_text, use_padding).await
|
||||
}
|
||||
fn encrypt(&mut self, plain_text: &[u8], use_padding: bool) -> Vec<u8> {
|
||||
encrypt(&self.local_cipher, &self.local_iv, &mut self.local_counter, plain_text, use_padding)
|
||||
}
|
||||
async fn send(&mut self, cipher_text: &[u8]) -> Result<(), SessionError> {
|
||||
send(&mut self.write_half, cipher_text).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
stream: TcpStream,
|
||||
handshake_sent_buff: Vec<u8>,
|
||||
handshake_recv_buff: Vec<u8>,
|
||||
local_cipher: Option<Aes128Gcm>,
|
||||
local_iv: Option<[u8; IV_LEN]>,
|
||||
local_counter: usize,
|
||||
peer_cipher: Option<Aes128Gcm>,
|
||||
peer_iv: Option<[u8; IV_LEN]>,
|
||||
peer_counter: usize,
|
||||
pub peer_public_key: Option<[u8; PUBLIC_KEY_LENGTH]>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
const PADDED_MAX_SIZE: usize = 32768000;
|
||||
const MAX_RECV_SIZE: usize = MESSAGE_LEN_LEN + Session::PADDED_MAX_SIZE + AES_TAG_LEN;
|
||||
|
||||
pub fn new(stream: TcpStream) -> Session {
|
||||
Session {
|
||||
stream: stream,
|
||||
handshake_sent_buff: Vec::new(),
|
||||
handshake_recv_buff: Vec::new(),
|
||||
local_cipher: None,
|
||||
local_iv: None,
|
||||
local_counter: 0,
|
||||
peer_cipher: None,
|
||||
peer_iv: None,
|
||||
peer_counter: 0,
|
||||
peer_public_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_spit(self) -> Option<(SessionRead, SessionWrite)> {
|
||||
let (read_half, write_half) = self.stream.into_split();
|
||||
Some((
|
||||
SessionRead {
|
||||
read_half,
|
||||
peer_cipher: self.peer_cipher?,
|
||||
peer_iv: self.peer_iv?,
|
||||
peer_counter: self.peer_counter,
|
||||
},
|
||||
SessionWrite {
|
||||
write_half,
|
||||
local_cipher: self.local_cipher?,
|
||||
local_iv: self.local_iv?,
|
||||
local_counter: self.local_counter,
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_ip(&self) -> IpAddr {
|
||||
self.stream.peer_addr().unwrap().ip()
|
||||
}
|
||||
|
||||
async fn receive(&mut self, buff: &mut [u8]) -> Result<usize, SessionError> {
|
||||
receive(&mut self.stream, buff).await
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, buff: &[u8]) -> Result<(), SessionError> {
|
||||
send(&mut self.stream, buff).await
|
||||
}
|
||||
|
||||
async fn handshake_read(&mut self, buff: &mut [u8]) -> Result<(), SessionError> {
|
||||
self.receive(buff).await?;
|
||||
self.handshake_recv_buff.extend(buff.as_ref());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handshake_write(&mut self, buff: &[u8]) -> Result<(), SessionError> {
|
||||
self.send(buff).await?;
|
||||
self.handshake_sent_buff.extend(buff);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hash_handshake(&self, i_am_bob: bool) -> [u8; 48] {
|
||||
let handshake_bytes = if i_am_bob {
|
||||
[self.handshake_sent_buff.as_slice(), self.handshake_recv_buff.as_slice()].concat()
|
||||
} else {
|
||||
[self.handshake_recv_buff.as_slice(), self.handshake_sent_buff.as_slice()].concat()
|
||||
};
|
||||
let mut hasher = Sha384::new();
|
||||
hasher.update(handshake_bytes);
|
||||
let handshake_hash = hasher.finalize();
|
||||
to_array_48(handshake_hash.as_slice())
|
||||
}
|
||||
|
||||
fn on_handshake_successful(&mut self, application_keys: ApplicationKeys){
|
||||
self.local_cipher = Some(Aes128Gcm::new_from_slice(&application_keys.local_key).unwrap());
|
||||
self.local_iv = Some(application_keys.local_iv);
|
||||
self.peer_cipher = Some(Aes128Gcm::new_from_slice(&application_keys.peer_key).unwrap());
|
||||
self.peer_iv = Some(application_keys.peer_iv);
|
||||
self.handshake_sent_buff.clear();
|
||||
self.handshake_sent_buff.shrink_to_fit();
|
||||
self.handshake_recv_buff.clear();
|
||||
self.handshake_recv_buff.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub async fn do_handshake(&mut self, identity: &Identity) -> Result<(), SessionError> {
|
||||
//ECDHE initial exchange
|
||||
//generate random bytes
|
||||
let mut handshake_buffer = [0; RANDOM_LEN+PUBLIC_KEY_LENGTH];
|
||||
OsRng.fill_bytes(&mut handshake_buffer[..RANDOM_LEN]);
|
||||
//generate ephemeral x25519 keys
|
||||
let ephemeral_secret = x25519_dalek::EphemeralSecret::new(OsRng);
|
||||
let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_secret);
|
||||
handshake_buffer[RANDOM_LEN..].copy_from_slice(&ephemeral_public_key.to_bytes());
|
||||
self.handshake_write(&handshake_buffer).await?;
|
||||
self.handshake_read(&mut handshake_buffer).await?;
|
||||
let peer_ephemeral_public_key = x25519_dalek::PublicKey::from(to_array_32(&handshake_buffer[RANDOM_LEN..]));
|
||||
//calc handshake keys
|
||||
let i_am_bob = self.handshake_sent_buff < self.handshake_recv_buff; //mutual consensus for keys attribution
|
||||
let handshake_hash = self.hash_handshake(i_am_bob);
|
||||
let shared_secret = ephemeral_secret.diffie_hellman(&peer_ephemeral_public_key);
|
||||
let handshake_keys = HandshakeKeys::derive_keys(shared_secret.to_bytes(), handshake_hash, i_am_bob);
|
||||
|
||||
|
||||
//encrypted handshake
|
||||
//generate random bytes
|
||||
let mut random_bytes = [0; RANDOM_LEN];
|
||||
OsRng.fill_bytes(&mut random_bytes);
|
||||
self.handshake_write(&random_bytes).await?;
|
||||
drop(random_bytes);
|
||||
//receive peer random bytes
|
||||
let mut peer_random = [0; RANDOM_LEN];
|
||||
self.handshake_read(&mut peer_random).await?;
|
||||
drop(peer_random);
|
||||
//get public key & sign our ephemeral public key
|
||||
let mut auth_msg = [0; PUBLIC_KEY_LENGTH+SIGNATURE_LENGTH];
|
||||
auth_msg[..PUBLIC_KEY_LENGTH].copy_from_slice(&identity.get_public_key());
|
||||
auth_msg[PUBLIC_KEY_LENGTH..].copy_from_slice(&identity.sign(ephemeral_public_key.as_bytes()));
|
||||
//encrypt auth_msg
|
||||
let local_cipher = Aes128Gcm::new_from_slice(&handshake_keys.local_key).unwrap();
|
||||
let mut local_handshake_counter = 0;
|
||||
let nonce = iv_to_nonce(&handshake_keys.local_iv, &mut local_handshake_counter);
|
||||
let encrypted_auth_msg = local_cipher.encrypt(Nonce::from_slice(&nonce), auth_msg.as_ref()).unwrap();
|
||||
self.handshake_write(&encrypted_auth_msg).await?;
|
||||
|
||||
let mut encrypted_peer_auth_msg = [0; PUBLIC_KEY_LENGTH+SIGNATURE_LENGTH+AES_TAG_LEN];
|
||||
self.handshake_read(&mut encrypted_peer_auth_msg).await?;
|
||||
//decrypt peer_auth_msg
|
||||
let peer_cipher = Aes128Gcm::new_from_slice(&handshake_keys.peer_key).unwrap();
|
||||
let mut peer_handshake_counter = 0;
|
||||
let peer_nonce = iv_to_nonce(&handshake_keys.peer_iv, &mut peer_handshake_counter);
|
||||
match peer_cipher.decrypt(Nonce::from_slice(&peer_nonce), encrypted_peer_auth_msg.as_ref()) {
|
||||
Ok(peer_auth_msg) => {
|
||||
//verify ephemeral public key signature
|
||||
self.peer_public_key = Some(to_array_32(&peer_auth_msg[..PUBLIC_KEY_LENGTH]));
|
||||
let peer_public_key = ed25519_dalek::PublicKey::from_bytes(&self.peer_public_key.unwrap()).unwrap();
|
||||
let peer_signature = Signature::from_bytes(&peer_auth_msg[PUBLIC_KEY_LENGTH..]).unwrap();
|
||||
if peer_public_key.verify(peer_ephemeral_public_key.as_bytes(), &peer_signature).is_ok() {
|
||||
let handshake_hash = self.hash_handshake(i_am_bob);
|
||||
//sending handshake finished
|
||||
let handshake_finished = compute_handshake_finished(handshake_keys.local_handshake_traffic_secret, handshake_hash);
|
||||
self.send(&handshake_finished).await?;
|
||||
let mut peer_handshake_finished = [0; HASH_OUTPUT_LEN];
|
||||
self.receive(&mut peer_handshake_finished).await?;
|
||||
if verify_handshake_finished(peer_handshake_finished, handshake_keys.peer_handshake_traffic_secret, handshake_hash) {
|
||||
//calc application keys
|
||||
let application_keys = ApplicationKeys::derive_keys(handshake_keys.handshake_secret, handshake_hash, i_am_bob);
|
||||
self.on_handshake_successful(application_keys);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
Err(SessionError::TransmissionCorrupted)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PSECWriter for Session {
|
||||
async fn encrypt_and_send(&mut self, plain_text: &[u8], use_padding: bool) -> Result<(), SessionError> {
|
||||
encrypt_and_send(&mut self.stream, self.local_cipher.as_ref().unwrap(), self.local_iv.as_ref().unwrap(), &mut self.local_counter, plain_text, use_padding).await
|
||||
}
|
||||
|
||||
fn encrypt(&mut self, plain_text: &[u8], use_padding: bool) -> Vec<u8> {
|
||||
encrypt(self.local_cipher.as_ref().unwrap(), &self.local_iv.unwrap(), &mut self.local_counter, plain_text, use_padding)
|
||||
}
|
||||
|
||||
async fn send(&mut self, cipher_text: &[u8]) -> Result<(), SessionError> {
|
||||
send(&mut self.stream, cipher_text).await
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ mod ui_messages {
|
||||
use std::{fmt::Display, iter::FromIterator, net::IpAddr, str::from_utf8};
|
||||
use tungstenite::Message;
|
||||
use uuid::Uuid;
|
||||
use crate::{print_error, session_manager::{LargeFileDownload, LargeFilesDownload, protocol}, utils::to_uuid_bytes};
|
||||
use crate::{print_error, session_manager::{LargeFileDownload, LargeFilesDownload}, protocol, utils::to_uuid_bytes};
|
||||
|
||||
fn simple_event(command: &str, session_id: &usize) -> Message {
|
||||
Message::from(format!("{} {}", command, session_id))
|
||||
|
@ -2,14 +2,6 @@ use std::{convert::TryInto, time::{SystemTime, UNIX_EPOCH}, path::PathBuf};
|
||||
use uuid::Bytes;
|
||||
use crate::print_error;
|
||||
|
||||
pub fn to_array_48(s: &[u8]) -> [u8; 48] {
|
||||
s.try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn to_array_32(s: &[u8]) -> [u8; 32] {
|
||||
s.try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn to_uuid_bytes(bytes: &[u8]) -> Option<Bytes> {
|
||||
match bytes.try_into() {
|
||||
Ok(uuid) => Some(uuid),
|
||||
|
Loading…
Reference in New Issue
Block a user