From 8527b42f8473ca76f032ecc3aa27d8492473e5a1 Mon Sep 17 00:00:00 2001 From: "Ericson \"Fogo\" Soares" Date: Thu, 15 Aug 2024 03:35:26 -0300 Subject: [PATCH 1/2] Crypto revamp (#2660) * Revamping crypto subcrate * Merge with main * More tweaks on crypto crate * Remove some unsused crypto references * Fix crypto erase example --- Cargo.lock | Bin 303890 -> 307928 bytes Cargo.toml | 15 +- core/src/crypto/error.rs | 62 - core/src/crypto/keymanager.rs | 939 -- core/src/crypto/mod.rs | 64 - core/src/lib.rs | 2 - core/src/object/fs/decrypt.rs | 159 - core/src/object/fs/encrypt.rs | 261 - crates/crypto/Cargo.toml | 160 +- crates/crypto/README.md | 22 +- crates/crypto/assets/eff_large_wordlist.txt | 7776 ----------------- .../crypto/benches/crypto/aes-256-gcm-siv.rs | 66 - crates/crypto/benches/crypto/aes-256-gcm.rs | 66 - .../benches/crypto/xchacha20-poly1305.rs | 66 - crates/crypto/benches/hashing/argon2id.rs | 43 - .../crypto/benches/hashing/blake3-balloon.rs | 43 - crates/crypto/benches/hashing/blake3-kdf.rs | 24 - crates/crypto/benches/hashing/blake3.rs | 31 - crates/crypto/examples/file_encryption.rs | 119 - crates/crypto/examples/secure_erase.rs | 15 +- crates/crypto/src/cloud/decrypt.rs | 105 + crates/crypto/src/cloud/encrypt.rs | 99 + crates/crypto/src/cloud/mod.rs | 3 + crates/crypto/src/cloud/secret_key.rs | 189 + crates/crypto/src/crypto/mod.rs | 1 - crates/crypto/src/crypto/stream.rs | 1 - crates/crypto/src/ct.rs | 68 +- crates/crypto/src/encoding/bincode.rs | 29 - crates/crypto/src/encoding/file/header.rs | 321 - crates/crypto/src/encoding/file/keyslot.rs | 74 - crates/crypto/src/encoding/file/mod.rs | 525 -- crates/crypto/src/encoding/file/object.rs | 92 - crates/crypto/src/encoding/mod.rs | 6 - crates/crypto/src/encrypted.rs | 143 - crates/crypto/src/{sys/fs => }/erase.rs | 196 +- crates/crypto/src/error.rs | 117 +- crates/crypto/src/hashing.rs | 399 - crates/crypto/src/keyring/apple/ios.rs | 40 - crates/crypto/src/keyring/apple/macos.rs | 54 - crates/crypto/src/keyring/apple/mod.rs | 9 - crates/crypto/src/keyring/identifier.rs | 48 - crates/crypto/src/keyring/linux/keyutils.rs | 69 - crates/crypto/src/keyring/linux/mod.rs | 7 - .../src/keyring/linux/secret_service.rs | 73 - crates/crypto/src/keyring/mod.rs | 212 - crates/crypto/src/keyring/session.rs | 39 - crates/crypto/src/keyring/windows.rs | 1 - crates/crypto/src/lib.rs | 26 +- crates/crypto/src/primitives.rs | 132 +- crates/crypto/src/protected.rs | 12 +- crates/crypto/src/rng/csprng.rs | 88 + crates/crypto/src/rng/csprng/chacha20.rs | 80 - crates/crypto/src/rng/csprng/mod.rs | 18 - crates/crypto/src/rng/mod.rs | 3 +- crates/crypto/src/sys/fs/mod.rs | 6 - crates/crypto/src/sys/mod.rs | 1 - crates/crypto/src/types.rs | 744 -- crates/crypto/src/utils.rs | 54 - crates/crypto/src/vault/ephemeral.rs | 84 - crates/crypto/src/vault/mod.rs | 5 - crates/crypto/src/vault/persistent.rs | 80 - rust-toolchain.toml | 2 +- 62 files changed, 774 insertions(+), 13414 deletions(-) delete mode 100644 core/src/crypto/error.rs delete mode 100644 core/src/crypto/keymanager.rs delete mode 100644 core/src/crypto/mod.rs delete mode 100644 core/src/object/fs/decrypt.rs delete mode 100644 core/src/object/fs/encrypt.rs delete mode 100644 crates/crypto/assets/eff_large_wordlist.txt delete mode 100644 crates/crypto/benches/crypto/aes-256-gcm-siv.rs delete mode 100644 crates/crypto/benches/crypto/aes-256-gcm.rs delete mode 100644 crates/crypto/benches/crypto/xchacha20-poly1305.rs delete mode 100644 crates/crypto/benches/hashing/argon2id.rs delete mode 100644 crates/crypto/benches/hashing/blake3-balloon.rs delete mode 100644 crates/crypto/benches/hashing/blake3-kdf.rs delete mode 100644 crates/crypto/benches/hashing/blake3.rs delete mode 100644 crates/crypto/examples/file_encryption.rs create mode 100644 crates/crypto/src/cloud/decrypt.rs create mode 100644 crates/crypto/src/cloud/encrypt.rs create mode 100644 crates/crypto/src/cloud/mod.rs create mode 100644 crates/crypto/src/cloud/secret_key.rs delete mode 100644 crates/crypto/src/encoding/bincode.rs delete mode 100644 crates/crypto/src/encoding/file/header.rs delete mode 100644 crates/crypto/src/encoding/file/keyslot.rs delete mode 100644 crates/crypto/src/encoding/file/mod.rs delete mode 100644 crates/crypto/src/encoding/file/object.rs delete mode 100644 crates/crypto/src/encoding/mod.rs delete mode 100644 crates/crypto/src/encrypted.rs rename crates/crypto/src/{sys/fs => }/erase.rs (71%) delete mode 100644 crates/crypto/src/hashing.rs delete mode 100644 crates/crypto/src/keyring/apple/ios.rs delete mode 100644 crates/crypto/src/keyring/apple/macos.rs delete mode 100644 crates/crypto/src/keyring/apple/mod.rs delete mode 100644 crates/crypto/src/keyring/identifier.rs delete mode 100644 crates/crypto/src/keyring/linux/keyutils.rs delete mode 100644 crates/crypto/src/keyring/linux/mod.rs delete mode 100644 crates/crypto/src/keyring/linux/secret_service.rs delete mode 100644 crates/crypto/src/keyring/mod.rs delete mode 100644 crates/crypto/src/keyring/session.rs delete mode 100644 crates/crypto/src/keyring/windows.rs create mode 100644 crates/crypto/src/rng/csprng.rs delete mode 100644 crates/crypto/src/rng/csprng/chacha20.rs delete mode 100644 crates/crypto/src/rng/csprng/mod.rs delete mode 100644 crates/crypto/src/sys/fs/mod.rs delete mode 100644 crates/crypto/src/sys/mod.rs delete mode 100644 crates/crypto/src/types.rs delete mode 100644 crates/crypto/src/utils.rs delete mode 100644 crates/crypto/src/vault/ephemeral.rs delete mode 100644 crates/crypto/src/vault/mod.rs delete mode 100644 crates/crypto/src/vault/persistent.rs diff --git a/Cargo.lock b/Cargo.lock index 89f652b3be1fc59e65ef79e7a6d8ab1b99616a26..a2e454cf761e125419ec314dcc01ffaadba76fc2 100644 GIT binary patch delta 2608 zcmZ`*TWlOx8P?8h9NVQAJE=F+N$XgJ6zVwVeo>Q{Dj-l0Rcs*fTRGRnO=3H1yTU3^ zAt50V2u%;lQH4UAmOfB%30gHVw8kF!dJXD%SLY_ZaqzdR>(;qLFWh}o`rkX+n|}0-+j}vB zD$O@Tp47!>cL<}kaopJ8l@gF@@Sa-DL-fYdfKg-+22v4eA+6PpQ;ReW!BQvE<_kml zwf^FkG<&pXV^!N~>y@^<`K1`l;!-m#q~9IelAb<#ZpU>Ea0d+h2m+HH$M5S!Wtg8k zw2Ud@@QxZsE4$pY7I#xfS6-XiNr==o7%6FFh#7+waa>0!PzcZ#q!F_eD$Jp(m{E_A z=LDMOw6m1vD@AQ++63dlg{bf|j=Hg23vab#H%H04VXUNkU95U#qfWrB7r%K&F$BA> zvSDU+ZfUW)r&U7lerx^Om7UU8e=s@WBz8(u&kYWWaEX}m))K8b^CaM?I5Yzr%mEuL zVjsG&QAl{hJx!aRs|ay7xabmMCG1tdD=Zm!tlZdo=h)eLd3f&8eB}AUOkj&W02Emx zx#x@$A-&ZE{2CP;hI(OT5S13XAAtl+OHNQ=7y&w>$#_e#p>-xbG@6e;P=2s=@0qu2 z)hfxCM$@O-!#!Qs>Bz#=1hUFOdRRFvC9nz$%0+;cuvB6eiEtzU{-C*1J7lE^5(x$n z>~PSe;|Cl0^o627pMJjVOYh7KwK9nB$C&-H?cKa~$H&vui+d)Rr$|^KH4y~5V1PTA8U+h5MzYAqzDWM9#ya$Q;Ded zTrkuj@=*J|<=VlInQEiC2AKWlwUF;LSV*Ij<9u(d*z6* zkw?qGr()V51vTTw8J3?|DjFX+uxy(%zN@!w@bJpJ`Sj&tB!BFgqEXXb-Ieeqqmo$R zq!wOEthB~T*gzcNTwxWxBiIS$MIarkjL~qUjSt$#{L}ejDF5e~Vp~UfyUfWQOm&>p z^QTW0Z&yCs)6W*Kv;6P##qE6^T9`xg2Tb1lRDEg(4TOsr1#uzJfIvzC+uSh^2_p<} zCe~sB$np+h6A=wsQ*PlR3CaV<>WzHt&qdF+il`98)$LalzG-It!)~q_ZhV$rUb`j# z{&KOVegDM*5IT0TSbr_F^x&5#cY>a4NYYXkg&@=tqk|F<1&3N-3EYfKcma`G4LEj8 zn4pYS1nOf^+G85|%}d2_|FwwHKeh~aoo;%gc)gb4y6&3O{NP2<;-G2d2Ihj}L^*Fj zm_ch{NwFo?D4`tZxZ^E=76WULgqK0`pd=_A=^M&l_-L^X;CgS@)8GG8Ebnc&=JNdF z+-U!!p}8dMyqVf3u&^@!TJnObDEH;BVTnV8)WDW*;gC<@lb$CUK zSG5tuq(wZ)h!;VYK1)FAtXwefK(H z%-ehGn^w41rF-(LI?!hDH+D zL6}fg#l+SuNMXW6hNb9y{)Q;?z)7)&uNI^xHl2<({a+TumAJwJD>9Rp+uW`6giuoj z(3|nI@xje1%k9lPZ{34BJF$=5om`Lp1L{YlA0?D3x{4gi4&;HVXbemqbKgw`#JY-| z%4A1JN?yC$KE391pUhs3)K>nE8}821esTB0c9ss8+C|)JD@BQ`4Pvv4*NdU5QY`!7 z3w!C0y5k==WOk|8z5CSV#hJ_e_&uSHy{hftsZ&BTug?oNlMXdh%YqPTP|;xmQpHrL zO$gSJZCjddYNf*1a)7Sbr4fVZsznUjQHW-$I%YdWF2}ZcW1T|7MHYF`mDE&SHyzDJ z8iK&KC~_cnj8ZzLP`3faR7a2kLaA*6!%I+j zn}Y?hl@C7^i)Gc~-Cq>9AkTglWp83z{3P%@-^3tG4qf*OU&TSeJO4w>-}Ww?kzzqU x+9ic|x54I%P2OUc#Qk2hN9wHiZaLBsS&YPZfcawH!nhQER9>U^>yU4I_Ah%(^;rM_ diff --git a/Cargo.toml b/Cargo.toml index a39b268bc..cdee6b5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [workspace] -exclude = ["crates/crypto"] members = [ "apps/deps-generator", "apps/desktop/crates/*", @@ -15,18 +14,20 @@ members = [ resolver = "2" [workspace.package] -edition = "2021" -license = "AGPL-3.0-only" -repository = "https://github.com/spacedriveapp/spacedrive" +edition = "2021" +license = "AGPL-3.0-only" +repository = "https://github.com/spacedriveapp/spacedrive" +rust-version = "1.80" [workspace.dependencies] # Third party dependencies used by one or more of our crates async-channel = "2.3" +async-stream = "0.3.5" async-trait = "0.1.80" axum = "0.6.20" # Update blocked by hyper base64 = "0.22.1" base91 = "0.1.0" -blake3 = "1.5.0" # Update blocked by custom patch below +blake3 = "1.5" chrono = "0.4.38" directories = "5.0" ed25519-dalek = "2.1.1" @@ -111,10 +112,6 @@ rev = "a005656df7" git = "https://github.com/spacedriveapp/rust-libp2p.git" rev = "a005656df7" -[patch.crates-io.blake3] -git = "https://github.com/spacedriveapp/blake3.git" -rev = "d3aab416c1" - [profile.dev] # Make compilation faster on macOS codegen-units = 256 diff --git a/core/src/crypto/error.rs b/core/src/crypto/error.rs deleted file mode 100644 index 787840918..000000000 --- a/core/src/crypto/error.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::num::TryFromIntError; - -use thiserror::Error; - -pub type Result = std::result::Result; - -impl From for rspc::Error { - fn from(value: KeyManagerError) -> Self { - Self::new(rspc::ErrorCode::InternalServerError, value.to_string()) - } -} - -#[derive(Debug, Error)] -pub enum KeyManagerError { - // #[error("crypto error: {0}")] - // Crypto(#[from] sd_crypto::Error), - #[error("the key specified was not found")] - KeyNotFound, - #[error("the key manager is locked")] - Locked, - #[error("the key is already mounted")] - AlreadyMounted, - #[error("key not mounted")] - NotMounted, - #[error("the key is already queued")] - AlreadyQueued, - - #[error("there was an error during a conversion")] - Conversion, - #[error("there was an error converting ints")] - IntConversion(#[from] TryFromIntError), - - #[error("the test vector failed (password is likely incorrect)")] - IncorrectPassword, - #[error("there was an issue while unlocking the key manager")] - Unlock, - - #[error("an unsupported operation was attempted")] - Unsupported, - - #[error("the word provided is too short")] - WordTooShort, - - #[error("the specified file already exists and would be overwritten")] - FileAlreadyExists, - #[error("the specified file doesn't exist")] - FileDoesntExist, - #[error("the specified file is too large")] - FileTooLarge, - - #[error("this action would delete the last root key (and make the key manager unusable)")] - LastRootKey, - - #[error("database error: {0}")] - Database(#[from] prisma_client_rust::QueryError), - - #[error("async IO error: {0}")] - IoAsync(#[from] tokio::io::Error), - - #[error("error while converting a UUID: {0}")] - Uuid(#[from] uuid::Error), -} diff --git a/core/src/crypto/keymanager.rs b/core/src/crypto/keymanager.rs deleted file mode 100644 index 17cf1ae97..000000000 --- a/core/src/crypto/keymanager.rs +++ /dev/null @@ -1,939 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use bincode::{Decode, Encode}; -use dashmap::DashSet; -use sd_crypto::crypto::{Decryptor, Encryptor}; -use sd_crypto::hashing::Hasher; -use sd_crypto::primitives::{BLOCK_LEN, SALT_LEN}; -use sd_crypto::types::{ - Aad, Algorithm, EncryptedKey, HashingAlgorithm, Key, Nonce, Salt, SecretKey, -}; -// use sd_crypto::utils::generate_passphrase; -use sd_crypto::{encoding, Protected}; -use serde::{Deserialize, Serialize}; -use specta::Type; -use tokio::fs::{self, File}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::Mutex; -use uuid::Uuid; - -use super::error::KeyManagerError; -use super::{Result, KEY_MOUNTING_CONTEXT, TEST_VECTOR_CONTEXT}; -use crate::crypto::ENCRYPTED_WORD_CONTEXT; -use crate::prisma::{key, mounted_key, PrismaClient}; - -pub struct KeyManager { - key: Mutex>, // the root key - queue: DashSet, - db: Arc, -} - -#[derive(Clone, bincode::Encode, bincode::Decode)] -pub struct MountedKey { - version: KeyVersion, - #[bincode(with_serde)] - uuid: Uuid, - algorithm: Algorithm, - salt: Salt, - key: EncryptedKey, -} - -impl MountedKey { - pub fn encrypt( - root_key: &Key, - key: &Key, - algorithm: Algorithm, - word: &Protected>, - ) -> Result { - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - - // TODO(brxken128): maybe give these separate contexts, or even remove the second derivation - let ek = Encryptor::encrypt_key( - &Hasher::derive_key(root_key, salt, KEY_MOUNTING_CONTEXT), - &nonce, - algorithm, - &Hasher::derive_key(key, word_to_salt(word)?, KEY_MOUNTING_CONTEXT), - Aad::Null, - )?; - - Ok(Self { - version: KeyVersion::V1, - uuid: Uuid::new_v4(), - algorithm, - salt, - key: ek, - }) - } - - pub fn decrypt(&self, root_key: &Key) -> Result { - Ok(Decryptor::decrypt_key( - &Hasher::derive_key(root_key, self.salt, KEY_MOUNTING_CONTEXT), - self.algorithm, - &self.key, - Aad::Null, - )?) - } -} - -impl TryFrom<&MountedKey> for mounted_key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: &MountedKey) -> std::result::Result { - #[allow(clippy::as_conversions)] - let s = Self { - version: value.version as i32, - uuid: Uuid::new_v4().as_bytes().to_vec(), // random uuid to prevent conflicts - algorithm: encoding::encode(&value.algorithm)?, - key: encoding::encode(&value.key)?, - salt: encoding::encode(&value.salt)?, - _params: vec![], - }; - - Ok(s) - } -} - -impl TryFrom for mounted_key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: MountedKey) -> std::result::Result { - (&value).try_into() - } -} - -impl TryFrom for MountedKey { - type Error = KeyManagerError; - - fn try_from(value: mounted_key::Data) -> std::result::Result { - let mk = Self { - version: KeyVersion::try_from(value.version)?, - uuid: Uuid::from_slice(&value.uuid)?, - algorithm: encoding::decode(&value.algorithm)?, - key: encoding::decode(&value.key)?, - salt: encoding::decode(&value.salt)?, - }; - - Ok(mk) - } -} - -#[derive(Clone, Encode, Decode)] -struct OnDiskBackup { - root_keys: Vec, - user_keys: Vec, -} - -#[derive(Clone, Encode, Decode)] -pub struct TestVector(Salt, EncryptedKey); - -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Hash)] -#[repr(i32)] -pub enum KeyType { - Root = 0, - User = 1, -} - -impl TryFrom for KeyType { - type Error = KeyManagerError; - - fn try_from(value: i32) -> std::result::Result { - match value { - 0 => Ok(Self::Root), - 1 => Ok(Self::User), - _ => Err(KeyManagerError::Conversion), - } - } -} - -#[derive(Clone, Copy, Encode, Decode, Serialize, Deserialize, Type)] -#[repr(i32)] -pub enum KeyVersion { - V1 = 0, -} - -impl TryFrom for KeyVersion { - type Error = KeyManagerError; - - fn try_from(value: i32) -> std::result::Result { - match value { - 0 => Ok(Self::V1), - _ => Err(KeyManagerError::Conversion), - } - } -} - -#[derive(Clone, Encode, Decode)] -pub struct EncryptedWord(Salt, Nonce, Vec); - -impl EncryptedWord { - pub fn decrypt(&self, root_key: &Key, algorithm: Algorithm) -> Result>> { - Decryptor::decrypt_tiny( - &Hasher::derive_key(root_key, self.0, ENCRYPTED_WORD_CONTEXT), - &self.1, - algorithm, - &self.2, - Aad::Null, - ) - .map_err(KeyManagerError::Crypto) - } - - pub fn encrypt( - root_key: &Key, - word: &Protected>, - algorithm: Algorithm, - ) -> Result { - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - let bytes = Encryptor::encrypt_tiny( - &Hasher::derive_key(root_key, salt, ENCRYPTED_WORD_CONTEXT), - &nonce, - algorithm, - word.expose(), - Aad::Null, - )?; - - Ok(Self(salt, nonce, bytes)) - } -} - -key::select!(key_info { - version - uuid - name - algorithm - hashing_algorithm - mounted_key: select { id } -}); - -#[derive(Serialize, Deserialize, Type, Clone)] -pub struct DisplayKey { - pub version: KeyVersion, - pub uuid: Uuid, - pub name: Option, - pub algorithm: Algorithm, - pub hashing_algorithm: HashingAlgorithm, - pub mounted: bool, -} - -impl TryFrom for DisplayKey { - type Error = KeyManagerError; - - fn try_from(value: key_info::Data) -> std::result::Result { - let dk = Self { - version: KeyVersion::try_from(value.version)?, - uuid: Uuid::from_slice(&value.uuid)?, - name: value.name, - algorithm: encoding::decode(&value.algorithm)?, - hashing_algorithm: encoding::decode(&value.hashing_algorithm)?, - mounted: value.mounted_key.is_some(), - }; - - Ok(dk) - } -} - -#[derive(Clone, Encode, Decode)] -pub struct UserKey { - pub version: KeyVersion, - #[bincode(with_serde)] - pub uuid: Uuid, - pub algorithm: Algorithm, - pub hashing_algorithm: HashingAlgorithm, - pub word: EncryptedWord, // word (once hashed with b3) acts like a salt - pub tv: TestVector, -} - -fn word_to_salt(word: &Protected>) -> Result { - Ok(Salt::try_from( - Hasher::blake3(word.expose()).expose()[..SALT_LEN].to_vec(), - )?) -} - -impl TryFrom<&UserKey> for key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: &UserKey) -> std::result::Result { - #[allow(clippy::as_conversions)] - let s = Self { - uuid: value.uuid.as_bytes().to_vec(), - version: value.version as i32, - key_type: KeyType::User as i32, - algorithm: encoding::encode(&value.algorithm)?, - hashing_algorithm: encoding::encode(&value.hashing_algorithm)?, - key: encoding::encode(&value.tv)?, - salt: encoding::encode(&value.word)?, - _params: vec![], - }; - - Ok(s) - } -} - -impl TryFrom for key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: UserKey) -> std::result::Result { - (&value).try_into() - } -} - -impl TryFrom for UserKey { - type Error = KeyManagerError; - - fn try_from(value: key::Data) -> std::result::Result { - if KeyType::try_from(value.key_type)? != KeyType::User { - return Err(KeyManagerError::Conversion); - } - - let uk = Self { - version: KeyVersion::try_from(value.version)?, - uuid: Uuid::from_slice(&value.uuid)?, - algorithm: encoding::decode(&value.algorithm)?, - hashing_algorithm: encoding::decode(&value.hashing_algorithm)?, - word: encoding::decode(&value.salt)?, - tv: encoding::decode(&value.key)?, - }; - - Ok(uk) - } -} - -impl TestVector { - pub fn validate(&self, algorithm: Algorithm, hashed_password: &Key) -> Result<()> { - Decryptor::decrypt_key( - &Hasher::derive_key(hashed_password, self.0, TEST_VECTOR_CONTEXT), - algorithm, - &self.1, - Aad::Null, - ) - .map_or(Err(KeyManagerError::IncorrectPassword), |_| Ok(())) - } -} - -impl KeyManager { - pub fn new(db: Arc) -> Self { - Self { - key: Mutex::new(None), - queue: DashSet::new(), - db, - } - } - - pub async fn is_unlocked(&self) -> bool { - self.key.lock().await.is_some() - } - - async fn get_root_key(&self) -> Result { - self.key.lock().await.clone().ok_or(KeyManagerError::Locked) - } - - async fn ensure_unlocked(&self) -> Result<()> { - self.key - .lock() - .await - .as_ref() - .map_or(Err(KeyManagerError::Locked), |_| Ok(())) - } - - fn ensure_not_queued(&self, uuid: Uuid) -> Result<()> { - (!self.queue.contains(&uuid)) - .then_some(()) - .ok_or(KeyManagerError::AlreadyQueued) - } - - pub async fn is_unlocking(&self) -> Result { - #[allow(clippy::as_conversions)] - Ok(self - .db - .key() - .find_many(vec![key::key_type::equals(KeyType::Root as i32)]) - .exec() - .await? - .into_iter() - .flat_map(|x| Uuid::from_slice(&x.uuid).map_err(KeyManagerError::Uuid)) - .any(|x| self.queue.contains(&x))) - } - - pub async fn unlock( - &self, - password: Protected, - secret_key: Option>, - ) -> Result<()> { - let password: Protected> = password.into_inner().into_bytes().into(); - - let secret_key: SecretKey = if let Some(secret_key) = secret_key { - secret_key.try_into()? - } else { - // TODO(brxken128): source from keyring here, or return error if that fails - SecretKey::generate() - }; - - #[allow(clippy::as_conversions)] - let root_keys = self - .db - .key() - .find_many(vec![key::key_type::equals(KeyType::Root as i32)]) - .exec() - .await?; - - let root_keys = root_keys - .into_iter() - .map(RootKey::try_from) - .collect::>>()?; - - let rk = root_keys - .into_iter() - .find_map(|k| { - self.ensure_not_queued(k.uuid).ok()?; - - self.queue.insert(k.uuid); - - let res = - Hasher::hash_password(k.hashing_algorithm, &password, k.salt, &secret_key); - - self.queue.remove(&k.uuid); - - let pw = res.ok()?; - - Decryptor::decrypt_key(&pw, k.algorithm, &k.key, Aad::Null).ok() - }) - .ok_or(KeyManagerError::Unlock)?; - - *self.key.lock().await = Some(rk); - Ok(()) - } - - pub async fn initial_setup( - &self, - algorithm: Algorithm, - hashing_algorithm: HashingAlgorithm, - password: Protected, - ) -> Result> { - let secret_key = SecretKey::generate(); - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - let password = password.into_inner().into_bytes().into(); - - let hashed_password = - Hasher::hash_password(hashing_algorithm, &password, salt, &secret_key)?; - - let root_key = Key::generate(); - let root_key_e = - Encryptor::encrypt_key(&hashed_password, &nonce, algorithm, &root_key, Aad::Null)?; - - let rk: key::CreateUnchecked = RootKey { - version: KeyVersion::V1, - uuid: Uuid::new_v4(), - algorithm, - hashing_algorithm, - salt, - key: root_key_e, - } - .try_into()?; - - rk.to_query(&self.db).exec().await?; - - *self.key.lock().await = Some(root_key); - - Ok(secret_key.to_string().into()) - } - - // This will become `add_root_key` at some point, and we'll have dedicated management for them - pub async fn add_root_key( - &self, - algorithm: Algorithm, - hashing_algorithm: HashingAlgorithm, - password: Protected, - ) -> Result> { - self.ensure_unlocked().await?; - - let secret_key = SecretKey::generate(); - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - let password = password.into_inner().into_bytes().into(); - - let hashed_password = - Hasher::hash_password(hashing_algorithm, &password, salt, &secret_key)?; - - let root_key = self.get_root_key().await?; - let root_key_e = - Encryptor::encrypt_key(&hashed_password, &nonce, algorithm, &root_key, Aad::Null)?; - - let rk: key::CreateUnchecked = RootKey { - version: KeyVersion::V1, - uuid: Uuid::new_v4(), - algorithm, - hashing_algorithm, - salt, - key: root_key_e, - } - .try_into()?; - - rk.to_query(&self.db).exec().await?; - - *self.key.lock().await = Some(root_key); - - Ok(secret_key.to_string().into()) - } - - pub async fn delete(&self, uuid: Uuid) -> Result<()> { - let key = self - .db - .key() - .find_unique(key::uuid::equals(uuid.as_bytes().to_vec())) - .exec() - .await? - .ok_or(KeyManagerError::KeyNotFound)?; - - #[allow(clippy::as_conversions)] - if KeyType::try_from(key.key_type)? == KeyType::Root - && self - .db - .key() - .find_many(vec![key::key_type::equals(KeyType::Root as i32)]) - .select(key::select!({ id })) - .exec() - .await? - .len() == 1 - { - return Err(KeyManagerError::LastRootKey); - } - - self.db - .key() - .delete(key::uuid::equals(uuid.as_bytes().to_vec())) - .exec() - .await - .map_err(|_| KeyManagerError::KeyNotFound)?; - - Ok(()) - } - - pub async fn reset(&self) -> Result<()> { - // this is for the sync system, it'll be used when we have sync delete - // let _key_uuids = self - // .db - // .key() - // .find_many(vec![]) - // .select(key::select!({ uuid })) - // .exec() - // .await? - // .into_iter() - // .map(|x| x.uuid) - // .collect::>(); - - self.db - ._batch(( - self.db.key().delete_many(vec![]), - self.db.mounted_key().delete_many(vec![]), - )) - .await?; - - *self.key.lock().await = None; - - Ok(()) - } - - pub async fn update_key_name(&self, uuid: Uuid, name: String) -> Result<()> { - self.db - .key() - .update( - key::uuid::equals(uuid.as_bytes().to_vec()), - vec![key::name::set(Some(name))], - ) - .exec() - .await - .map_or(Err(KeyManagerError::KeyNotFound), |_| Ok(())) - } - - pub async fn insert_new( - &self, - algorithm: Algorithm, - hashing_algorithm: HashingAlgorithm, - password: Protected, - word: Option>, - ) -> Result { - self.ensure_unlocked().await?; - - word.as_ref().map(|w| { - if w.expose().len() < 3 { - Err(KeyManagerError::WordTooShort) - } else { - Ok(()) - } - }); - - // let word: Protected> = word - // .map_or( - // // generate_passphrase(1, '_').into_inner(), - // Protected::into_inner, - // ) - // .into_bytes() - // .into(); - - // TODO(brxken128): remove this and replace with the above once mnemonic/word generation has been optimised - let word: Protected> = Protected::new(b"word".to_vec()); - - let uuid = Uuid::new_v4(); - let tv_key = Key::generate(); - let tv_nonce = Nonce::generate(algorithm); - let tv_salt = Salt::generate(); - - let hashed_password = Hasher::hash_password( - hashing_algorithm, - &password.into_inner().into_bytes().into(), - word_to_salt(&word)?, - &SecretKey::Null, - )?; - - let tv_key = Encryptor::encrypt_key( - &Hasher::derive_key(&hashed_password, tv_salt, TEST_VECTOR_CONTEXT), - &tv_nonce, - algorithm, - &tv_key, - Aad::Null, - )?; - - let ew = EncryptedWord::encrypt(&self.get_root_key().await?, &word, algorithm)?; - - let key: key::CreateUnchecked = UserKey { - version: KeyVersion::V1, - uuid, - algorithm, - hashing_algorithm, - tv: TestVector(tv_salt, tv_key), - word: ew, - } - .try_into()?; - - key.to_query(&self.db).exec().await?; - - let mk = MountedKey::encrypt( - &self.get_root_key().await?, - &hashed_password, - algorithm, - &word, - )?; - - let mkc: mounted_key::CreateUnchecked = mk.try_into()?; - let mk_uuid = mkc.uuid.clone(); - - mkc.to_query(&self.db).exec().await?; - - self.db - .mounted_key() - .update( - mounted_key::uuid::equals(mk_uuid), - vec![mounted_key::SetParam::ConnectAssociatedKey( - key::uuid::equals(uuid.as_bytes().to_vec()), - )], - ) - .exec() - .await?; - - Ok(uuid) - } - - pub async fn list(&self, key_type: KeyType) -> Result> { - self.ensure_unlocked().await?; - - #[allow(clippy::as_conversions)] - self.db - .key() - .find_many(vec![key::key_type::equals(key_type as i32)]) - .select(key_info::select()) - .exec() - .await? - .into_iter() - .map(DisplayKey::try_from) - .collect() - } - - pub async fn mount(&self, uuid: Uuid, password: Protected) -> Result<()> { - self.ensure_unlocked().await?; - - self.db - .key() - .find_unique(key::uuid::equals(uuid.as_bytes().to_vec())) - .select(key::select!({ mounted_key })) - .exec() - .await? - .ok_or(KeyManagerError::KeyNotFound)? - .mounted_key - .map_or(Ok(()), |_| Err(KeyManagerError::AlreadyMounted))?; - - let key = self - .db - .key() - .find_unique(key::uuid::equals(uuid.as_bytes().to_vec())) - .exec() - .await? - .ok_or(KeyManagerError::KeyNotFound)?; - - let key = UserKey::try_from(key)?; - - let word = key - .word - .decrypt(&self.get_root_key().await?, key.algorithm)?; - - let hashed_password = Hasher::hash_password( - key.hashing_algorithm, - &password.into_inner().into_bytes().into(), - word_to_salt(&word)?, - &SecretKey::Null, - )?; - - key.tv.validate(key.algorithm, &hashed_password)?; - - let mk = MountedKey::encrypt( - &self.get_root_key().await?, - &hashed_password, - key.algorithm, - &word, - )?; - - let mkc: mounted_key::CreateUnchecked = mk.try_into()?; - let mk_uuid = mkc.uuid.clone(); - - mkc.to_query(&self.db).exec().await?; - - self.db - .mounted_key() - .update( - mounted_key::uuid::equals(mk_uuid), - vec![mounted_key::SetParam::ConnectAssociatedKey( - key::uuid::equals(uuid.as_bytes().to_vec()), - )], - ) - .exec() - .await?; - - Ok(()) - } - - pub async fn unmount(&self, uuid: Uuid) -> Result<()> { - if self - .db - .mounted_key() - .delete_many(vec![mounted_key::associated_key::is(vec![ - key::uuid::equals(uuid.as_bytes().to_vec()), - ])]) - .exec() - .await? == 1 - { - Ok(()) - } else { - Err(KeyManagerError::KeyNotFound) - } - } - - pub async fn unmount_all(&self) -> Result { - Ok(self - .db - .mounted_key() - .delete_many(vec![]) - .exec() - .await? - .try_into()?) - } - - pub async fn lock(&self) -> Result<()> { - self.ensure_unlocked().await?; - *self.key.lock().await = None; - - Ok(()) - } - - pub async fn get_key(&self, uuid: Uuid) -> Result { - self.ensure_unlocked().await?; - - let key = self - .db - .key() - .find_unique(key::uuid::equals(uuid.as_bytes().to_vec())) - .select(key::select!({ mounted_key })) - .exec() - .await? - .ok_or(KeyManagerError::KeyNotFound)? - .mounted_key - .map_or(Err(KeyManagerError::NotMounted), MountedKey::try_from)?; - - key.decrypt(&self.get_root_key().await?) - } - - pub async fn enumerate_hashed_keys(&self) -> Result> { - self.ensure_unlocked().await?; - - let rk = self.get_root_key().await?; - - self.db - .mounted_key() - .find_many(vec![]) - .exec() - .await? - .into_iter() - .flat_map(MountedKey::try_from) - .map(|x| x.decrypt(&rk)) - .collect() - } - - pub async fn backup_to_file(&self, path: PathBuf) -> Result { - if fs::metadata(&path).await.is_ok() { - return Err(KeyManagerError::FileAlreadyExists); - } - - #[allow(clippy::as_conversions)] - let user_keys = self - .db - .key() - .find_many(vec![key::key_type::equals(KeyType::User as i32)]) - .exec() - .await? - .into_iter() - .map(UserKey::try_from) - .collect::>>()?; - - #[allow(clippy::as_conversions)] - let root_keys = self - .db - .key() - .find_many(vec![key::key_type::equals(KeyType::Root as i32)]) - .exec() - .await? - .into_iter() - .map(RootKey::try_from) - .collect::>>()?; - - let count = user_keys.len() + root_keys.len(); - - let backup = OnDiskBackup { - root_keys, - user_keys, - }; - - let mut file = File::create(&path).await?; - file.write_all(&encoding::encode(&backup)?).await?; - - Ok(count) - } - - pub async fn restore_from_file( - &self, - path: PathBuf, - password: Protected, - secret_key: Protected, - ) -> Result { - let file_len: usize = fs::metadata(&path).await.map_or( - Err(KeyManagerError::FileDoesntExist), - |x: std::fs::Metadata| x.len().try_into().map_err(KeyManagerError::IntConversion), - )?; - - if file_len > (BLOCK_LEN * 16) { - return Err(KeyManagerError::FileTooLarge); - } - - let mut bytes = vec![0u8; file_len]; - let mut file = File::open(&path).await?; - file.read_to_end(&mut bytes).await?; - - let backup: OnDiskBackup = encoding::decode(&bytes)?; - - let password: Protected> = password.into_inner().into_bytes().into(); - let secret_key = secret_key.try_into()?; - - let backup_rk = backup - .root_keys - .into_iter() - .find_map(|k| { - let pw = Hasher::hash_password(k.hashing_algorithm, &password, k.salt, &secret_key) - .ok()?; - - Decryptor::decrypt_key(&pw, k.algorithm, &k.key, Aad::Null).ok() - }) - .ok_or(KeyManagerError::IncorrectPassword)?; - - let rk = self.get_root_key().await?; - - let user_keys = backup - .user_keys - .into_iter() - .map(|mut key| { - let word = key.word.decrypt(&backup_rk, key.algorithm)?; - key.word = EncryptedWord::encrypt(&rk, &word, key.algorithm)?; - - key.try_into() - }) - .collect::>>()?; - - Ok(self - .db - .key() - .create_many(user_keys) - .skip_duplicates() - .exec() - .await? - .try_into()?) - } -} - -#[derive(Clone, Encode, Decode)] -pub struct RootKey { - pub version: KeyVersion, - #[bincode(with_serde)] - pub uuid: Uuid, - pub algorithm: Algorithm, - pub hashing_algorithm: HashingAlgorithm, - pub salt: Salt, - pub key: EncryptedKey, -} - -impl TryFrom for RootKey { - type Error = KeyManagerError; - - fn try_from(value: key::Data) -> std::result::Result { - if KeyType::try_from(value.key_type)? != KeyType::Root { - return Err(KeyManagerError::Conversion); - } - - let rk = Self { - version: KeyVersion::try_from(value.version)?, - uuid: Uuid::from_slice(&value.uuid)?, - algorithm: encoding::decode(&value.algorithm)?, - hashing_algorithm: encoding::decode(&value.hashing_algorithm)?, - key: encoding::decode(&value.key)?, - salt: encoding::decode(&value.salt)?, - }; - - Ok(rk) - } -} - -impl TryFrom<&RootKey> for key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: &RootKey) -> std::result::Result { - #[allow(clippy::as_conversions)] - let s = Self { - uuid: value.uuid.as_bytes().to_vec(), - version: value.version as i32, - key_type: KeyType::Root as i32, - algorithm: encoding::encode(&value.algorithm)?, - hashing_algorithm: encoding::encode(&value.hashing_algorithm)?, - key: encoding::encode(&value.key)?, - salt: encoding::encode(&value.salt)?, - _params: vec![], - }; - - Ok(s) - } -} - -impl TryFrom for key::CreateUnchecked { - type Error = KeyManagerError; - - fn try_from(value: RootKey) -> std::result::Result { - (&value).try_into() - } -} diff --git a/core/src/crypto/mod.rs b/core/src/crypto/mod.rs deleted file mode 100644 index 7691eb0fc..000000000 --- a/core/src/crypto/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![warn( - clippy::all, - clippy::pedantic, - clippy::correctness, - clippy::perf, - clippy::style, - clippy::suspicious, - clippy::complexity, - clippy::nursery, - clippy::unwrap_used, - unused_qualifications, - clippy::expect_used, - trivial_casts, - trivial_numeric_casts, - unused_allocation, - clippy::as_conversions, - clippy::dbg_macro -)] -#![forbid(unsafe_code)] -#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] - -// use sd_crypto::types::{DerivationContext, MagicBytes}; - -// pub mod error; -// pub use error::{KeyManagerError, Result}; - -// pub mod keymanager; -// pub use keymanager::{DisplayKey, KeyManager, KeyType, KeyVersion, RootKey, UserKey}; - -/* -/// Used for OS keyrings to identify our items. -pub const KEYRING_APP_IDENTIFIER: &str = "Spacedrive"; - -/// Used for OS keyrings to identify our items. -pub const SECRET_KEY_IDENTIFIER: &str = "Secret key"; - -/// Defines the context string for BLAKE3-KDF in regards to root key derivation -pub const ROOT_KEY_CONTEXT: DerivationContext = - DerivationContext::new("spacedrive 2022-12-14 12:53:54 root key derivation"); - -/// Defines the context string for BLAKE3-KDF in regards to master password hash derivation -pub const MASTER_PASSWORD_CONTEXT: DerivationContext = - DerivationContext::new("spacedrive 2022-12-14 15:35:41 master password hash derivation"); - -/// Defines the context string for BLAKE3-KDF in regards to file key derivation (for file encryption) -pub const FILE_KEYSLOT_CONTEXT: DerivationContext = - DerivationContext::new("spacedrive 2022-12-14 12:54:12 file key derivation"); -*/ - -// /// Defines the context string for BLAKE3-KDF in regards to key derivation (for the key manager) -// pub const KEY_MOUNTING_CONTEXT: DerivationContext = -// DerivationContext::new("spacedrive 2023-05-24 11:43:07 key mounting derivation"); - -// /// Defines the context string for BLAKE3-KDF in regards to key derivation (for encrypted words) -// pub const ENCRYPTED_WORD_CONTEXT: DerivationContext = -// DerivationContext::new("spacedrive 2023-05-22 18:01:02 encrypted word derivation"); - -// /// Defines the context string for BLAKE3-KDF in regards to key derivation (for test vectors) -// pub const TEST_VECTOR_CONTEXT: DerivationContext = -// DerivationContext::new("spacedrive 2023-05-22 14:37:16 test vector derivation"); - -// /// Encrypted file magic bytes - "ballapp" and then a null byte. -// pub const FILE_MAGIC_BYTES: MagicBytes<8> = -// MagicBytes::new([0x62, 0x61, 0x6C, 0x6C, 0x61, 0x70, 0x70, 0x00]); diff --git a/core/src/lib.rs b/core/src/lib.rs index bac56e73f..5be82ede2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -39,8 +39,6 @@ use tracing_subscriber::{ pub mod api; mod cloud; mod context; -#[cfg(feature = "crypto")] -pub(crate) mod crypto; pub mod custom_uri; mod env; pub mod library; diff --git a/core/src/object/fs/decrypt.rs b/core/src/object/fs/decrypt.rs deleted file mode 100644 index a8f8424f6..000000000 --- a/core/src/object/fs/decrypt.rs +++ /dev/null @@ -1,159 +0,0 @@ -// use crate::{ -// invalidate_query, -// job::{ -// JobError, JobInitData, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext, -// }, -// library::Library, -// location::{file_path_helper:: location::id::Type}, -// util::error::FileIOError, -// }; - -// use sd_crypto::{crypto::Decryptor, header::file::FileHeader, Protected}; - -// use serde::{Deserialize, Serialize}; -// use specta::Type; -// use tokio::fs::File; - -// use super::{get_location_path_from_location_id, get_many_files_datas, FileData, BYTES_EXT}; -// pub struct FileDecryptorJob; - -// // decrypt could have an option to restore metadata (and another specific option for file name? - would turn "output file" into "output path" in the UI) -// #[derive(Serialize, Deserialize, Debug, Type, Hash)] -// pub struct FileDecryptorJobInit { -// pub location_id: location::id::Type, -// pub file_path_ids: Vec, -// pub mount_associated_key: bool, -// pub password: Option, // if this is set, we can assume the user chose password decryption -// pub save_to_library: Option, -// } - -// impl JobInitData for FileDecryptorJobInit { -// type Job = FileDecryptorJob; -// } - -// #[async_trait::async_trait] -// impl StatefulJob for FileDecryptorJob { -// type Init = FileDecryptorJobInit; -// type Data = (); -// type Step = FileData; - -// const NAME: &'static str = "file_decryptor"; - -// fn new() -> Self { -// Self {} -// } - -// async fn init(&self, ctx: WorkerContext, state: &mut JobState) -> Result<(), JobError> { -// let Library { db, .. } = &*ctx.library; - -// state.steps = get_many_files_datas( -// db, -// get_location_path_from_location_id(db, state.init.location_id).await?, -// &state.init.file_path_ids, -// ) -// .await? -// .into(); - -// ctx.progress(vec![JobReportUpdate::TaskCount(state.steps.len())]); - -// Ok(()) -// } - -// async fn execute_step( -// &self, -// ctx: WorkerContext, -// state: &mut JobState, -// ) -> Result<(), JobError> { -// let step = &state.steps[0]; -// let key_manager = &ctx.library.key_manager; - -// // handle overwriting checks, and making sure there's enough available space -// let output_path = { -// let mut path = step.full_path.clone(); -// let extension = path.extension().map_or("decrypted", |ext| { -// if ext == BYTES_EXT { -// "" -// } else { -// "decrypted" -// } -// }); -// path.set_extension(extension); -// path -// }; - -// let mut reader = File::open(&step.full_path) -// .await -// .map_err(|e| FileIOError::from((&step.full_path, e)))?; -// let mut writer = File::create(&output_path) -// .await -// .map_err(|e| FileIOError::from((output_path, e)))?; - -// let (header, aad) = FileHeader::from_reader(&mut reader).await?; - -// let master_key = if let Some(password) = state.init.password.clone() { -// if let Some(save_to_library) = state.init.save_to_library { -// // we can do this first, as `find_key_index` requires a successful decryption (just like `decrypt_master_key`) -// let password_bytes = Protected::new(password.as_bytes().to_vec()); - -// if save_to_library { -// let index = header.find_key_index(password_bytes.clone()).await?; - -// // inherit the encryption algorithm from the keyslot -// key_manager -// .add_to_keystore( -// Protected::new(password), -// header.algorithm, -// header.keyslots[index].hashing_algorithm, -// false, -// false, -// Some(header.keyslots[index].salt), -// ) -// .await?; -// } - -// header.decrypt_master_key(password_bytes).await? -// } else { -// return Err(JobError::JobDataNotFound(String::from( -// "Password decryption selected, but save to library boolean was not included", -// ))); -// } -// } else { -// if state.init.mount_associated_key { -// for key in key_manager.dump_keystore().iter().filter(|x| { -// header -// .keyslots -// .iter() -// .any(|k| k.content_salt == x.content_salt) -// }) { -// key_manager.mount(key.uuid).await.ok(); -// } -// } - -// let keys = key_manager.enumerate_hashed_keys(); - -// header.decrypt_master_key_from_prehashed(keys).await? -// }; - -// let decryptor = Decryptor::new(master_key, header.nonce, header.algorithm)?; - -// decryptor -// .decrypt_streams(&mut reader, &mut writer, &aad) -// .await?; - -// // need to decrypt preview media/metadata, and maybe add an option in the UI so the user can chosoe to restore these values -// // for now this can't easily be implemented, as we don't know what the new object id for the file will be (we know the old one, but it may differ) - -// ctx.progress(vec![JobReportUpdate::CompletedTaskCount( -// state.step_number + 1, -// )]); - -// Ok(()) -// } - -// async fn finalize(&self, ctx: WorkerContext, state: &mut JobState) -> JobResult { -// invalidate_query!(ctx.library, "search.paths"); - -// // mark job as successful -// Ok(Some(serde_json::to_value(&state.init)?)) -// } -// } diff --git a/core/src/object/fs/encrypt.rs b/core/src/object/fs/encrypt.rs deleted file mode 100644 index 0ca165087..000000000 --- a/core/src/object/fs/encrypt.rs +++ /dev/null @@ -1,261 +0,0 @@ -// use crate::{ -// invalidate_query, -// job::*, -// library::Library, -// location::{file_path_helper:: location::id::Type}, -// util::error::{FileIOError, NonUtf8PathError}, -// }; - -// use sd_crypto::{ -// crypto::Encryptor, -// header::{file::FileHeader, keyslot::Keyslot}, -// primitives::{LATEST_FILE_HEADER, LATEST_KEYSLOT, LATEST_METADATA, LATEST_PREVIEW_MEDIA}, -// types::{Algorithm, Key}, -// }; - -// use chrono::FixedOffset; -// use serde::{Deserialize, Serialize}; -// use specta::Type; -// use tokio::{ -// fs::{self, File}, -// io, -// }; -// use tracing::{error, warn}; -// use uuid::Uuid; - -// use super::{ -// error::FileSystemJobsError, get_location_path_from_location_id, get_many_files_datas, FileData, -// BYTES_EXT, -// }; - -// pub struct FileEncryptorJob; - -// #[derive(Serialize, Deserialize, Type, Hash)] -// pub struct FileEncryptorJobInit { -// pub location_id: location::id::Type, -// pub file_path_ids: Vec, -// pub key_uuid: Uuid, -// pub algorithm: Algorithm, -// pub metadata: bool, -// pub preview_media: bool, -// } - -// #[derive(Serialize, Deserialize)] -// pub struct Metadata { -// pub file_path_id: file_path::id::Type, -// pub name: String, -// pub hidden: bool, -// pub favorite: bool, -// pub important: bool, -// pub note: Option, -// pub date_created: chrono::DateTime, -// } - -// impl JobInitData for FileEncryptorJobInit { -// type Job = FileEncryptorJob; -// } - -// #[async_trait::async_trait] -// impl StatefulJob for FileEncryptorJob { -// type Init = FileEncryptorJobInit; -// type Data = (); -// type Step = FileData; - -// const NAME: &'static str = "file_encryptor"; - -// fn new() -> Self { -// Self {} -// } - -// async fn init(&self, ctx: WorkerContext, state: &mut JobState) -> Result<(), JobError> { -// let Library { db, .. } = &*ctx.library; - -// state.steps = get_many_files_datas( -// db, -// get_location_path_from_location_id(db, state.init.location_id).await?, -// &state.init.file_path_ids, -// ) -// .await? -// .into(); - -// ctx.progress(vec![JobReportUpdate::TaskCount(state.steps.len())]); - -// Ok(()) -// } - -// async fn execute_step( -// &self, -// ctx: WorkerContext, -// state: &mut JobState, -// ) -> Result<(), JobError> { -// let step = &state.steps[0]; - -// let Library { key_manager, .. } = &*ctx.library; - -// if !step.file_path.is_dir { -// // handle overwriting checks, and making sure there's enough available space - -// let user_key = key_manager -// .access_keymount(state.init.key_uuid) -// .await? -// .hashed_key; - -// let user_key_details = key_manager.access_keystore(state.init.key_uuid).await?; - -// let output_path = { -// let mut path = step.full_path.clone(); -// let extension = path.extension().map_or_else( -// || Ok("bytes".to_string()), -// |extension| { -// Ok::(format!( -// "{}{BYTES_EXT}", -// extension.to_str().ok_or(FileSystemJobsError::FilePath( -// NonUtf8PathError(step.full_path.clone().into_boxed_path()).into() -// ))? -// )) -// }, -// )?; - -// path.set_extension(extension); -// path -// }; - -// let _guard = ctx -// .library -// .location_manager() -// .temporary_ignore_events_for_path( -// state.init.location_id, -// ctx.library.clone(), -// &output_path, -// ) -// .await -// .map_or_else( -// |e| { -// error!( -// "Failed to make location manager ignore the path {}; Error: {e:#?}", -// output_path.display() -// ); -// None -// }, -// Some, -// ); - -// let mut reader = File::open(&step.full_path) -// .await -// .map_err(|e| FileIOError::from((&step.full_path, e)))?; -// let mut writer = File::create(&output_path) -// .await -// .map_err(|e| FileIOError::from((output_path, e)))?; - -// let master_key = Key::generate(); - -// let mut header = FileHeader::new( -// LATEST_FILE_HEADER, -// state.init.algorithm, -// vec![ -// Keyslot::new( -// LATEST_KEYSLOT, -// state.init.algorithm, -// user_key_details.hashing_algorithm, -// user_key_details.content_salt, -// user_key, -// master_key.clone(), -// ) -// .await?, -// ], -// )?; - -// if state.init.metadata || state.init.preview_media { -// // if any are requested, we can make the query as it'll be used at least once -// if let Some(ref object) = step.file_path.object { -// if state.init.metadata { -// let metadata = Metadata { -// file_path_id: step.file_path.id, -// name: step.file_path.materialized_path.clone(), -// hidden: object.hidden, -// favorite: object.favorite, -// important: object.important, -// note: object.note.clone(), -// date_created: object.date_created, -// }; - -// header -// .add_metadata( -// LATEST_METADATA, -// state.init.algorithm, -// master_key.clone(), -// &metadata, -// ) -// .await?; -// } - -// // if state.init.preview_media -// // && (object.has_thumbnail -// // || object.has_video_preview || object.has_thumbstrip) - -// // may not be the best - preview media (thumbnail) isn't guaranteed to be webp -// let thumbnail_path = ctx -// .library -// .config() -// .data_directory() -// .join("thumbnails") -// .join( -// step.file_path -// .cas_id -// .as_ref() -// .ok_or(JobError::MissingCasId)?, -// ) -// .with_extension("wepb"); - -// match fs::read(&thumbnail_path).await { -// Ok(thumbnail_bytes) => { -// header -// .add_preview_media( -// LATEST_PREVIEW_MEDIA, -// state.init.algorithm, -// master_key.clone(), -// &thumbnail_bytes, -// ) -// .await?; -// } -// Err(e) if e.kind() == io::ErrorKind::NotFound => { -// // If the file just doesn't exist, then we don't care -// } -// Err(e) => { -// return Err(FileIOError::from((thumbnail_path, e)).into()); -// } -// } -// } else { -// // should use container encryption if it's a directory -// warn!("skipping metadata/preview media inclusion, no associated object found") -// } -// } - -// header.write(&mut writer).await?; - -// let encryptor = Encryptor::new(master_key, header.nonce, header.algorithm)?; - -// encryptor -// .encrypt_streams(&mut reader, &mut writer, &header.generate_aad()) -// .await?; -// } else { -// warn!( -// "encryption is skipping {}/{} as it isn't a file", -// step.file_path.materialized_path, step.file_path.name -// ) -// } - -// ctx.progress(vec![JobReportUpdate::CompletedTaskCount( -// state.step_number + 1, -// )]); - -// Ok(()) -// } - -// async fn finalize(&self, ctx: WorkerContext, state: &mut JobState) -> JobResult { -// invalidate_query!(ctx.library, "search.paths"); - -// // mark job as successful -// Ok(Some(serde_json::to_value(&state.init)?)) -// } -// } diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 3e8810500..4a5e2f305 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -1,142 +1,48 @@ [package] name = "sd-crypto" -version = "0.0.0" +version = "0.0.1" -authors = ["Jake Robinson "] +authors = ["Ericson Soares ", "Jake Robinson "] description = """ A cryptographic library that provides safe and high-level encryption, hashing, and encoding interfaces. """ -edition.workspace = true -keywords = ["crypto"] -license.workspace = true -readme = "README.md" -repository.workspace = true -rust-version = "1.72" - -[features] -experimental = [] -keyring = ["dep:linux-keyutils", "dep:security-framework"] -secret-service = [ - "dep:secret-service", - "dep:zbus", - "keyring" -] # explicit enabling required as the secret service api requires `zbus` and is messy -serde = ["bincode/serde", "dep:serde", "dep:serde-big-array", "dep:serde_json", "dep:serdect"] -sys = [] +edition.workspace = true +keywords = ["crypto"] +license.workspace = true +readme = "README.md" +repository.workspace = true +rust-version.workspace = true [dependencies] -# rng -rand = "0.9.0-alpha.0" -rand_chacha = "0.9.0-alpha.0" -rand_core = "0.9.0-alpha.0" +# Workspace dependencies +async-stream = { workspace = true } +blake3 = { workspace = true } +futures = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["io-util", "macros", "rt-multi-thread", "sync"] } -# hashing -argon2 = { version = "0.6.0-pre.0", default_features = false, features = ["alloc", "zeroize"] } -balloon-hash = { version = "0.5.0-pre.0", default_features = false, features = [ - "alloc", - "zeroize" -] } -blake3 = { version = "1.5.0", features = ["traits-preview", "zeroize"] } - -# constant time -cmov = "0.3.1" - -# aeads -aead = { version = "0.5.2", default-features = false, features = ["stream"] } -aes-gcm-siv = "0.11.1" -bincode = { version = "2.0.0-rc.3", features = ["alloc", "derive"] } -chacha20poly1305 = "0.10.1" -thiserror = "1.0.57" - -zeroize = { version = "1.7.0", features = ["aarch64", "derive"] } - -serde = { version = "1.0.197", features = ["derive"], optional = true } -serde-big-array = { version = "0.5.1", optional = true } -serde_json = { version = "1.0.114", optional = true } -serdect = { version = "0.3.0-pre.0", optional = true } - -specta = { workspace = true, optional = true } - -# for asynchronous crypto -tokio = { workspace = true, features = [ - "io-util", - "macros", - "rt-multi-thread", - "sync" -], optional = true } - -redb = "1.5.0" - -hex = "0.4.3" - -uuid = { version = "1.7.0", features = ["v4"] } - -# ed25519-dalek = { version = "2.1.1", feature = ["std", "zeroize"] } -# x25519-dalek = { version = "2.0.1", feature = [ -# "std", -# "zeroize", -# ] } # ReusableSecrets feature may have to come out for X3DH - -# linux OS keyring -[target.'cfg(target_os = "linux")'.dependencies] -linux-keyutils = { version = "0.2.4", features = ["std"], optional = true } -secret-service = { version = "3.0.1", features = [ - "crypto-rust", - "rt-tokio-crypto-rust" -], optional = true } - -# this needs to remain at versions < 4, as they made some changes and i can't get it -# to compile for the time being -zbus = { version = "4.0", default_features = false, features = [ - "blocking", - "tokio" -], optional = true } - -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -security-framework = { version = "2.9.2", optional = true } +# External dependencies +aead = { version = "0.6.0-rc.0", default-features = false, features = ["stream"] } +chacha20poly1305 = "0.11.0-pre.1" +cmov = "0.3.1" +# Some deps use this same version, so we just use it too +generic-array = { version = "=0.14.7", features = ["serde", "zeroize"] } +hex = "0.4.3" +rand = "0.9.0-alpha.2" +rand_chacha = "0.9.0-alpha.2" +rand_core = "0.9.0-alpha.2" +serde-big-array = "0.5.1" +serdect = "0.3.0-pre.0" +typenum = "1.17.0" +zeroize = { version = "1.7.0", features = ["aarch64", "derive"] } [dev-dependencies] -criterion = "0.5.1" -paste = "1.0.14" -tempfile = "3.10.1" - -[clippy] -allow = ["unwrap_in_tests"] - -[[bench]] -harness = false -name = "aes-256-gcm-siv" -path = "benches/crypto/aes-256-gcm-siv.rs" - -[[bench]] -harness = false -name = "xchacha20-poly1305" -path = "benches/crypto/xchacha20-poly1305.rs" - -[[bench]] -bench = false -harness = false -name = "argon2id" -path = "benches/hashing/argon2id.rs" - -[[bench]] -bench = false -harness = false -name = "blake3-balloon" -path = "benches/hashing/blake3-balloon.rs" - -[[bench]] -harness = false -name = "blake3" -path = "benches/hashing/blake3.rs" - -[[bench]] -harness = false -name = "blake3-kdf" -path = "benches/hashing/blake3-kdf.rs" +paste = "1.0.14" +tempfile = "3.10.1" [[example]] -name = "file_encryption" -path = "examples/file_encryption.rs" +name = "secure_erase" +path = "examples/secure_erase.rs" diff --git a/crates/crypto/README.md b/crates/crypto/README.md index de00fbe84..58f4b2306 100644 --- a/crates/crypto/README.md +++ b/crates/crypto/README.md @@ -4,40 +4,20 @@ This crate contains Spacedrive's cryptographic modules. This includes things such as: -- The key manager - Encryption and decryption -- Encrypted file header formats (with extremely fast serialization and deserialization) -- Key hashing and derivation -- Keyring interfaces to access native OS keystores It has support for the following cryptographic functions: -- `Argon2id` -- `Balloon` hashing -- `BLAKE3` key derivation - `XChaCha20-Poly1305` -- `AES-256-GCM-SIV` It aims to be (relatively) lightweight, easy to maintain and platform-agnostic where possible. It does contain some platform-specific code, although it's only built if the target matches. -## Features - -A list of all features can be found below (NOTE: none of these features are enabled by default) - -- `serde` - provides integration with `serde` and `serde_json` - -- `tokio` - provides integration with the `tokio` crate -- `specta` - provides integration with the `specta` crate -- `bincode` - provides integration with the `bincode` crate (this will likely become part of the crate) -- `keyring` - provides a unified interface for interacting with OS-keyrings (currently only supports MacOS/iOS/Linux `keyutils`). `keyutils` is not persistent, so is best used in a headless server/docker environment, as keys are wiped on-reboot. The Secret Service API is not practically available in headless environments. -- `secret-service` - enables `keyring` but also enables the Secret Service API (a persistent keyring targeted at Gnome/KDE (via `gnome-keyring` and `kwallet` respectively)). Is a pretty heavy dependency. - ## Security Notice This crate has NOT received any security audit - however, a couple of our upstream libraries (provided by [RustCrypto](https://github.com/RustCrypto)) have. You may find them below: -- AES-GCM and XChaCha20-Poly1305 audit by NCC group ([link](https://research.nccgroup.com/wp-content/uploads/2020/02/NCC_Group_MobileCoin_RustCrypto_AESGCM_ChaCha20Poly1305_Implementation_Review_2020-02-12_v1.0.pdf)) +- XChaCha20-Poly1305 audit by NCC group ([link](https://research.nccgroup.com/wp-content/uploads/2020/02/NCC_Group_MobileCoin_RustCrypto_AESGCM_ChaCha20Poly1305_Implementation_Review_2020-02-12_v1.0.pdf)) Breaking changes are very likely! Use at your own risk - no stability or security is guaranteed. diff --git a/crates/crypto/assets/eff_large_wordlist.txt b/crates/crypto/assets/eff_large_wordlist.txt deleted file mode 100644 index 20a63e458..000000000 --- a/crates/crypto/assets/eff_large_wordlist.txt +++ /dev/null @@ -1,7776 +0,0 @@ -abacus -abdomen -abdominal -abide -abiding -ability -ablaze -able -abnormal -abrasion -abrasive -abreast -abridge -abroad -abruptly -absence -absentee -absently -absinthe -absolute -absolve -abstain -abstract -absurd -accent -acclaim -acclimate -accompany -account -accuracy -accurate -accustom -acetone -achiness -aching -acid -acorn -acquaint -acquire -acre -acrobat -acronym -acting -action -activate -activator -active -activism -activist -activity -actress -acts -acutely -acuteness -aeration -aerobics -aerosol -aerospace -afar -affair -affected -affecting -affection -affidavit -affiliate -affirm -affix -afflicted -affluent -afford -affront -aflame -afloat -aflutter -afoot -afraid -afterglow -afterlife -aftermath -aftermost -afternoon -aged -ageless -agency -agenda -agent -aggregate -aghast -agile -agility -aging -agnostic -agonize -agonizing -agony -agreeable -agreeably -agreed -agreeing -agreement -aground -ahead -ahoy -aide -aids -aim -ajar -alabaster -alarm -albatross -album -alfalfa -algebra -algorithm -alias -alibi -alienable -alienate -aliens -alike -alive -alkaline -alkalize -almanac -almighty -almost -aloe -aloft -aloha -alone -alongside -aloof -alphabet -alright -although -altitude -alto -aluminum -alumni -always -amaretto -amaze -amazingly -amber -ambiance -ambiguity -ambiguous -ambition -ambitious -ambulance -ambush -amendable -amendment -amends -amenity -amiable -amicably -amid -amigo -amino -amiss -ammonia -ammonium -amnesty -amniotic -among -amount -amperage -ample -amplifier -amplify -amply -amuck -amulet -amusable -amused -amusement -amuser -amusing -anaconda -anaerobic -anagram -anatomist -anatomy -anchor -anchovy -ancient -android -anemia -anemic -aneurism -anew -angelfish -angelic -anger -angled -angler -angles -angling -angrily -angriness -anguished -angular -animal -animate -animating -animation -animator -anime -animosity -ankle -annex -annotate -announcer -annoying -annually -annuity -anointer -another -answering -antacid -antarctic -anteater -antelope -antennae -anthem -anthill -anthology -antibody -antics -antidote -antihero -antiquely -antiques -antiquity -antirust -antitoxic -antitrust -antiviral -antivirus -antler -antonym -antsy -anvil -anybody -anyhow -anymore -anyone -anyplace -anything -anytime -anyway -anywhere -aorta -apache -apostle -appealing -appear -appease -appeasing -appendage -appendix -appetite -appetizer -applaud -applause -apple -appliance -applicant -applied -apply -appointee -appraisal -appraiser -apprehend -approach -approval -approve -apricot -april -apron -aptitude -aptly -aqua -aqueduct -arbitrary -arbitrate -ardently -area -arena -arguable -arguably -argue -arise -armadillo -armband -armchair -armed -armful -armhole -arming -armless -armoire -armored -armory -armrest -army -aroma -arose -around -arousal -arrange -array -arrest -arrival -arrive -arrogance -arrogant -arson -art -ascend -ascension -ascent -ascertain -ashamed -ashen -ashes -ashy -aside -askew -asleep -asparagus -aspect -aspirate -aspire -aspirin -astonish -astound -astride -astrology -astronaut -astronomy -astute -atlantic -atlas -atom -atonable -atop -atrium -atrocious -atrophy -attach -attain -attempt -attendant -attendee -attention -attentive -attest -attic -attire -attitude -attractor -attribute -atypical -auction -audacious -audacity -audible -audibly -audience -audio -audition -augmented -august -authentic -author -autism -autistic -autograph -automaker -automated -automatic -autopilot -available -avalanche -avatar -avenge -avenging -avenue -average -aversion -avert -aviation -aviator -avid -avoid -await -awaken -award -aware -awhile -awkward -awning -awoke -awry -axis -babble -babbling -babied -baboon -backache -backboard -backboned -backdrop -backed -backer -backfield -backfire -backhand -backing -backlands -backlash -backless -backlight -backlit -backlog -backpack -backpedal -backrest -backroom -backshift -backside -backslid -backspace -backspin -backstab -backstage -backtalk -backtrack -backup -backward -backwash -backwater -backyard -bacon -bacteria -bacterium -badass -badge -badland -badly -badness -baffle -baffling -bagel -bagful -baggage -bagged -baggie -bagginess -bagging -baggy -bagpipe -baguette -baked -bakery -bakeshop -baking -balance -balancing -balcony -balmy -balsamic -bamboo -banana -banish -banister -banjo -bankable -bankbook -banked -banker -banking -banknote -bankroll -banner -bannister -banshee -banter -barbecue -barbed -barbell -barber -barcode -barge -bargraph -barista -baritone -barley -barmaid -barman -barn -barometer -barrack -barracuda -barrel -barrette -barricade -barrier -barstool -bartender -barterer -bash -basically -basics -basil -basin -basis -basket -batboy -batch -bath -baton -bats -battalion -battered -battering -battery -batting -battle -bauble -bazooka -blabber -bladder -blade -blah -blame -blaming -blanching -blandness -blank -blaspheme -blasphemy -blast -blatancy -blatantly -blazer -blazing -bleach -bleak -bleep -blemish -blend -bless -blighted -blimp -bling -blinked -blinker -blinking -blinks -blip -blissful -blitz -blizzard -bloated -bloating -blob -blog -bloomers -blooming -blooper -blot -blouse -blubber -bluff -bluish -blunderer -blunt -blurb -blurred -blurry -blurt -blush -blustery -boaster -boastful -boasting -boat -bobbed -bobbing -bobble -bobcat -bobsled -bobtail -bodacious -body -bogged -boggle -bogus -boil -bok -bolster -bolt -bonanza -bonded -bonding -bondless -boned -bonehead -boneless -bonelike -boney -bonfire -bonnet -bonsai -bonus -bony -boogeyman -boogieman -book -boondocks -booted -booth -bootie -booting -bootlace -bootleg -boots -boozy -borax -boring -borough -borrower -borrowing -boss -botanical -botanist -botany -botch -both -bottle -bottling -bottom -bounce -bouncing -bouncy -bounding -boundless -bountiful -bovine -boxcar -boxer -boxing -boxlike -boxy -breach -breath -breeches -breeching -breeder -breeding -breeze -breezy -brethren -brewery -brewing -briar -bribe -brick -bride -bridged -brigade -bright -brilliant -brim -bring -brink -brisket -briskly -briskness -bristle -brittle -broadband -broadcast -broaden -broadly -broadness -broadside -broadways -broiler -broiling -broken -broker -bronchial -bronco -bronze -bronzing -brook -broom -brought -browbeat -brownnose -browse -browsing -bruising -brunch -brunette -brunt -brush -brussels -brute -brutishly -bubble -bubbling -bubbly -buccaneer -bucked -bucket -buckle -buckshot -buckskin -bucktooth -buckwheat -buddhism -buddhist -budding -buddy -budget -buffalo -buffed -buffer -buffing -buffoon -buggy -bulb -bulge -bulginess -bulgur -bulk -bulldog -bulldozer -bullfight -bullfrog -bullhorn -bullion -bullish -bullpen -bullring -bullseye -bullwhip -bully -bunch -bundle -bungee -bunion -bunkbed -bunkhouse -bunkmate -bunny -bunt -busboy -bush -busily -busload -bust -busybody -buzz -cabana -cabbage -cabbie -cabdriver -cable -caboose -cache -cackle -cacti -cactus -caddie -caddy -cadet -cadillac -cadmium -cage -cahoots -cake -calamari -calamity -calcium -calculate -calculus -caliber -calibrate -calm -caloric -calorie -calzone -camcorder -cameo -camera -camisole -camper -campfire -camping -campsite -campus -canal -canary -cancel -candied -candle -candy -cane -canine -canister -cannabis -canned -canning -cannon -cannot -canola -canon -canopener -canopy -canteen -canyon -capable -capably -capacity -cape -capillary -capital -capitol -capped -capricorn -capsize -capsule -caption -captivate -captive -captivity -capture -caramel -carat -caravan -carbon -cardboard -carded -cardiac -cardigan -cardinal -cardstock -carefully -caregiver -careless -caress -caretaker -cargo -caring -carless -carload -carmaker -carnage -carnation -carnival -carnivore -carol -carpenter -carpentry -carpool -carport -carried -carrot -carrousel -carry -cartel -cartload -carton -cartoon -cartridge -cartwheel -carve -carving -carwash -cascade -case -cash -casing -casino -casket -cassette -casually -casualty -catacomb -catalog -catalyst -catalyze -catapult -cataract -catatonic -catcall -catchable -catcher -catching -catchy -caterer -catering -catfight -catfish -cathedral -cathouse -catlike -catnap -catnip -catsup -cattail -cattishly -cattle -catty -catwalk -caucasian -caucus -causal -causation -cause -causing -cauterize -caution -cautious -cavalier -cavalry -caviar -cavity -cedar -celery -celestial -celibacy -celibate -celtic -cement -census -ceramics -ceremony -certainly -certainty -certified -certify -cesarean -cesspool -chafe -chaffing -chain -chair -chalice -challenge -chamber -chamomile -champion -chance -change -channel -chant -chaos -chaperone -chaplain -chapped -chaps -chapter -character -charbroil -charcoal -charger -charging -chariot -charity -charm -charred -charter -charting -chase -chasing -chaste -chastise -chastity -chatroom -chatter -chatting -chatty -cheating -cheddar -cheek -cheer -cheese -cheesy -chef -chemicals -chemist -chemo -cherisher -cherub -chess -chest -chevron -chevy -chewable -chewer -chewing -chewy -chief -chihuahua -childcare -childhood -childish -childless -childlike -chili -chill -chimp -chip -chirping -chirpy -chitchat -chivalry -chive -chloride -chlorine -choice -chokehold -choking -chomp -chooser -choosing -choosy -chop -chosen -chowder -chowtime -chrome -chubby -chuck -chug -chummy -chump -chunk -churn -chute -cider -cilantro -cinch -cinema -cinnamon -circle -circling -circular -circulate -circus -citable -citadel -citation -citizen -citric -citrus -city -civic -civil -clad -claim -clambake -clammy -clamor -clamp -clamshell -clang -clanking -clapped -clapper -clapping -clarify -clarinet -clarity -clash -clasp -class -clatter -clause -clavicle -claw -clay -clean -clear -cleat -cleaver -cleft -clench -clergyman -clerical -clerk -clever -clicker -client -climate -climatic -cling -clinic -clinking -clip -clique -cloak -clobber -clock -clone -cloning -closable -closure -clothes -clothing -cloud -clover -clubbed -clubbing -clubhouse -clump -clumsily -clumsy -clunky -clustered -clutch -clutter -coach -coagulant -coastal -coaster -coasting -coastland -coastline -coat -coauthor -cobalt -cobbler -cobweb -cocoa -coconut -cod -coeditor -coerce -coexist -coffee -cofounder -cognition -cognitive -cogwheel -coherence -coherent -cohesive -coil -coke -cola -cold -coleslaw -coliseum -collage -collapse -collar -collected -collector -collide -collie -collision -colonial -colonist -colonize -colony -colossal -colt -coma -come -comfort -comfy -comic -coming -comma -commence -commend -comment -commerce -commode -commodity -commodore -common -commotion -commute -commuting -compacted -compacter -compactly -compactor -companion -company -compare -compel -compile -comply -component -composed -composer -composite -compost -composure -compound -compress -comprised -computer -computing -comrade -concave -conceal -conceded -concept -concerned -concert -conch -concierge -concise -conclude -concrete -concur -condense -condiment -condition -condone -conducive -conductor -conduit -cone -confess -confetti -confidant -confident -confider -confiding -configure -confined -confining -confirm -conflict -conform -confound -confront -confused -confusing -confusion -congenial -congested -congrats -congress -conical -conjoined -conjure -conjuror -connected -connector -consensus -consent -console -consoling -consonant -constable -constant -constrain -constrict -construct -consult -consumer -consuming -contact -container -contempt -contend -contented -contently -contents -contest -context -contort -contour -contrite -control -contusion -convene -convent -copartner -cope -copied -copier -copilot -coping -copious -copper -copy -coral -cork -cornball -cornbread -corncob -cornea -corned -corner -cornfield -cornflake -cornhusk -cornmeal -cornstalk -corny -coronary -coroner -corporal -corporate -corral -correct -corridor -corrode -corroding -corrosive -corsage -corset -cortex -cosigner -cosmetics -cosmic -cosmos -cosponsor -cost -cottage -cotton -couch -cough -could -countable -countdown -counting -countless -country -county -courier -covenant -cover -coveted -coveting -coyness -cozily -coziness -cozy -crabbing -crabgrass -crablike -crabmeat -cradle -cradling -crafter -craftily -craftsman -craftwork -crafty -cramp -cranberry -crane -cranial -cranium -crank -crate -crave -craving -crawfish -crawlers -crawling -crayfish -crayon -crazed -crazily -craziness -crazy -creamed -creamer -creamlike -crease -creasing -creatable -create -creation -creative -creature -credible -credibly -credit -creed -creme -creole -crepe -crept -crescent -crested -cresting -crestless -crevice -crewless -crewman -crewmate -crib -cricket -cried -crier -crimp -crimson -cringe -cringing -crinkle -crinkly -crisped -crisping -crisply -crispness -crispy -criteria -critter -croak -crock -crook -croon -crop -cross -crouch -crouton -crowbar -crowd -crown -crucial -crudely -crudeness -cruelly -cruelness -cruelty -crumb -crummiest -crummy -crumpet -crumpled -cruncher -crunching -crunchy -crusader -crushable -crushed -crusher -crushing -crust -crux -crying -cryptic -crystal -cubbyhole -cube -cubical -cubicle -cucumber -cuddle -cuddly -cufflink -culinary -culminate -culpable -culprit -cultivate -cultural -culture -cupbearer -cupcake -cupid -cupped -cupping -curable -curator -curdle -cure -curfew -curing -curled -curler -curliness -curling -curly -curry -curse -cursive -cursor -curtain -curtly -curtsy -curvature -curve -curvy -cushy -cusp -cussed -custard -custodian -custody -customary -customer -customize -customs -cut -cycle -cyclic -cycling -cyclist -cylinder -cymbal -cytoplasm -cytoplast -dab -dad -daffodil -dagger -daily -daintily -dainty -dairy -daisy -dallying -dance -dancing -dandelion -dander -dandruff -dandy -danger -dangle -dangling -daredevil -dares -daringly -darkened -darkening -darkish -darkness -darkroom -darling -darn -dart -darwinism -dash -dastardly -data -datebook -dating -daughter -daunting -dawdler -dawn -daybed -daybreak -daycare -daydream -daylight -daylong -dayroom -daytime -dazzler -dazzling -deacon -deafening -deafness -dealer -dealing -dealmaker -dealt -dean -debatable -debate -debating -debit -debrief -debtless -debtor -debug -debunk -decade -decaf -decal -decathlon -decay -deceased -deceit -deceiver -deceiving -december -decency -decent -deception -deceptive -decibel -decidable -decimal -decimeter -decipher -deck -declared -decline -decode -decompose -decorated -decorator -decoy -decrease -decree -dedicate -dedicator -deduce -deduct -deed -deem -deepen -deeply -deepness -deface -defacing -defame -default -defeat -defection -defective -defendant -defender -defense -defensive -deferral -deferred -defiance -defiant -defile -defiling -define -definite -deflate -deflation -deflator -deflected -deflector -defog -deforest -defraud -defrost -deftly -defuse -defy -degraded -degrading -degrease -degree -dehydrate -deity -dejected -delay -delegate -delegator -delete -deletion -delicacy -delicate -delicious -delighted -delirious -delirium -deliverer -delivery -delouse -delta -deluge -delusion -deluxe -demanding -demeaning -demeanor -demise -democracy -democrat -demote -demotion -demystify -denatured -deniable -denial -denim -denote -dense -density -dental -dentist -denture -deny -deodorant -deodorize -departed -departure -depict -deplete -depletion -deplored -deploy -deport -depose -depraved -depravity -deprecate -depress -deprive -depth -deputize -deputy -derail -deranged -derby -derived -desecrate -deserve -deserving -designate -designed -designer -designing -deskbound -desktop -deskwork -desolate -despair -despise -despite -destiny -destitute -destruct -detached -detail -detection -detective -detector -detention -detergent -detest -detonate -detonator -detoxify -detract -deuce -devalue -deviancy -deviant -deviate -deviation -deviator -device -devious -devotedly -devotee -devotion -devourer -devouring -devoutly -dexterity -dexterous -diabetes -diabetic -diabolic -diagnoses -diagnosis -diagram -dial -diameter -diaper -diaphragm -diary -dice -dicing -dictate -dictation -dictator -difficult -diffused -diffuser -diffusion -diffusive -dig -dilation -diligence -diligent -dill -dilute -dime -diminish -dimly -dimmed -dimmer -dimness -dimple -diner -dingbat -dinghy -dinginess -dingo -dingy -dining -dinner -diocese -dioxide -diploma -dipped -dipper -dipping -directed -direction -directive -directly -directory -direness -dirtiness -disabled -disagree -disallow -disarm -disarray -disaster -disband -disbelief -disburse -discard -discern -discharge -disclose -discolor -discount -discourse -discover -discuss -disdain -disengage -disfigure -disgrace -dish -disinfect -disjoin -disk -dislike -disliking -dislocate -dislodge -disloyal -dismantle -dismay -dismiss -dismount -disobey -disorder -disown -disparate -disparity -dispatch -dispense -dispersal -dispersed -disperser -displace -display -displease -disposal -dispose -disprove -dispute -disregard -disrupt -dissuade -distance -distant -distaste -distill -distinct -distort -distract -distress -district -distrust -ditch -ditto -ditzy -dividable -divided -dividend -dividers -dividing -divinely -diving -divinity -divisible -divisibly -division -divisive -divorcee -dizziness -dizzy -doable -docile -dock -doctrine -document -dodge -dodgy -doily -doing -dole -dollar -dollhouse -dollop -dolly -dolphin -domain -domelike -domestic -dominion -dominoes -donated -donation -donator -donor -donut -doodle -doorbell -doorframe -doorknob -doorman -doormat -doornail -doorpost -doorstep -doorstop -doorway -doozy -dork -dormitory -dorsal -dosage -dose -dotted -doubling -douche -dove -down -dowry -doze -drab -dragging -dragonfly -dragonish -dragster -drainable -drainage -drained -drainer -drainpipe -dramatic -dramatize -drank -drapery -drastic -draw -dreaded -dreadful -dreadlock -dreamboat -dreamily -dreamland -dreamless -dreamlike -dreamt -dreamy -drearily -dreary -drench -dress -drew -dribble -dried -drier -drift -driller -drilling -drinkable -drinking -dripping -drippy -drivable -driven -driver -driveway -driving -drizzle -drizzly -drone -drool -droop -drop-down -dropbox -dropkick -droplet -dropout -dropper -drove -drown -drowsily -drudge -drum -dry -dubbed -dubiously -duchess -duckbill -ducking -duckling -ducktail -ducky -duct -dude -duffel -dugout -duh -duke -duller -dullness -duly -dumping -dumpling -dumpster -duo -dupe -duplex -duplicate -duplicity -durable -durably -duration -duress -during -dusk -dust -dutiful -duty -duvet -dwarf -dweeb -dwelled -dweller -dwelling -dwindle -dwindling -dynamic -dynamite -dynasty -dyslexia -dyslexic -each -eagle -earache -eardrum -earflap -earful -earlobe -early -earmark -earmuff -earphone -earpiece -earplugs -earring -earshot -earthen -earthlike -earthling -earthly -earthworm -earthy -earwig -easeful -easel -easiest -easily -easiness -easing -eastbound -eastcoast -easter -eastward -eatable -eaten -eatery -eating -eats -ebay -ebony -ebook -ecard -eccentric -echo -eclair -eclipse -ecologist -ecology -economic -economist -economy -ecosphere -ecosystem -edge -edginess -edging -edgy -edition -editor -educated -education -educator -eel -effective -effects -efficient -effort -eggbeater -egging -eggnog -eggplant -eggshell -egomaniac -egotism -egotistic -either -eject -elaborate -elastic -elated -elbow -eldercare -elderly -eldest -electable -election -elective -elephant -elevate -elevating -elevation -elevator -eleven -elf -eligible -eligibly -eliminate -elite -elitism -elixir -elk -ellipse -elliptic -elm -elongated -elope -eloquence -eloquent -elsewhere -elude -elusive -elves -email -embargo -embark -embassy -embattled -embellish -ember -embezzle -emblaze -emblem -embody -embolism -emboss -embroider -emcee -emerald -emergency -emission -emit -emote -emoticon -emotion -empathic -empathy -emperor -emphases -emphasis -emphasize -emphatic -empirical -employed -employee -employer -emporium -empower -emptier -emptiness -empty -emu -enable -enactment -enamel -enchanted -enchilada -encircle -enclose -enclosure -encode -encore -encounter -encourage -encroach -encrust -encrypt -endanger -endeared -endearing -ended -ending -endless -endnote -endocrine -endorphin -endorse -endowment -endpoint -endurable -endurance -enduring -energetic -energize -energy -enforced -enforcer -engaged -engaging -engine -engorge -engraved -engraver -engraving -engross -engulf -enhance -enigmatic -enjoyable -enjoyably -enjoyer -enjoying -enjoyment -enlarged -enlarging -enlighten -enlisted -enquirer -enrage -enrich -enroll -enslave -ensnare -ensure -entail -entangled -entering -entertain -enticing -entire -entitle -entity -entomb -entourage -entrap -entree -entrench -entrust -entryway -entwine -enunciate -envelope -enviable -enviably -envious -envision -envoy -envy -enzyme -epic -epidemic -epidermal -epidermis -epidural -epilepsy -epileptic -epilogue -epiphany -episode -equal -equate -equation -equator -equinox -equipment -equity -equivocal -eradicate -erasable -erased -eraser -erasure -ergonomic -errand -errant -erratic -error -erupt -escalate -escalator -escapable -escapade -escapist -escargot -eskimo -esophagus -espionage -espresso -esquire -essay -essence -essential -establish -estate -esteemed -estimate -estimator -estranged -estrogen -etching -eternal -eternity -ethanol -ether -ethically -ethics -euphemism -evacuate -evacuee -evade -evaluate -evaluator -evaporate -evasion -evasive -even -everglade -evergreen -everybody -everyday -everyone -evict -evidence -evident -evil -evoke -evolution -evolve -exact -exalted -example -excavate -excavator -exceeding -exception -excess -exchange -excitable -exciting -exclaim -exclude -excluding -exclusion -exclusive -excretion -excretory -excursion -excusable -excusably -excuse -exemplary -exemplify -exemption -exerciser -exert -exes -exfoliate -exhale -exhaust -exhume -exile -existing -exit -exodus -exonerate -exorcism -exorcist -expand -expanse -expansion -expansive -expectant -expedited -expediter -expel -expend -expenses -expensive -expert -expire -expiring -explain -expletive -explicit -explode -exploit -explore -exploring -exponent -exporter -exposable -expose -exposure -express -expulsion -exquisite -extended -extending -extent -extenuate -exterior -external -extinct -extortion -extradite -extras -extrovert -extrude -extruding -exuberant -fable -fabric -fabulous -facebook -facecloth -facedown -faceless -facelift -faceplate -faceted -facial -facility -facing -facsimile -faction -factoid -factor -factsheet -factual -faculty -fade -fading -failing -falcon -fall -false -falsify -fame -familiar -family -famine -famished -fanatic -fancied -fanciness -fancy -fanfare -fang -fanning -fantasize -fantastic -fantasy -fascism -fastball -faster -fasting -fastness -faucet -favorable -favorably -favored -favoring -favorite -fax -feast -federal -fedora -feeble -feed -feel -feisty -feline -felt-tip -feminine -feminism -feminist -feminize -femur -fence -fencing -fender -ferment -fernlike -ferocious -ferocity -ferret -ferris -ferry -fervor -fester -festival -festive -festivity -fetal -fetch -fever -fiber -fiction -fiddle -fiddling -fidelity -fidgeting -fidgety -fifteen -fifth -fiftieth -fifty -figment -figure -figurine -filing -filled -filler -filling -film -filter -filth -filtrate -finale -finalist -finalize -finally -finance -financial -finch -fineness -finer -finicky -finished -finisher -finishing -finite -finless -finlike -fiscally -fit -five -flaccid -flagman -flagpole -flagship -flagstick -flagstone -flail -flakily -flaky -flame -flammable -flanked -flanking -flannels -flap -flaring -flashback -flashbulb -flashcard -flashily -flashing -flashy -flask -flatbed -flatfoot -flatly -flatness -flatten -flattered -flatterer -flattery -flattop -flatware -flatworm -flavored -flavorful -flavoring -flaxseed -fled -fleshed -fleshy -flick -flier -flight -flinch -fling -flint -flip -flirt -float -flock -flogging -flop -floral -florist -floss -flounder -flyable -flyaway -flyer -flying -flyover -flypaper -foam -foe -fog -foil -folic -folk -follicle -follow -fondling -fondly -fondness -fondue -font -food -fool -footage -football -footbath -footboard -footer -footgear -foothill -foothold -footing -footless -footman -footnote -footpad -footpath -footprint -footrest -footsie -footsore -footwear -footwork -fossil -foster -founder -founding -fountain -fox -foyer -fraction -fracture -fragile -fragility -fragment -fragrance -fragrant -frail -frame -framing -frantic -fraternal -frayed -fraying -frays -freckled -freckles -freebase -freebee -freebie -freedom -freefall -freehand -freeing -freeload -freely -freemason -freeness -freestyle -freeware -freeway -freewill -freezable -freezing -freight -french -frenzied -frenzy -frequency -frequent -fresh -fretful -fretted -friction -friday -fridge -fried -friend -frighten -frightful -frigidity -frigidly -frill -fringe -frisbee -frisk -fritter -frivolous -frolic -from -front -frostbite -frosted -frostily -frosting -frostlike -frosty -froth -frown -frozen -fructose -frugality -frugally -fruit -frustrate -frying -gab -gaffe -gag -gainfully -gaining -gains -gala -gallantly -galleria -gallery -galley -gallon -gallows -gallstone -galore -galvanize -gambling -game -gaming -gamma -gander -gangly -gangrene -gangway -gap -garage -garbage -garden -gargle -garland -garlic -garment -garnet -garnish -garter -gas -gatherer -gathering -gating -gauging -gauntlet -gauze -gave -gawk -gazing -gear -gecko -geek -geiger -gem -gender -generic -generous -genetics -genre -gentile -gentleman -gently -gents -geography -geologic -geologist -geology -geometric -geometry -geranium -gerbil -geriatric -germicide -germinate -germless -germproof -gestate -gestation -gesture -getaway -getting -getup -giant -gibberish -giblet -giddily -giddiness -giddy -gift -gigabyte -gigahertz -gigantic -giggle -giggling -giggly -gigolo -gilled -gills -gimmick -girdle -giveaway -given -giver -giving -gizmo -gizzard -glacial -glacier -glade -gladiator -gladly -glamorous -glamour -glance -glancing -glandular -glare -glaring -glass -glaucoma -glazing -gleaming -gleeful -glider -gliding -glimmer -glimpse -glisten -glitch -glitter -glitzy -gloater -gloating -gloomily -gloomy -glorified -glorifier -glorify -glorious -glory -gloss -glove -glowing -glowworm -glucose -glue -gluten -glutinous -glutton -gnarly -gnat -goal -goatskin -goes -goggles -going -goldfish -goldmine -goldsmith -golf -goliath -gonad -gondola -gone -gong -good -gooey -goofball -goofiness -goofy -google -goon -gopher -gore -gorged -gorgeous -gory -gosling -gossip -gothic -gotten -gout -gown -grab -graceful -graceless -gracious -gradation -graded -grader -gradient -grading -gradually -graduate -graffiti -grafted -grafting -grain -granddad -grandkid -grandly -grandma -grandpa -grandson -granite -granny -granola -grant -granular -grape -graph -grapple -grappling -grasp -grass -gratified -gratify -grating -gratitude -gratuity -gravel -graveness -graves -graveyard -gravitate -gravity -gravy -gray -grazing -greasily -greedily -greedless -greedy -green -greeter -greeting -grew -greyhound -grid -grief -grievance -grieving -grievous -grill -grimace -grimacing -grime -griminess -grimy -grinch -grinning -grip -gristle -grit -groggily -groggy -groin -groom -groove -grooving -groovy -grope -ground -grouped -grout -grove -grower -growing -growl -grub -grudge -grudging -grueling -gruffly -grumble -grumbling -grumbly -grumpily -grunge -grunt -guacamole -guidable -guidance -guide -guiding -guileless -guise -gulf -gullible -gully -gulp -gumball -gumdrop -gumminess -gumming -gummy -gurgle -gurgling -guru -gush -gusto -gusty -gutless -guts -gutter -guy -guzzler -gyration -habitable -habitant -habitat -habitual -hacked -hacker -hacking -hacksaw -had -haggler -haiku -half -halogen -halt -halved -halves -hamburger -hamlet -hammock -hamper -hamster -hamstring -handbag -handball -handbook -handbrake -handcart -handclap -handclasp -handcraft -handcuff -handed -handful -handgrip -handgun -handheld -handiness -handiwork -handlebar -handled -handler -handling -handmade -handoff -handpick -handprint -handrail -handsaw -handset -handsfree -handshake -handstand -handwash -handwork -handwoven -handwrite -handyman -hangnail -hangout -hangover -hangup -hankering -hankie -hanky -haphazard -happening -happier -happiest -happily -happiness -happy -harbor -hardcopy -hardcore -hardcover -harddisk -hardened -hardener -hardening -hardhat -hardhead -hardiness -hardly -hardness -hardship -hardware -hardwired -hardwood -hardy -harmful -harmless -harmonica -harmonics -harmonize -harmony -harness -harpist -harsh -harvest -hash -hassle -haste -hastily -hastiness -hasty -hatbox -hatchback -hatchery -hatchet -hatching -hatchling -hate -hatless -hatred -haunt -haven -hazard -hazelnut -hazily -haziness -hazing -hazy -headache -headband -headboard -headcount -headdress -headed -header -headfirst -headgear -heading -headlamp -headless -headlock -headphone -headpiece -headrest -headroom -headscarf -headset -headsman -headstand -headstone -headway -headwear -heap -heat -heave -heavily -heaviness -heaving -hedge -hedging -heftiness -hefty -helium -helmet -helper -helpful -helping -helpless -helpline -hemlock -hemstitch -hence -henchman -henna -herald -herbal -herbicide -herbs -heritage -hermit -heroics -heroism -herring -herself -hertz -hesitancy -hesitant -hesitate -hexagon -hexagram -hubcap -huddle -huddling -huff -hug -hula -hulk -hull -human -humble -humbling -humbly -humid -humiliate -humility -humming -hummus -humongous -humorist -humorless -humorous -humpback -humped -humvee -hunchback -hundredth -hunger -hungrily -hungry -hunk -hunter -hunting -huntress -huntsman -hurdle -hurled -hurler -hurling -hurray -hurricane -hurried -hurry -hurt -husband -hush -husked -huskiness -hut -hybrid -hydrant -hydrated -hydration -hydrogen -hydroxide -hyperlink -hypertext -hyphen -hypnoses -hypnosis -hypnotic -hypnotism -hypnotist -hypnotize -hypocrisy -hypocrite -ibuprofen -ice -iciness -icing -icky -icon -icy -idealism -idealist -idealize -ideally -idealness -identical -identify -identity -ideology -idiocy -idiom -idly -igloo -ignition -ignore -iguana -illicitly -illusion -illusive -image -imaginary -imagines -imaging -imbecile -imitate -imitation -immature -immerse -immersion -imminent -immobile -immodest -immorally -immortal -immovable -immovably -immunity -immunize -impaired -impale -impart -impatient -impeach -impeding -impending -imperfect -imperial -impish -implant -implement -implicate -implicit -implode -implosion -implosive -imply -impolite -important -importer -impose -imposing -impotence -impotency -impotent -impound -imprecise -imprint -imprison -impromptu -improper -improve -improving -improvise -imprudent -impulse -impulsive -impure -impurity -iodine -iodize -ion -ipad -iphone -ipod -irate -irk -iron -irregular -irrigate -irritable -irritably -irritant -irritate -islamic -islamist -isolated -isolating -isolation -isotope -issue -issuing -italicize -italics -item -itinerary -itunes -ivory -ivy -jab -jackal -jacket -jackknife -jackpot -jailbird -jailbreak -jailer -jailhouse -jalapeno -jam -janitor -january -jargon -jarring -jasmine -jaundice -jaunt -java -jawed -jawless -jawline -jaws -jaybird -jaywalker -jazz -jeep -jeeringly -jellied -jelly -jersey -jester -jet -jiffy -jigsaw -jimmy -jingle -jingling -jinx -jitters -jittery -job -jockey -jockstrap -jogger -jogging -john -joining -jokester -jokingly -jolliness -jolly -jolt -jot -jovial -joyfully -joylessly -joyous -joyride -joystick -jubilance -jubilant -judge -judgingly -judicial -judiciary -judo -juggle -juggling -jugular -juice -juiciness -juicy -jujitsu -jukebox -july -jumble -jumbo -jump -junction -juncture -june -junior -juniper -junkie -junkman -junkyard -jurist -juror -jury -justice -justifier -justify -justly -justness -juvenile -kabob -kangaroo -karaoke -karate -karma -kebab -keenly -keenness -keep -keg -kelp -kennel -kept -kerchief -kerosene -kettle -kick -kiln -kilobyte -kilogram -kilometer -kilowatt -kilt -kimono -kindle -kindling -kindly -kindness -kindred -kinetic -kinfolk -king -kinship -kinsman -kinswoman -kissable -kisser -kissing -kitchen -kite -kitten -kitty -kiwi -kleenex -knapsack -knee -knelt -knickers -knoll -koala -kooky -kosher -krypton -kudos -kung -labored -laborer -laboring -laborious -labrador -ladder -ladies -ladle -ladybug -ladylike -lagged -lagging -lagoon -lair -lake -lance -landed -landfall -landfill -landing -landlady -landless -landline -landlord -landmark -landmass -landmine -landowner -landscape -landside -landslide -language -lankiness -lanky -lantern -lapdog -lapel -lapped -lapping -laptop -lard -large -lark -lash -lasso -last -latch -late -lather -latitude -latrine -latter -latticed -launch -launder -laundry -laurel -lavender -lavish -laxative -lazily -laziness -lazy -lecturer -left -legacy -legal -legend -legged -leggings -legible -legibly -legislate -lego -legroom -legume -legwarmer -legwork -lemon -lend -length -lens -lent -leotard -lesser -letdown -lethargic -lethargy -letter -lettuce -level -leverage -levers -levitate -levitator -liability -liable -liberty -librarian -library -licking -licorice -lid -life -lifter -lifting -liftoff -ligament -likely -likeness -likewise -liking -lilac -lilly -lily -limb -limeade -limelight -limes -limit -limping -limpness -line -lingo -linguini -linguist -lining -linked -linoleum -linseed -lint -lion -lip -liquefy -liqueur -liquid -lisp -list -litigate -litigator -litmus -litter -little -livable -lived -lively -liver -livestock -lividly -living -lizard -lubricant -lubricate -lucid -luckily -luckiness -luckless -lucrative -ludicrous -lugged -lukewarm -lullaby -lumber -luminance -luminous -lumpiness -lumping -lumpish -lunacy -lunar -lunchbox -luncheon -lunchroom -lunchtime -lung -lurch -lure -luridness -lurk -lushly -lushness -luster -lustfully -lustily -lustiness -lustrous -lusty -luxurious -luxury -lying -lyrically -lyricism -lyricist -lyrics -macarena -macaroni -macaw -mace -machine -machinist -magazine -magenta -maggot -magical -magician -magma -magnesium -magnetic -magnetism -magnetize -magnifier -magnify -magnitude -magnolia -mahogany -maimed -majestic -majesty -majorette -majority -makeover -maker -makeshift -making -malformed -malt -mama -mammal -mammary -mammogram -manager -managing -manatee -mandarin -mandate -mandatory -mandolin -manger -mangle -mango -mangy -manhandle -manhole -manhood -manhunt -manicotti -manicure -manifesto -manila -mankind -manlike -manliness -manly -manmade -manned -mannish -manor -manpower -mantis -mantra -manual -many -map -marathon -marauding -marbled -marbles -marbling -march -mardi -margarine -margarita -margin -marigold -marina -marine -marital -maritime -marlin -marmalade -maroon -married -marrow -marry -marshland -marshy -marsupial -marvelous -marxism -mascot -masculine -mashed -mashing -massager -masses -massive -mastiff -matador -matchbook -matchbox -matcher -matching -matchless -material -maternal -maternity -math -mating -matriarch -matrimony -matrix -matron -matted -matter -maturely -maturing -maturity -mauve -maverick -maximize -maximum -maybe -mayday -mayflower -moaner -moaning -mobile -mobility -mobilize -mobster -mocha -mocker -mockup -modified -modify -modular -modulator -module -moisten -moistness -moisture -molar -molasses -mold -molecular -molecule -molehill -mollusk -mom -monastery -monday -monetary -monetize -moneybags -moneyless -moneywise -mongoose -mongrel -monitor -monkhood -monogamy -monogram -monologue -monopoly -monorail -monotone -monotype -monoxide -monsieur -monsoon -monstrous -monthly -monument -moocher -moodiness -moody -mooing -moonbeam -mooned -moonlight -moonlike -moonlit -moonrise -moonscape -moonshine -moonstone -moonwalk -mop -morale -morality -morally -morbidity -morbidly -morphine -morphing -morse -mortality -mortally -mortician -mortified -mortify -mortuary -mosaic -mossy -most -mothball -mothproof -motion -motivate -motivator -motive -motocross -motor -motto -mountable -mountain -mounted -mounting -mourner -mournful -mouse -mousiness -moustache -mousy -mouth -movable -move -movie -moving -mower -mowing -much -muck -mud -mug -mulberry -mulch -mule -mulled -mullets -multiple -multiply -multitask -multitude -mumble -mumbling -mumbo -mummified -mummify -mummy -mumps -munchkin -mundane -municipal -muppet -mural -murkiness -murky -murmuring -muscular -museum -mushily -mushiness -mushroom -mushy -music -musket -muskiness -musky -mustang -mustard -muster -mustiness -musty -mutable -mutate -mutation -mute -mutilated -mutilator -mutiny -mutt -mutual -muzzle -myself -myspace -mystified -mystify -myth -nacho -nag -nail -name -naming -nanny -nanometer -nape -napkin -napped -napping -nappy -narrow -nastily -nastiness -national -native -nativity -natural -nature -naturist -nautical -navigate -navigator -navy -nearby -nearest -nearly -nearness -neatly -neatness -nebula -nebulizer -nectar -negate -negation -negative -neglector -negligee -negligent -negotiate -nemeses -nemesis -neon -nephew -nerd -nervous -nervy -nest -net -neurology -neuron -neurosis -neurotic -neuter -neutron -never -next -nibble -nickname -nicotine -niece -nifty -nimble -nimbly -nineteen -ninetieth -ninja -nintendo -ninth -nuclear -nuclei -nucleus -nugget -nullify -number -numbing -numbly -numbness -numeral -numerate -numerator -numeric -numerous -nuptials -nursery -nursing -nurture -nutcase -nutlike -nutmeg -nutrient -nutshell -nuttiness -nutty -nuzzle -nylon -oaf -oak -oasis -oat -obedience -obedient -obituary -object -obligate -obliged -oblivion -oblivious -oblong -obnoxious -oboe -obscure -obscurity -observant -observer -observing -obsessed -obsession -obsessive -obsolete -obstacle -obstinate -obstruct -obtain -obtrusive -obtuse -obvious -occultist -occupancy -occupant -occupier -occupy -ocean -ocelot -octagon -octane -october -octopus -ogle -oil -oink -ointment -okay -old -olive -olympics -omega -omen -ominous -omission -omit -omnivore -onboard -oncoming -ongoing -onion -online -onlooker -only -onscreen -onset -onshore -onslaught -onstage -onto -onward -onyx -oops -ooze -oozy -opacity -opal -open -operable -operate -operating -operation -operative -operator -opium -opossum -opponent -oppose -opposing -opposite -oppressed -oppressor -opt -opulently -osmosis -other -otter -ouch -ought -ounce -outage -outback -outbid -outboard -outbound -outbreak -outburst -outcast -outclass -outcome -outdated -outdoors -outer -outfield -outfit -outflank -outgoing -outgrow -outhouse -outing -outlast -outlet -outline -outlook -outlying -outmatch -outmost -outnumber -outplayed -outpost -outpour -output -outrage -outrank -outreach -outright -outscore -outsell -outshine -outshoot -outsider -outskirts -outsmart -outsource -outspoken -outtakes -outthink -outward -outweigh -outwit -oval -ovary -oven -overact -overall -overarch -overbid -overbill -overbite -overblown -overboard -overbook -overbuilt -overcast -overcoat -overcome -overcook -overcrowd -overdraft -overdrawn -overdress -overdrive -overdue -overeager -overeater -overexert -overfed -overfeed -overfill -overflow -overfull -overgrown -overhand -overhang -overhaul -overhead -overhear -overheat -overhung -overjoyed -overkill -overlabor -overlaid -overlap -overlay -overload -overlook -overlord -overlying -overnight -overpass -overpay -overplant -overplay -overpower -overprice -overrate -overreach -overreact -override -overripe -overrule -overrun -overshoot -overshot -oversight -oversized -oversleep -oversold -overspend -overstate -overstay -overstep -overstock -overstuff -oversweet -overtake -overthrow -overtime -overtly -overtone -overture -overturn -overuse -overvalue -overview -overwrite -owl -oxford -oxidant -oxidation -oxidize -oxidizing -oxygen -oxymoron -oyster -ozone -paced -pacemaker -pacific -pacifier -pacifism -pacifist -pacify -padded -padding -paddle -paddling -padlock -pagan -pager -paging -pajamas -palace -palatable -palm -palpable -palpitate -paltry -pampered -pamperer -pampers -pamphlet -panama -pancake -pancreas -panda -pandemic -pang -panhandle -panic -panning -panorama -panoramic -panther -pantomime -pantry -pants -pantyhose -paparazzi -papaya -paper -paprika -papyrus -parabola -parachute -parade -paradox -paragraph -parakeet -paralegal -paralyses -paralysis -paralyze -paramedic -parameter -paramount -parasail -parasite -parasitic -parcel -parched -parchment -pardon -parish -parka -parking -parkway -parlor -parmesan -parole -parrot -parsley -parsnip -partake -parted -parting -partition -partly -partner -partridge -party -passable -passably -passage -passcode -passenger -passerby -passing -passion -passive -passivism -passover -passport -password -pasta -pasted -pastel -pastime -pastor -pastrami -pasture -pasty -patchwork -patchy -paternal -paternity -path -patience -patient -patio -patriarch -patriot -patrol -patronage -patronize -pauper -pavement -paver -pavestone -pavilion -paving -pawing -payable -payback -paycheck -payday -payee -payer -paying -payment -payphone -payroll -pebble -pebbly -pecan -pectin -peculiar -peddling -pediatric -pedicure -pedigree -pedometer -pegboard -pelican -pellet -pelt -pelvis -penalize -penalty -pencil -pendant -pending -penholder -penknife -pennant -penniless -penny -penpal -pension -pentagon -pentagram -pep -perceive -percent -perch -percolate -perennial -perfected -perfectly -perfume -periscope -perish -perjurer -perjury -perkiness -perky -perm -peroxide -perpetual -perplexed -persecute -persevere -persuaded -persuader -pesky -peso -pessimism -pessimist -pester -pesticide -petal -petite -petition -petri -petroleum -petted -petticoat -pettiness -petty -petunia -phantom -phobia -phoenix -phonebook -phoney -phonics -phoniness -phony -phosphate -photo -phrase -phrasing -placard -placate -placidly -plank -planner -plant -plasma -plaster -plastic -plated -platform -plating -platinum -platonic -platter -platypus -plausible -plausibly -playable -playback -player -playful -playgroup -playhouse -playing -playlist -playmaker -playmate -playoff -playpen -playroom -playset -plaything -playtime -plaza -pleading -pleat -pledge -plentiful -plenty -plethora -plexiglas -pliable -plod -plop -plot -plow -ploy -pluck -plug -plunder -plunging -plural -plus -plutonium -plywood -poach -pod -poem -poet -pogo -pointed -pointer -pointing -pointless -pointy -poise -poison -poker -poking -polar -police -policy -polio -polish -politely -polka -polo -polyester -polygon -polygraph -polymer -poncho -pond -pony -popcorn -pope -poplar -popper -poppy -popsicle -populace -popular -populate -porcupine -pork -porous -porridge -portable -portal -portfolio -porthole -portion -portly -portside -poser -posh -posing -possible -possibly -possum -postage -postal -postbox -postcard -posted -poster -posting -postnasal -posture -postwar -pouch -pounce -pouncing -pound -pouring -pout -powdered -powdering -powdery -power -powwow -pox -praising -prance -prancing -pranker -prankish -prankster -prayer -praying -preacher -preaching -preachy -preamble -precinct -precise -precision -precook -precut -predator -predefine -predict -preface -prefix -preflight -preformed -pregame -pregnancy -pregnant -preheated -prelaunch -prelaw -prelude -premiere -premises -premium -prenatal -preoccupy -preorder -prepaid -prepay -preplan -preppy -preschool -prescribe -preseason -preset -preshow -president -presoak -press -presume -presuming -preteen -pretended -pretender -pretense -pretext -pretty -pretzel -prevail -prevalent -prevent -preview -previous -prewar -prewashed -prideful -pried -primal -primarily -primary -primate -primer -primp -princess -print -prior -prism -prison -prissy -pristine -privacy -private -privatize -prize -proactive -probable -probably -probation -probe -probing -probiotic -problem -procedure -process -proclaim -procreate -procurer -prodigal -prodigy -produce -product -profane -profanity -professed -professor -profile -profound -profusely -progeny -prognosis -program -progress -projector -prologue -prolonged -promenade -prominent -promoter -promotion -prompter -promptly -prone -prong -pronounce -pronto -proofing -proofread -proofs -propeller -properly -property -proponent -proposal -propose -props -prorate -protector -protegee -proton -prototype -protozoan -protract -protrude -proud -provable -proved -proven -provided -provider -providing -province -proving -provoke -provoking -provolone -prowess -prowler -prowling -proximity -proxy -prozac -prude -prudishly -prune -pruning -pry -psychic -public -publisher -pucker -pueblo -pug -pull -pulmonary -pulp -pulsate -pulse -pulverize -puma -pumice -pummel -punch -punctual -punctuate -punctured -pungent -punisher -punk -pupil -puppet -puppy -purchase -pureblood -purebred -purely -pureness -purgatory -purge -purging -purifier -purify -purist -puritan -purity -purple -purplish -purposely -purr -purse -pursuable -pursuant -pursuit -purveyor -pushcart -pushchair -pusher -pushiness -pushing -pushover -pushpin -pushup -pushy -putdown -putt -puzzle -puzzling -pyramid -pyromania -python -quack -quadrant -quail -quaintly -quake -quaking -qualified -qualifier -qualify -quality -qualm -quantum -quarrel -quarry -quartered -quarterly -quarters -quartet -quench -query -quicken -quickly -quickness -quicksand -quickstep -quiet -quill -quilt -quintet -quintuple -quirk -quit -quiver -quizzical -quotable -quotation -quote -rabid -race -racing -racism -rack -racoon -radar -radial -radiance -radiantly -radiated -radiation -radiator -radio -radish -raffle -raft -rage -ragged -raging -ragweed -raider -railcar -railing -railroad -railway -raisin -rake -raking -rally -ramble -rambling -ramp -ramrod -ranch -rancidity -random -ranged -ranger -ranging -ranked -ranking -ransack -ranting -rants -rare -rarity -rascal -rash -rasping -ravage -raven -ravine -raving -ravioli -ravishing -reabsorb -reach -reacquire -reaction -reactive -reactor -reaffirm -ream -reanalyze -reappear -reapply -reappoint -reapprove -rearrange -rearview -reason -reassign -reassure -reattach -reawake -rebalance -rebate -rebel -rebirth -reboot -reborn -rebound -rebuff -rebuild -rebuilt -reburial -rebuttal -recall -recant -recapture -recast -recede -recent -recess -recharger -recipient -recital -recite -reckless -reclaim -recliner -reclining -recluse -reclusive -recognize -recoil -recollect -recolor -reconcile -reconfirm -reconvene -recopy -record -recount -recoup -recovery -recreate -rectal -rectangle -rectified -rectify -recycled -recycler -recycling -reemerge -reenact -reenter -reentry -reexamine -referable -referee -reference -refill -refinance -refined -refinery -refining -refinish -reflected -reflector -reflex -reflux -refocus -refold -reforest -reformat -reformed -reformer -reformist -refract -refrain -refreeze -refresh -refried -refueling -refund -refurbish -refurnish -refusal -refuse -refusing -refutable -refute -regain -regalia -regally -reggae -regime -region -register -registrar -registry -regress -regretful -regroup -regular -regulate -regulator -rehab -reheat -rehire -rehydrate -reimburse -reissue -reiterate -rejoice -rejoicing -rejoin -rekindle -relapse -relapsing -relatable -related -relation -relative -relax -relay -relearn -release -relenting -reliable -reliably -reliance -reliant -relic -relieve -relieving -relight -relish -relive -reload -relocate -relock -reluctant -rely -remake -remark -remarry -rematch -remedial -remedy -remember -reminder -remindful -remission -remix -remnant -remodeler -remold -remorse -remote -removable -removal -removed -remover -removing -rename -renderer -rendering -rendition -renegade -renewable -renewably -renewal -renewed -renounce -renovate -renovator -rentable -rental -rented -renter -reoccupy -reoccur -reopen -reorder -repackage -repacking -repaint -repair -repave -repaying -repayment -repeal -repeated -repeater -repent -rephrase -replace -replay -replica -reply -reporter -repose -repossess -repost -repressed -reprimand -reprint -reprise -reproach -reprocess -reproduce -reprogram -reps -reptile -reptilian -repugnant -repulsion -repulsive -repurpose -reputable -reputably -request -require -requisite -reroute -rerun -resale -resample -rescuer -reseal -research -reselect -reseller -resemble -resend -resent -reset -reshape -reshoot -reshuffle -residence -residency -resident -residual -residue -resigned -resilient -resistant -resisting -resize -resolute -resolved -resonant -resonate -resort -resource -respect -resubmit -result -resume -resupply -resurface -resurrect -retail -retainer -retaining -retake -retaliate -retention -rethink -retinal -retired -retiree -retiring -retold -retool -retorted -retouch -retrace -retract -retrain -retread -retreat -retrial -retrieval -retriever -retry -return -retying -retype -reunion -reunite -reusable -reuse -reveal -reveler -revenge -revenue -reverb -revered -reverence -reverend -reversal -reverse -reversing -reversion -revert -revisable -revise -revision -revisit -revivable -revival -reviver -reviving -revocable -revoke -revolt -revolver -revolving -reward -rewash -rewind -rewire -reword -rework -rewrap -rewrite -rhyme -ribbon -ribcage -rice -riches -richly -richness -rickety -ricotta -riddance -ridden -ride -riding -rifling -rift -rigging -rigid -rigor -rimless -rimmed -rind -rink -rinse -rinsing -riot -ripcord -ripeness -ripening -ripping -ripple -rippling -riptide -rise -rising -risk -risotto -ritalin -ritzy -rival -riverbank -riverbed -riverboat -riverside -riveter -riveting -roamer -roaming -roast -robbing -robe -robin -robotics -robust -rockband -rocker -rocket -rockfish -rockiness -rocking -rocklike -rockslide -rockstar -rocky -rogue -roman -romp -rope -roping -roster -rosy -rotten -rotting -rotunda -roulette -rounding -roundish -roundness -roundup -roundworm -routine -routing -rover -roving -royal -rubbed -rubber -rubbing -rubble -rubdown -ruby -ruckus -rudder -rug -ruined -rule -rumble -rumbling -rummage -rumor -runaround -rundown -runner -running -runny -runt -runway -rupture -rural -ruse -rush -rust -rut -sabbath -sabotage -sacrament -sacred -sacrifice -sadden -saddlebag -saddled -saddling -sadly -sadness -safari -safeguard -safehouse -safely -safeness -saffron -saga -sage -sagging -saggy -said -saint -sake -salad -salami -salaried -salary -saline -salon -saloon -salsa -salt -salutary -salute -salvage -salvaging -salvation -same -sample -sampling -sanction -sanctity -sanctuary -sandal -sandbag -sandbank -sandbar -sandblast -sandbox -sanded -sandfish -sanding -sandlot -sandpaper -sandpit -sandstone -sandstorm -sandworm -sandy -sanitary -sanitizer -sank -santa -sapling -sappiness -sappy -sarcasm -sarcastic -sardine -sash -sasquatch -sassy -satchel -satiable -satin -satirical -satisfied -satisfy -saturate -saturday -sauciness -saucy -sauna -savage -savanna -saved -savings -savior -savor -saxophone -say -scabbed -scabby -scalded -scalding -scale -scaling -scallion -scallop -scalping -scam -scandal -scanner -scanning -scant -scapegoat -scarce -scarcity -scarecrow -scared -scarf -scarily -scariness -scarring -scary -scavenger -scenic -schedule -schematic -scheme -scheming -schilling -schnapps -scholar -science -scientist -scion -scoff -scolding -scone -scoop -scooter -scope -scorch -scorebook -scorecard -scored -scoreless -scorer -scoring -scorn -scorpion -scotch -scoundrel -scoured -scouring -scouting -scouts -scowling -scrabble -scraggly -scrambled -scrambler -scrap -scratch -scrawny -screen -scribble -scribe -scribing -scrimmage -script -scroll -scrooge -scrounger -scrubbed -scrubber -scruffy -scrunch -scrutiny -scuba -scuff -sculptor -sculpture -scurvy -scuttle -secluded -secluding -seclusion -second -secrecy -secret -sectional -sector -secular -securely -security -sedan -sedate -sedation -sedative -sediment -seduce -seducing -segment -seismic -seizing -seldom -selected -selection -selective -selector -self -seltzer -semantic -semester -semicolon -semifinal -seminar -semisoft -semisweet -senate -senator -send -senior -senorita -sensation -sensitive -sensitize -sensually -sensuous -sepia -september -septic -septum -sequel -sequence -sequester -series -sermon -serotonin -serpent -serrated -serve -service -serving -sesame -sessions -setback -setting -settle -settling -setup -sevenfold -seventeen -seventh -seventy -severity -shabby -shack -shaded -shadily -shadiness -shading -shadow -shady -shaft -shakable -shakily -shakiness -shaking -shaky -shale -shallot -shallow -shame -shampoo -shamrock -shank -shanty -shape -shaping -share -sharpener -sharper -sharpie -sharply -sharpness -shawl -sheath -shed -sheep -sheet -shelf -shell -shelter -shelve -shelving -sherry -shield -shifter -shifting -shiftless -shifty -shimmer -shimmy -shindig -shine -shingle -shininess -shining -shiny -ship -shirt -shivering -shock -shone -shoplift -shopper -shopping -shoptalk -shore -shortage -shortcake -shortcut -shorten -shorter -shorthand -shortlist -shortly -shortness -shorts -shortwave -shorty -shout -shove -showbiz -showcase -showdown -shower -showgirl -showing -showman -shown -showoff -showpiece -showplace -showroom -showy -shrank -shrapnel -shredder -shredding -shrewdly -shriek -shrill -shrimp -shrine -shrink -shrivel -shrouded -shrubbery -shrubs -shrug -shrunk -shucking -shudder -shuffle -shuffling -shun -shush -shut -shy -siamese -siberian -sibling -siding -sierra -siesta -sift -sighing -silenced -silencer -silent -silica -silicon -silk -silliness -silly -silo -silt -silver -similarly -simile -simmering -simple -simplify -simply -sincere -sincerity -singer -singing -single -singular -sinister -sinless -sinner -sinuous -sip -siren -sister -sitcom -sitter -sitting -situated -situation -sixfold -sixteen -sixth -sixties -sixtieth -sixtyfold -sizable -sizably -size -sizing -sizzle -sizzling -skater -skating -skedaddle -skeletal -skeleton -skeptic -sketch -skewed -skewer -skid -skied -skier -skies -skiing -skilled -skillet -skillful -skimmed -skimmer -skimming -skimpily -skincare -skinhead -skinless -skinning -skinny -skintight -skipper -skipping -skirmish -skirt -skittle -skydiver -skylight -skyline -skype -skyrocket -skyward -slab -slacked -slacker -slacking -slackness -slacks -slain -slam -slander -slang -slapping -slapstick -slashed -slashing -slate -slather -slaw -sled -sleek -sleep -sleet -sleeve -slept -sliceable -sliced -slicer -slicing -slick -slider -slideshow -sliding -slighted -slighting -slightly -slimness -slimy -slinging -slingshot -slinky -slip -slit -sliver -slobbery -slogan -sloped -sloping -sloppily -sloppy -slot -slouching -slouchy -sludge -slug -slum -slurp -slush -sly -small -smartly -smartness -smasher -smashing -smashup -smell -smelting -smile -smilingly -smirk -smite -smith -smitten -smock -smog -smoked -smokeless -smokiness -smoking -smoky -smolder -smooth -smother -smudge -smudgy -smuggler -smuggling -smugly -smugness -snack -snagged -snaking -snap -snare -snarl -snazzy -sneak -sneer -sneeze -sneezing -snide -sniff -snippet -snipping -snitch -snooper -snooze -snore -snoring -snorkel -snort -snout -snowbird -snowboard -snowbound -snowcap -snowdrift -snowdrop -snowfall -snowfield -snowflake -snowiness -snowless -snowman -snowplow -snowshoe -snowstorm -snowsuit -snowy -snub -snuff -snuggle -snugly -snugness -speak -spearfish -spearhead -spearman -spearmint -species -specimen -specked -speckled -specks -spectacle -spectator -spectrum -speculate -speech -speed -spellbind -speller -spelling -spendable -spender -spending -spent -spew -sphere -spherical -sphinx -spider -spied -spiffy -spill -spilt -spinach -spinal -spindle -spinner -spinning -spinout -spinster -spiny -spiral -spirited -spiritism -spirits -spiritual -splashed -splashing -splashy -splatter -spleen -splendid -splendor -splice -splicing -splinter -splotchy -splurge -spoilage -spoiled -spoiler -spoiling -spoils -spoken -spokesman -sponge -spongy -sponsor -spoof -spookily -spooky -spool -spoon -spore -sporting -sports -sporty -spotless -spotlight -spotted -spotter -spotting -spotty -spousal -spouse -spout -sprain -sprang -sprawl -spray -spree -sprig -spring -sprinkled -sprinkler -sprint -sprite -sprout -spruce -sprung -spry -spud -spur -sputter -spyglass -squabble -squad -squall -squander -squash -squatted -squatter -squatting -squeak -squealer -squealing -squeamish -squeegee -squeeze -squeezing -squid -squiggle -squiggly -squint -squire -squirt -squishier -squishy -stability -stabilize -stable -stack -stadium -staff -stage -staging -stagnant -stagnate -stainable -stained -staining -stainless -stalemate -staleness -stalling -stallion -stamina -stammer -stamp -stand -stank -staple -stapling -starboard -starch -stardom -stardust -starfish -stargazer -staring -stark -starless -starlet -starlight -starlit -starring -starry -starship -starter -starting -startle -startling -startup -starved -starving -stash -state -static -statistic -statue -stature -status -statute -statutory -staunch -stays -steadfast -steadier -steadily -steadying -steam -steed -steep -steerable -steering -steersman -stegosaur -stellar -stem -stench -stencil -step -stereo -sterile -sterility -sterilize -sterling -sternness -sternum -stew -stick -stiffen -stiffly -stiffness -stifle -stifling -stillness -stilt -stimulant -stimulate -stimuli -stimulus -stinger -stingily -stinging -stingray -stingy -stinking -stinky -stipend -stipulate -stir -stitch -stock -stoic -stoke -stole -stomp -stonewall -stoneware -stonework -stoning -stony -stood -stooge -stool -stoop -stoplight -stoppable -stoppage -stopped -stopper -stopping -stopwatch -storable -storage -storeroom -storewide -storm -stout -stove -stowaway -stowing -straddle -straggler -strained -strainer -straining -strangely -stranger -strangle -strategic -strategy -stratus -straw -stray -streak -stream -street -strength -strenuous -strep -stress -stretch -strewn -stricken -strict -stride -strife -strike -striking -strive -striving -strobe -strode -stroller -strongbox -strongly -strongman -struck -structure -strudel -struggle -strum -strung -strut -stubbed -stubble -stubbly -stubborn -stucco -stuck -student -studied -studio -study -stuffed -stuffing -stuffy -stumble -stumbling -stump -stung -stunned -stunner -stunning -stunt -stupor -sturdily -sturdy -styling -stylishly -stylist -stylized -stylus -suave -subarctic -subatomic -subdivide -subdued -subduing -subfloor -subgroup -subheader -subject -sublease -sublet -sublevel -sublime -submarine -submerge -submersed -submitter -subpanel -subpar -subplot -subprime -subscribe -subscript -subsector -subside -subsiding -subsidize -subsidy -subsoil -subsonic -substance -subsystem -subtext -subtitle -subtly -subtotal -subtract -subtype -suburb -subway -subwoofer -subzero -succulent -such -suction -sudden -sudoku -suds -sufferer -suffering -suffice -suffix -suffocate -suffrage -sugar -suggest -suing -suitable -suitably -suitcase -suitor -sulfate -sulfide -sulfite -sulfur -sulk -sullen -sulphate -sulphuric -sultry -superbowl -superglue -superhero -superior -superjet -superman -supermom -supernova -supervise -supper -supplier -supply -support -supremacy -supreme -surcharge -surely -sureness -surface -surfacing -surfboard -surfer -surgery -surgical -surging -surname -surpass -surplus -surprise -surreal -surrender -surrogate -surround -survey -survival -survive -surviving -survivor -sushi -suspect -suspend -suspense -sustained -sustainer -swab -swaddling -swagger -swampland -swan -swapping -swarm -sway -swear -sweat -sweep -swell -swept -swerve -swifter -swiftly -swiftness -swimmable -swimmer -swimming -swimsuit -swimwear -swinger -swinging -swipe -swirl -switch -swivel -swizzle -swooned -swoop -swoosh -swore -sworn -swung -sycamore -sympathy -symphonic -symphony -symptom -synapse -syndrome -synergy -synopses -synopsis -synthesis -synthetic -syrup -system -t-shirt -tabasco -tabby -tableful -tables -tablet -tableware -tabloid -tackiness -tacking -tackle -tackling -tacky -taco -tactful -tactical -tactics -tactile -tactless -tadpole -taekwondo -tag -tainted -take -taking -talcum -talisman -tall -talon -tamale -tameness -tamer -tamper -tank -tanned -tannery -tanning -tantrum -tapeless -tapered -tapering -tapestry -tapioca -tapping -taps -tarantula -target -tarmac -tarnish -tarot -tartar -tartly -tartness -task -tassel -taste -tastiness -tasting -tasty -tattered -tattle -tattling -tattoo -taunt -tavern -thank -that -thaw -theater -theatrics -thee -theft -theme -theology -theorize -thermal -thermos -thesaurus -these -thesis -thespian -thicken -thicket -thickness -thieving -thievish -thigh -thimble -thing -think -thinly -thinner -thinness -thinning -thirstily -thirsting -thirsty -thirteen -thirty -thong -thorn -those -thousand -thrash -thread -threaten -threefold -thrift -thrill -thrive -thriving -throat -throbbing -throng -throttle -throwaway -throwback -thrower -throwing -thud -thumb -thumping -thursday -thus -thwarting -thyself -tiara -tibia -tidal -tidbit -tidiness -tidings -tidy -tiger -tighten -tightly -tightness -tightrope -tightwad -tigress -tile -tiling -till -tilt -timid -timing -timothy -tinderbox -tinfoil -tingle -tingling -tingly -tinker -tinkling -tinsel -tinsmith -tint -tinwork -tiny -tipoff -tipped -tipper -tipping -tiptoeing -tiptop -tiring -tissue -trace -tracing -track -traction -tractor -trade -trading -tradition -traffic -tragedy -trailing -trailside -train -traitor -trance -tranquil -transfer -transform -translate -transpire -transport -transpose -trapdoor -trapeze -trapezoid -trapped -trapper -trapping -traps -trash -travel -traverse -travesty -tray -treachery -treading -treadmill -treason -treat -treble -tree -trekker -tremble -trembling -tremor -trench -trend -trespass -triage -trial -triangle -tribesman -tribunal -tribune -tributary -tribute -triceps -trickery -trickily -tricking -trickle -trickster -tricky -tricolor -tricycle -trident -tried -trifle -trifocals -trillion -trilogy -trimester -trimmer -trimming -trimness -trinity -trio -tripod -tripping -triumph -trivial -trodden -trolling -trombone -trophy -tropical -tropics -trouble -troubling -trough -trousers -trout -trowel -truce -truck -truffle -trump -trunks -trustable -trustee -trustful -trusting -trustless -truth -try -tubby -tubeless -tubular -tucking -tuesday -tug -tuition -tulip -tumble -tumbling -tummy -turban -turbine -turbofan -turbojet -turbulent -turf -turkey -turmoil -turret -turtle -tusk -tutor -tutu -tux -tweak -tweed -tweet -tweezers -twelve -twentieth -twenty -twerp -twice -twiddle -twiddling -twig -twilight -twine -twins -twirl -twistable -twisted -twister -twisting -twisty -twitch -twitter -tycoon -tying -tyke -udder -ultimate -ultimatum -ultra -umbilical -umbrella -umpire -unabashed -unable -unadorned -unadvised -unafraid -unaired -unaligned -unaltered -unarmored -unashamed -unaudited -unawake -unaware -unbaked -unbalance -unbeaten -unbend -unbent -unbiased -unbitten -unblended -unblessed -unblock -unbolted -unbounded -unboxed -unbraided -unbridle -unbroken -unbuckled -unbundle -unburned -unbutton -uncanny -uncapped -uncaring -uncertain -unchain -unchanged -uncharted -uncheck -uncivil -unclad -unclaimed -unclamped -unclasp -uncle -unclip -uncloak -unclog -unclothed -uncoated -uncoiled -uncolored -uncombed -uncommon -uncooked -uncork -uncorrupt -uncounted -uncouple -uncouth -uncover -uncross -uncrown -uncrushed -uncured -uncurious -uncurled -uncut -undamaged -undated -undaunted -undead -undecided -undefined -underage -underarm -undercoat -undercook -undercut -underdog -underdone -underfed -underfeed -underfoot -undergo -undergrad -underhand -underline -underling -undermine -undermost -underpaid -underpass -underpay -underrate -undertake -undertone -undertook -undertow -underuse -underwear -underwent -underwire -undesired -undiluted -undivided -undocked -undoing -undone -undrafted -undress -undrilled -undusted -undying -unearned -unearth -unease -uneasily -uneasy -uneatable -uneaten -unedited -unelected -unending -unengaged -unenvied -unequal -unethical -uneven -unexpired -unexposed -unfailing -unfair -unfasten -unfazed -unfeeling -unfiled -unfilled -unfitted -unfitting -unfixable -unfixed -unflawed -unfocused -unfold -unfounded -unframed -unfreeze -unfrosted -unfrozen -unfunded -unglazed -ungloved -unglue -ungodly -ungraded -ungreased -unguarded -unguided -unhappily -unhappy -unharmed -unhealthy -unheard -unhearing -unheated -unhelpful -unhidden -unhinge -unhitched -unholy -unhook -unicorn -unicycle -unified -unifier -uniformed -uniformly -unify -unimpeded -uninjured -uninstall -uninsured -uninvited -union -uniquely -unisexual -unison -unissued -unit -universal -universe -unjustly -unkempt -unkind -unknotted -unknowing -unknown -unlaced -unlatch -unlawful -unleaded -unlearned -unleash -unless -unleveled -unlighted -unlikable -unlimited -unlined -unlinked -unlisted -unlit -unlivable -unloaded -unloader -unlocked -unlocking -unlovable -unloved -unlovely -unloving -unluckily -unlucky -unmade -unmanaged -unmanned -unmapped -unmarked -unmasked -unmasking -unmatched -unmindful -unmixable -unmixed -unmolded -unmoral -unmovable -unmoved -unmoving -unnamable -unnamed -unnatural -unneeded -unnerve -unnerving -unnoticed -unopened -unopposed -unpack -unpadded -unpaid -unpainted -unpaired -unpaved -unpeeled -unpicked -unpiloted -unpinned -unplanned -unplanted -unpleased -unpledged -unplowed -unplug -unpopular -unproven -unquote -unranked -unrated -unraveled -unreached -unread -unreal -unreeling -unrefined -unrelated -unrented -unrest -unretired -unrevised -unrigged -unripe -unrivaled -unroasted -unrobed -unroll -unruffled -unruly -unrushed -unsaddle -unsafe -unsaid -unsalted -unsaved -unsavory -unscathed -unscented -unscrew -unsealed -unseated -unsecured -unseeing -unseemly -unseen -unselect -unselfish -unsent -unsettled -unshackle -unshaken -unshaved -unshaven -unsheathe -unshipped -unsightly -unsigned -unskilled -unsliced -unsmooth -unsnap -unsocial -unsoiled -unsold -unsolved -unsorted -unspoiled -unspoken -unstable -unstaffed -unstamped -unsteady -unsterile -unstirred -unstitch -unstopped -unstuck -unstuffed -unstylish -unsubtle -unsubtly -unsuited -unsure -unsworn -untagged -untainted -untaken -untamed -untangled -untapped -untaxed -unthawed -unthread -untidy -untie -until -untimed -untimely -untitled -untoasted -untold -untouched -untracked -untrained -untreated -untried -untrimmed -untrue -untruth -unturned -untwist -untying -unusable -unused -unusual -unvalued -unvaried -unvarying -unveiled -unveiling -unvented -unviable -unvisited -unvocal -unwanted -unwarlike -unwary -unwashed -unwatched -unweave -unwed -unwelcome -unwell -unwieldy -unwilling -unwind -unwired -unwitting -unwomanly -unworldly -unworn -unworried -unworthy -unwound -unwoven -unwrapped -unwritten -unzip -upbeat -upchuck -upcoming -upcountry -update -upfront -upgrade -upheaval -upheld -uphill -uphold -uplifted -uplifting -upload -upon -upper -upright -uprising -upriver -uproar -uproot -upscale -upside -upstage -upstairs -upstart -upstate -upstream -upstroke -upswing -uptake -uptight -uptown -upturned -upward -upwind -uranium -urban -urchin -urethane -urgency -urgent -urging -urologist -urology -usable -usage -useable -used -uselessly -user -usher -usual -utensil -utility -utilize -utmost -utopia -utter -vacancy -vacant -vacate -vacation -vagabond -vagrancy -vagrantly -vaguely -vagueness -valiant -valid -valium -valley -valuables -value -vanilla -vanish -vanity -vanquish -vantage -vaporizer -variable -variably -varied -variety -various -varmint -varnish -varsity -varying -vascular -vaseline -vastly -vastness -veal -vegan -veggie -vehicular -velcro -velocity -velvet -vendetta -vending -vendor -veneering -vengeful -venomous -ventricle -venture -venue -venus -verbalize -verbally -verbose -verdict -verify -verse -version -versus -vertebrae -vertical -vertigo -very -vessel -vest -veteran -veto -vexingly -viability -viable -vibes -vice -vicinity -victory -video -viewable -viewer -viewing -viewless -viewpoint -vigorous -village -villain -vindicate -vineyard -vintage -violate -violation -violator -violet -violin -viper -viral -virtual -virtuous -virus -visa -viscosity -viscous -viselike -visible -visibly -vision -visiting -visitor -visor -vista -vitality -vitalize -vitally -vitamins -vivacious -vividly -vividness -vixen -vocalist -vocalize -vocally -vocation -voice -voicing -void -volatile -volley -voltage -volumes -voter -voting -voucher -vowed -vowel -voyage -wackiness -wad -wafer -waffle -waged -wager -wages -waggle -wagon -wake -waking -walk -walmart -walnut -walrus -waltz -wand -wannabe -wanted -wanting -wasabi -washable -washbasin -washboard -washbowl -washcloth -washday -washed -washer -washhouse -washing -washout -washroom -washstand -washtub -wasp -wasting -watch -water -waviness -waving -wavy -whacking -whacky -wham -wharf -wheat -whenever -whiff -whimsical -whinny -whiny -whisking -whoever -whole -whomever -whoopee -whooping -whoops -why -wick -widely -widen -widget -widow -width -wieldable -wielder -wife -wifi -wikipedia -wildcard -wildcat -wilder -wildfire -wildfowl -wildland -wildlife -wildly -wildness -willed -willfully -willing -willow -willpower -wilt -wimp -wince -wincing -wind -wing -winking -winner -winnings -winter -wipe -wired -wireless -wiring -wiry -wisdom -wise -wish -wisplike -wispy -wistful -wizard -wobble -wobbling -wobbly -wok -wolf -wolverine -womanhood -womankind -womanless -womanlike -womanly -womb -woof -wooing -wool -woozy -word -work -worried -worrier -worrisome -worry -worsening -worshiper -worst -wound -woven -wow -wrangle -wrath -wreath -wreckage -wrecker -wrecking -wrench -wriggle -wriggly -wrinkle -wrinkly -wrist -writing -written -wrongdoer -wronged -wrongful -wrongly -wrongness -wrought -xbox -xerox -yahoo -yam -yanking -yapping -yard -yarn -yeah -yearbook -yearling -yearly -yearning -yeast -yelling -yelp -yen -yesterday -yiddish -yield -yin -yippee -yo-yo -yodel -yoga -yogurt -yonder -yoyo -yummy -zap -zealous -zebra -zen -zeppelin -zero -zestfully -zesty -zigzagged -zipfile -zipping -zippy -zips -zit -zodiac -zombie -zone -zoning -zookeeper -zoologist -zoology -zoom \ No newline at end of file diff --git a/crates/crypto/benches/crypto/aes-256-gcm-siv.rs b/crates/crypto/benches/crypto/aes-256-gcm-siv.rs deleted file mode 100644 index ac3bcfec1..000000000 --- a/crates/crypto/benches/crypto/aes-256-gcm-siv.rs +++ /dev/null @@ -1,66 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use sd_crypto::{ - crypto::{Decryptor, Encryptor}, - primitives::{BLOCK_LEN, KEY_LEN}, - types::{Aad, Algorithm, Key, Nonce}, -}; - -const ALGORITHM: Algorithm = Algorithm::Aes256GcmSiv; -const SIZES: [usize; 3] = [BLOCK_LEN, BLOCK_LEN * 2, BLOCK_LEN * 4]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group(ALGORITHM.to_string().to_ascii_lowercase()); - - let key = Key::generate(); - let nonce = Nonce::generate(ALGORITHM); - - { - group.throughput(Throughput::Bytes(KEY_LEN as u64)); - - let test_key = Key::generate(); - let test_key_encrypted = - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap(); - - group.bench_function(BenchmarkId::new("encrypt", "key"), |b| { - b.iter(|| { - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap() - }); - }); - - group.bench_function(BenchmarkId::new("decrypt", "key"), |b| { - b.iter(|| { - Decryptor::decrypt_key(&key, ALGORITHM, &test_key_encrypted, Aad::Null).unwrap() - }); - }); - } - - for size in SIZES { - group.throughput(Throughput::Bytes(size as u64)); - - let buf = vec![0u8; size].into_boxed_slice(); - - let encrypted_bytes = - Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap(); // bytes to decrypt - - group.bench_function(BenchmarkId::new("encrypt", size), |b| { - b.iter(|| Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap()); - }); - - group.bench_function(BenchmarkId::new("decrypt", size), |b| { - b.iter(|| { - Decryptor::decrypt_bytes(&key, &nonce, ALGORITHM, &encrypted_bytes, Aad::Null) - .unwrap() - }) - }); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/crypto/aes-256-gcm.rs b/crates/crypto/benches/crypto/aes-256-gcm.rs deleted file mode 100644 index cf270b892..000000000 --- a/crates/crypto/benches/crypto/aes-256-gcm.rs +++ /dev/null @@ -1,66 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use sd_crypto::{ - crypto::{Decryptor, Encryptor}, - primitives::{BLOCK_LEN, KEY_LEN}, - types::{Aad, Algorithm, Key, Nonce}, -}; - -const ALGORITHM: Algorithm = Algorithm::Aes256Gcm; -const SIZES: [usize; 3] = [BLOCK_LEN, BLOCK_LEN * 2, BLOCK_LEN * 4]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group(ALGORITHM.to_string().to_ascii_lowercase()); - - let key = Key::generate(); - let nonce = Nonce::generate(ALGORITHM); - - { - group.throughput(Throughput::Bytes(KEY_LEN as u64)); - - let test_key = Key::generate(); - let test_key_encrypted = - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap(); - - group.bench_function(BenchmarkId::new("encrypt", "key"), |b| { - b.iter(|| { - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap() - }); - }); - - group.bench_function(BenchmarkId::new("decrypt", "key"), |b| { - b.iter(|| { - Decryptor::decrypt_key(&key, ALGORITHM, &test_key_encrypted, Aad::Null).unwrap() - }); - }); - } - - for size in SIZES { - group.throughput(Throughput::Bytes(size as u64)); - - let buf = vec![0u8; size].into_boxed_slice(); - - let encrypted_bytes = - Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap(); // bytes to decrypt - - group.bench_function(BenchmarkId::new("encrypt", size), |b| { - b.iter(|| Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap()); - }); - - group.bench_function(BenchmarkId::new("decrypt", size), |b| { - b.iter(|| { - Decryptor::decrypt_bytes(&key, &nonce, ALGORITHM, &encrypted_bytes, Aad::Null) - .unwrap() - }) - }); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/crypto/xchacha20-poly1305.rs b/crates/crypto/benches/crypto/xchacha20-poly1305.rs deleted file mode 100644 index b8608d018..000000000 --- a/crates/crypto/benches/crypto/xchacha20-poly1305.rs +++ /dev/null @@ -1,66 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use sd_crypto::{ - crypto::{Decryptor, Encryptor}, - primitives::{BLOCK_LEN, KEY_LEN}, - types::{Aad, Algorithm, Key, Nonce}, -}; - -const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305; -const SIZES: [usize; 3] = [BLOCK_LEN, BLOCK_LEN * 2, BLOCK_LEN * 4]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group(ALGORITHM.to_string().to_ascii_lowercase()); - - let key = Key::generate(); - let nonce = Nonce::generate(ALGORITHM); - - { - group.throughput(Throughput::Bytes(KEY_LEN as u64)); - - let test_key = Key::generate(); - let test_key_encrypted = - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap(); - - group.bench_function(BenchmarkId::new("encrypt", "key"), |b| { - b.iter(|| { - Encryptor::encrypt_key(&key, &nonce, ALGORITHM, &test_key, Aad::Null).unwrap() - }); - }); - - group.bench_function(BenchmarkId::new("decrypt", "key"), |b| { - b.iter(|| { - Decryptor::decrypt_key(&key, ALGORITHM, &test_key_encrypted, Aad::Null).unwrap() - }); - }); - } - - for size in SIZES { - group.throughput(Throughput::Bytes(size as u64)); - - let buf = vec![0u8; size].into_boxed_slice(); - - let encrypted_bytes = - Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap(); // bytes to decrypt - - group.bench_function(BenchmarkId::new("encrypt", size), |b| { - b.iter(|| Encryptor::encrypt_bytes(&key, &nonce, ALGORITHM, &buf, Aad::Null).unwrap()); - }); - - group.bench_function(BenchmarkId::new("decrypt", size), |b| { - b.iter(|| { - Decryptor::decrypt_bytes(&key, &nonce, ALGORITHM, &encrypted_bytes, Aad::Null) - .unwrap() - }) - }); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/hashing/argon2id.rs b/crates/crypto/benches/hashing/argon2id.rs deleted file mode 100644 index c4f16b9cf..000000000 --- a/crates/crypto/benches/hashing/argon2id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use sd_crypto::{ - hashing::Hasher, - rng::CryptoRng, - types::{HashingAlgorithm, Params, Salt, SecretKey}, - Protected, -}; - -const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group("argon2id"); - group.sample_size(10); // TODO(brxken128): probably remove this - - for param in PARAMS { - let password: Protected> = CryptoRng::generate_vec(16).into(); - let salt = Salt::generate(); - let hashing_algorithm = HashingAlgorithm::Argon2id(param); - - group.bench_function( - BenchmarkId::new("hash", hashing_algorithm.get_parameters().0), - |b| { - b.iter_batched( - || (password.clone(), salt), - |(password, salt)| { - Hasher::hash_password(hashing_algorithm, &password, salt, &SecretKey::Null) - }, - BatchSize::LargeInput, - ) - }, - ); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/hashing/blake3-balloon.rs b/crates/crypto/benches/hashing/blake3-balloon.rs deleted file mode 100644 index 5e1cffe8f..000000000 --- a/crates/crypto/benches/hashing/blake3-balloon.rs +++ /dev/null @@ -1,43 +0,0 @@ -use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use sd_crypto::{ - hashing::Hasher, - rng::CryptoRng, - types::{HashingAlgorithm, Params, Salt, SecretKey}, - Protected, -}; - -const PARAMS: [Params; 3] = [Params::Standard, Params::Hardened, Params::Paranoid]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group("blake3-balloon"); - group.sample_size(10); // TODO(brxken128): probably remove this - - for param in PARAMS { - let password: Protected> = CryptoRng::generate_vec(16).into(); - let salt = Salt::generate(); - let hashing_algorithm = HashingAlgorithm::Blake3Balloon(param); - - group.bench_function( - BenchmarkId::new("hash", hashing_algorithm.get_parameters().0), - |b| { - b.iter_batched( - || (password.clone(), salt), - |(password, salt)| { - Hasher::hash_password(hashing_algorithm, &password, salt, &SecretKey::Null) - }, - BatchSize::LargeInput, - ) - }, - ); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/hashing/blake3-kdf.rs b/crates/crypto/benches/hashing/blake3-kdf.rs deleted file mode 100644 index 7c93fe3a1..000000000 --- a/crates/crypto/benches/hashing/blake3-kdf.rs +++ /dev/null @@ -1,24 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use sd_crypto::{ - hashing::Hasher, - types::{DerivationContext, Key, Salt}, -}; - -const CONTEXT: DerivationContext = - DerivationContext::new("crypto 2023-03-21 11:31:38 benchmark testing context"); - -fn bench(c: &mut Criterion) { - let key = Key::generate(); - let salt = Salt::generate(); - c.bench_function("blake3-kdf", |b| { - b.iter(|| Hasher::derive_key(&key, salt, CONTEXT)) - }); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/benches/hashing/blake3.rs b/crates/crypto/benches/hashing/blake3.rs deleted file mode 100644 index f4fa6b22d..000000000 --- a/crates/crypto/benches/hashing/blake3.rs +++ /dev/null @@ -1,31 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use sd_crypto::{ - hashing::Hasher, - primitives::{BLOCK_LEN, KEY_LEN}, -}; - -const SIZES: [usize; 2] = [KEY_LEN, BLOCK_LEN]; - -fn bench(c: &mut Criterion) { - let mut group = c.benchmark_group("blake3"); - - for size in SIZES { - let buf = vec![0u8; size].into_boxed_slice(); - - group.throughput(Throughput::Bytes(size as u64)); - - group.bench_function(BenchmarkId::new("hash", size), |b| { - b.iter(|| Hasher::blake3(&buf)) - }); - } - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default(); - targets = bench -); - -criterion_main!(benches); diff --git a/crates/crypto/examples/file_encryption.rs b/crates/crypto/examples/file_encryption.rs deleted file mode 100644 index dcc38fc8c..000000000 --- a/crates/crypto/examples/file_encryption.rs +++ /dev/null @@ -1,119 +0,0 @@ -use sd_crypto::{ - crypto::{Decryptor, Encryptor}, - encoding::Header, - hashing::Hasher, - types::{ - Algorithm, DerivationContext, HashingAlgorithm, Key, MagicBytes, Params, Salt, SecretKey, - }, - Protected, -}; -use std::io::{Cursor, Read, Seek, Write}; - -const MAGIC_BYTES: MagicBytes<6> = MagicBytes::new(*b"crypto"); - -const HEADER_KEY_CONTEXT: DerivationContext = - DerivationContext::new("crypto 2023-03-21 11:24:53 example header key context"); - -const HEADER_OBJECT_CONTEXT: DerivationContext = - DerivationContext::new("crypto 2023-03-21 11:25:08 example header object context"); - -const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305; -const HASHING_ALGORITHM: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard); - -const OBJECT_DATA: [u8; 15] = *b"a nice mountain"; - -fn encrypt(reader: &mut R, writer: &mut W) -where - R: Read, - W: Write + Seek, -{ - let password = Protected::new(b"password".to_vec()); - - // This needs to be generated here, otherwise we won't have access to it for encryption - let master_key = Key::generate(); - - // These should ideally be done by a key management system - let content_salt = Salt::generate(); - let hashed_password = - Hasher::hash_password(HASHING_ALGORITHM, &password, content_salt, &SecretKey::Null) - .unwrap(); - - // Create the header for the encrypted file - let mut header = Header::new(ALGORITHM); - - // Create a keyslot to be added to the header - header - .add_keyslot( - HASHING_ALGORITHM, - content_salt, - &hashed_password, - &master_key, - HEADER_KEY_CONTEXT, - ) - .unwrap(); - - header - .add_object( - "FileMetadata", - HEADER_OBJECT_CONTEXT, - &master_key, - &OBJECT_DATA, - ) - .unwrap(); - - // Write the header to the file - header.to_writer(writer, MAGIC_BYTES).unwrap(); - - // Use the nonce created by the header to initialize an encryptor - let encryptor = Encryptor::new(&master_key, &header.nonce, header.algorithm).unwrap(); - - // Encrypt the data from the reader, and write it to the writer - // Use AAD so the header can be authenticated against every block of data - encryptor - .encrypt_streams(reader, writer, header.generate_aad()) - .unwrap(); -} - -fn decrypt(reader: &mut R, writer: &mut W) -> Vec -where - R: Read + Seek, - W: Write, -{ - let password = Protected::new(b"password".to_vec()); - - // Deserialize the header from the encrypted file - let (header, aad) = Header::from_reader(reader, MAGIC_BYTES).unwrap(); - - let (master_key, index) = header - .decrypt_master_key_with_password(&password, HEADER_KEY_CONTEXT) - .unwrap(); - - println!("key is in slot: {index}"); - - let decryptor = Decryptor::new(&master_key, &header.nonce, header.algorithm).unwrap(); - - // Decrypt data the from the reader, and write it to the writer - decryptor.decrypt_streams(reader, writer, aad).unwrap(); - - // Decrypt the object - let object = header - .decrypt_object("FileMetadata", HEADER_OBJECT_CONTEXT, &master_key) - .unwrap(); - - object.into_inner() -} - -fn main() { - // Open both the source and the output file - let mut source = Cursor::new(vec![5u8; 256]); - let mut dest = Cursor::new(vec![]); - let mut source_comparison = Cursor::new(vec![]); - - encrypt(&mut source, &mut dest); - - dest.rewind().unwrap(); - - let object_data = decrypt(&mut dest, &mut source_comparison); - - assert_eq!(&object_data, &OBJECT_DATA); -} diff --git a/crates/crypto/examples/secure_erase.rs b/crates/crypto/examples/secure_erase.rs index 8d0bfb55a..f849cdce3 100644 --- a/crates/crypto/examples/secure_erase.rs +++ b/crates/crypto/examples/secure_erase.rs @@ -1,20 +1,19 @@ -use sd_crypto::rng::CryptoRng; +use sd_crypto::{erase::erase_sync, rng::CryptoRng}; + use std::io::{Seek, Write}; + use tempfile::tempfile; fn main() { let mut file = tempfile().unwrap(); - let data = CryptoRng::generate_vec(1048576 * 16); + let mut rng = CryptoRng::new().unwrap(); + let data = rng.generate_vec(1048576 * 16); file.write_all(&data).unwrap(); file.rewind().unwrap(); - #[cfg(feature = "sys")] - { - use sd_crypto::sys::fs; - // Erase the file (the size would normally be obtained via `fs::Metadata::len()` or similar) - fs::erase(&mut file, 1048576 * 16, 2).unwrap(); - } + // Erase the file (the size would normally be obtained via `fs::Metadata::len()` or similar) + erase_sync(&mut file, 1048576 * 16, 2).unwrap(); // Truncate the file to a length of zero file.set_len(0).unwrap(); diff --git a/crates/crypto/src/cloud/decrypt.rs b/crates/crypto/src/cloud/decrypt.rs new file mode 100644 index 000000000..1ba41f35b --- /dev/null +++ b/crates/crypto/src/cloud/decrypt.rs @@ -0,0 +1,105 @@ +use crate::{ + primitives::{EncryptedBlock, StreamNonce}, + Error, +}; + +use std::future::Future; + +use aead::{stream::DecryptorLE31, Aead, KeyInit}; +use chacha20poly1305::XChaCha20Poly1305; +use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}; + +use super::secret_key::SecretKey; + +pub trait OneShotDecryption { + fn decrypt(&self, cipher_text: &EncryptedBlock) -> Result, Error>; +} + +pub trait StreamDecryption { + fn decrypt( + &self, + nonce: &StreamNonce, + reader: impl AsyncRead + Unpin + Send, + writer: impl AsyncWrite + Unpin + Send, + ) -> impl Future> + Send; +} + +impl OneShotDecryption for SecretKey { + fn decrypt( + &self, + EncryptedBlock { nonce, cipher_text }: &EncryptedBlock, + ) -> Result, Error> { + XChaCha20Poly1305::new(&self.0) + .decrypt(nonce, cipher_text.as_slice()) + .map_err(|aead::Error| Error::Decrypt) + } +} + +impl StreamDecryption for SecretKey { + async fn decrypt( + &self, + nonce: &StreamNonce, + reader: impl AsyncRead + Unpin + Send, + writer: impl AsyncWrite + Unpin + Send, + ) -> Result<(), Error> { + let mut reader = BufReader::with_capacity(EncryptedBlock::CIPHER_TEXT_SIZE, reader); + let mut writer = BufWriter::with_capacity(EncryptedBlock::PLAIN_TEXT_SIZE * 5, writer); + + let mut buf = Vec::with_capacity(EncryptedBlock::CIPHER_TEXT_SIZE); + + let mut decryptor = DecryptorLE31::from_aead(XChaCha20Poly1305::new(&self.0), nonce); + + loop { + match reader.fill_buf().await { + Ok([]) => { + // Jobs done + break; + } + + Ok(bytes) => { + let total_bytes = bytes.len(); + + buf.clear(); + buf.extend_from_slice(bytes); + + reader.consume(total_bytes); + + if total_bytes == EncryptedBlock::CIPHER_TEXT_SIZE { + decryptor + .decrypt_next_in_place(b"", &mut buf) + .map_err(|aead::Error| Error::Decrypt)?; + + writer.write_all(&buf).await.map_err(|e| Error::DecryptIo { + context: "Writing decrypted block to writer", + source: e, + })?; + } else { + decryptor + .decrypt_last_in_place(b"", &mut buf) + .map_err(|aead::Error| Error::Decrypt)?; + + writer.write_all(&buf).await.map_err(|e| Error::DecryptIo { + context: "Writing last decrypted block to writer", + source: e, + })?; + break; + } + } + + Err(e) => { + return Err(Error::DecryptIo { + context: "Reading a block from the reader", + source: e, + }); + } + } + } + + writer.flush().await.map_err(|e| Error::DecryptIo { + context: "Flushing writer", + source: e, + })?; + + Ok(()) + } +} diff --git a/crates/crypto/src/cloud/encrypt.rs b/crates/crypto/src/cloud/encrypt.rs new file mode 100644 index 000000000..efdcd670a --- /dev/null +++ b/crates/crypto/src/cloud/encrypt.rs @@ -0,0 +1,99 @@ +use crate::{ + primitives::{EncryptedBlock, StreamNonce}, + Error, +}; + +use aead::{stream::EncryptorLE31, Aead, KeyInit}; +use async_stream::stream; +use chacha20poly1305::{XChaCha20Poly1305, XNonce}; +use futures::Stream; +use rand::CryptoRng; +use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader}; + +use super::secret_key::SecretKey; + +pub trait OneShotEncryption { + fn encrypt(&self, plaintext: &[u8], rng: &mut impl CryptoRng) -> Result; +} + +pub trait StreamEncryption { + fn encrypt( + &self, + reader: impl AsyncRead + Unpin + Send, + rng: &mut (impl CryptoRng + Send), + ) -> ( + StreamNonce, + impl Stream, Error>> + Send, + ); +} + +impl OneShotEncryption for SecretKey { + fn encrypt(&self, plaintext: &[u8], rng: &mut impl CryptoRng) -> Result { + if plaintext.len() > EncryptedBlock::PLAIN_TEXT_SIZE { + return Err(Error::BlockTooBig(plaintext.len())); + } + + let cipher = XChaCha20Poly1305::new(&self.0); + let mut nonce = XNonce::default(); + rng.fill_bytes(&mut nonce); + + Ok(EncryptedBlock { + nonce, + cipher_text: cipher + .encrypt(&nonce, plaintext) + .map_err(|aead::Error| Error::Encrypt)?, + }) + } +} + +impl StreamEncryption for SecretKey { + fn encrypt( + &self, + reader: impl AsyncRead + Unpin + Send, + rng: &mut (impl CryptoRng + Send), + ) -> ( + StreamNonce, + impl Stream, Error>> + Send, + ) { + let mut nonce = StreamNonce::default(); + rng.fill_bytes(&mut nonce); + + ( + nonce, + stream! { + let mut reader = BufReader::with_capacity(EncryptedBlock::PLAIN_TEXT_SIZE, reader); + let mut encryptor = EncryptorLE31::from_aead(XChaCha20Poly1305::new(&self.0), &nonce); + + loop { + match reader.fill_buf().await { + Ok([]) => { + // Jobs done + break; + } + + Ok(bytes) => { + let total_bytes = bytes.len(); + if bytes.len() == EncryptedBlock::PLAIN_TEXT_SIZE { + let cipher_text = encryptor.encrypt_next(bytes).map_err(|aead::Error| Error::Encrypt)?; + assert_eq!(cipher_text.len(), EncryptedBlock::CIPHER_TEXT_SIZE); + yield Ok(cipher_text); + reader.consume(total_bytes); + } else { + yield encryptor.encrypt_last(bytes).map_err(|aead::Error| Error::Encrypt); + break; + } + } + + Err(e) => { + yield Err(Error::EncryptIo { + context: "Reading a block from the reader", + source: e, + }); + break; + } + } + } + }, + ) + } +} diff --git a/crates/crypto/src/cloud/mod.rs b/crates/crypto/src/cloud/mod.rs new file mode 100644 index 000000000..4a09a47d8 --- /dev/null +++ b/crates/crypto/src/cloud/mod.rs @@ -0,0 +1,3 @@ +pub mod decrypt; +pub mod encrypt; +pub mod secret_key; diff --git a/crates/crypto/src/cloud/secret_key.rs b/crates/crypto/src/cloud/secret_key.rs new file mode 100644 index 000000000..9fbf78b21 --- /dev/null +++ b/crates/crypto/src/cloud/secret_key.rs @@ -0,0 +1,189 @@ +use crate::{ + ct::{Choice, ConstantTimeEq, ConstantTimeEqNull}, + rng::CryptoRng, +}; + +use std::fmt; + +use aead::array::Array; +use blake3::{Hash, Hasher}; +use generic_array::GenericArray; +use serde::{Deserialize, Serialize}; +use typenum::consts::U32; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// This should be used for encrypting and decrypting data. +/// +/// You can pass an existing key to [`SecretKey::new`] or you may also generate +/// a secure random key with [`SecretKey::generate`], passing the [`CryptoRng`] to generate +/// random bytes. +#[derive(Clone, Zeroize, ZeroizeOnDrop)] +#[repr(transparent)] +pub struct SecretKey(pub(crate) Array); + +impl fmt::Debug for SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SecretKey()") + } +} + +impl SecretKey { + #[inline] + #[must_use] + pub fn new(v: impl Into>) -> Self { + Self(v.into()) + } + + #[inline] + #[must_use] + pub fn generate(rng: &mut CryptoRng) -> Self { + let mut key_candidate = rng.generate_fixed(); + + while bool::from(key_candidate.ct_eq_null()) { + key_candidate = rng.generate_fixed(); + } + + Self(key_candidate.into()) + } + + #[must_use] + pub fn to_hash(&self) -> Hash { + let mut hasher = Hasher::new(); + hasher.update(&self.0); + hasher.finalize() + } +} + +impl ConstantTimeEq for SecretKey { + fn ct_eq(&self, rhs: &Self) -> Choice { + self.0.ct_eq(&rhs.0) + } +} + +impl PartialEq for SecretKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Serialize for SecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serdect::array::serialize_hex_lower_or_bin(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let mut buf = [0u8; 32]; + serdect::array::deserialize_hex_or_bin(&mut buf, deserializer)?; + Ok(Self::new(buf)) + } +} + +impl From<&SecretKey> for Array { + fn from(SecretKey(key): &SecretKey) -> Self { + *key + } +} + +impl From> for SecretKey { + fn from(key: GenericArray) -> Self { + Self(Array([ + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], key[8], key[9], + key[10], key[11], key[12], key[13], key[14], key[15], key[16], key[17], key[18], + key[19], key[20], key[21], key[22], key[23], key[24], key[25], key[26], key[27], + key[28], key[29], key[30], key[31], + ])) + } +} + +#[cfg(test)] +mod tests { + use std::pin::pin; + + use futures::StreamExt; + use rand::RngCore; + + use crate::primitives::EncryptedBlock; + + use super::*; + + #[test] + fn one_shot_test() { + use super::super::{decrypt::OneShotDecryption, encrypt::OneShotEncryption}; + let mut rng = CryptoRng::new().unwrap(); + + let message = b"Eu queria um apartamento no Guarujah; \ + Mas o melhor que eu consegui foi um barraco em Itaquah."; + + let key = SecretKey::generate(&mut rng); + + let encrypted_block = key.encrypt(message, &mut rng).unwrap(); + let decrypted_message = key.decrypt(&encrypted_block).unwrap(); + + assert_eq!(message, decrypted_message.as_slice()); + } + + async fn stream_test(rng: &mut CryptoRng, message: &[u8]) { + use super::super::{decrypt::StreamDecryption, encrypt::StreamEncryption}; + + let key = SecretKey::generate(rng); + + let mut encrypted_message = vec![]; + + let (nonce, stream) = key.encrypt(message, rng); + + let mut stream = pin!(stream); + + while let Some(res) = stream.next().await { + encrypted_message.extend(res.unwrap()); + } + + let mut decrypted_message = vec![]; + + key.decrypt(&nonce, encrypted_message.as_slice(), &mut decrypted_message) + .await + .unwrap(); + + assert_eq!(message, decrypted_message.as_slice()); + } + + #[tokio::test] + async fn stream_test_small() { + let message = b"Eu sou cagado, veja so como eh que eh; \ + Se der uma chuva de Xuxa, no meu colo cai Peleh; \ + E como aquele ditado que jah dizia; \ + Pau que nasce torto mija fora da bacia"; + + stream_test(&mut CryptoRng::new().unwrap(), message).await; + } + + #[tokio::test] + async fn stream_test_big() { + let mut rng = CryptoRng::new().unwrap(); + + let mut message = + vec![0u8; EncryptedBlock::PLAIN_TEXT_SIZE * 10 + EncryptedBlock::PLAIN_TEXT_SIZE / 2]; + + rng.fill_bytes(&mut message); + + stream_test(&mut rng, &message).await; + } + + #[tokio::test] + async fn stream_test_big_exact() { + let mut rng = CryptoRng::new().unwrap(); + + let mut message = vec![0u8; EncryptedBlock::PLAIN_TEXT_SIZE * 20]; + + rng.fill_bytes(&mut message); + + stream_test(&mut rng, &message).await; + } +} diff --git a/crates/crypto/src/crypto/mod.rs b/crates/crypto/src/crypto/mod.rs index 487454fba..6ab19cca1 100644 --- a/crates/crypto/src/crypto/mod.rs +++ b/crates/crypto/src/crypto/mod.rs @@ -341,7 +341,6 @@ mod tests { } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn aes_256_gcm_siv_encrypt_and_decrypt_5_blocks_async() { let buf = CryptoRng::generate_vec(BLOCK_LEN * 5); diff --git a/crates/crypto/src/crypto/stream.rs b/crates/crypto/src/crypto/stream.rs index 22781eead..dce48d57b 100644 --- a/crates/crypto/src/crypto/stream.rs +++ b/crates/crypto/src/crypto/stream.rs @@ -46,7 +46,6 @@ macro_rules! impl_stream { /// For more information, view `Key::validate()` and `Nonce::validate()` pub fn new(key: &Key, nonce: &Nonce, algorithm: Algorithm) -> Result { nonce.validate(algorithm)?; - key.validate()?; let s = match algorithm { $( diff --git a/crates/crypto/src/ct.rs b/crates/crypto/src/ct.rs index 70b1708c1..e7edf6a89 100644 --- a/crates/crypto/src/ct.rs +++ b/crates/crypto/src/ct.rs @@ -1,3 +1,4 @@ +use aead::array::{Array, ArraySize}; use cmov::{Cmov, CmovEq}; // The basic principle of most `ct_eq()` functions is to "null" out `x` (which is = 1 by default) @@ -46,6 +47,21 @@ where } } +impl ConstantTimeEq for Array +where + T: CmovEq, +{ + fn ct_eq(&self, rhs: &Self) -> Choice { + let mut x = 1u8; + + self.iter() + .zip(rhs.iter()) + .for_each(|(l, r)| l.cmovne(r, 0u8, &mut x)); + + Choice::from(x) + } +} + impl ConstantTimeEq for [T] { fn ct_eq(&self, rhs: &Self) -> Choice { // Here we can short-circuit as it's obvious that they're not equal @@ -86,7 +102,7 @@ impl Choice { #[inline] #[must_use] pub fn unwrap_u8(&self) -> u8 { - // could use an unsafe volatile read as an optimisation barrier + // could use an unsafe volatile read as an optimization barrier // i think cmov does a great job at being the barrier as well though let mut x = 0u8; x.cmovnz(&1, self.0); @@ -166,31 +182,65 @@ impl ConstantTimeEqNull for [u8] { #[cfg(test)] mod tests { - use crate::{ - ct::{ConstantTimeEq, ConstantTimeEqNull}, - primitives::SALT_LEN, - }; + + use aead::array::Array; + use typenum::consts::U32; + + use crate::ct::{ConstantTimeEq, ConstantTimeEqNull}; #[test] fn eq_null() { - assert!(bool::from([0u8; SALT_LEN].ct_eq_null())); + assert!(bool::from([0u8; 16].ct_eq_null())); } #[test] #[should_panic(expected = "assertion")] fn eq_null_fail() { - assert!(bool::from([1u8; SALT_LEN].ct_eq_null())); + assert!(bool::from([1u8; 16].ct_eq_null())); } #[test] fn ne_null() { - assert!(bool::from([1u8; SALT_LEN].ct_ne_null())); + assert!(bool::from([1u8; 16].ct_ne_null())); } #[test] #[should_panic(expected = "assertion")] fn ne_null_fail() { - assert!(bool::from([0u8; SALT_LEN].ct_ne_null())); + assert!(bool::from([0u8; 16].ct_ne_null())); + } + + #[test] + fn generic_array_eq() { + assert!(bool::from(Array::::ct_eq( + &Array::from([0u8; 32]), + &Array::from([0u8; 32]), + ))); + } + #[test] + #[should_panic(expected = "assertion")] + fn generic_array_eq_fail() { + assert!(bool::from(Array::::ct_eq( + &Array::from([0u8; 32]), + &Array::from([1u8; 32]), + ))); + } + + #[test] + fn generic_array_ne() { + assert!(bool::from(Array::::ct_ne( + &Array::from([0u8; 32]), + &Array::from([1u8; 32]), + ))); + } + + #[test] + #[should_panic(expected = "assertion")] + fn generic_array_ne_fail() { + assert!(bool::from(Array::::ct_ne( + &Array::from([0u8; 32]), + &Array::from([0u8; 32]), + ))); } macro_rules! create_tests { diff --git a/crates/crypto/src/encoding/bincode.rs b/crates/crypto/src/encoding/bincode.rs deleted file mode 100644 index 2a32e31ad..000000000 --- a/crates/crypto/src/encoding/bincode.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub const CONFIG: Configuration = bincode::config::standard(); - -use crate::{Error, Result}; -use bincode::{config::Configuration, de::read::Reader}; - -pub fn decode(bytes: &[u8]) -> Result -where - T: bincode::Decode, -{ - bincode::decode_from_slice::(bytes, CONFIG) - .map(|t| t.0) - .map_err(Error::BincodeDecode) -} - -pub fn decode_from_reader(reader: R) -> Result -where - T: bincode::Decode, -{ - bincode::decode_from_reader::(reader, CONFIG).map_err(Error::BincodeDecode) -} - -pub fn encode(object: &T) -> Result> -where - T: bincode::Encode, -{ - bincode::encode_to_vec(object, CONFIG).map_err(Error::BincodeEncode) -} - -// TODO(brxken128): this should probably go but it's convenient diff --git a/crates/crypto/src/encoding/file/header.rs b/crates/crypto/src/encoding/file/header.rs deleted file mode 100644 index 695a3e633..000000000 --- a/crates/crypto/src/encoding/file/header.rs +++ /dev/null @@ -1,321 +0,0 @@ -use std::io::Cursor; - -use super::{keyslot::Keyslot, object::HeaderObject, HeaderEncode, KEYSLOT_LIMIT, OBJECT_LIMIT}; -use crate::{ - hashing::Hasher, - primitives::AAD_HEADER_LEN, - types::{ - Aad, Algorithm, DerivationContext, HashingAlgorithm, Key, MagicBytes, Nonce, Salt, - SecretKey, - }, - utils::ToArray, - Error, Protected, Result, -}; - -pub struct Header { - pub version: HeaderVersion, - pub algorithm: Algorithm, - pub nonce: Nonce, - pub keyslots: Vec, - pub objects: Vec, -} - -#[derive(Eq, PartialEq, Debug)] -pub enum HeaderVersion { - V1, -} - -impl std::fmt::Display for HeaderVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::V1 => write!(f, "V1"), - } - } -} - -impl Header { - #[must_use] - pub fn new(algorithm: Algorithm) -> Self { - Self { - version: HeaderVersion::V1, - algorithm, - nonce: Nonce::generate(algorithm), - keyslots: vec![], - objects: vec![], - } - } - - pub fn to_writer( - &self, - writer: &mut W, - magic_bytes: MagicBytes, - ) -> Result<()> - where - W: std::io::Write, - { - let b = self.as_bytes()?; - - writer.write_all(magic_bytes.inner())?; - - // we're good here for up to 4096mib~ (headers should never be this large) - writer.write_all( - &(TryInto::::try_into(b.len()).map_err(|_| Error::Validity)?).to_le_bytes(), - )?; - writer.write_all(&b)?; - - Ok(()) - } - - #[cfg(feature = "tokio")] - pub async fn to_writer_async( - &self, - writer: &mut W, - magic_bytes: MagicBytes, - ) -> Result<()> - where - W: tokio::io::AsyncWriteExt + tokio::io::AsyncSeekExt + Unpin + Send, - { - let b = self.as_bytes()?; - - writer.write_all(magic_bytes.inner()).await?; - - // we're good here for up to 4096mib~ (headers should never be this large) - writer - .write_all( - &(TryInto::::try_into(b.len()).map_err(|_| Error::Validity)?).to_le_bytes(), - ) - .await?; - writer.write_all(&b).await?; - - Ok(()) - } - - pub fn from_reader( - reader: &mut R, - magic_bytes: MagicBytes, - ) -> Result<(Self, Aad)> - where - R: std::io::Read + std::io::Seek, - { - let mut b = [0u8; I]; - reader.read_exact(&mut b)?; - - if &b != magic_bytes.inner() { - return Err(Error::Validity); - } - - let mut len = [0u8; 4]; - reader.read_exact(&mut len)?; - let len = u32::from_le_bytes(len); - - let mut header_bytes = vec![0u8; len.try_into().map_err(|_| Error::Validity)?]; - reader.read_exact(&mut header_bytes)?; - let h = Self::from_reader_raw(&mut Cursor::new(&header_bytes))?; - - Ok((h, Aad::Header(header_bytes[..AAD_HEADER_LEN].to_array()?))) - } - - #[cfg(feature = "tokio")] - pub async fn from_reader_async( - reader: &mut R, - magic_bytes: MagicBytes, - ) -> Result<(Self, Aad)> - where - R: tokio::io::AsyncReadExt + tokio::io::AsyncSeekExt + Unpin + Send, - { - let mut b = [0u8; I]; - reader.read_exact(&mut b).await?; - - if &b != magic_bytes.inner() { - return Err(Error::Validity); - } - - let mut len = [0u8; 4]; - reader.read_exact(&mut len).await?; - let len = u32::from_le_bytes(len); - - let mut header_bytes = vec![0u8; len.try_into().map_err(|_| Error::Validity)?]; - reader.read_exact(&mut header_bytes).await?; - let h = Self::from_reader_raw(&mut Cursor::new(&header_bytes))?; - - Ok((h, Aad::Header(header_bytes[..AAD_HEADER_LEN].to_array()?))) - } - - #[must_use] - pub fn generate_aad(&self) -> Aad { - let mut o = [0u8; 38]; - o[..2].copy_from_slice(&[0xFA, 0xDA]); - o[2..4].copy_from_slice(&self.version.as_bytes()); - o[4..6].copy_from_slice(&self.algorithm.as_bytes()); - o[6..38].copy_from_slice(&self.nonce.as_bytes()); - Aad::Header(o) - } - - pub fn remove_keyslot(&mut self, index: usize) -> Result<()> { - if index > self.keyslots.len() - 1 { - return Err(Error::Validity); - } - - self.keyslots.remove(index); - Ok(()) - } - - pub fn decrypt_object( - &self, - name: &'static str, - context: DerivationContext, - master_key: &Key, - ) -> Result>> { - let rhs = Hasher::blake3(name.as_bytes()); - - self.objects - .iter() - .filter_map(|o| { - o.identifier - .decrypt_id(master_key, self.algorithm, context, self.generate_aad()) - .ok() - .and_then(|i| (i == rhs).then_some(o)) - }) - // .cloned() - .collect::>() - .first() - .ok_or(Error::NoObjects)? - .decrypt(self.algorithm, self.generate_aad(), master_key) - } - - pub fn add_keyslot( - &mut self, - hashing_algorithm: HashingAlgorithm, - hash_salt: Salt, - hashed_password: &Key, - master_key: &Key, - context: DerivationContext, - ) -> Result<()> { - if self.keyslots.len() + 1 > KEYSLOT_LIMIT { - return Err(Error::TooManyKeyslots); - } - - self.keyslots.push(Keyslot::new( - self.algorithm, - hashing_algorithm, - hash_salt, - hashed_password, - master_key, - self.generate_aad(), - context, - )?); - - Ok(()) - } - - pub fn add_object( - &mut self, - name: &'static str, - context: DerivationContext, - master_key: &Key, - data: &[u8], - ) -> Result<()> { - if self.objects.len() + 1 > OBJECT_LIMIT { - return Err(Error::TooManyObjects); - } - - let rhs = Hasher::blake3(name.as_bytes()); - - if self - .objects - .iter() - .filter_map(|o| { - o.identifier - .decrypt_id(master_key, self.algorithm, context, self.generate_aad()) - .ok() - .map(|i| i == rhs) - }) - .any(|x| x) - { - return Err(Error::TooManyObjects); - } - - self.objects.push(HeaderObject::new( - name, - self.algorithm, - master_key, - context, - self.generate_aad(), - data, - )?); - Ok(()) - } - - pub fn decrypt_master_key( - &self, - keys: &[Key], - context: DerivationContext, - ) -> Result<(Key, usize)> { - if self.keyslots.is_empty() { - return Err(Error::NoKeyslots); - } - - keys.iter() - .enumerate() - .find_map(|(i, k)| { - self.keyslots.iter().find_map(|z| { - z.decrypt(self.algorithm, k, self.generate_aad(), context) - .ok() - .map(|x| (x, i)) - }) - }) - .ok_or(Error::Decrypt) - } - - pub fn decrypt_master_key_with_password( - &self, - password: &Protected>, - context: DerivationContext, - ) -> Result<(Key, usize)> { - if self.keyslots.is_empty() { - return Err(Error::NoKeyslots); - } - - self.keyslots - .iter() - .enumerate() - .find_map(|(i, z)| { - let k = Hasher::hash_password( - z.hashing_algorithm, - password, - z.hash_salt, - &SecretKey::Null, - ) - .ok()?; - z.decrypt(self.algorithm, &k, self.generate_aad(), context) - .ok() - .map(|x| (x, i)) - }) - .ok_or(Error::Decrypt) - } -} - -#[cfg(test)] -mod tests { - use crate::{ct::ConstantTimeEq, encoding::Header, types::MagicBytes}; - use std::io::{Cursor, Seek}; - - const MAGIC_BYTES: MagicBytes<6> = MagicBytes::new(*b"crypto"); - - #[test] - fn encode_and_decode() { - let mut w = Cursor::new(vec![]); - let h_source = Header::new(crate::types::Algorithm::XChaCha20Poly1305); - - h_source.to_writer(&mut w, MAGIC_BYTES).unwrap(); - w.rewind().unwrap(); - - let (h_read, aad) = Header::from_reader(&mut w, MAGIC_BYTES).unwrap(); - - assert_eq!(w.into_inner().len(), 294); - assert_eq!(h_source.algorithm, h_read.algorithm); - assert_eq!(h_source.version, h_read.version); - assert!(bool::from(h_source.nonce.ct_eq(&h_read.nonce))); - assert!(bool::from(h_source.generate_aad().ct_eq(&aad))); - } -} diff --git a/crates/crypto/src/encoding/file/keyslot.rs b/crates/crypto/src/encoding/file/keyslot.rs deleted file mode 100644 index 9c8d72b8b..000000000 --- a/crates/crypto/src/encoding/file/keyslot.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::{ - crypto::{Decryptor, Encryptor}, - hashing::Hasher, - rng::CryptoRng, - types::{Aad, Algorithm, DerivationContext, EncryptedKey, HashingAlgorithm, Key, Nonce, Salt}, - Result, -}; - -pub struct Keyslot { - pub hashing_algorithm: HashingAlgorithm, // password hashing algorithm - pub hash_salt: Salt, // salt to hash the password with - pub salt: Salt, // the salt used for key derivation with the hash digest - pub encrypted_key: EncryptedKey, // encrypted -} - -impl Keyslot { - pub fn new( - algorithm: Algorithm, - hashing_algorithm: HashingAlgorithm, - hash_salt: Salt, - hashed_password: &Key, - master_key: &Key, - aad: Aad, - context: DerivationContext, - ) -> Result { - let nonce = Nonce::generate(algorithm); - let salt = Salt::generate(); - - let encrypted_key = Encryptor::encrypt_key( - &Hasher::derive_key(hashed_password, salt, context), - &nonce, - algorithm, - master_key, - aad, - )?; - - Ok(Self { - hashing_algorithm, - hash_salt, - salt, - encrypted_key, - }) - } - - pub(super) fn decrypt( - &self, - algorithm: Algorithm, - key: &Key, - aad: Aad, - context: DerivationContext, - ) -> Result { - Decryptor::decrypt_key( - &Hasher::derive_key(key, self.salt, context), - algorithm, - &self.encrypted_key.clone(), - aad, - ) - } -} - -impl Keyslot { - #[must_use] - pub fn random() -> Self { - Self { - hash_salt: Salt::generate(), - hashing_algorithm: HashingAlgorithm::default(), - encrypted_key: EncryptedKey::new( - CryptoRng::generate_fixed(), - Nonce::generate(Algorithm::default()), - ), - salt: Salt::generate(), - } - } -} diff --git a/crates/crypto/src/encoding/file/mod.rs b/crates/crypto/src/encoding/file/mod.rs deleted file mode 100644 index b685376df..000000000 --- a/crates/crypto/src/encoding/file/mod.rs +++ /dev/null @@ -1,525 +0,0 @@ -use std::mem; - -use crate::{ - primitives::{ - AES_256_GCM_SIV_NONCE_LEN, ENCRYPTED_KEY_LEN, SALT_LEN, XCHACHA20_POLY1305_NONCE_LEN, - }, - types::{Algorithm, EncryptedKey, HashingAlgorithm, Nonce, Params, Salt}, - utils::ToArray, - Error, Result, -}; - -use self::{ - header::HeaderVersion, - keyslot::Keyslot, - object::{HeaderObject, HeaderObjectIdentifier}, -}; - -use super::Header; - -pub mod header; -pub mod keyslot; -pub mod object; - -const KEYSLOT_LIMIT: usize = 2; -const OBJECT_LIMIT: usize = 2; - -pub trait HeaderEncode { - const OUTPUT_LEN: usize; - type Identifier; - type Output: Default; - - fn as_bytes(&self) -> Self::Output; - - fn from_bytes(b: Self::Output) -> Result - where - Self: Sized; - - fn from_reader(reader: &mut R) -> Result - where - Self: Sized, - R: std::io::Read + std::io::Seek; // make this a provided method eventually via `hybrid-array`? -} - -// TODO(brxken128): convert as many of these as possible to vec -// also define the identifiers as consts where possble? -// typenum/hybrid-array/generic-array too - -impl HeaderEncode for Params { - const OUTPUT_LEN: usize = 1; - type Identifier = u8; - type Output = u8; - - fn as_bytes(&self) -> Self::Output { - match self { - Self::Standard => 18u8, - Self::Hardened => 39u8, - Self::Paranoid => 56u8, - } - } - - fn from_bytes(b: Self::Output) -> Result { - match b { - 18u8 => Ok(Self::Standard), - 39u8 => Ok(Self::Hardened), - 56u8 => Ok(Self::Paranoid), - _ => Err(Error::Validity), - } - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = [0u8; Self::OUTPUT_LEN]; - reader.read_exact(&mut b)?; - Self::from_bytes(b[0]) - } -} - -impl HeaderEncode for HashingAlgorithm { - const OUTPUT_LEN: usize = 1 + Params::OUTPUT_LEN; - type Identifier = [u8; 2]; - type Output = [u8; Self::OUTPUT_LEN]; - - fn as_bytes(&self) -> Self::Output { - match self { - Self::Argon2id(p) => [0xF2u8, p.as_bytes()], - Self::Blake3Balloon(p) => [0xA8u8, p.as_bytes()], - } - } - - fn from_bytes(b: Self::Output) -> Result { - let x = match b[0] { - 0xF2u8 => Self::Argon2id(Params::from_bytes(b[1])?), - 0xA8u8 => Self::Blake3Balloon(Params::from_bytes(b[1])?), - _ => return Err(Error::Validity), - }; - - Ok(x) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = Self::Output::default(); - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for Algorithm { - const OUTPUT_LEN: usize = 2; - type Identifier = [u8; 2]; - type Output = [u8; Self::OUTPUT_LEN]; - - fn as_bytes(&self) -> Self::Output { - let s = match self { - Self::Aes256GcmSiv => 0xD3, - Self::XChaCha20Poly1305 => 0xD5, - }; - - [13u8, s] - } - - fn from_bytes(b: Self::Output) -> Result { - if b[0] != 13u8 { - return Err(Error::Validity); - } - - let a = match b[1] { - 0xD3 => Self::Aes256GcmSiv, - 0xD5 => Self::XChaCha20Poly1305, - _ => return Err(Error::Validity), - }; - - Ok(a) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = Self::Output::default(); - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for Salt { - const OUTPUT_LEN: usize = SALT_LEN + 2; - type Identifier = [u8; 2]; - type Output = [u8; 18]; - - fn as_bytes(&self) -> Self::Output { - let mut s = [0u8; Self::OUTPUT_LEN]; - s[0] = 12u8; - s[1] = 4u8; - s[2..].copy_from_slice(self.inner()); - s - } - - fn from_bytes(b: Self::Output) -> Result { - if b[..2] != [12u8, 4u8] { - return Err(Error::Validity); - } - - let mut o = [0u8; SALT_LEN]; - o.copy_from_slice(&b[2..]); - - Ok(Self::new(o)) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = Self::Output::default(); - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for Nonce { - const OUTPUT_LEN: usize = 32; - type Identifier = [u8; 2]; - type Output = [u8; Self::OUTPUT_LEN]; - - fn as_bytes(&self) -> Self::Output { - let b = match self { - Self::Aes256GcmSiv(_) => 0xB5u8, - Self::XChaCha20Poly1305(_) => 0xB7u8, - }; - - let len = self.algorithm().nonce_len(); - - let mut s = [0u8; Self::OUTPUT_LEN]; - s[0] = 99u8; - s[1] = b; - s[2..len + 2].copy_from_slice(self.inner()); - - s[len + 2..].copy_from_slice(&self.inner()[..Self::OUTPUT_LEN - 2 - len]); - - s - } - - fn from_bytes(b: Self::Output) -> Result { - if b[0] != 99u8 { - return Err(Error::Validity); - } - - let x = match b[1] { - 0xB5u8 => Self::Aes256GcmSiv(b[2..2 + AES_256_GCM_SIV_NONCE_LEN].to_array()?), - 0xB7u8 => Self::XChaCha20Poly1305(b[2..2 + XCHACHA20_POLY1305_NONCE_LEN].to_array()?), - _ => return Err(Error::Validity), - }; - - Ok(x) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = Self::Output::default(); - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for EncryptedKey { - const OUTPUT_LEN: usize = ENCRYPTED_KEY_LEN + Nonce::OUTPUT_LEN + 2; - type Identifier = [u8; 2]; - type Output = Vec; - - fn as_bytes(&self) -> Self::Output { - let mut s = Vec::with_capacity(Self::OUTPUT_LEN); - - s.extend_from_slice(&[0x9, 0xF3]); - s.extend_from_slice(self.inner()); - s.extend_from_slice(&self.nonce().as_bytes()); - s - } - - fn from_bytes(b: Self::Output) -> Result { - if b[..2] != [9u8, 0xF3u8] { - return Err(Error::Validity); - } - - let e = Vec::from(&b[2..ENCRYPTED_KEY_LEN]).to_array()?; - let n = Nonce::from_bytes(b[2 + ENCRYPTED_KEY_LEN..].to_array()?)?; - - Ok(Self::new(e, n)) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = vec![0u8; Self::OUTPUT_LEN]; - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for Keyslot { - const OUTPUT_LEN: usize = - EncryptedKey::OUTPUT_LEN + (Salt::OUTPUT_LEN * 2) + HashingAlgorithm::OUTPUT_LEN + 2; - type Identifier = [u8; 2]; - type Output = Vec; - - fn as_bytes(&self) -> Self::Output { - let mut o = vec![0x83, 0x31]; - o.extend_from_slice(&self.hashing_algorithm.as_bytes()); - o.extend_from_slice(&self.hash_salt.as_bytes()); - o.extend_from_slice(&self.salt.as_bytes()); - o.extend_from_slice(&self.encrypted_key.as_bytes()); - o - } - - fn from_bytes(b: Self::Output) -> Result { - if b[..2] != [0x83, 0x21] { - return Err(Error::Validity); - } - - let hashing_algorithm = HashingAlgorithm::from_bytes(b[2..4].to_array()?)?; - let hash_salt = Salt::from_bytes(b[4..Salt::OUTPUT_LEN + 4].to_array()?)?; - let salt = Salt::from_bytes(b[Salt::OUTPUT_LEN + 8..Salt::OUTPUT_LEN + 12].to_array()?)?; - let ek = EncryptedKey::from_bytes(b[Salt::OUTPUT_LEN + 12..].to_vec())?; - - Ok(Self { - hashing_algorithm, - hash_salt, - salt, - encrypted_key: ek, - }) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = vec![0u8; Self::OUTPUT_LEN]; - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for HeaderObject { - const OUTPUT_LEN: usize = 0; - type Identifier = [u8; 2]; - type Output = Vec; - - fn as_bytes(&self) -> Self::Output { - let mut o = Vec::new(); - - o.extend_from_slice(&[0xF1, 51u8]); - o.extend_from_slice(&self.identifier.as_bytes()); - o.extend_from_slice(&self.nonce.as_bytes()); - - // SAFETY: this unwrap is safe as the length of the objects is capped - // will be removed in a trait overhaul which focuses on versioning too - #[allow(clippy::unwrap_used)] - o.extend_from_slice(&(TryInto::::try_into(self.data.len()).unwrap()).to_le_bytes()); - o.extend_from_slice(&self.data); - - o - } - - fn from_bytes(b: Self::Output) -> Result { - if b[..2] != [0xF1, 51u8] { - return Err(Error::Validity); - } - - let identifier = - HeaderObjectIdentifier::from_bytes(b[2..HeaderObjectIdentifier::OUTPUT_LEN].to_vec())?; - let nonce = Nonce::from_bytes( - b[HeaderObjectIdentifier::OUTPUT_LEN + 2 - ..HeaderObjectIdentifier::OUTPUT_LEN + 2 + Nonce::OUTPUT_LEN] - .to_array()?, - )?; - let data_len = u64::from_le_bytes( - b[HeaderObjectIdentifier::OUTPUT_LEN + Nonce::OUTPUT_LEN + 2 - ..HeaderObjectIdentifier::OUTPUT_LEN + Nonce::OUTPUT_LEN + 2 + 8] - .to_array()?, - ); - let data = b[HeaderObjectIdentifier::OUTPUT_LEN + Nonce::OUTPUT_LEN + 10 - ..data_len.try_into().map_err(|_| Error::Validity)?] - .to_vec(); - - Ok(Self { - identifier, - nonce, - data, - }) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut buffer = [0u8; mem::size_of::()]; - reader.read_exact(&mut buffer)?; - let size = u64::from_le_bytes(buffer); - - let mut buffer = vec![0u8; size.try_into().map_err(|_| Error::Validity)?]; - reader.read_exact(&mut buffer)?; - - Self::from_bytes(buffer) - } -} - -impl HeaderEncode for HeaderObjectIdentifier { - const OUTPUT_LEN: usize = 2 + EncryptedKey::OUTPUT_LEN + Salt::OUTPUT_LEN; - type Identifier = [u8; 2]; - type Output = Vec; - - fn as_bytes(&self) -> Self::Output { - let mut o = vec![0xC2, 0xE9]; - o.extend_from_slice(&self.key.as_bytes()); - o.extend_from_slice(&self.salt.as_bytes()); - o - } - - fn from_bytes(b: Self::Output) -> Result { - if b[..2] != [0xC2, 0xE9] { - return Err(Error::Validity); - } - - let ek = EncryptedKey::from_bytes(b[2..EncryptedKey::OUTPUT_LEN].to_vec())?; - let salt = Salt::from_bytes(b[EncryptedKey::OUTPUT_LEN + 2..].to_array()?)?; - - Ok(Self { key: ek, salt }) - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = vec![0u8; Self::OUTPUT_LEN]; - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl HeaderEncode for HeaderVersion { - const OUTPUT_LEN: usize = 2; - type Identifier = [u8; 2]; - type Output = [u8; Self::OUTPUT_LEN]; - - fn as_bytes(&self) -> Self::Output { - match self { - Self::V1 => [0xDA; 2], - } - } - - fn from_bytes(b: Self::Output) -> Result { - match b { - [0xDA, 0xDA] => Ok(Self::V1), - _ => Err(Error::Validity), - } - } - - fn from_reader(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut b = [0u8; Self::OUTPUT_LEN]; - reader.read_exact(&mut b)?; - Self::from_bytes(b) - } -} - -impl Header { - pub fn as_bytes(&self) -> Result> { - match self.version { - HeaderVersion::V1 => self.as_bytes_v1(), - } - } - - fn as_bytes_v1(&self) -> Result> { - let mut o = vec![]; - o.extend_from_slice(&[0xFA, 0xDA]); - - o.extend_from_slice(&self.version.as_bytes()); - o.extend_from_slice(&self.algorithm.as_bytes()); - o.extend_from_slice(&self.nonce.as_bytes()); - - self.keyslots - .iter() - .for_each(|k| o.extend_from_slice(&k.as_bytes())); - - (0..KEYSLOT_LIMIT - self.keyslots.len()) - .for_each(|_| o.extend_from_slice(&Keyslot::random().as_bytes())); - - o.extend_from_slice( - &(TryInto::::try_into(self.objects.len()).map_err(|_| Error::Validity)?) - .to_le_bytes(), - ); - - self.objects.iter().try_for_each(|k| { - let b = k.as_bytes(); - o.extend_from_slice( - &(TryInto::::try_into(b.len()).map_err(|_| Error::Validity)?).to_le_bytes(), - ); - o.extend_from_slice(&b); - - Ok::<_, Error>(()) - })?; - - Ok(o) - } - - pub(super) fn from_reader_raw(reader: &mut R) -> Result - where - R: std::io::Read + std::io::Seek, - { - let mut m = [0u8; 2]; - reader.read_exact(&mut m)?; - - if m != [0xFA, 0xDA] { - return Err(Error::Validity); - } - - let mut buffer = [0u8; HeaderVersion::OUTPUT_LEN]; - reader.read_exact(&mut buffer)?; - let version = HeaderVersion::from_bytes(buffer)?; - - let mut buffer = [0u8; Algorithm::OUTPUT_LEN]; - reader.read_exact(&mut buffer)?; - let algorithm = Algorithm::from_bytes(buffer)?; - - let mut nonce_buffer = [0u8; Nonce::OUTPUT_LEN]; - reader.read_exact(&mut nonce_buffer)?; - let nonce = Nonce::from_bytes(nonce_buffer)?; - nonce.validate(algorithm)?; - - // we always read the limit as there will always be extra room for additional keyslots after header creation - let keyslots = (0..KEYSLOT_LIMIT) - .filter_map(|_| { - let mut buffer = [0u8; Keyslot::OUTPUT_LEN]; - reader.read_exact(&mut buffer).ok(); - Keyslot::from_bytes(buffer.to_vec()).ok() - }) - .collect::>(); - - let mut buffer = [0u8; mem::size_of::()]; - reader.read_exact(&mut buffer)?; - let objects_len = u16::from_le_bytes(buffer); - - let objects = (0..objects_len) - .map(|_| HeaderObject::from_reader(reader)) - .collect::>>()?; - - let h = Self { - version, - algorithm, - nonce, - keyslots, - objects, - }; - - Ok(h) - } -} diff --git a/crates/crypto/src/encoding/file/object.rs b/crates/crypto/src/encoding/file/object.rs deleted file mode 100644 index 559210fa6..000000000 --- a/crates/crypto/src/encoding/file/object.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::{ - crypto::{Decryptor, Encryptor}, - hashing::Hasher, - types::{Aad, Algorithm, DerivationContext, EncryptedKey, Key, Nonce, Salt}, - Protected, Result, -}; - -#[derive(Clone)] -pub struct HeaderObjectIdentifier { - pub(super) key: EncryptedKey, // technically a key, although used as an identifier here - pub(super) salt: Salt, -} - -pub struct HeaderObject { - pub identifier: HeaderObjectIdentifier, - pub nonce: Nonce, - pub data: Vec, -} - -impl HeaderObject { - pub fn new( - name: &'static str, - algorithm: Algorithm, - master_key: &Key, - context: DerivationContext, - aad: Aad, - data: &[u8], - ) -> Result { - let identifier = HeaderObjectIdentifier::new(name, master_key, algorithm, context, aad)?; - - let nonce = Nonce::generate(algorithm); - let encrypted_data = Encryptor::encrypt_bytes(master_key, &nonce, algorithm, data, aad)?; - - let object = Self { - identifier, - nonce, - data: encrypted_data, - }; - - Ok(object) - } - - pub(super) fn decrypt( - &self, - algorithm: Algorithm, - aad: Aad, - master_key: &Key, - ) -> Result>> { - Decryptor::decrypt_bytes(master_key, &self.nonce, algorithm, &self.data, aad) - } -} - -impl HeaderObjectIdentifier { - pub fn new( - name: &'static str, - master_key: &Key, - algorithm: Algorithm, - context: DerivationContext, - aad: Aad, - ) -> Result { - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - - let encrypted_key = Encryptor::encrypt_key( - &Hasher::derive_key(master_key, salt, context), - &nonce, - algorithm, - &Hasher::blake3(name.as_bytes()), - aad, - )?; - - Ok(Self { - key: encrypted_key, - salt, - }) - } - - pub(super) fn decrypt_id( - &self, - master_key: &Key, - algorithm: Algorithm, - context: DerivationContext, - aad: Aad, - ) -> Result { - Decryptor::decrypt_key( - &Hasher::derive_key(master_key, self.salt, context), - algorithm, - &self.key, - aad, - ) - } -} diff --git a/crates/crypto/src/encoding/mod.rs b/crates/crypto/src/encoding/mod.rs deleted file mode 100644 index 8d2fe1005..000000000 --- a/crates/crypto/src/encoding/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod bincode; -pub mod file; - -pub use self::bincode::{decode, decode_from_reader, encode}; - -pub use file::header::Header; diff --git a/crates/crypto/src/encrypted.rs b/crates/crypto/src/encrypted.rs deleted file mode 100644 index 2adc7e460..000000000 --- a/crates/crypto/src/encrypted.rs +++ /dev/null @@ -1,143 +0,0 @@ -use bincode::{Decode, Encode}; -use std::marker::PhantomData; - -use crate::{ - crypto::{Decryptor, Encryptor}, - encoding::{decode, encode}, - hashing::Hasher, - primitives::ENCRYPTED_TYPE_CONTEXT, - types::{Aad, Algorithm, Key, Nonce, Salt}, - Protected, Result, -}; - -#[derive(Clone, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct Encrypted { - data: Vec, - algorithm: Algorithm, - nonce: Nonce, - salt: Salt, - #[cfg_attr(feature = "specta", specta(skip))] - _type: PhantomData, -} - -impl Encrypted { - pub fn new(key: &Key, item: &T, algorithm: Algorithm) -> Result - where - T: Encode + Decode, - { - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - - let bytes = Encryptor::encrypt_tiny( - &Hasher::derive_key(key, salt, ENCRYPTED_TYPE_CONTEXT), - &nonce, - algorithm, - &encode(item)?, - Aad::Null, - )?; - - Ok(Self { - data: bytes, - algorithm, - salt, - nonce, - _type: PhantomData, - }) - } - - pub fn new_from_bytes( - key: &Key, - item: &Protected>, - algorithm: Algorithm, - ) -> Result { - let salt = Salt::generate(); - let nonce = Nonce::generate(algorithm); - - let bytes = Encryptor::encrypt_tiny( - &Hasher::derive_key(key, salt, ENCRYPTED_TYPE_CONTEXT), - &nonce, - algorithm, - item.expose(), - Aad::Null, - )?; - - Ok(Self { - data: bytes, - algorithm, - salt, - nonce, - _type: PhantomData, - }) - } - - pub fn decrypt(self, key: &Key) -> Result - where - T: Encode + Decode, - { - let bytes = Decryptor::decrypt_bytes( - &Hasher::derive_key(key, self.salt, ENCRYPTED_TYPE_CONTEXT), - &self.nonce, - self.algorithm, - &self.data, - Aad::Null, - )? - .into_inner(); - - decode(&bytes) - } - - pub fn decrypt_bytes(self, key: &Key) -> Result>> { - let bytes = Decryptor::decrypt_bytes( - &Hasher::derive_key(key, self.salt, ENCRYPTED_TYPE_CONTEXT), - &self.nonce, - self.algorithm, - &self.data, - Aad::Null, - )? - .into_inner(); - - Ok(bytes.into()) - } - - pub fn as_bytes(&self) -> Result> - where - T: Encode + Decode, - { - encode(&self) - } - - // check if key is okay - #[must_use] - pub fn validate_key(&self, key: &Key) -> bool { - Decryptor::decrypt_bytes( - &Hasher::derive_key(key, self.salt, ENCRYPTED_TYPE_CONTEXT), - &self.nonce, - self.algorithm, - &self.data, - Aad::Null, - ) - .is_ok() - } - - #[must_use] - pub fn get_bytes(&self) -> Vec { - self.data.clone() - } - - #[must_use] - pub const fn get_salt(&self) -> Salt { - self.salt - } - - #[must_use] - pub const fn get_nonce(&self) -> Nonce { - self.nonce - } - - #[must_use] - pub const fn get_algorithm(&self) -> Algorithm { - self.algorithm - } -} diff --git a/crates/crypto/src/sys/fs/erase.rs b/crates/crypto/src/erase.rs similarity index 71% rename from crates/crypto/src/sys/fs/erase.rs rename to crates/crypto/src/erase.rs index d5e1f9bc0..f9a327f61 100644 --- a/crates/crypto/src/sys/fs/erase.rs +++ b/crates/crypto/src/erase.rs @@ -1,51 +1,12 @@ -use crate::{primitives::BLOCK_LEN, rng::CryptoRng, Result}; +use crate::{rng::CryptoRng, Error}; + use std::io::{Read, Seek, Write}; use rand_core::RngCore; -#[cfg(feature = "tokio")] use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; -/// This is used for erasing a stream. -/// -/// It requires the size, an input stream and the amount of passes (to overwrite the entire stream with random data) -/// -/// It works against `BLOCK_LEN`. -/// -/// Note, it will not be ideal on flash-based storage devices. -/// The drive will be worn down, and due to wear-levelling built into the drive's firmware no tool (short of an ATA secure erase command) -/// can guarantee a perfect erasure on solid-state drives. -/// -/// This also does not factor in temporary files, caching, thumbnails, etc. -/// -/// If you are dealing with files, ensure that you truncate the length to zero before removing it via the standard -/// filesystem deletion function. -pub fn erase(stream: &mut RW, size: usize, passes: usize) -> Result -where - RW: Read + Write + Seek, -{ - let mut count = 0usize; - - let mut buf = vec![0u8; BLOCK_LEN].into_boxed_slice(); - let mut end_buf = vec![0u8; size % BLOCK_LEN].into_boxed_slice(); - - for _ in 0..passes { - stream.rewind()?; - for _ in 0..(size / BLOCK_LEN) { - CryptoRng::new().fill_bytes(&mut buf); - stream.write_all(&buf)?; - count += BLOCK_LEN; - } - - CryptoRng::new().fill_bytes(&mut end_buf); - stream.write_all(&end_buf)?; - stream.flush()?; - count += size % BLOCK_LEN; - } - - stream.rewind()?; - - Ok(count) -} +/// Erasing in blocks of 1MiB +const BLOCK_LEN: usize = 1_048_576; /// This is used for erasing a stream asynchronously. /// @@ -61,50 +22,124 @@ where /// /// If you are dealing with files, ensure that you truncate the length to zero before removing it via the standard /// filesystem deletion function. -#[cfg(feature = "tokio")] -pub async fn erase_async(stream: &mut RW, size: usize, passes: usize) -> Result +pub async fn erase(stream: &mut RW, size: usize, passes: usize) -> Result where RW: AsyncReadExt + AsyncWriteExt + AsyncSeekExt + Unpin + Send, { - let mut count = 0usize; + let mut rng = CryptoRng::new()?; let mut buf = vec![0u8; BLOCK_LEN].into_boxed_slice(); let mut end_buf = vec![0u8; size % BLOCK_LEN].into_boxed_slice(); + let mut count = 0usize; for _ in 0..passes { - stream.rewind().await?; + stream.rewind().await.map_err(|e| Error::EraseIo { + context: "Rewinding stream", + source: e, + })?; for _ in 0..(size / BLOCK_LEN) { - CryptoRng::new().fill_bytes(&mut buf); - stream.write_all(&buf).await?; + rng.fill_bytes(&mut buf); + stream.write_all(&buf).await.map_err(|e| Error::EraseIo { + context: "Writing random bytes to stream", + source: e, + })?; count += BLOCK_LEN; } - CryptoRng::new().fill_bytes(&mut end_buf); - stream.write_all(&end_buf).await?; - stream.flush().await?; + rng.fill_bytes(&mut end_buf); + stream + .write_all(&end_buf) + .await + .map_err(|e| Error::EraseIo { + context: "Writing last block to stream", + source: e, + })?; + stream.flush().await.map_err(|e| Error::EraseIo { + context: "Flushing stream", + source: e, + })?; count += size % BLOCK_LEN; } - stream.rewind().await?; + stream.rewind().await.map_err(|e| Error::EraseIo { + context: "Final stream rewind", + source: e, + })?; + + Ok(count) +} + +/// This is used for erasing a stream. +/// +/// It requires the size, an input stream and the amount of passes (to overwrite the entire stream with random data) +/// +/// It works against `BLOCK_LEN`. +/// +/// Note, it will not be ideal on flash-based storage devices. +/// The drive will be worn down, and due to wear-levelling built into the drive's firmware no tool (short of an ATA secure erase command) +/// can guarantee a perfect erasure on solid-state drives. +/// +/// This also does not factor in temporary files, caching, thumbnails, etc. +/// +/// If you are dealing with files, ensure that you truncate the length to zero before removing it via the standard +/// filesystem deletion function. +pub fn erase_sync(stream: &mut RW, size: usize, passes: usize) -> Result +where + RW: Read + Write + Seek, +{ + let mut rng = CryptoRng::new()?; + + let mut buf = vec![0u8; BLOCK_LEN].into_boxed_slice(); + let mut end_buf = vec![0u8; size % BLOCK_LEN].into_boxed_slice(); + + let mut count = 0; + for _ in 0..passes { + stream.rewind().map_err(|e| Error::EraseIo { + context: "Rewinding stream", + source: e, + })?; + for _ in 0..(size / BLOCK_LEN) { + rng.fill_bytes(&mut buf); + stream.write_all(&buf).map_err(|e| Error::EraseIo { + context: "Writing random bytes to stream", + source: e, + })?; + count += BLOCK_LEN; + } + + rng.fill_bytes(&mut end_buf); + stream.write_all(&end_buf).map_err(|e| Error::EraseIo { + context: "Writing last block to stream", + source: e, + })?; + stream.flush().map_err(|e| Error::EraseIo { + context: "Flushing stream", + source: e, + })?; + count += size % BLOCK_LEN; + } + + stream.rewind().map_err(|e| Error::EraseIo { + context: "Final stream rewind", + source: e, + })?; Ok(count) } #[cfg(test)] mod tests { - use crate::{ct::ConstantTimeEqNull, primitives::BLOCK_LEN}; + use crate::ct::ConstantTimeEqNull; + use std::io::Cursor; - use super::erase; - - #[cfg(feature = "tokio")] - use super::erase_async; + use super::{erase, erase_sync, BLOCK_LEN}; #[test] #[cfg_attr(miri, ignore)] fn erase_block_one_pass() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase(&mut buffer, BLOCK_LEN, 1).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN, 1).unwrap(); assert_eq!(count, BLOCK_LEN); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -114,7 +149,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_block_two_passes() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase(&mut buffer, BLOCK_LEN, 2).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN, 2).unwrap(); assert_eq!(count, BLOCK_LEN * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -124,7 +159,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_5_blocks_one_pass() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN * 5]); - let count = erase(&mut buffer, BLOCK_LEN * 5, 1).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN * 5, 1).unwrap(); assert_eq!(count, BLOCK_LEN * 5); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -134,7 +169,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_5_blocks_two_passes() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN * 5]); - let count = erase(&mut buffer, BLOCK_LEN * 5, 2).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN * 5, 2).unwrap(); assert_eq!(count, (BLOCK_LEN * 5) * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -144,7 +179,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_small() { let mut buffer = Cursor::new(vec![0u8; 1024]); - let count = erase(&mut buffer, 1024, 1).unwrap(); + let count = erase_sync(&mut buffer, 1024, 1).unwrap(); assert_eq!(count, 1024); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -154,7 +189,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_small_two_passes() { let mut buffer = Cursor::new(vec![0u8; 1024]); - let count = erase(&mut buffer, 1024, 2).unwrap(); + let count = erase_sync(&mut buffer, 1024, 2).unwrap(); assert_eq!(count, 1024 * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -164,7 +199,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_block_plus_512() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN + 512]); - let count = erase(&mut buffer, BLOCK_LEN + 512, 1).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN + 512, 1).unwrap(); assert_eq!(count, BLOCK_LEN + 512); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -174,7 +209,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_block_plus_512_two_passes() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN + 512]); - let count = erase(&mut buffer, BLOCK_LEN + 512, 2).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN + 512, 2).unwrap(); assert_eq!(count, (BLOCK_LEN + 512) * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); @@ -184,106 +219,97 @@ mod tests { #[cfg_attr(miri, ignore)] fn erase_block_eight_passes() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase(&mut buffer, BLOCK_LEN, 8).unwrap(); + let count = erase_sync(&mut buffer, BLOCK_LEN, 8).unwrap(); assert_eq!(count, BLOCK_LEN * 8); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_block_one_pass_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase_async(&mut buffer, BLOCK_LEN, 1).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN, 1).await.unwrap(); assert_eq!(count, BLOCK_LEN); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_block_two_passes_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase_async(&mut buffer, BLOCK_LEN, 2).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN, 2).await.unwrap(); assert_eq!(count, BLOCK_LEN * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_5_blocks_one_pass_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN * 5]); - let count = erase_async(&mut buffer, BLOCK_LEN * 5, 1).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN * 5, 1).await.unwrap(); assert_eq!(count, BLOCK_LEN * 5); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_5_blocks_two_passes_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN * 5]); - let count = erase_async(&mut buffer, BLOCK_LEN * 5, 2).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN * 5, 2).await.unwrap(); assert_eq!(count, (BLOCK_LEN * 5) * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_small_async() { let mut buffer = Cursor::new(vec![0u8; 1024]); - let count = erase_async(&mut buffer, 1024, 1).await.unwrap(); + let count = erase(&mut buffer, 1024, 1).await.unwrap(); assert_eq!(count, 1024); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_small_two_passes_async() { let mut buffer = Cursor::new(vec![0u8; 1024]); - let count = erase_async(&mut buffer, 1024, 2).await.unwrap(); + let count = erase(&mut buffer, 1024, 2).await.unwrap(); assert_eq!(count, 1024 * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_block_plus_512_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN + 512]); - let count = erase_async(&mut buffer, BLOCK_LEN + 512, 1).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN + 512, 1).await.unwrap(); assert_eq!(count, BLOCK_LEN + 512); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_block_plus_512_two_passes_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN + 512]); - let count = erase_async(&mut buffer, BLOCK_LEN + 512, 2).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN + 512, 2).await.unwrap(); assert_eq!(count, (BLOCK_LEN + 512) * 2); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); } #[tokio::test] - #[cfg(feature = "tokio")] #[cfg_attr(miri, ignore)] async fn erase_block_eight_passes_async() { let mut buffer = Cursor::new(vec![0u8; BLOCK_LEN]); - let count = erase_async(&mut buffer, BLOCK_LEN, 8).await.unwrap(); + let count = erase(&mut buffer, BLOCK_LEN, 8).await.unwrap(); assert_eq!(count, BLOCK_LEN * 8); assert_eq!(buffer.position(), 0); assert!(bool::from(buffer.into_inner().ct_ne_null())); diff --git a/crates/crypto/src/error.rs b/crates/crypto/src/error.rs index 83856b37d..f7444b20f 100644 --- a/crates/crypto/src/error.rs +++ b/crates/crypto/src/error.rs @@ -1,102 +1,45 @@ //! This module contains all possible errors that this crate can return. -use std::string::FromUtf8Error; - -impl From for bincode::error::EncodeError { - fn from(value: Error) -> Self { - Self::OtherString(value.to_string()) - } -} - -pub type Result = std::result::Result; +use tokio::io; /// This enum defines all possible errors that this crate can give -#[allow(deprecated)] #[derive(thiserror::Error, Debug)] pub enum Error { - // crypto primitive errors (STREAM, hashing) - #[error("there was an error while password hashing")] - Hashing, - #[error("error while encrypting")] + #[error("Block too big for oneshot encryption: size in bytes = {0}")] + BlockTooBig(usize), + + /// Encrypt and decrypt errors, AEAD crate doesn't provide any error context for these + /// as it can be a security hazard to leak information about the error. + #[error("Encryption error")] Encrypt, - #[error("error while decrypting (could be: wrong key, wrong data, wrong aad, etc)")] + #[error("Decryption error")] Decrypt, - // header errors - #[error("no keyslots available")] - NoKeyslots, - #[error("tried adding too many keyslots to a header")] - TooManyKeyslots, - #[error("no header objects available (or none that match)")] - NoObjects, - #[error("tried adding too many objects to a header (or too many with the same name)")] - TooManyObjects, - #[error("read magic bytes aren't equal to the expected bytes")] - MagicByteMismatch, + /// I/O error while encrypting + #[error("I/O error while encrypting: {{context: {context}, source: {source}}}")] + EncryptIo { + context: &'static str, + #[source] + source: io::Error, + }, + #[error("I/O error while decrypting: {{context: {context}, source: {source}}}")] + DecryptIo { + context: &'static str, + #[source] + source: io::Error, + }, - #[error("error while encoding with bincode: {0}")] - BincodeEncode(#[from] bincode::error::EncodeError), - - #[error("error while decoding with bincode: {0}")] - BincodeDecode(#[from] bincode::error::DecodeError), - - // #[cfg(feature = "serde")] - // #[error("error while encoding with serde")] - // Serde, - #[error("keystore error")] - Keystore, - - #[error("redb error: {0}")] - Redb(#[from] redb::Error), - #[error("redb error: {0}")] - RedbDatabase(#[from] redb::DatabaseError), - #[error("redb error: {0}")] - RedbTransaction(#[from] redb::TransactionError), - #[error("redb error: {0}")] - RedbTable(#[from] redb::TableError), - #[error("redb error: {0}")] - RedbStorage(#[from] redb::StorageError), - #[error("redb error: {0}")] - RedbCommit(#[from] redb::CommitError), - - #[error("vault root key already exists")] - RootKeyAlreadyExists, - - // general errors - #[error("expected length differs from provided length")] - LengthMismatch, - - // TODO(brxken128): remove this, and add appropriate/correct errors - #[error("expected type/value differs from provided")] - Validity, - #[error("string parse error")] - StringParse(#[from] FromUtf8Error), - - // i/o - #[cfg(not(feature = "tokio"))] - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), - #[cfg(feature = "tokio")] - #[error("I/O error: {0}")] - AsyncIo(#[from] tokio::io::Error), - #[cfg(feature = "tokio")] - #[error("Async task join error: {0}")] - JoinError(#[from] tokio::task::JoinError), + /// I/O error while erasing a file + #[error("I/O error while erasing: {{context: {context}, source: {source}}}")] + EraseIo { + context: &'static str, + #[source] + source: io::Error, + }, #[error("hex error: {0}")] Hex(#[from] hex::FromHexError), - // keyring - #[cfg(all(target_os = "linux", feature = "keyring"))] - #[error("error with the keyutils keyring: {0}")] - KeyUtils(#[from] linux_keyutils::KeyError), - #[cfg(all(target_os = "linux", feature = "keyring", feature = "secret-service"))] - #[error("error with the secret service keyring: {0}")] - SecretService(#[from] secret_service::Error), - #[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "keyring"))] - #[error("error with the apple keyring: {0}")] - AppleKeyring(#[from] security_framework::base::Error), - #[cfg(feature = "keyring")] - #[error("generic keyring error")] - Keyring, + #[error("Entropy source error: {0}")] + EntropySource(#[from] rand_core::getrandom::Error), } diff --git a/crates/crypto/src/hashing.rs b/crates/crypto/src/hashing.rs deleted file mode 100644 index 398fe6938..000000000 --- a/crates/crypto/src/hashing.rs +++ /dev/null @@ -1,399 +0,0 @@ -//! This module contains all password-hashing related functions. -//! -//! Everything contained within is used to hash a user's password into strong key material, suitable for encrypting master keys. -//! -//! # Examples -//! -//! ```rust,ignore -//! let password = Protected::new(b"password".to_vec()); -//! let hashing_algorithm = HashingAlgorithm::default(); -//! let salt = generate_salt(); -//! let hashed_password = hashing_algorithm.hash(password, salt).unwrap(); -//! ``` - -use crate::{ - primitives::KEY_LEN, - types::{DerivationContext, HashingAlgorithm, Key, Salt, SecretKey}, - Error, Protected, Result, -}; -use argon2::Argon2; -use balloon_hash::Balloon; -use zeroize::Zeroizing; - -pub struct Hasher; - -impl Hasher { - #[must_use] - pub fn blake3(bytes: &[u8]) -> Key { - blake3::hash(bytes).into() - } - - /// This is the same as `Hasher::blake3`, but returns a lowercase hex `String` - /// - /// This is not implemented for `Key` as a safety measure. - #[must_use] - pub fn blake3_hex(bytes: &[u8]) -> String { - blake3::hash(bytes).to_hex().to_string() - } - - /// This can be used to derive a key with BLAKE3-KDF, with both a salt and a derivation context. - #[must_use] - pub fn derive_key(key: &Key, salt: Salt, context: DerivationContext) -> Key { - let k = blake3::derive_key(context.inner(), &[key.expose(), salt.inner()].concat()); - Key::new(k) - } - - pub fn hash_password( - algorithm: HashingAlgorithm, - password: &Protected>, - salt: Salt, - secret: &SecretKey, - ) -> Result { - let d = algorithm.get_parameters(); - - match algorithm { - HashingAlgorithm::Argon2id(_) => Self::argon2id(password, salt, secret, d), - HashingAlgorithm::Blake3Balloon(_) => Self::blake3_balloon(password, salt, secret, d), - } - } - - fn argon2id( - password: &Protected>, - salt: Salt, - secret: &SecretKey, - params: (u32, u32, u32), - ) -> Result { - let p = - argon2::Params::new(params.0, params.1, params.2, None).map_err(|_| Error::Hashing)?; - - let mut key = Zeroizing::new([0u8; KEY_LEN]); - - let argon2 = Argon2::new_with_secret( - secret.expose(), - argon2::Algorithm::Argon2id, - argon2::Version::V0x13, - p, - ) - .map_err(|_| Error::Hashing)?; - - argon2 - .hash_password_into(password.expose(), salt.inner(), key.as_mut_slice()) - .map_or(Err(Error::Hashing), |()| Ok(Key::new(*key))) - } - - fn blake3_balloon( - password: &Protected>, - salt: Salt, - secret: &SecretKey, - params: (u32, u32, u32), - ) -> Result { - let p = - balloon_hash::Params::new(params.0, params.1, params.2).map_err(|_| Error::Hashing)?; - - let mut key = Zeroizing::new([0u8; KEY_LEN]); - - let balloon = Balloon::::new( - balloon_hash::Algorithm::Balloon, - p, - Some(secret.expose()), - ); - - balloon - .hash_into(password.expose(), salt.inner(), key.as_mut_slice()) - .map_or(Err(Error::Hashing), |()| Ok(Key::new(*key))) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - hashing::Hasher, - primitives::{KEY_LEN, SALT_LEN, SECRET_KEY_LEN}, - types::{DerivationContext, HashingAlgorithm, Key, Params, Salt, SecretKey}, - }; - - // don't do this in production code - use separate contexts for keys and objects - const CONTEXT: DerivationContext = - DerivationContext::new("crypto 2023-03-20 20:12:42 global test context"); - - const ARGON2ID_STANDARD: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Standard); - const ARGON2ID_HARDENED: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Hardened); - const ARGON2ID_PARANOID: HashingAlgorithm = HashingAlgorithm::Argon2id(Params::Paranoid); - const BLAKE3_BALLOON_STANDARD: HashingAlgorithm = - HashingAlgorithm::Blake3Balloon(Params::Standard); - const BLAKE3_BALLOON_HARDENED: HashingAlgorithm = - HashingAlgorithm::Blake3Balloon(Params::Hardened); - const BLAKE3_BALLOON_PARANOID: HashingAlgorithm = - HashingAlgorithm::Blake3Balloon(Params::Paranoid); - - const PASSWORD: [u8; 8] = [0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64]; - - const SALT: Salt = Salt::new([0xFF; SALT_LEN]); - const SECRET_KEY: SecretKey = SecretKey::new([0x55; SECRET_KEY_LEN]); - - #[test] - #[ignore] - fn argon2id_standard() { - let output = Hasher::hash_password( - ARGON2ID_STANDARD, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 194, 153, 245, 125, 12, 102, 65, 30, 254, 191, 9, 125, 4, 113, 99, 209, 162, 43, - 140, 93, 217, 220, 222, 46, 105, 48, 123, 220, 180, 103, 20, 11, - ]) - ); - } - - #[test] - #[ignore] - fn argon2id_standard_with_secret() { - let output = Hasher::hash_password( - ARGON2ID_STANDARD, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 132, 102, 123, 67, 87, 219, 88, 76, 81, 191, 128, 41, 246, 201, 103, 155, 200, 114, - 54, 116, 240, 66, 155, 78, 73, 44, 87, 174, 231, 196, 206, 236, - ]) - ); - } - - #[test] - #[ignore] - fn argon2id_hardened() { - let output = Hasher::hash_password( - ARGON2ID_HARDENED, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 173, 45, 167, 171, 125, 13, 245, 47, 231, 62, 175, 215, 21, 253, 84, 188, 249, 68, - 229, 98, 16, 55, 110, 202, 105, 109, 102, 71, 216, 125, 170, 66, - ]) - ); - } - - #[test] - #[ignore] - fn argon2id_hardened_with_secret() { - let output = Hasher::hash_password( - ARGON2ID_HARDENED, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 246, 200, 29, 33, 86, 21, 66, 177, 154, 2, 134, 181, 254, 148, 104, 205, 235, 108, - 121, 127, 184, 230, 109, 240, 128, 101, 137, 179, 212, 89, 37, 41, - ]) - ); - } - - #[test] - #[ignore] - fn argon2id_paranoid() { - let output = Hasher::hash_password( - ARGON2ID_PARANOID, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 27, 158, 230, 75, 99, 236, 40, 137, 60, 237, 145, 119, 159, 207, 56, 50, 210, 5, - 157, 227, 162, 162, 148, 142, 230, 237, 138, 133, 112, 182, 156, 198, - ]) - ); - } - - #[test] - #[ignore] - fn argon2id_paranoid_with_secret() { - let output = Hasher::hash_password( - ARGON2ID_PARANOID, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 3, 60, 179, 196, 172, 30, 0, 201, 15, 9, 213, 59, 37, 219, 173, 134, 132, 166, 32, - 60, 33, 216, 3, 249, 185, 120, 110, 14, 155, 242, 134, 215, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_standard() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_STANDARD, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 105, 36, 165, 219, 22, 136, 156, 19, 32, 143, 237, 150, 236, 194, 70, 113, 73, 137, - 243, 106, 80, 31, 43, 73, 207, 210, 29, 251, 88, 6, 132, 77, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_standard_with_secret() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_STANDARD, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 188, 0, 43, 39, 137, 199, 91, 142, 97, 31, 98, 6, 130, 75, 251, 71, 150, 109, 29, - 62, 237, 171, 210, 22, 139, 108, 94, 190, 91, 74, 134, 47, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_hardened() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_HARDENED, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 179, 71, 60, 122, 54, 72, 132, 209, 146, 96, 15, 115, 41, 95, 5, 75, 214, 135, 6, - 122, 82, 42, 158, 9, 117, 19, 19, 40, 48, 233, 207, 237, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_hardened_with_secret() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_HARDENED, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 19, 247, 102, 192, 129, 184, 29, 147, 68, 215, 234, 146, 153, 221, 65, 134, 68, - 120, 207, 209, 184, 246, 127, 131, 9, 245, 91, 250, 220, 61, 76, 248, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_paranoid() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_PARANOID, - &PASSWORD.to_vec().into(), - SALT, - &SecretKey::Null, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 233, 60, 62, 184, 29, 152, 111, 46, 239, 126, 98, 90, 211, 255, 151, 0, 10, 189, - 61, 84, 229, 11, 245, 228, 47, 114, 87, 74, 227, 67, 24, 141, - ]) - ); - } - - #[test] - #[ignore] - fn blake3_balloon_paranoid_with_secret() { - let output = Hasher::hash_password( - BLAKE3_BALLOON_PARANOID, - &PASSWORD.to_vec().into(), - SALT, - &SECRET_KEY, - ) - .unwrap(); - - assert_eq!( - output, - Key::new([ - 165, 240, 162, 25, 172, 3, 232, 2, 43, 230, 226, 128, 174, 28, 211, 61, 139, 136, - 221, 197, 16, 83, 221, 18, 212, 190, 138, 79, 239, 148, 89, 215, - ]) - ); - } - - #[test] - fn blake3_kdf() { - let output = Hasher::derive_key(&Key::new([0x23; KEY_LEN]), SALT, CONTEXT); - - assert_eq!( - output, - Key::new([ - 88, 23, 212, 172, 220, 212, 247, 196, 129, 100, 18, 49, 208, 134, 247, 53, 83, 242, - 143, 131, 58, 249, 130, 168, 70, 245, 250, 128, 106, 170, 175, 255, - ]) - ); - } - - #[test] - fn blake3_hash() { - let output = Hasher::blake3(&PASSWORD); - - assert_eq!( - output, - Key::new([ - 127, 38, 17, 186, 21, 139, 109, 206, 164, 166, 156, 34, 156, 48, 51, 88, 197, 224, - 68, 147, 171, 234, 222, 225, 6, 164, 191, 164, 100, 213, 87, 135, - ]) - ); - } -} diff --git a/crates/crypto/src/keyring/apple/ios.rs b/crates/crypto/src/keyring/apple/ios.rs deleted file mode 100644 index 2278d4710..000000000 --- a/crates/crypto/src/keyring/apple/ios.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! This is Spacedrive's `iOS` keyring integration. It has no strict dependencies. -use crate::{ - keyring::{Identifier, KeyringBackend, KeyringInterface}, - Error, Protected, Result, -}; -use security_framework::passwords::{ - delete_generic_password, get_generic_password, set_generic_password, -}; - -pub struct IosKeyring; - -impl KeyringInterface for IosKeyring { - fn new() -> Result { - Ok(Self {}) - } - - fn name(&self) -> KeyringBackend { - KeyringBackend::Ios - } - - fn get(&self, id: &Identifier) -> Result>> { - get_generic_password(&id.application(), &id.as_apple_identifer()) - .map_err(Error::AppleKeyring) - .map(Into::into) - } - - fn contains_key(&self, id: &Identifier) -> bool { - get_generic_password(&id.application(), &id.as_apple_identifer()).map_or(false, |_| true) - } - - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - set_generic_password(&id.application(), &id.as_apple_identifer(), value.expose()) - .map_err(Error::AppleKeyring) - } - - fn remove(&self, id: &Identifier) -> Result<()> { - delete_generic_password(&id.application(), &id.as_apple_identifer()) - .map_err(Error::AppleKeyring) - } -} diff --git a/crates/crypto/src/keyring/apple/macos.rs b/crates/crypto/src/keyring/apple/macos.rs deleted file mode 100644 index 8606ff493..000000000 --- a/crates/crypto/src/keyring/apple/macos.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! This is Spacedrive's `MacOS` keyring integration. It has no strict dependencies. -use crate::{ - keyring::{Identifier, KeyringBackend, KeyringInterface}, - Error, Protected, Result, -}; -use security_framework::os::macos::keychain::SecKeychain; - -pub struct MacosKeyring { - inner: SecKeychain, -} - -impl KeyringInterface for MacosKeyring { - fn new() -> Result { - Ok(Self { - inner: SecKeychain::default()?, - }) - } - - fn get(&self, id: &Identifier) -> Result>> { - Ok(self - .inner - .find_generic_password(&id.application(), &id.as_apple_identifer()) - .map_err(Error::AppleKeyring)? - .0 - .to_owned() - .into()) - } - - fn contains_key(&self, id: &Identifier) -> bool { - self.inner - .find_generic_password(&id.application(), &id.as_apple_identifer()) - .map_or(false, |_| true) - } - - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - self.inner - .set_generic_password(&id.application(), &id.as_apple_identifer(), value.expose()) - .map_err(Error::AppleKeyring) - } - - fn remove(&self, id: &Identifier) -> Result<()> { - self.inner - .find_generic_password(&id.application(), &id.as_apple_identifer()) - .map_err(Error::AppleKeyring)? - .1 - .delete(); - - Ok(()) - } - - fn name(&self) -> KeyringBackend { - KeyringBackend::MacOS - } -} diff --git a/crates/crypto/src/keyring/apple/mod.rs b/crates/crypto/src/keyring/apple/mod.rs deleted file mode 100644 index 34eb56e2d..000000000 --- a/crates/crypto/src/keyring/apple/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[cfg(target_os = "macos")] -mod macos; -#[cfg(target_os = "macos")] -pub use macos::MacosKeyring; - -#[cfg(target_os = "ios")] -mod ios; -#[cfg(target_os = "ios")] -pub use ios::IosKeyring; diff --git a/crates/crypto/src/keyring/identifier.rs b/crates/crypto/src/keyring/identifier.rs deleted file mode 100644 index e184023d2..000000000 --- a/crates/crypto/src/keyring/identifier.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::hashing::Hasher; - -#[derive(Clone)] -pub struct Identifier { - id: String, - usage: String, - application: String, -} - -impl Identifier { - #[inline] - #[must_use] - pub fn new(id: &str, usage: &str, application: &str) -> Self { - Self { - id: id.to_string(), - usage: usage.to_string(), - application: application.to_string(), - } - } - - pub fn application(&self) -> String { - self.application.to_string() - } - - #[inline] - #[must_use] - pub(super) fn hash(&self) -> String { - format!( - "{}:{}", - self.application, - Hasher::blake3_hex(&[self.id.as_bytes(), self.usage.as_bytes()].concat()) - ) - } - - #[inline] - #[must_use] - #[cfg(any(target_os = "ios", target_os = "macos"))] - pub(super) fn as_apple_identifer(&self) -> String { - format!("{} - {}", self.id, self.usage) - } - - #[inline] - #[must_use] - #[cfg(all(target_os = "linux", feature = "secret-service"))] - pub(super) fn as_sec_ser_identifier(&self) -> std::collections::HashMap<&str, &str> { - std::collections::HashMap::from([(self.id.as_str(), self.usage.as_str())]) - } -} diff --git a/crates/crypto/src/keyring/linux/keyutils.rs b/crates/crypto/src/keyring/linux/keyutils.rs deleted file mode 100644 index af282b574..000000000 --- a/crates/crypto/src/keyring/linux/keyutils.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! This is Spacedrive's Linux keyring implementation, which makes use of the `keyutils` API (provided by modern Linux kernels). -use crate::keyring::{Identifier, KeyringBackend, KeyringInterface, LinuxKeyring}; -use crate::{Protected, Result}; -use linux_keyutils::{KeyPermissionsBuilder, KeyRing, KeyRingIdentifier, Permission}; - -pub struct KeyutilsKeyring { - session: KeyRing, - persistent: KeyRing, -} - -const WEEK: usize = 604_800; - -impl KeyutilsKeyring { - pub fn new() -> Result { - Ok(Self { - session: KeyRing::from_special_id(KeyRingIdentifier::Session, false)?, - persistent: KeyRing::get_persistent(KeyRingIdentifier::Session)?, - }) - } -} - -impl KeyringInterface for KeyutilsKeyring { - fn new() -> Result { - Self::new() - } - - fn name(&self) -> KeyringBackend { - KeyringBackend::Linux(LinuxKeyring::Keyutils) - } - - fn contains_key(&self, id: &Identifier) -> bool { - self.session.search(&id.hash()).map_or(false, |_| true) - } - - fn get(&self, id: &Identifier) -> Result>> { - let key = self.session.search(&id.hash())?; - - self.session.link_key(key)?; - self.persistent.link_key(key)?; - - Ok(Protected::new(key.read_to_vec()?)) - } - - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - let key = self.session.add_key(&id.hash(), value.expose())?; - key.set_timeout(WEEK)?; - - let perms = KeyPermissionsBuilder::builder() - .posessor(Permission::ALL) - .user(Permission::empty()) - .group(Permission::empty()) - .world(Permission::empty()) - .build(); - - key.set_perms(perms)?; - - self.persistent.link_key(key)?; - - Ok(()) - } - - fn remove(&self, id: &Identifier) -> Result<()> { - let key = self.session.search(&id.hash())?; - - key.invalidate()?; - - Ok(()) - } -} diff --git a/crates/crypto/src/keyring/linux/mod.rs b/crates/crypto/src/keyring/linux/mod.rs deleted file mode 100644 index 0e8bf9bef..000000000 --- a/crates/crypto/src/keyring/linux/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod keyutils; -pub use keyutils::KeyutilsKeyring; - -#[cfg(feature = "secret-service")] -mod secret_service; -#[cfg(feature = "secret-service")] -pub use secret_service::SecretServiceKeyring; diff --git a/crates/crypto/src/keyring/linux/secret_service.rs b/crates/crypto/src/keyring/linux/secret_service.rs deleted file mode 100644 index 334a52b88..000000000 --- a/crates/crypto/src/keyring/linux/secret_service.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! This is Spacedrive's Linux keyring implementation, which makes use of the `secret-service` API (provided by `gnome-passwords` and `kwallet`). -use crate::keyring::{Identifier, KeyringBackend, KeyringInterface, LinuxKeyring}; -use crate::{Error, Protected, Result}; -use secret_service::blocking::{Collection, SecretService}; -use secret_service::EncryptionType; - -pub struct SecretServiceKeyring { - session: SecretService<'static>, -} - -impl SecretServiceKeyring { - fn new() -> Result { - Ok(Self { - session: SecretService::connect(EncryptionType::Dh)?, - }) - } - - fn get_collection(&self) -> Result> { - let k = self.session.get_default_collection()?; - k.unlock()?; - - Ok(k) - } -} - -impl KeyringInterface for SecretServiceKeyring { - fn new() -> Result { - Self::new() - } - - fn name(&self) -> KeyringBackend { - KeyringBackend::Linux(LinuxKeyring::SecretService) - } - - fn contains_key(&self, id: &Identifier) -> bool { - self.get_collection().ok().is_some_and(|k| { - k.search_items(id.as_sec_ser_identifier()) - .ok() - .map_or(false, |x| !x.is_empty()) - }) - } - - fn get(&self, id: &Identifier) -> Result>> { - self.get_collection()? - .search_items(id.as_sec_ser_identifier())? - .first() - .map_or(Err(Error::Keyring), |k| { - Ok(Protected::new(hex::decode(k.get_secret()?)?)) - }) - } - - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - self.get_collection()?.create_item( - &id.application(), - id.as_sec_ser_identifier(), - hex::encode(value.expose()).as_bytes(), - true, - "text/plain", - )?; - - Ok(()) - } - - fn remove(&self, id: &Identifier) -> Result<()> { - self.get_collection()? - .search_items(id.as_sec_ser_identifier())? - .first() - .map_or(Err(Error::Keyring), |k| { - k.delete()?; - Ok(()) - }) - } -} diff --git a/crates/crypto/src/keyring/mod.rs b/crates/crypto/src/keyring/mod.rs deleted file mode 100644 index 07cf3fff8..000000000 --- a/crates/crypto/src/keyring/mod.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::{Error, Protected, Result}; -use std::fmt::Display; - -mod identifier; -mod session; - -use identifier::Identifier; -use session::SessionKeyring; - -#[cfg(target_os = "linux")] -mod linux; - -#[cfg(any(target_os = "macos", target_os = "ios"))] -mod apple; - -// #[cfg(target_os = "windows")] -// mod windows; - -const MAX_LEN: usize = 128; - -// TODO(brxken128): use `Encrypted` type here? - -pub trait KeyringInterface { - fn new() -> Result - where - Self: Sized; - fn name(&self) -> KeyringBackend; - fn get(&self, id: &Identifier) -> Result>>; - fn remove(&self, id: &Identifier) -> Result<()>; - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()>; - fn contains_key(&self, id: &Identifier) -> bool; -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum KeyringBackend { - Session, - #[cfg(target_os = "macos")] - MacOS, - #[cfg(target_os = "ios")] - Ios, - #[cfg(target_os = "linux")] - Linux(LinuxKeyring), -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum LinuxKeyring { - #[cfg(target_os = "linux")] - Keyutils, - #[cfg(all(target_os = "linux", feature = "secret-service"))] - SecretService, -} - -impl Display for KeyringBackend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - Self::Session => "Session", - #[cfg(target_os = "macos")] - Self::MacOS => "MacOS", - #[cfg(target_os = "ios")] - Self::Ios => "iOS", - #[cfg(target_os = "linux")] - Self::Linux(k) => match k { - LinuxKeyring::Keyutils => "KeyUtils", - #[cfg(feature = "secret-service")] - LinuxKeyring::SecretService => "Secret Service", - }, - }; - - f.write_str(s) - } -} - -pub struct Keyring { - inner: Box, -} - -impl Keyring { - pub fn new(backend: KeyringBackend) -> Result { - let inner: Box = match backend { - KeyringBackend::Session => Box::new(SessionKeyring::new()?), - #[cfg(target_os = "macos")] - KeyringBackend::MacOS => Box::new(apple::MacosKeyring::new()?), - #[cfg(target_os = "Ios")] - KeyringBackend::Ios => Box::new(apple::IosKeyring::new()?), - #[cfg(target_os = "linux")] - KeyringBackend::Linux(k) => match k { - LinuxKeyring::Keyutils => Box::new(linux::KeyutilsKeyring::new()?), - #[cfg(feature = "secret-service")] - LinuxKeyring::SecretService => Box::new(linux::SecretServiceKeyring::new()?), - }, - }; - - Ok(Self { inner }) - } - - #[inline] - pub fn get(&self, id: &Identifier) -> Result>> { - self.inner.get(id) - } - - #[inline] - #[must_use] - pub fn contains_key(&self, id: &Identifier) -> bool { - self.inner.contains_key(id) - } - - #[inline] - pub fn remove(&self, id: &Identifier) -> Result<()> { - self.inner.remove(id) - } - - #[inline] - pub fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - if value.expose().len() > MAX_LEN { - return Err(Error::Validity); // should be "value too long" - } - - self.inner.insert(id, value) - } - - #[inline] - #[must_use] - pub fn name(&self) -> KeyringBackend { - self.inner.name() - } -} - -#[cfg(test)] -mod tests { - use crate::Protected; - - use super::{Identifier, Keyring, KeyringBackend}; - - #[test] - fn full_session() { - let password = Protected::new(b"SuperSecurePassword".to_vec()); - let identifier = Identifier::new("0000-0000-0000-0000", "Password", "Crypto"); - let keyring = Keyring::new(KeyringBackend::Session).unwrap(); - - keyring.insert(&identifier, password.clone()).unwrap(); - assert!(keyring.contains_key(&identifier)); - - let pw = keyring.get(&identifier).unwrap(); - - assert_eq!(pw.expose(), password.expose()); - - keyring.remove(&identifier).unwrap(); - - assert!(!keyring.contains_key(&identifier)); - } - - #[test] - #[cfg(target_os = "linux")] - #[ignore] - fn linux_keyutils() { - let password = Protected::new(b"SuperSecurePassword".to_vec()); - let identifier = Identifier::new("0000-0000-0000-0000", "Password", "Crypto"); - let keyring = Keyring::new(KeyringBackend::Linux(super::LinuxKeyring::Keyutils)).unwrap(); - - keyring.insert(&identifier, password.clone()).unwrap(); - assert!(keyring.contains_key(&identifier)); - - let pw = keyring.get(&identifier).unwrap(); - - assert_eq!(pw.expose(), password.expose()); - - keyring.remove(&identifier).unwrap(); - - assert!(!keyring.contains_key(&identifier)); - } - - #[test] - #[cfg(target_os = "linux")] - #[ignore] - fn linux_secret_service() { - let password = Protected::new(b"SuperSecurePassword".to_vec()); - let identifier = Identifier::new("0000-0000-0000-0000", "Password", "Crypto"); - let keyring = - Keyring::new(KeyringBackend::Linux(super::LinuxKeyring::SecretService)).unwrap(); - - keyring.insert(&identifier, password.clone()).unwrap(); - assert!(keyring.contains_key(&identifier)); - - let pw = keyring.get(&identifier).unwrap(); - - assert_eq!(pw.expose(), password.expose()); - - keyring.remove(&identifier).unwrap(); - - assert!(!keyring.contains_key(&identifier)); - } - - #[test] - #[cfg(target_os = "macos")] - #[ignore] - fn macos() { - let password = Protected::new(b"SuperSecurePassword".to_vec()); - let identifier = Identifier::new("0000-0000-0000-0000", "Password", "Crypto"); - let keyring = Keyring::new(KeyringBackend::MacOS).unwrap(); - - keyring.insert(&identifier, password.clone()).unwrap(); - assert!(keyring.contains_key(&identifier)); - - let pw = keyring.get(&identifier).unwrap(); - - assert_eq!(pw.expose(), password.expose()); - - keyring.remove(&identifier).unwrap(); - - assert!(!keyring.contains_key(&identifier)); - } -} diff --git a/crates/crypto/src/keyring/session.rs b/crates/crypto/src/keyring/session.rs deleted file mode 100644 index 756bbc2f9..000000000 --- a/crates/crypto/src/keyring/session.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{types::Algorithm, vault::EphemeralVault, Error, Protected, Result}; - -use super::{Identifier, KeyringBackend, KeyringInterface}; - -pub struct SessionKeyring { - inner: EphemeralVault>, -} - -impl KeyringInterface for SessionKeyring { - fn new() -> Result { - Ok(Self { - inner: EphemeralVault::new(Algorithm::default()), - }) - } - - fn name(&self) -> KeyringBackend { - KeyringBackend::Session - } - - fn contains_key(&self, id: &Identifier) -> bool { - self.inner.contains_key(&id.hash()).map_or(false, |x| x) - } - - fn get(&self, id: &Identifier) -> Result>> { - Ok(Protected::new( - self.inner.get(&id.hash()).map_err(|_| Error::Keyring)?, - )) - } - - fn insert(&self, id: &Identifier, value: Protected>) -> Result<()> { - self.inner - .insert(id.hash(), value.into_inner()) - .map_err(|_| Error::Keyring) - } - - fn remove(&self, id: &Identifier) -> Result<()> { - self.inner.remove(&id.hash()).map_err(|_| Error::Keyring) - } -} diff --git a/crates/crypto/src/keyring/windows.rs b/crates/crypto/src/keyring/windows.rs deleted file mode 100644 index 73bc13012..000000000 --- a/crates/crypto/src/keyring/windows.rs +++ /dev/null @@ -1 +0,0 @@ -// This file is a placeholder diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs index 05f21562e..d5b5fee06 100644 --- a/crates/crypto/src/lib.rs +++ b/crates/crypto/src/lib.rs @@ -28,29 +28,17 @@ clippy::similar_names )] -pub mod crypto; +// pub mod crypto; +pub mod cloud; pub mod ct; -pub mod encoding; -pub mod encrypted; +pub mod erase; pub mod error; -pub mod hashing; pub mod primitives; pub mod protected; pub mod rng; -pub mod types; -pub mod utils; -pub mod vault; -#[cfg(all( - feature = "keyring", - any(target_os = "macos", target_os = "ios", target_os = "linux") -))] -pub mod keyring; - -#[cfg(feature = "sys")] -pub mod sys; - -pub use self::error::{Error, Result}; -pub use aead::Payload; +pub use error::Error; pub use protected::Protected; -pub use zeroize::Zeroize; +pub use rng::CryptoRng; + +pub use rand_core::{RngCore, SeedableRng}; diff --git a/crates/crypto/src/primitives.rs b/crates/crypto/src/primitives.rs index 393ee4db6..efa7bd33b 100644 --- a/crates/crypto/src/primitives.rs +++ b/crates/crypto/src/primitives.rs @@ -1,130 +1,56 @@ //! This module contains constant values, functions and types that are used around the crate. -//! -//! This includes things such as cryptographically-secure random salt/master key/nonce generation, -//! lengths for master keys and even the STREAM block size. // DO NOT EDIT THIS FILE. IF THESE CONSTANTS CHANGE, THINGS CAN (AND PROBABLY WILL) BREAK -use crate::types::DerivationContext; +use aead::stream::{Nonce, StreamLE31}; +use chacha20poly1305::{Tag, XChaCha20Poly1305, XNonce}; -/// This is the salt size -pub const SALT_LEN: usize = 16; +pub type OneShotNonce = XNonce; +pub type StreamNonce = Nonce>; -/// The nonce size for XChaCha20-Poly1305, minus the last 4 bytes (due to STREAM with a 31+1 bit counter) -pub const XCHACHA20_POLY1305_NONCE_LEN: usize = 20; +#[derive(Debug, Clone)] +pub struct EncryptedBlock { + pub nonce: OneShotNonce, + pub cipher_text: Vec, +} -/// The nonce size for AES-256-GCM-SIV, minus the last 4 bytes (due to STREAM with a 31+1 bit counter) -pub const AES_256_GCM_SIV_NONCE_LEN: usize = 8; +impl EncryptedBlock { + /// The block size used for STREAM encryption/decryption. This size seems to offer + /// the best performance compared to alternatives. + /// + /// The file size gain is 24 bytes per 1MiB due to nonce of XChaCha20-Poly1305 + pub const PLAIN_TEXT_SIZE: usize = 1_048_576; -/// The length of a secret key, in bytes. -pub const SECRET_KEY_LEN: usize = 18; - -/// The block size used for STREAM encryption/decryption. This size seems to offer the best performance compared to alternatives. -/// -/// The file size gain is 16 bytes per 1048576 bytes (due to the AEAD tag), plus the size of the header. -pub const BLOCK_LEN: usize = 1_048_576; - -/// This is the default AEAD tag size for all encryption algorithms used within the crate. -pub const AEAD_TAG_LEN: usize = 16; - -/// Length of the AAD -pub const AAD_LEN: usize = 32; - -/// Length of the AAD (for headers) -pub const AAD_HEADER_LEN: usize = 38; - -/// The length of encrypted master keys -pub const ENCRYPTED_KEY_LEN: usize = KEY_LEN + AEAD_TAG_LEN; - -/// The length of plain master/hashed keys -pub const KEY_LEN: usize = 32; - -pub const ENCRYPTED_TYPE_CONTEXT: DerivationContext = - DerivationContext::new("2023-10-02 03:19:34 Encrypted type derivation context"); - -pub(super) const ARGON2ID_STANDARD: (u32, u32, u32) = (131_072, 8, 4); -pub(super) const ARGON2ID_HARDENED: (u32, u32, u32) = (262_144, 8, 4); -pub(super) const ARGON2ID_PARANOID: (u32, u32, u32) = (524_288, 8, 4); -pub(super) const BLAKE3_BALLOON_STANDARD: (u32, u32, u32) = (131_072, 2, 1); -pub(super) const BLAKE3_BALLOON_HARDENED: (u32, u32, u32) = (262_144, 2, 1); -pub(super) const BLAKE3_BALLOON_PARANOID: (u32, u32, u32) = (524_288, 2, 1); // could increase first value 2x, and lower 2nd value to 1? + /// The size of a encrypted block with its tag. + pub const CIPHER_TEXT_SIZE: usize = Self::PLAIN_TEXT_SIZE + size_of::(); +} #[cfg(test)] mod tests { - use crate::primitives::{ - AAD_LEN, AEAD_TAG_LEN, AES_256_GCM_SIV_NONCE_LEN, ARGON2ID_HARDENED, ARGON2ID_PARANOID, - ARGON2ID_STANDARD, BLAKE3_BALLOON_HARDENED, BLAKE3_BALLOON_PARANOID, - BLAKE3_BALLOON_STANDARD, BLOCK_LEN, ENCRYPTED_KEY_LEN, KEY_LEN, SECRET_KEY_LEN, - XCHACHA20_POLY1305_NONCE_LEN, - }; + use super::*; #[test] - fn argon2id_standard_params() { - assert_eq!(ARGON2ID_STANDARD, (131_072, 8, 4)); + fn test_encrypted_block_plain_text_size() { + assert_eq!(EncryptedBlock::PLAIN_TEXT_SIZE, 1_048_576); } #[test] - fn argon2id_hardened_params() { - assert_eq!(ARGON2ID_HARDENED, (262_144, 8, 4)); + fn test_one_shot_nonce_size() { + assert_eq!(size_of::(), 24); } #[test] - fn argon2id_paranoid_params() { - assert_eq!(ARGON2ID_PARANOID, (524_288, 8, 4)); + fn test_stream_nonce_size() { + assert_eq!(size_of::(), 20); } #[test] - fn blake3_balloon_standard_params() { - assert_eq!(BLAKE3_BALLOON_STANDARD, (131_072, 2, 1)); + fn xchacha_tag_size() { + assert_eq!(size_of::(), 16); } #[test] - fn blake3_balloon_hardened_params() { - assert_eq!(BLAKE3_BALLOON_HARDENED, (262_144, 2, 1)); - } - - #[test] - fn blake3_balloon_paranoid_params() { - assert_eq!(BLAKE3_BALLOON_PARANOID, (524_288, 2, 1)); - } - - #[test] - fn block_len() { - assert_eq!(BLOCK_LEN, 1_048_576); - } - - #[test] - fn secret_key_len() { - assert_eq!(SECRET_KEY_LEN, 18); - } - - #[test] - fn key_len() { - assert_eq!(KEY_LEN, 32); - } - - #[test] - fn aead_tag_len() { - assert_eq!(AEAD_TAG_LEN, 16); - } - - #[test] - fn encrypted_key_len() { - assert_eq!(ENCRYPTED_KEY_LEN, 48); - } - - #[test] - fn aad_len() { - assert_eq!(AAD_LEN, 32); - } - - #[test] - fn xchacha20_poly1305_nonce_len() { - assert_eq!(XCHACHA20_POLY1305_NONCE_LEN, 20); - } - - #[test] - fn aes_256_gcm_siv_nonce_len() { - assert_eq!(AES_256_GCM_SIV_NONCE_LEN, 8); + fn test_encrypted_block_cipher_text_size() { + assert_eq!(EncryptedBlock::CIPHER_TEXT_SIZE, 1_048_576 + 16); } } diff --git a/crates/crypto/src/protected.rs b/crates/crypto/src/protected.rs index 90de4accf..63d3e97c3 100644 --- a/crates/crypto/src/protected.rs +++ b/crates/crypto/src/protected.rs @@ -28,16 +28,14 @@ //! let value = protected_data.expose(); //! ``` //! + use std::{fmt::Debug, mem}; + +use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize), - serde(transparent) -)] -#[cfg_attr(feature = "specta", derive(specta::Type))] +#[derive(Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +#[serde(transparent)] pub struct Protected(T) where T: Zeroize; diff --git a/crates/crypto/src/rng/csprng.rs b/crates/crypto/src/rng/csprng.rs new file mode 100644 index 000000000..6275aafea --- /dev/null +++ b/crates/crypto/src/rng/csprng.rs @@ -0,0 +1,88 @@ +use crate::Error; + +use rand::RngCore; +use rand_chacha::ChaCha20Rng; +use rand_core::{impl_try_crypto_rng_from_crypto_rng, SeedableRng}; +use zeroize::{Zeroize, Zeroizing}; + +/// This RNG should be used throughout the entire crate. +/// +/// On `Drop`, it re-seeds the inner RNG, erasing the previous state and making all future +/// values unpredictable. +#[derive(Debug)] +pub struct CryptoRng(ChaCha20Rng); + +impl CryptoRng { + /// This creates a new [`ChaCha20Rng`]-backed [`rand::CryptoRng`] from entropy + /// (via the [getrandom](https://docs.rs/getrandom) crate). + #[inline] + pub fn new() -> Result { + ChaCha20Rng::try_from_os_rng().map(Self).map_err(Into::into) + } + + /// Used to generate completely random bytes, with the use of [`ChaCha20Rng`] + /// + /// Ideally this should be used for small amounts only (as it's stack allocated) + #[inline] + #[must_use] + pub fn generate_fixed(&mut self) -> [u8; I] { + let mut bytes = Zeroizing::new([0u8; I]); + self.fill_bytes(bytes.as_mut()); + *bytes + } + + /// Used to generate completely random bytes, with the use of [`ChaCha20Rng`] + #[inline] + #[must_use] + pub fn generate_vec(&mut self, size: usize) -> Vec { + let mut bytes = vec![0u8; size]; + self.fill_bytes(bytes.as_mut()); + bytes + } +} + +impl RngCore for CryptoRng { + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest); + } + + #[inline] + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } +} + +impl SeedableRng for CryptoRng { + type Seed = ::Seed; + + fn from_seed(seed: Self::Seed) -> Self { + Self(ChaCha20Rng::from_seed(seed)) + } +} + +impl Zeroize for CryptoRng { + #[inline] + fn zeroize(&mut self) { + let mut seed = ::Seed::default(); + self.0.fill_bytes(&mut seed); + + self.0 = ChaCha20Rng::from_seed(seed); + } +} + +impl rand::CryptoRng for CryptoRng {} + +impl_try_crypto_rng_from_crypto_rng!(CryptoRng); + +impl Drop for CryptoRng { + #[inline] + fn drop(&mut self) { + self.zeroize(); + } +} diff --git a/crates/crypto/src/rng/csprng/chacha20.rs b/crates/crypto/src/rng/csprng/chacha20.rs deleted file mode 100644 index 6bbe2e09d..000000000 --- a/crates/crypto/src/rng/csprng/chacha20.rs +++ /dev/null @@ -1,80 +0,0 @@ -use rand::RngCore; -use rand_chacha::ChaCha20Rng; -use rand_core::{block::BlockRngCore, SeedableRng}; -use zeroize::{Zeroize, Zeroizing}; - -const STATE_WORDS: usize = 16; - -/// This RNG should be used throughout the entire crate. -/// -/// On `Drop`, it re-seeds the inner RNG, erasing the previous state and making all future -/// values unpredictable. -pub struct CryptoRng(Box); - -impl CryptoRng { - /// This creates a new `ChaCha20Rng`-backed `CryptoRng` from entropy (via the `getrandom` crate). - #[inline] - #[must_use] - pub fn new() -> Self { - Self(Box::new(ChaCha20Rng::from_entropy())) - } - - /// Used to generate completely random bytes, with the use of `ChaCha20` - /// - /// Ideally this should be used for small amounts only (as it's stack allocated) - #[inline] - #[must_use] - pub fn generate_fixed() -> [u8; I] { - let mut bytes = Zeroizing::new([0u8; I]); - Self::new().0.fill_bytes(bytes.as_mut()); - *bytes - } - - /// Used to generate completely random bytes, with the use of `ChaCha20` - #[inline] - #[must_use] - pub fn generate_vec(size: usize) -> Vec { - let mut bytes = Zeroizing::new(vec![0u8; size]); - Self::new().fill_bytes(bytes.as_mut()); - bytes.to_vec() - } -} - -impl RngCore for CryptoRng { - #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest); - } - - #[inline] - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - #[inline] - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - self.0.try_fill_bytes(dest) - } -} - -impl BlockRngCore for CryptoRng { - type Item = u32; - type Results = [u32; STATE_WORDS]; - - #[inline] - fn generate(&mut self, results: &mut Self::Results) { - (0..STATE_WORDS).for_each(|i| results[i] = self.next_u32()); - } -} - -impl Zeroize for CryptoRng { - #[inline] - fn zeroize(&mut self) { - *self.0 = ChaCha20Rng::from_entropy(); - } -} diff --git a/crates/crypto/src/rng/csprng/mod.rs b/crates/crypto/src/rng/csprng/mod.rs deleted file mode 100644 index 5e594a2aa..000000000 --- a/crates/crypto/src/rng/csprng/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use zeroize::Zeroize; -mod chacha20; -pub use chacha20::CryptoRng; - -impl rand::CryptoRng for CryptoRng {} - -impl Default for CryptoRng { - fn default() -> Self { - Self::new() - } -} - -impl Drop for CryptoRng { - #[inline] - fn drop(&mut self) { - self.zeroize(); - } -} diff --git a/crates/crypto/src/rng/mod.rs b/crates/crypto/src/rng/mod.rs index e0bf86300..d8df631ce 100644 --- a/crates/crypto/src/rng/mod.rs +++ b/crates/crypto/src/rng/mod.rs @@ -1,5 +1,4 @@ mod csprng; -// mod mnemonic; +/// CSPRNG stands for Cryptographically Secure Pseudo Random Number Generator pub use csprng::CryptoRng; -// pub use mnemonic::{Mnemonic, MnemonicDelimiter}; diff --git a/crates/crypto/src/sys/fs/mod.rs b/crates/crypto/src/sys/fs/mod.rs deleted file mode 100644 index b58feca00..000000000 --- a/crates/crypto/src/sys/fs/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod erase; - -pub use erase::erase; - -#[cfg(feature = "tokio")] -pub use erase::erase_async; diff --git a/crates/crypto/src/sys/mod.rs b/crates/crypto/src/sys/mod.rs deleted file mode 100644 index d521fbd77..000000000 --- a/crates/crypto/src/sys/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod fs; diff --git a/crates/crypto/src/types.rs b/crates/crypto/src/types.rs deleted file mode 100644 index 45416ba48..000000000 --- a/crates/crypto/src/types.rs +++ /dev/null @@ -1,744 +0,0 @@ -//! This module defines all of the possible types used throughout this crate, -//! in an effort to add additional type safety. -use crate::{ - ct::{Choice, ConstantTimeEq, ConstantTimeEqNull}, - rng::CryptoRng, - utils::ToArray, - Error, Protected, -}; - -use aead::generic_array::{ArrayLength, GenericArray}; -use bincode::{Decode, Encode}; -use cmov::Cmov; -use std::fmt::{Debug, Display, Write}; -use zeroize::{DefaultIsZeroes, Zeroize, ZeroizeOnDrop}; - -use crate::primitives::{ - AAD_HEADER_LEN, AAD_LEN, AES_256_GCM_SIV_NONCE_LEN, ARGON2ID_HARDENED, ARGON2ID_PARANOID, - ARGON2ID_STANDARD, BLAKE3_BALLOON_HARDENED, BLAKE3_BALLOON_PARANOID, BLAKE3_BALLOON_STANDARD, - ENCRYPTED_KEY_LEN, KEY_LEN, SALT_LEN, SECRET_KEY_LEN, XCHACHA20_POLY1305_NONCE_LEN, -}; - -#[derive(Clone, Copy)] -pub struct MagicBytes([u8; I]); - -impl MagicBytes { - #[inline] - #[must_use] - pub const fn new(bytes: [u8; I]) -> Self { - Self(bytes) - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &[u8; I] { - &self.0 - } -} - -#[derive(Clone, Copy)] -pub struct DerivationContext(&'static str); - -impl DerivationContext { - #[inline] - #[must_use] - pub const fn new(context: &'static str) -> Self { - Self(context) - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &'static str { - self.0 - } -} - -/// These parameters define the password-hashing level. -/// -/// The greater the parameter, the longer the password will take to hash. -#[derive(Clone, Copy, Default, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum Params { - #[default] - Standard, - Hardened, - Paranoid, -} - -/// This defines all available password hashing algorithms. -#[derive(Clone, Copy, Encode, Decode)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "name", content = "params") -)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum HashingAlgorithm { - Argon2id(Params), - Blake3Balloon(Params), -} - -impl Default for HashingAlgorithm { - fn default() -> Self { - Self::Argon2id(Params::default()) - } -} - -impl HashingAlgorithm { - #[inline] - #[must_use] - pub const fn get_parameters(&self) -> (u32, u32, u32) { - match self { - Self::Argon2id(p) => match p { - Params::Standard => ARGON2ID_STANDARD, - Params::Hardened => ARGON2ID_HARDENED, - Params::Paranoid => ARGON2ID_PARANOID, - }, - Self::Blake3Balloon(p) => match p { - Params::Standard => BLAKE3_BALLOON_STANDARD, - Params::Hardened => BLAKE3_BALLOON_HARDENED, - Params::Paranoid => BLAKE3_BALLOON_PARANOID, - }, - } - } -} - -/// This should be used for providing a nonce to encrypt/decrypt functions. -/// -/// You may also generate a nonce for a given algorithm with `Nonce::generate()` -#[derive(Clone, Copy, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum Nonce { - Aes256GcmSiv([u8; AES_256_GCM_SIV_NONCE_LEN]), - XChaCha20Poly1305([u8; XCHACHA20_POLY1305_NONCE_LEN]), -} - -impl Nonce { - #[inline] - #[must_use] - pub fn generate(algorithm: Algorithm) -> Self { - match algorithm { - Algorithm::Aes256GcmSiv => Self::Aes256GcmSiv(CryptoRng::generate_fixed()), - Algorithm::XChaCha20Poly1305 => Self::XChaCha20Poly1305(CryptoRng::generate_fixed()), - } - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &[u8] { - match self { - Self::Aes256GcmSiv(x) => x, - Self::XChaCha20Poly1305(x) => x, - } - } - - #[inline] - #[must_use] - pub const fn len(&self) -> usize { - match self { - Self::Aes256GcmSiv(x) => x.len(), - Self::XChaCha20Poly1305(x) => x.len(), - } - } - - #[inline] - #[must_use] - pub const fn is_empty(&self) -> bool { - match self { - Self::Aes256GcmSiv(x) => x.is_empty(), - Self::XChaCha20Poly1305(x) => x.is_empty(), - } - } - - #[inline] - #[must_use] - pub const fn algorithm(&self) -> Algorithm { - match self { - Self::Aes256GcmSiv(_) => Algorithm::Aes256GcmSiv, - Self::XChaCha20Poly1305(_) => Algorithm::XChaCha20Poly1305, - } - } - - pub fn validate(&self, algorithm: Algorithm) -> crate::Result<()> { - let mut x = 1u8; - x.cmovz(&0, (self.algorithm().ct_eq(&algorithm)).unwrap_u8()); - x.cmovz(&0, (self.inner().ct_ne_null()).unwrap_u8()); - - bool::from(Choice::from(x)) - .then_some(()) - .ok_or(Error::Validity) - } -} - -impl ConstantTimeEq for Nonce { - fn ct_eq(&self, rhs: &Self) -> Choice { - self.inner().ct_eq(rhs.inner()) - } -} - -impl From<&Nonce> for GenericArray -where - I: ArrayLength, -{ - fn from(value: &Nonce) -> Self { - match value { - Nonce::Aes256GcmSiv(x) => Self::clone_from_slice(x), - Nonce::XChaCha20Poly1305(x) => Self::clone_from_slice(x), - } - } -} - -/// These are all possible algorithms that can be used for encryption and decryption -#[derive(Clone, Copy, Default, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum Algorithm { - Aes256GcmSiv, - #[default] - XChaCha20Poly1305, -} - -impl ConstantTimeEq for Algorithm { - fn ct_eq(&self, rhs: &Self) -> Choice { - #[allow(clippy::as_conversions)] - (*self as u8).ct_eq(&(*rhs as u8)) - } -} - -impl PartialEq for Algorithm { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -impl Algorithm { - /// This function returns the nonce length for a given encryption algorithm - #[inline] - #[must_use] - pub const fn nonce_len(&self) -> usize { - match self { - Self::Aes256GcmSiv => AES_256_GCM_SIV_NONCE_LEN, - Self::XChaCha20Poly1305 => XCHACHA20_POLY1305_NONCE_LEN, - } - } -} - -/// This should be used for providing a key to functions. -/// -/// It can either be a random key, or a hashed key. -/// -/// You may also generate a secure random key with `Key::generate()` -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -#[repr(transparent)] -pub struct Key(Box<[u8; KEY_LEN]>); - -impl Key { - #[inline] - #[must_use] - pub fn new(v: [u8; KEY_LEN]) -> Self { - Self(Box::new(v)) - } - - #[inline] - #[must_use] - pub const fn expose(&self) -> &[u8] { - self.0.as_slice() - } - - #[inline] - #[must_use] - pub fn generate() -> Self { - Self::new(CryptoRng::generate_fixed()) - } - - pub fn validate(&self) -> crate::Result<()> { - bool::from(self.expose().ct_ne_null()) - .then_some(()) - .ok_or(Error::Validity) - } -} - -impl ConstantTimeEq for Key { - fn ct_eq(&self, rhs: &Self) -> Choice { - self.expose().ct_eq(rhs.expose()) - } -} - -impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for Key { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serdect::array::serialize_hex_lower_or_bin(&self.expose(), serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Key { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let mut buf = [0u8; 32]; - serdect::array::deserialize_hex_or_bin(&mut buf, deserializer)?; - Ok(Self::new(buf)) - } -} - -// The `serde` feature is needed as this makes use of a crate called `serdect` which -// allows for constant-time serialization and deserialization. We then use `bincode`s -// compatability layer to serialize through that, so in theory it should remain constant-time -#[cfg(feature = "serde")] -impl Encode for Key { - fn encode( - &self, - encoder: &mut E, - ) -> Result<(), bincode::error::EncodeError> { - bincode::serde::Compat(self).encode(encoder)?; - - Ok(()) - } -} - -// The `serde` feature is needed as this makes use of a crate called `serdect` which -// allows for constant-time serialization and deserialization. We then use `bincode`s -// compatability layer to serialize through that, so in theory it should remain constant-time -#[cfg(feature = "serde")] -impl Decode for Key { - fn decode( - decoder: &mut D, - ) -> Result { - Ok(bincode::serde::Compat::decode(decoder)?.0) - } -} - -impl From<&Key> for GenericArray -where - I: ArrayLength, -{ - fn from(value: &Key) -> Self { - GenericArray::clone_from_slice(value.expose()) - } -} - -impl From for Key { - fn from(value: blake3::Hash) -> Self { - Self::new(value.into()) - } -} - -impl TryFrom>> for Key { - type Error = Error; - - fn try_from(value: Protected>) -> Result { - Ok(Self::new(value.into_inner().to_array()?)) - } -} - -impl TryFrom>> for Key { - type Error = Error; - - fn try_from(value: Protected>) -> Result { - Ok(Self::new(value.expose().to_array()?)) - } -} - -// -// impl bincode::Encode for Key { -// fn encode( -// &self, -// encoder: &mut E, -// ) -> Result<(), bincode::error::EncodeError> { -// serdect::array::serialize_hex_lower_or_bin(self.expose(), bincode::serde::) -// } -// } - -/// This should be used for providing a secret key to functions. -/// -// /// You may also generate a secret key with `SecretKey::generate()` -#[derive(Zeroize, ZeroizeOnDrop, Clone)] -pub enum SecretKey { - Standard([u8; SECRET_KEY_LEN]), - Variable(Vec), - Null, -} - -impl SecretKey { - #[inline] - #[must_use] - pub const fn new(v: [u8; SECRET_KEY_LEN]) -> Self { - Self::Standard(v) - } - - #[inline] - #[must_use] - pub fn expose(&self) -> &[u8] { - match self { - Self::Standard(v) => v, - Self::Variable(v) => v, - Self::Null => &[], - } - } - - #[inline] - #[must_use] - pub fn generate() -> Self { - Self::new(CryptoRng::generate_fixed()) - } -} - -impl TryFrom>> for SecretKey { - type Error = Error; - - fn try_from(value: Protected>) -> Result { - let sk = match value.expose().len() { - // this won't fail as we check the size - SECRET_KEY_LEN => Self::new(value.into_inner().to_array()?), - 0 => Self::Null, - _ => Self::Variable(value.into_inner()), - }; - - Ok(sk) - } -} - -impl TryFrom> for SecretKey { - type Error = Error; - - fn try_from(value: Protected) -> Result { - let mut s = value.into_inner(); - s.retain(|c| c.is_ascii_hexdigit()); - - // shouldn't fail as `SecretKey::try_from` is (essentially) infallible - hex::decode(&s) - .ok() - .map_or(Protected::new(vec![]), Protected::new) - .try_into() - .map_err(|_| Error::Validity) - } -} - -impl Display for SecretKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = hex::encode(self.expose()).to_uppercase(); - let separator_distance = s.len() / 6; - s.chars().enumerate().try_for_each(|(i, c)| { - f.write_char(c)?; - if (i + 1) % separator_distance == 0 && (i + 1) != s.len() { - f.write_char('-')?; - } - - Ok(()) - }) - } -} - -/// This should be used for passing an encrypted key around. -/// -/// The length of the encrypted key is `ENCRYPTED_KEY_LEN` (which is `KEY_LEM` + `AEAD_TAG_LEN`). -/// -/// This also stores the associated `Nonce`, in order to make the API a lot cleaner. -#[derive(Clone, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct EncryptedKey( - #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] - [u8; ENCRYPTED_KEY_LEN], - Nonce, -); - -impl EncryptedKey { - #[inline] - #[must_use] - pub const fn new(v: [u8; ENCRYPTED_KEY_LEN], nonce: Nonce) -> Self { - Self(v, nonce) - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &[u8] { - &self.0 - } - - #[inline] - #[must_use] - pub const fn nonce(&self) -> &Nonce { - &self.1 - } -} - -impl ConstantTimeEq for EncryptedKey { - fn ct_eq(&self, rhs: &Self) -> Choice { - // short circuit if algorithm (and therefore nonce lengths) don't match - if !bool::from(self.nonce().algorithm().ct_eq(&rhs.nonce().algorithm())) { - return Choice::from(0); - } - - let mut x = 1u8; - x.cmovz(&0u8, self.nonce().ct_eq(rhs.nonce()).unwrap_u8()); - x.cmovz(&0u8, self.inner().ct_eq(rhs.inner()).unwrap_u8()); - Choice::from(x) - } -} - -impl PartialEq for EncryptedKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -#[derive(Clone, Copy, Default, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum Aad { - Standard([u8; AAD_LEN]), - Header( - #[cfg_attr(feature = "serde", serde(with = "serde_big_array::BigArray"))] - [u8; AAD_HEADER_LEN], - ), - #[default] - Null, -} - -impl Aad { - #[inline] - #[must_use] - pub fn generate() -> Self { - Self::Standard(CryptoRng::generate_fixed()) - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &[u8] { - match self { - Self::Standard(b) => b, - Self::Header(b) => b, - Self::Null => &[], - } - } -} - -impl ConstantTimeEq for Aad { - fn ct_eq(&self, other: &Self) -> Choice { - self.inner().ct_eq(other.inner()) - } -} - -impl PartialEq for Aad { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -/// This should be used for passing a salt around. -/// -/// You may also generate a salt with `Salt::generate()` -#[derive(Clone, Copy, Encode, Decode)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct Salt([u8; SALT_LEN]); - -impl DefaultIsZeroes for Salt {} - -impl Default for Salt { - fn default() -> Self { - Self([0u8; SALT_LEN]) - } -} - -impl Salt { - #[inline] - #[must_use] - pub fn generate() -> Self { - Self(CryptoRng::generate_fixed()) - } - - #[inline] - #[must_use] - pub const fn new(v: [u8; SALT_LEN]) -> Self { - Self(v) - } - - #[inline] - #[must_use] - pub const fn inner(&self) -> &[u8] { - &self.0 - } -} - -impl TryFrom> for Salt { - type Error = Error; - - fn try_from(value: Vec) -> Result { - Ok(Self::new(value.to_array()?)) - } -} - -impl Display for HashingAlgorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::Argon2id(p) => write!(f, "Argon2id ({p})"), - Self::Blake3Balloon(p) => write!(f, "BLAKE3-Balloon ({p})"), - } - } -} - -impl Display for Params { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::Standard => write!(f, "Standard"), - Self::Hardened => write!(f, "Hardened"), - Self::Paranoid => write!(f, "Paranoid"), - } - } -} - -impl Display for Algorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::Aes256GcmSiv => write!(f, "AES-256-GCM-SIV"), - Self::XChaCha20Poly1305 => write!(f, "XChaCha20-Poly1305"), - } - } -} - -impl Debug for Algorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self}") - } -} - -impl Debug for Key { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("[REDACTED]") - } -} - -impl Debug for EncryptedKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("[REDACTED]") - } -} - -#[cfg(test)] -mod tests { - use super::Algorithm; - use crate::{ - primitives::{ - AES_256_GCM_SIV_NONCE_LEN, ENCRYPTED_KEY_LEN, KEY_LEN, XCHACHA20_POLY1305_NONCE_LEN, - }, - types::{EncryptedKey, Key, Nonce}, - }; - - const EK: [[u8; ENCRYPTED_KEY_LEN]; 2] = [[0x20; ENCRYPTED_KEY_LEN], [0x21; ENCRYPTED_KEY_LEN]]; - const NONCES: [Nonce; 2] = [ - Nonce::XChaCha20Poly1305([5u8; XCHACHA20_POLY1305_NONCE_LEN]), - Nonce::Aes256GcmSiv([8u8; AES_256_GCM_SIV_NONCE_LEN]), - ]; - - #[test] - fn encrypted_key_eq() { - // same key and nonce - assert_eq!( - EncryptedKey::new(EK[0], NONCES[0]), - EncryptedKey::new(EK[0], NONCES[0]) - ); - - // same key, different nonce - assert_ne!( - EncryptedKey::new(EK[0], NONCES[0]), - EncryptedKey::new(EK[0], NONCES[1]) - ); - - // different key, same nonce - assert_ne!( - EncryptedKey::new(EK[0], NONCES[0]), - EncryptedKey::new(EK[1], NONCES[0]) - ); - } - - #[test] - #[should_panic(expected = "assertion")] - fn encrypted_key_eq_different_key() { - // different key, same nonce - assert_eq!( - EncryptedKey::new(EK[0], NONCES[0]), - EncryptedKey::new(EK[1], NONCES[0]) - ); - } - - #[test] - #[should_panic(expected = "assertion")] - fn encrypted_key_eq_different_nonce() { - // same key, different nonce - assert_eq!( - EncryptedKey::new(EK[0], NONCES[0]), - EncryptedKey::new(EK[0], NONCES[1]) - ); - } - - #[test] - fn key_eq() { - assert_eq!(Key::new([0x23; KEY_LEN]), Key::new([0x23; KEY_LEN])); - } - - #[test] - #[should_panic(expected = "assertion")] - fn key_eq_fail() { - assert_eq!(Key::new([0x23; KEY_LEN]), Key::new([0x24; KEY_LEN])); - } - - #[test] - fn algorithm_eq() { - assert_eq!(Algorithm::XChaCha20Poly1305, Algorithm::XChaCha20Poly1305); - } - - #[test] - #[should_panic(expected = "assertion")] - fn algorithm_eq_fail() { - assert_eq!(Algorithm::XChaCha20Poly1305, Algorithm::Aes256GcmSiv); - } - - #[test] - fn key_validate() { - Key::new([0x23; KEY_LEN]).validate().unwrap(); - } - - #[test] - #[should_panic(expected = "Validity")] - fn key_validate_null() { - Key::new([0u8; KEY_LEN]).validate().unwrap(); - } - - #[test] - fn nonce_validate() { - Nonce::generate(Algorithm::default()) - .validate(Algorithm::default()) - .unwrap(); - } - - #[test] - #[should_panic(expected = "Validity")] - fn nonce_validate_different_algorithms() { - Nonce::generate(Algorithm::XChaCha20Poly1305) - .validate(Algorithm::Aes256GcmSiv) - .unwrap(); - } - - #[test] - #[should_panic(expected = "Validity")] - fn nonce_validate_null() { - Nonce::XChaCha20Poly1305([0u8; XCHACHA20_POLY1305_NONCE_LEN]) - .validate(Algorithm::XChaCha20Poly1305) - .unwrap(); - } -} diff --git a/crates/crypto/src/utils.rs b/crates/crypto/src/utils.rs deleted file mode 100644 index e6b81d8fc..000000000 --- a/crates/crypto/src/utils.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::{Error, Result}; -use zeroize::Zeroize; - -pub(crate) trait ToArray { - fn to_array(self) -> Result<[u8; I]>; -} - -impl ToArray for Vec { - /// This function uses `try_into()`, and calls `zeroize` in the event of an error. - fn to_array(self) -> Result<[u8; I]> { - self.try_into().map_err(|mut b: Self| { - b.zeroize(); - Error::LengthMismatch - }) - } -} - -impl ToArray for &[u8] { - /// **Using this can be risky - ensure that you `zeroize` the source buffer before returning.** - /// - /// `zeroize` cannot be called on the input as we do not have ownership. - /// - /// This function copies `self` into a `Vec`, before using the `ToArray` implementation for `Vec` - fn to_array(self) -> Result<[u8; I]> { - self.to_vec().to_array() - } -} - -#[cfg(test)] -mod tests { - use crate::{ct::ConstantTimeEqNull, primitives::SALT_LEN, utils::ToArray}; - - #[test] - fn vec_to_array() { - let vec = vec![1u8; SALT_LEN]; - let array: [u8; SALT_LEN] = vec.clone().to_array().unwrap(); - - assert!(!bool::from(vec.ct_eq_null())); - assert_eq!(vec, array); - assert_eq!(vec.len(), SALT_LEN); - assert_eq!(array.len(), SALT_LEN); - } - - #[test] - fn slice_to_array() { - let slice: &[u8] = [1u8; SALT_LEN].as_ref(); - let array: [u8; SALT_LEN] = slice.to_array().unwrap(); - - assert!(!bool::from(slice.ct_eq_null())); - assert_eq!(slice, array); - assert_eq!(slice.len(), SALT_LEN); - assert_eq!(array.len(), SALT_LEN); - } -} diff --git a/crates/crypto/src/vault/ephemeral.rs b/crates/crypto/src/vault/ephemeral.rs deleted file mode 100644 index 58a4b31f1..000000000 --- a/crates/crypto/src/vault/ephemeral.rs +++ /dev/null @@ -1,84 +0,0 @@ -use zeroize::{Zeroize, Zeroizing}; - -use crate::{ - encrypted::Encrypted, - types::{Algorithm, Key}, - Error, Result, -}; -use std::{collections::HashMap, hash::Hash, sync::Mutex}; - -pub const EPHEMERAL_VAULT_ITEM_LIMIT: usize = 128; - -pub struct EphemeralVault -where - K: Hash + Eq, -{ - key: Key, - algorithm: Algorithm, - inner: Mutex>>, -} - -impl EphemeralVault -where - K: Hash + Eq, - T: bincode::Encode + bincode::Decode + Zeroize + Clone, -{ - #[must_use] - pub fn new(algorithm: Algorithm) -> Self { - Self { - key: Key::generate(), - algorithm, - inner: Mutex::new(HashMap::new()), - } - } - - #[must_use] - pub fn new_with_key(key: Key, algorithm: Algorithm) -> Self { - Self { - key, - algorithm, - inner: Mutex::new(HashMap::new()), - } - } - - pub fn contains_key(&self, id: &K) -> Result { - self.inner - .lock() - .map_or(Err(Error::Keystore), |x| Ok(x.contains_key(id))) - } - - pub fn get(&self, id: &K) -> Result { - self.inner - .lock() - .map_err(|_| Error::Keystore)? - .get(id) - .cloned() - .ok_or(Error::Keystore)? - .decrypt(&self.key) - } - - pub fn insert(&self, id: K, value: T) -> Result<()> { - let value = Zeroizing::new(value); - - if self.inner.lock().map_err(|_| Error::Keystore)?.len() + 1 > EPHEMERAL_VAULT_ITEM_LIMIT { - return Err(Error::Keystore); - } - - let encrypted = Encrypted::new(&self.key.clone(), &*value, self.algorithm)?; - - self.inner - .lock() - .map_err(|_| Error::Keystore)? - .insert(id, encrypted); - - Ok(()) - } - - pub fn remove(&self, id: &K) -> Result<()> { - self.inner - .lock() - .map_err(|_| Error::Keystore)? - .remove(id) - .map_or_else(|| Err(Error::Keystore), |_| Ok(())) - } -} diff --git a/crates/crypto/src/vault/mod.rs b/crates/crypto/src/vault/mod.rs deleted file mode 100644 index 4d6412f67..000000000 --- a/crates/crypto/src/vault/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod ephemeral; -#[cfg(feature = "experimental")] -mod persistent; - -pub use ephemeral::EphemeralVault; diff --git a/crates/crypto/src/vault/persistent.rs b/crates/crypto/src/vault/persistent.rs deleted file mode 100644 index ca281b62a..000000000 --- a/crates/crypto/src/vault/persistent.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![allow(dead_code)] - -use std::path::PathBuf; - -use redb::{Database, ReadableTable, TableDefinition}; - -use crate::{ - encoding, - encrypted::Encrypted, - types::{Algorithm, Key, Salt}, - Error, Result, -}; - -const SECRET_KEY_TABLE: TableDefinition<'_, &'_ [u8; 32], Vec> = - TableDefinition::new("secret_keys"); -const META_TABLE: TableDefinition<'_, &'_ str, Vec> = TableDefinition::new("meta"); - -const ROOT_KEY_ID: &str = "root_key"; -const ROOT_SALT_ID: &str = "root_salt"; - -pub struct Vault { - db: Database, - key: Option, -} - -impl Vault { - pub fn open(path: PathBuf, key: Option) -> Result { - let db = Database::create(path)?; - - let txn = db.begin_write()?; - { - txn.open_table(SECRET_KEY_TABLE)?; - txn.open_table(META_TABLE)?; - } - txn.commit()?; - - Ok(Self { db, key }) - } - - pub fn setup(&self, key: &Key, algorithm: Option) -> Result<()> { - // provided key should be master password, (generated) salt (store that in here, like have a `retrieve decrypt info`), - // and the vault key from the OS keyring. the vault key will have to be provided ed manually if OS keyrings/the key isn't available. - // can use a QR code, or copy/paste/type (last resort) the 16-byte (hex encoed) key - - let algorithm = algorithm.unwrap_or_default(); - - let root_key = Key::generate(); - let salt = Salt::generate(); - - let encrypted_key = Encrypted::new(key, &root_key, algorithm)?; - - let txn = self.db.begin_write()?; - - { - let mut table = txn.open_table(META_TABLE)?; - if table.get(ROOT_KEY_ID).is_err() || table.get(ROOT_SALT_ID).is_err() { - return Err(Error::RootKeyAlreadyExists); - } - - table.insert(ROOT_KEY_ID, encrypted_key.as_bytes()?)?; - table.insert(ROOT_SALT_ID, encoding::encode(&salt)?)?; - } - - txn.commit()?; - - Ok(()) - } - - pub fn unlock(&self, _key: &Key) -> Result<()> { - todo!() - } - - pub fn wipe(self) -> Result<()> { - todo!() - } - - pub const fn is_unlocked(&self) -> bool { - self.key.is_some() - } -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c6e4d7d50..8cca5be05 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.79" +channel = "1.80" From 6672c6cffc601ab68cfd7fe3a65333cb7ee19655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Thu, 15 Aug 2024 17:09:03 +0000 Subject: [PATCH 2/2] Remove unused code and update rust deps (#2662) * Remove deps-generator app - It is broken for quite a while and not being used * Remove unused code and unused deps * auto format * Restore incorrectly removed files - Remove aws deps and test code using it * Replace winapi-util with windows-rs - Adjust some deps versions * Autoformat * Update rand, remove unused dep * Fix image crate due to changes in pdf-renderer - Dont use default features in reqwest - Use native-tls tauri * Remove incorrect notice about blake3 --- Cargo.lock | Bin 307928 -> 284790 bytes Cargo.toml | 20 +- apps/deps-generator/Cargo.toml | 20 - apps/deps-generator/src/main.rs | 75 --- apps/deps-generator/src/types/backend.rs | 12 - apps/deps-generator/src/types/cli.rs | 33 -- apps/deps-generator/src/types/frontend.rs | 36 -- apps/deps-generator/src/types/mod.rs | 3 - apps/desktop/crates/macos/Cargo.toml | 4 +- apps/desktop/crates/windows/Cargo.toml | 5 +- apps/desktop/src-tauri/Cargo.toml | 12 +- apps/desktop/src-tauri/src/tauri_plugins.rs | 2 +- .../modules/sd-core/android/crate/Cargo.toml | 2 +- .../modules/sd-core/ios/crate/Cargo.toml | 2 +- apps/p2p-relay/Cargo.toml | 28 -- apps/p2p-relay/deploy.sh | 15 - apps/p2p-relay/src/config.rs | 75 --- apps/p2p-relay/src/main.rs | 217 --------- apps/p2p-relay/src/utils.rs | 17 - apps/server/Cargo.toml | 1 - core/Cargo.toml | 140 +++--- core/crates/file-path-helper/Cargo.toml | 6 +- core/crates/file-path-helper/src/lib.rs | 47 +- core/crates/heavy-lifting/Cargo.toml | 4 +- core/crates/indexer-rules/Cargo.toml | 4 +- core/crates/sync/Cargo.toml | 1 - core/src/api/categories.rs | 49 -- core/src/api/cloud.rs | 107 +---- core/src/api/keys.rs | 436 ------------------ core/src/api/mod.rs | 4 - crates/ai/Cargo.toml | 5 +- crates/cloud-api/Cargo.toml | 2 - crates/crypto/Cargo.toml | 22 +- crates/file-ext/Cargo.toml | 1 + crates/images/Cargo.toml | 2 +- crates/images/src/pdf.rs | 5 +- crates/media-metadata/Cargo.toml | 1 - crates/p2p/Cargo.toml | 62 +-- crates/p2p/crates/block/Cargo.toml | 1 - crates/p2p/crates/tunnel/Cargo.toml | 1 - crates/prisma-cli/Cargo.toml | 8 +- crates/prisma/Cargo.toml | 1 - crates/sync-generator/Cargo.toml | 3 +- .../$libraryId/settings/client/account.tsx | 28 +- packages/client/src/core.ts | 3 - 45 files changed, 182 insertions(+), 1340 deletions(-) delete mode 100644 apps/deps-generator/Cargo.toml delete mode 100644 apps/deps-generator/src/main.rs delete mode 100644 apps/deps-generator/src/types/backend.rs delete mode 100644 apps/deps-generator/src/types/cli.rs delete mode 100644 apps/deps-generator/src/types/frontend.rs delete mode 100644 apps/deps-generator/src/types/mod.rs delete mode 100644 apps/p2p-relay/Cargo.toml delete mode 100755 apps/p2p-relay/deploy.sh delete mode 100644 apps/p2p-relay/src/config.rs delete mode 100644 apps/p2p-relay/src/main.rs delete mode 100644 apps/p2p-relay/src/utils.rs delete mode 100644 core/src/api/categories.rs delete mode 100644 core/src/api/keys.rs diff --git a/Cargo.lock b/Cargo.lock index a2e454cf761e125419ec314dcc01ffaadba76fc2..ee66333d4584282117ef7a7a28a91c0e0273bcb1 100644 GIT binary patch delta 13689 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 delta 24882 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 diff --git a/Cargo.toml b/Cargo.toml index cdee6b5a1..bf23e5190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "apps/deps-generator", "apps/desktop/crates/*", "apps/desktop/src-tauri", "apps/mobile/modules/sd-core/android/crate", @@ -26,14 +25,11 @@ async-stream = "0.3.5" async-trait = "0.1.80" axum = "0.6.20" # Update blocked by hyper base64 = "0.22.1" -base91 = "0.1.0" blake3 = "1.5" chrono = "0.4.38" -directories = "5.0" -ed25519-dalek = "2.1.1" +ed25519-dalek = "2.1" futures = "0.3.30" futures-concurrency = "7.6" -gix-ignore = "0.11.2" globset = "0.4.14" http = "0.2" # Update blocked by axum hyper = "0.14" # Update blocked due to API breaking changes @@ -44,18 +40,16 @@ libc = "0.2" normpath = "1.2" once_cell = "1.19" pin-project-lite = "0.2.14" -rand = "0.8.5" +rand = "0.9.0-alpha.2" regex = "1.10" -reqwest = "0.11" # Update blocked by hyper +reqwest = { version = "0.11", default-features = false } # Update blocked by hyper rmp = "0.8.14" -rmp-serde = "1.3.0" +rmp-serde = "1.3" rmpv = { version = "1.3", features = ["with-serde"] } rspc = "0.1.4" # Update blocked by custom patch below serde = "1.0" serde_json = "1.0" specta = "=2.0.0-rc.20" -specta-typescript = "=0.0.7" -static_assertions = "1.1" strum = "0.26" strum_macros = "0.26" tempfile = "3.10" @@ -76,12 +70,6 @@ features = ["migrations", "specta", "sqlite", "sqlite-create-many"] git = "https://github.com/brendonovich/prisma-client-rust" rev = "4f9ef9d38c" -[workspace.dependencies.prisma-client-rust-cli] -default-features = false -features = ["migrations", "specta", "sqlite", "sqlite-create-many"] -git = "https://github.com/brendonovich/prisma-client-rust" -rev = "4f9ef9d38c" - [workspace.dependencies.prisma-client-rust-sdk] default-features = false features = ["sqlite"] diff --git a/apps/deps-generator/Cargo.toml b/apps/deps-generator/Cargo.toml deleted file mode 100644 index e775b7913..000000000 --- a/apps/deps-generator/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "sd-deps-generator" -version = "0.0.0" - -authors = ["Jake Robinson "] -description = "A tool to compile all Spacedrive dependencies and their respective licenses" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -# Workspace dependencies -reqwest = { workspace = true, features = ["blocking", "native-tls-vendored"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } - -# Specific Deps Generator dependencies -anyhow = "1.0" -cargo_metadata = "0.18.1" -clap = { version = "4.5", features = ["derive"] } diff --git a/apps/deps-generator/src/main.rs b/apps/deps-generator/src/main.rs deleted file mode 100644 index 42c81ec84..000000000 --- a/apps/deps-generator/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use anyhow::Result; -use cargo_metadata::CargoOpt; -use clap::Parser; -use std::{fs::File, path::PathBuf}; -use types::{ - backend::BackendDependency, - cli::{Action, Arguments}, - frontend::FrontendDependency, -}; - -pub mod types; - -const FOSSA_BASE_URL: &str = - "https://app.fossa.com/api/revisions/git%2Bgithub.com%2Fspacedriveapp%2Fspacedrive%24"; - -fn main() -> Result<()> { - let args = Arguments::parse(); - - match args.action { - Action::Frontend(sub_args) => write_frontend_deps(sub_args.revision, sub_args.path), - Action::Backend(sub_args) => { - write_backend_deps(sub_args.manifest_path, sub_args.output_path) - } - } -} - -fn write_backend_deps(manifest_path: PathBuf, output_path: PathBuf) -> Result<()> { - let cmd = cargo_metadata::MetadataCommand::new() - .manifest_path(manifest_path) - .features(CargoOpt::AllFeatures) - .exec()?; - - let deps: Vec = cmd - .packages - .into_iter() - .filter_map(|p| { - (!cmd.workspace_members.iter().any(|t| &p.id == t)).then_some(BackendDependency { - title: p.name, - description: p.description, - url: p.repository, - version: p.version.to_string(), - authors: p.authors, - license: p.license, - }) - }) - .collect(); - - let mut file = File::create(output_path)?; - serde_json::to_writer(&mut file, &deps)?; - - Ok(()) -} - -fn write_frontend_deps(rev: String, path: PathBuf) -> Result<()> { - let url = format!("{FOSSA_BASE_URL}{rev}/dependencies"); - - let response = reqwest::blocking::get(url)?.text()?; - let json: Vec = serde_json::from_str(&response)?; - - let deps: Vec<_> = json - .into_iter() - .map(|dep| FrontendDependency { - title: dep.project.title, - authors: dep.project.authors, - description: dep.project.description, - url: dep.project.url, - license: dep.licenses, - }) - .collect(); - - let mut file = File::create(path)?; - serde_json::to_writer(&mut file, &deps)?; - - Ok(()) -} diff --git a/apps/deps-generator/src/types/backend.rs b/apps/deps-generator/src/types/backend.rs deleted file mode 100644 index c78440e5d..000000000 --- a/apps/deps-generator/src/types/backend.rs +++ /dev/null @@ -1,12 +0,0 @@ -use serde::Serialize; - -#[allow(clippy::module_name_repetitions)] -#[derive(Serialize)] -pub struct BackendDependency { - pub title: String, - pub description: Option, - pub url: Option, - pub version: String, - pub authors: Vec, - pub license: Option, -} diff --git a/apps/deps-generator/src/types/cli.rs b/apps/deps-generator/src/types/cli.rs deleted file mode 100644 index 0ad792921..000000000 --- a/apps/deps-generator/src/types/cli.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::path::PathBuf; - -use clap::{Args, Parser, Subcommand}; - -#[derive(Parser)] -pub struct Arguments { - #[command(subcommand)] - pub action: Action, -} - -#[derive(Subcommand)] -pub enum Action { - Frontend(FrontendArgs), - Backend(BackendArgs), -} - -#[derive(Args)] -pub struct FrontendArgs { - // could source this from `$GITHUB_SHA` for CI, if not set - #[arg(help = "the git revision")] - pub revision: String, - #[arg(help = "the output path")] - pub path: PathBuf, -} - -#[derive(Args)] -pub struct BackendArgs { - // could use `Cargo.toml` as the default from current dir (if not set) - #[arg(help = "path to the cargo manifest")] - pub manifest_path: PathBuf, - #[arg(help = "the output path")] - pub output_path: PathBuf, -} diff --git a/apps/deps-generator/src/types/frontend.rs b/apps/deps-generator/src/types/frontend.rs deleted file mode 100644 index dcc0b3e02..000000000 --- a/apps/deps-generator/src/types/frontend.rs +++ /dev/null @@ -1,36 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct Dependency { - pub project: Project, - pub licenses: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct Project { - // pub locator: Option, - pub title: String, - pub description: Option, - pub url: Option, - pub authors: Vec>, -} - -#[derive(Serialize, Deserialize)] -pub struct License { - pub text: Option, - // pub license_id: Option, // always null AFAIK - pub copyright: Option, - // pub license_group_id: i64, - // pub ignored: bool, // always false from my testing - // pub revision_id: Option, -} - -#[allow(clippy::module_name_repetitions)] -#[derive(Serialize)] -pub struct FrontendDependency { - pub title: String, - pub description: Option, - pub url: Option, - pub authors: Vec>, - pub license: Vec, -} diff --git a/apps/deps-generator/src/types/mod.rs b/apps/deps-generator/src/types/mod.rs deleted file mode 100644 index 8e51afb58..000000000 --- a/apps/deps-generator/src/types/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod backend; -pub mod cli; -pub mod frontend; diff --git a/apps/desktop/crates/macos/Cargo.toml b/apps/desktop/crates/macos/Cargo.toml index cc5b156fe..8c812d62d 100644 --- a/apps/desktop/crates/macos/Cargo.toml +++ b/apps/desktop/crates/macos/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true repository.workspace = true [target.'cfg(target_os = "macos")'.dependencies] -swift-rs = { version = "1.0.6", features = ["serde"] } +swift-rs = { version = "1.0", features = ["serde"] } [target.'cfg(target_os = "macos")'.build-dependencies] -swift-rs = { version = "1.0.6", features = ["build"] } +swift-rs = { version = "1.0", features = ["build"] } diff --git a/apps/desktop/crates/windows/Cargo.toml b/apps/desktop/crates/windows/Cargo.toml index 7f3fe0bdf..a85799fed 100644 --- a/apps/desktop/crates/windows/Cargo.toml +++ b/apps/desktop/crates/windows/Cargo.toml @@ -7,9 +7,8 @@ license.workspace = true repository.workspace = true [dependencies] -libc = { workspace = true } -normpath = { workspace = true } -thiserror = { workspace = true } +libc = { workspace = true } +normpath = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies.windows] features = ["Win32_Foundation", "Win32_System_Com", "Win32_UI_Shell"] diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 993b13832..712c0412a 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -17,7 +17,6 @@ sd-prisma = { path = "../../../crates/prisma" } # Workspace dependencies axum = { workspace = true, features = ["headers", "query"] } -directories = { workspace = true } futures = { workspace = true } http = { workspace = true } hyper = { workspace = true } @@ -27,7 +26,6 @@ rspc = { workspace = true, features = ["tauri", "tracing"] } serde = { workspace = true } serde_json = { workspace = true } specta = { workspace = true } -specta-typescript = { workspace = true } strum = { workspace = true, features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"] } @@ -37,13 +35,15 @@ uuid = { workspace = true, features = ["serde"] } # Specific Desktop dependencies # WARNING: Do NOT enable default features, as that vendors dbus (see below) opener = { version = "0.7.1", features = ["reveal"], default-features = false } -tauri = { version = "=2.0.0-rc.2", features = ["linux-libxdo", "macos-private-api", "unstable"] } +specta-typescript = "=0.0.7" tauri-plugin-dialog = "=2.0.0-rc.0" tauri-plugin-os = "=2.0.0-rc.0" tauri-plugin-shell = "=2.0.0-rc.0" tauri-plugin-updater = "=2.0.0-rc.0" -tauri-runtime = { version = "=2.0.0-rc.2" } -tauri-utils = { version = "=2.0.0-rc.2" } + +[dependencies.tauri] +features = ["linux-libxdo", "macos-private-api", "native-tls-vendored", "unstable"] +version = "=2.0.0-rc.2" [dependencies.tauri-specta] features = ["derive", "typescript"] @@ -57,7 +57,7 @@ sd-desktop-linux = { path = "../crates/linux" } # Specific Desktop dependencies # WARNING: dbus must NOT be vendored, as that breaks the app on Linux,X11,Nvidia dbus = { version = "0.9.7", features = ["stdfd"] } -# https://github.com/tauri-apps/tauri/blob/tauri-v2.0.0-rc.2/core/tauri/Cargo.toml +# https://github.com/tauri-apps/tauri/blob/tauri-v2.0.0-rc.2/core/tauri/Cargo.toml#L86 webkit2gtk = { version = "=2.0.1", features = ["v2_38"] } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/apps/desktop/src-tauri/src/tauri_plugins.rs b/apps/desktop/src-tauri/src/tauri_plugins.rs index 79fd1f665..6e2fe7eb4 100644 --- a/apps/desktop/src-tauri/src/tauri_plugins.rs +++ b/apps/desktop/src-tauri/src/tauri_plugins.rs @@ -15,7 +15,7 @@ use axum::{ }; use http::Method; use hyper::server::{accept::Accept, conn::AddrIncoming}; -use rand::{distributions::Alphanumeric, Rng}; +use rand::{distr::Alphanumeric, Rng}; use sd_core::{custom_uri, Node, NodeError}; use serde::Deserialize; use tauri::{async_runtime::block_on, plugin::TauriPlugin, RunEvent, Runtime}; diff --git a/apps/mobile/modules/sd-core/android/crate/Cargo.toml b/apps/mobile/modules/sd-core/android/crate/Cargo.toml index f727988d2..c33ebc6e6 100644 --- a/apps/mobile/modules/sd-core/android/crate/Cargo.toml +++ b/apps/mobile/modules/sd-core/android/crate/Cargo.toml @@ -11,7 +11,7 @@ rust-version = "1.64" # Android can use dynamic linking since all FFI is done via JNI crate-type = ["cdylib"] -[dependencies] +[target.'cfg(target_os = "android")'.dependencies] # Spacedrive Sub-crates sd-mobile-core = { path = "../../core" } diff --git a/apps/mobile/modules/sd-core/ios/crate/Cargo.toml b/apps/mobile/modules/sd-core/ios/crate/Cargo.toml index db5a25fdb..60f59f295 100644 --- a/apps/mobile/modules/sd-core/ios/crate/Cargo.toml +++ b/apps/mobile/modules/sd-core/ios/crate/Cargo.toml @@ -13,6 +13,6 @@ rust-version = "1.64" # which are only available when linking against the app's ObjC crate-type = ["staticlib"] -[dependencies] +[target.'cfg(target_os = "ios")'.dependencies] # Spacedrive Sub-crates sd-mobile-core = { path = "../../core" } diff --git a/apps/p2p-relay/Cargo.toml b/apps/p2p-relay/Cargo.toml deleted file mode 100644 index e85208e69..000000000 --- a/apps/p2p-relay/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "sd-p2p-relay" -version = "0.0.1" - -edition.workspace = true -license.workspace = true -publish = false -repository.workspace = true - -[dependencies] -# Workspace dependencies -libp2p = { version = "0.53.2", features = [ - "autonat", - "macros", - "quic", - "relay", - "tokio" -] } # Update blocked due to custom patch -reqwest = { workspace = true, features = ["json", "native-tls-vendored"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } -uuid = { workspace = true, features = ["serde", "v4"] } - -# Specific P2P Relay dependencies -hex = "0.4.3" diff --git a/apps/p2p-relay/deploy.sh b/apps/p2p-relay/deploy.sh deleted file mode 100755 index 78dd7b558..000000000 --- a/apps/p2p-relay/deploy.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# A temporary script to deploy the p2p-relay to the server for testing - -set -e - -SERVER="" -TARGET_DIR=$(cargo metadata | jq -r .target_directory) -cargo zigbuild --target aarch64-unknown-linux-musl --release - -scp "$TARGET_DIR/aarch64-unknown-linux-musl/release/sd-p2p-relay" ec2-user@$SERVER:/home/ec2-user/sd-p2p-relay - -# ssh ec2-user@$SERVER -# ./sd-p2p-relay init -# Enter the `P2P_SECRET` secret env var from Vercel -# ./sd-p2p-relay diff --git a/apps/p2p-relay/src/config.rs b/apps/p2p-relay/src/config.rs deleted file mode 100644 index bbf03e390..000000000 --- a/apps/p2p-relay/src/config.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{borrow::Cow, path::Path}; - -use libp2p::identity::Keypair; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - // Unique ID of this relay server. - pub id: Uuid, - // URL of the cloud API. - #[serde(skip_serializing_if = "Option::is_none")] - api_url: Option, - // Secret used for authenticating with cloud backend. - pub p2p_secret: String, - // Port to listen on. - #[serde(skip_serializing_if = "Option::is_none")] - port: Option, - // Private/public keypair to use for the relay. - #[serde(with = "keypair")] - pub keypair: Keypair, -} - -impl Config { - pub fn init( - path: impl AsRef, - p2p_secret: String, - ) -> Result> { - let config = Self { - id: Uuid::new_v4(), - api_url: None, - p2p_secret, - port: None, - keypair: Keypair::generate_ed25519(), - }; - std::fs::write(path, serde_json::to_string_pretty(&config)?)?; - Ok(config) - } - - pub fn load(path: impl AsRef) -> Result> { - let config = std::fs::read_to_string(path)?; - Ok(serde_json::from_str(&config)?) - } - - pub fn api_url(&self) -> Cow<'_, str> { - match self.api_url { - Some(ref url) => Cow::Borrowed(url), - None => Cow::Borrowed("https://app.spacedrive.com"), - } - } - - pub fn port(&self) -> u16 { - self.port.unwrap_or(7373) // TODO: Should we use HTTPS port to avoid strict internet filters??? - } -} - -mod keypair { - use libp2p::identity::Keypair; - use serde::{de::Error, Deserialize, Deserializer, Serializer}; - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let bytes = hex::decode(s).map_err(D::Error::custom)?; - Keypair::from_protobuf_encoding(bytes.as_slice()).map_err(D::Error::custom) - } - - pub fn serialize(v: &Keypair, serializer: S) -> Result { - serializer.serialize_str(&hex::encode( - v.to_protobuf_encoding().expect("invalid keypair type"), - )) - } -} diff --git a/apps/p2p-relay/src/main.rs b/apps/p2p-relay/src/main.rs deleted file mode 100644 index 60eb1720d..000000000 --- a/apps/p2p-relay/src/main.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::{ - io::{stdin, stdout, Write}, - net::{Ipv4Addr, Ipv6Addr, SocketAddr}, - path::PathBuf, -}; - -use libp2p::{ - autonat, - futures::StreamExt, - relay, - swarm::{NetworkBehaviour, SwarmEvent}, -}; -use reqwest::header::{self, HeaderMap, HeaderValue}; -use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; -use uuid::Uuid; - -use crate::utils::socketaddr_to_quic_multiaddr; - -mod config; -mod utils; - -// TODO: Authentication with the Spacedrive Cloud -// TODO: Rate-limit data usage by Spacedrive account. -// TODO: Expose libp2p metrics like - https://github.com/mxinden/rust-libp2p-server/blob/master/src/behaviour.rs - -#[derive(NetworkBehaviour)] -pub struct Behaviour { - relay: relay::Behaviour, - autonat: autonat::Behaviour, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RelayServerEntry { - id: Uuid, - // TODO: Try and drop this field cause it's libp2p specific - peer_id: String, - addrs: Vec, -} - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt() - // .with_env_filter(EnvFilter::from_default_env()) // TODO: ??? - .init(); - - let config_path = - PathBuf::from(std::env::var("CONFIG_PATH").unwrap_or("./config.json".to_string())); - - let mut args = std::env::args(); - args.next(); // Skip binary name - if args.next().as_deref() == Some("init") { - println!("Initializing config at '{config_path:?}'..."); - - if config_path.exists() { - panic!("Config already exists at path '{config_path:?}'. Please delete it first!"); - // TODO: Error handling - } - - print!("Please enter the p2p secret: "); - let mut p2p_secret = String::new(); - let _ = stdout().flush(); - stdin() - .read_line(&mut p2p_secret) - .expect("Did not enter a correct string"); - - config::Config::init(&config_path, p2p_secret.replace('\n', "")).unwrap(); // TODO: Error handling - println!("\nSuccessfully initialized config at '{config_path:?}'!"); - return; - } - - if !config_path.exists() { - panic!("Unable to find config at path '{config_path:?}'. Please create it!"); // TODO: Error handling - } - let config = config::Config::load(&config_path).unwrap(); // TODO: Error handling - - info!("Starting..."); - - let public_ipv4: Ipv4Addr = reqwest::get("https://api.ipify.org") - .await - .unwrap() // TODO: Error handling - .text() - .await - .unwrap() // TODO: Error handling - .parse() - .unwrap(); // TODO: Error handling - - let public_ipv6: Option = match reqwest::get("https://api6.ipify.org").await { - Ok(v) => Some( - v.text() - .await - .unwrap() // TODO: Error handling - .parse() - .unwrap(), // TODO: Error handling - ), - Err(_) => { - warn!("Error getting public IPv6 address. Skipping IPv6 configuration."); - None - } - }; - - info!("Determined public addresses of the current relay to be: '{public_ipv4}' and '{public_ipv6:?}'"); - - let (first_advertisement_tx, mut first_advertisement_rx) = tokio::sync::mpsc::channel(1); - tokio::spawn({ - let config = config.clone(); - async move { - let client = reqwest::Client::new(); - - let mut first_advertisement_tx = Some(first_advertisement_tx); - loop { - let result = client - .post(format!("{}/api/p2p/relays", config.api_url())) - .headers({ - let mut map = HeaderMap::new(); - map.insert( - header::AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", config.p2p_secret)) - .unwrap(), - ); - map - }) - .json(&RelayServerEntry { - id: config.id, - peer_id: config.keypair.public().to_peer_id().to_base58(), - addrs: { - let mut ips: Vec = - vec![SocketAddr::from((public_ipv4, config.port()))]; - if let Some(ip) = public_ipv6 { - ips.push(SocketAddr::from((ip, config.port()))); - } - ips - }, - }) - .send() - .await; - - let mut is_ok = result.is_ok(); - match result { - Ok(result) => { - if result.status() != 200 { - error!( - "Failed to register relay server with cloud status {}: {:?}", - result.status(), - result.text().await - ); - is_ok = false; - } else { - info!( - "Successfully registered '{}' as relay server with cloud", - config.id - ); - } - } - Err(e) => error!("Failed to register relay server with cloud: {e}"), - } - - if let Some(tx) = first_advertisement_tx.take() { - tx.send(is_ok).await.ok(); - } - - tokio::time::sleep(std::time::Duration::from_secs(9 * 60)).await; - } - } - }); - - if !first_advertisement_rx - .recv() - .await - .expect("Advertisement task died during startup!") - { - panic!( - "Failed to register relay server with cloud. Please check your config and try again." - ); // TODO: Error handling - } - - // TODO: Setup logging to filesystem with auto-rotation - - let peer_id = config.keypair.public().to_peer_id(); - - let mut swarm = libp2p::SwarmBuilder::with_existing_identity(config.keypair.clone()) - .with_tokio() - .with_quic() - .with_behaviour(|key| Behaviour { - relay: relay::Behaviour::new(key.public().to_peer_id(), Default::default()), // TODO: Proper config - autonat: autonat::Behaviour::new(key.public().to_peer_id(), Default::default()), // TODO: Proper config - }) - .unwrap() // TODO: Error handling - .build(); - - swarm - .listen_on(socketaddr_to_quic_multiaddr(&SocketAddr::from(( - Ipv6Addr::UNSPECIFIED, - config.port(), - )))) - .unwrap(); // TODO: Error handling - swarm - .listen_on(socketaddr_to_quic_multiaddr(&SocketAddr::from(( - Ipv4Addr::UNSPECIFIED, - config.port(), - )))) - .unwrap(); // TODO: Error handling - - info!("Started Relay as PeerId '{peer_id}'"); - - loop { - match swarm.next().await.expect("Infinite Stream.") { - // SwarmEvent::Behaviour(event) => { - // println!("{event:?}") - // } - SwarmEvent::NewListenAddr { address, .. } => { - info!("Listening on {address:?}"); - } - event => println!("{event:?}"), - } - } -} diff --git a/apps/p2p-relay/src/utils.rs b/apps/p2p-relay/src/utils.rs deleted file mode 100644 index 760a78c0c..000000000 --- a/apps/p2p-relay/src/utils.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! This file contains some fairly meaningless glue code for integrating with libp2p. - -use std::net::SocketAddr; - -use libp2p::{multiaddr::Protocol, Multiaddr}; - -#[must_use] -pub(crate) fn socketaddr_to_quic_multiaddr(m: &SocketAddr) -> Multiaddr { - let mut addr = Multiaddr::empty(); - match m { - SocketAddr::V4(ip) => addr.push(Protocol::Ip4(*ip.ip())), - SocketAddr::V6(ip) => addr.push(Protocol::Ip6(*ip.ip())), - } - addr.push(Protocol::Udp(m.port())); - addr.push(Protocol::QuicV1); - addr -} diff --git a/apps/server/Cargo.toml b/apps/server/Cargo.toml index 841928131..f649ab23b 100644 --- a/apps/server/Cargo.toml +++ b/apps/server/Cargo.toml @@ -17,7 +17,6 @@ sd-core = { path = "../../core", features = ["ffmpeg", "heif"] } # Workspace dependencies axum = { workspace = true, features = ["headers"] } -base64 = { workspace = true } http = { workspace = true } rspc = { workspace = true, features = ["axum"] } tempfile = { workspace = true } diff --git a/core/Cargo.toml b/core/Cargo.toml index 4f6f4d9d0..393c35fd8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,7 +15,7 @@ default = [] mobile = [] # This feature controls whether the Spacedrive Core contains functionality which requires FFmpeg. ai = ["dep:sd-ai"] -ffmpeg = ["dep:sd-ffmpeg", "sd-core-heavy-lifting/ffmpeg", "sd-media-metadata/ffmpeg"] +ffmpeg = ["sd-core-heavy-lifting/ffmpeg", "sd-media-metadata/ffmpeg"] heif = ["sd-images/heif"] [dependencies] @@ -30,7 +30,6 @@ sd-core-sync = { path = "./crates/sync" } sd-actors = { path = "../crates/actors" } sd-ai = { path = "../crates/ai", optional = true } sd-cloud-api = { path = "../crates/cloud-api" } -sd-ffmpeg = { path = "../crates/ffmpeg", optional = true } sd-file-ext = { path = "../crates/file-ext" } sd-images = { path = "../crates/images", features = ["rspc", "serde", "specta"] } sd-media-metadata = { path = "../crates/media-metadata" } @@ -44,80 +43,72 @@ sd-task-system = { path = "../crates/task-system" } sd-utils = { path = "../crates/utils" } # Workspace dependencies -async-channel = { workspace = true } -async-trait = { workspace = true } -axum = { workspace = true, features = ["ws"] } -base64 = { workspace = true } -base91 = { workspace = true } -blake3 = { workspace = true } -chrono = { workspace = true, features = ["serde"] } -directories = { workspace = true } -futures = { workspace = true } +async-channel = { workspace = true } +async-stream = { workspace = true } +async-trait = { workspace = true } +axum = { workspace = true, features = ["ws"] } +base64 = { workspace = true } +blake3 = { workspace = true } +chrono = { workspace = true, features = ["serde"] } +futures = { workspace = true } futures-concurrency = { workspace = true } -gix-ignore = { workspace = true } -hyper = { workspace = true, features = ["client", "http1", "server"] } -image = { workspace = true } -itertools = { workspace = true } -libc = { workspace = true } -normpath = { workspace = true, features = ["localization"] } -once_cell = { workspace = true } -pin-project-lite = { workspace = true } -prisma-client-rust = { workspace = true, features = ["rspc"] } -regex = { workspace = true } -reqwest = { workspace = true, features = ["json", "native-tls-vendored"] } -rmp = { workspace = true } -rmp-serde = { workspace = true } -rmpv = { workspace = true } -rspc = { workspace = true, features = ["alpha", "axum", "chrono", "tracing", "unstable", "uuid"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -specta = { workspace = true } -static_assertions = { workspace = true } -strum = { workspace = true, features = ["derive"] } -strum_macros = { workspace = true } -tempfile = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, features = [ - "io-util", - "macros", - "process", - "rt-multi-thread", - "sync", - "time" -] } -tokio-stream = { workspace = true, features = ["fs"] } -tokio-util = { workspace = true, features = ["io"] } -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } -uuid = { workspace = true, features = ["serde", "v4"] } -webp = { workspace = true } +hyper = { workspace = true, features = ["client", "http1", "server"] } +image = { workspace = true } +itertools = { workspace = true } +libc = { workspace = true } +normpath = { workspace = true, features = ["localization"] } +once_cell = { workspace = true } +pin-project-lite = { workspace = true } +prisma-client-rust = { workspace = true, features = ["rspc"] } +regex = { workspace = true } +reqwest = { workspace = true, features = ["json", "native-tls-vendored"] } +rmp-serde = { workspace = true } +rmpv = { workspace = true } +rspc = { workspace = true, features = ["alpha", "axum", "chrono", "tracing", "unstable", "uuid"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +specta = { workspace = true } +strum = { workspace = true, features = ["derive"] } +strum_macros = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio-stream = { workspace = true, features = ["fs"] } +tokio-util = { workspace = true, features = ["io"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } +uuid = { workspace = true, features = ["serde", "v4"] } # Specific Core dependencies -async-recursion = "1.1" -async-stream = "0.3.5" -aws-config = "1.5" -aws-credential-types = "1.2" -aws-sdk-s3 = { version = "1.34", features = ["behavior-version-latest"] } -bytes = "1.6" -ctor = "0.2.8" -flate2 = "1.0" -hostname = "0.4.0" -http-body = "0.4.6" # Update blocked by http -http-range = "0.1.5" -int-enum = "0.5" # Update blocked due to API breaking changes -mini-moka = "0.10.3" -notify = { git = "https://github.com/notify-rs/notify.git", rev = "c3929ed114", default-features = false, features = [ - "macos_fsevent" -] } -serde-hashkey = "0.4.5" -serde_repr = "0.1.19" -serde_with = "3.8" -slotmap = "1.0" -sysinfo = "0.29.11" # Update blocked due to API breaking changes -tar = "0.4.41" -tower-service = "0.3.2" +async-recursion = "1.1" +base91 = "0.1.0" +bytes = "1.6" +ctor = "0.2.8" +directories = "5.0" +flate2 = "1.0" +hostname = "0.4.0" +http-body = "0.4.6" # Update blocked by http +http-range = "0.1.5" +int-enum = "0.5" # Update blocked due to API breaking changes +mini-moka = "0.10.3" +serde-hashkey = "0.4.5" +serde_repr = "0.1.19" +serde_with = "3.8" +slotmap = "1.0" +sysinfo = "0.29.11" # Update blocked due to API breaking changes +tar = "0.4.41" +tower-service = "0.3.2" tracing-appender = "0.2.3" +[dependencies.tokio] +features = ["io-util", "macros", "process", "rt-multi-thread", "sync", "time"] +workspace = true + +[dependencies.notify] +default-features = false +features = ["macos_fsevent"] +git = "https://github.com/notify-rs/notify.git" +rev = "c3929ed114" + # Override features of transitive dependencies [dependencies.openssl] features = ["vendored"] @@ -129,13 +120,13 @@ version = "0.9.103" # Platform-specific dependencies [target.'cfg(target_os = "macos")'.dependencies] plist = "1.6" -trash = "4.1" +trash = "5.1" [target.'cfg(target_os = "linux")'.dependencies] -trash = "4.1" +trash = "5.1" [target.'cfg(target_os = "windows")'.dependencies] -trash = "4.1" +trash = "5.1" [target.'cfg(target_os = "ios")'.dependencies] icrate = { version = "0.1.2", features = [ @@ -150,7 +141,6 @@ tracing-android = "0.2.0" [dev-dependencies] # Workspace dependencies -globset = { workspace = true } tracing-test = { workspace = true } # Specific Core dependencies diff --git a/core/crates/file-path-helper/Cargo.toml b/core/crates/file-path-helper/Cargo.toml index f3e408e43..94960869b 100644 --- a/core/crates/file-path-helper/Cargo.toml +++ b/core/crates/file-path-helper/Cargo.toml @@ -27,5 +27,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } tracing = { workspace = true } -[target.'cfg(windows)'.dependencies] -winapi-util = "0.1.8" +# Specific File Path Helper dependencies +[target.'cfg(target_os = "windows")'.dependencies.windows] +features = ["Win32_Security", "Win32_Storage_FileSystem"] +version = "0.58" diff --git a/core/crates/file-path-helper/src/lib.rs b/core/crates/file-path-helper/src/lib.rs index 600981688..7691f896d 100644 --- a/core/crates/file-path-helper/src/lib.rs +++ b/core/crates/file-path-helper/src/lib.rs @@ -102,6 +102,43 @@ pub fn path_is_hidden(path: impl AsRef, metadata: &Metadata) -> bool { false } +#[cfg(target_family = "windows")] +fn get_inode_windows(path: &str) -> Result { + use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr::null_mut}; + use windows::{ + Win32::Foundation::HANDLE, + Win32::Storage::FileSystem::{ + CreateFileW, GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, + FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, OPEN_EXISTING, + }, + }; + + let handle = unsafe { + CreateFileW( + PCWSTR::from(&HSTRING::from(path)), + 0, + FILE_SHARE_READ, + null_mut(), + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, + HANDLE(0), + ) + }; + + if handle.is_invalid() { + return Err(std::io::Error::last_os_error()); + } + + let mut file_info = BY_HANDLE_FILE_INFORMATION::default(); + let result = unsafe { GetFileInformationByHandle(handle, &mut file_info) }; + + if result.as_bool() { + Ok(file_info.nFileIndexLow as u64 | ((file_info.nFileIndexHigh as u64) << 32)) + } else { + Err(std::io::Error::last_os_error()) + } +} + impl FilePathMetadata { pub fn from_path(path: impl AsRef, metadata: &Metadata) -> Result { let path = path.as_ref(); @@ -113,15 +150,9 @@ impl FilePathMetadata { #[cfg(target_family = "windows")] { - use winapi_util::{file::information, Handle}; - - let info = tokio::task::block_in_place(|| { - Handle::from_path_any(path) - .and_then(|ref handle| information(handle)) - .map_err(|e| FileIOError::from((path, e))) + tokio::task::block_in_place(|| { + get_inode_windows(path).map_err(|e| FileIOError::from((path, e))) })?; - - info.file_index() } }; diff --git a/core/crates/heavy-lifting/Cargo.toml b/core/crates/heavy-lifting/Cargo.toml index 1dcc11bb5..cf69c7d6b 100644 --- a/core/crates/heavy-lifting/Cargo.toml +++ b/core/crates/heavy-lifting/Cargo.toml @@ -48,7 +48,6 @@ rspc = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } specta = { workspace = true } -static_assertions = { workspace = true } strum = { workspace = true, features = ["derive", "phf"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs", "parking_lot", "sync"] } @@ -57,6 +56,9 @@ tracing = { workspace = true } uuid = { workspace = true, features = ["serde", "v4"] } webp = { workspace = true } +# Specific Heavy Lifting dependencies +static_assertions = "1.1" + [dev-dependencies] tempfile = { workspace = true } tracing-test = { workspace = true } diff --git a/core/crates/indexer-rules/Cargo.toml b/core/crates/indexer-rules/Cargo.toml index db7a6dcb3..e79efb83b 100644 --- a/core/crates/indexer-rules/Cargo.toml +++ b/core/crates/indexer-rules/Cargo.toml @@ -15,7 +15,6 @@ sd-utils = { path = "../../../crates/utils" } # Workspace dependencies chrono = { workspace = true } futures-concurrency = { workspace = true } -gix-ignore = { workspace = true, features = ["serde"] } globset = { workspace = true, features = ["serde1"] } once_cell = { workspace = true } prisma-client-rust = { workspace = true } @@ -28,5 +27,8 @@ tokio = { workspace = true, features = ["fs"] } tracing = { workspace = true } uuid = { workspace = true, features = ["serde", "v4"] } +# Specific Indexer Rules dependencies +gix-ignore = { version = "0.11.2", features = ["serde"] } + [dev-dependencies] tempfile = { workspace = true } diff --git a/core/crates/sync/Cargo.toml b/core/crates/sync/Cargo.toml index 84ca5c3d1..930c3cdd0 100644 --- a/core/crates/sync/Cargo.toml +++ b/core/crates/sync/Cargo.toml @@ -23,7 +23,6 @@ rmp-serde = { workspace = true } rmpv = { workspace = true } rspc = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/core/src/api/categories.rs b/core/src/api/categories.rs deleted file mode 100644 index 5f6a8fdf2..000000000 --- a/core/src/api/categories.rs +++ /dev/null @@ -1,49 +0,0 @@ -// TODO: Ensure this file has normalised caching setup before reenabling - -// use crate::library::Category; - -// use std::{collections::BTreeMap, str::FromStr}; - -// use rspc::{alpha::AlphaRouter, ErrorCode}; -// use strum::VariantNames; - -// use super::{utils::library, Ctx, R}; - -// pub(crate) fn mount() -> AlphaRouter { -// R.router().procedure("list", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// let (categories, queries): (Vec<_>, Vec<_>) = Category::VARIANTS -// .iter() -// .map(|category| { -// let category = Category::from_str(category) -// .expect("it's alright this category string exists"); -// ( -// category, -// library.db.object().count(vec![category.to_where_param()]), -// ) -// }) -// .unzip(); - -// Ok(categories -// .into_iter() -// .zip( -// library -// .db -// ._batch(queries) -// .await? -// .into_iter() -// // TODO(@Oscar): rspc bigint support -// .map(|count| { -// i32::try_from(count).map_err(|_| { -// rspc::Error::new( -// ErrorCode::InternalServerError, -// "category item count overflowed 'i32'!".into(), -// ) -// }) -// }) -// .collect::, _>>()?, -// ) -// .collect::>()) -// }) -// }) -// } diff --git a/core/src/api/cloud.rs b/core/src/api/cloud.rs index ecffa5fe8..a782431e4 100644 --- a/core/src/api/cloud.rs +++ b/core/src/api/cloud.rs @@ -180,67 +180,17 @@ mod library { }) } } - mod locations { - use aws_config::{Region, SdkConfig}; - use aws_credential_types::provider::future; - use aws_sdk_s3::{ - config::{Credentials, ProvideCredentials, SharedCredentialsProvider}, - primitives::ByteStream, - }; - use http_body::Full; - use once_cell::sync::OnceCell; - use serde::{Deserialize, Serialize}; - use specta::Type; - use super::*; + use serde::{Deserialize, Serialize}; + use specta::Type; #[derive(Type, Serialize, Deserialize)] pub struct CloudLocation { id: String, name: String, } - #[derive(Debug)] - pub struct CredentialsProvider(sd_cloud_api::locations::authorize::Response); - - impl ProvideCredentials for CredentialsProvider { - fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a> - where - Self: 'a, - { - future::ProvideCredentials::ready(Ok(Credentials::new( - self.0.access_key_id.clone(), - self.0.secret_access_key.clone(), - Some(self.0.session_token.clone()), - None, // TODO: Get this from the SD Cloud backend - "sd-cloud", - ))) - } - - fn fallback_on_interrupt(&self) -> Option { - None - } - } - - static AWS_S3_CLIENT: OnceCell = OnceCell::new(); - - // Reuse the client between procedure calls - fn get_aws_s3_client( - token: sd_cloud_api::locations::authorize::Response, - ) -> &'static aws_sdk_s3::Client { - AWS_S3_CLIENT.get_or_init(|| { - aws_sdk_s3::Client::new( - &SdkConfig::builder() - .region(Region::new("us-west-1")) // TODO: From cloud config - .credentials_provider(SharedCredentialsProvider::new(CredentialsProvider( - token, - ))) - .build(), - ) - }) - } - pub fn mount() -> AlphaRouter { R.router() .procedure("list", { @@ -264,58 +214,5 @@ mod locations { .map_err(Into::into) }) }) - // TODO: Remove this - .procedure("testing", { - // // TODO: Move this off a static. This is just for debugging. - // static AUTH_TOKEN: Lazy>> = - // Lazy::new(|| Mutex::new(None)); - - #[derive(Type, Deserialize)] - pub struct TestingParams { - id: String, - path: String, - } - - R.mutation(|node, params: TestingParams| async move { - let token = { - let token = &mut None; // AUTH_TOKEN.lock().await; // TODO: Caching of the token. For now it's annoying when debugging. - if token.is_none() { - *token = Some( - sd_cloud_api::locations::authorize( - node.cloud_api_config().await, - params.id, - ) - .await?, - ); - } - - token.clone().expect("Checked above") - }; - - println!("{token:?}"); // TODO - - // Initializes the client on the first call. Retrieves the same client on subsequent calls. - let client = get_aws_s3_client(token); - - client - .put_object() - .bucket("spacedrive-cloud") // TODO: From cloud config - .key(params.path) // TODO: Proper access control to only the current locations files - .body(ByteStream::from_body_0_4(Full::from("Hello, world!"))) - .send() - .await - .map_err(|e| { - tracing::error!(?e, "S3 error;"); - rspc::Error::new( - rspc::ErrorCode::InternalServerError, - "Failed to upload to S3".to_string(), - ) - })?; // TODO: Error handling - - println!("Uploaded file!"); - - Ok(()) - }) - }) } } diff --git a/core/src/api/keys.rs b/core/src/api/keys.rs deleted file mode 100644 index 890e7d35b..000000000 --- a/core/src/api/keys.rs +++ /dev/null @@ -1,436 +0,0 @@ -// TODO: Ensure this file has normalised caching setup before reenabling - -// use rspc::alpha::AlphaRouter; -// use rspc::ErrorCode; -// use sd_crypto::keys::keymanager::{StoredKey, StoredKeyType}; -// use sd_crypto::primitives::SECRET_KEY_IDENTIFIER; -// use sd_crypto::types::{Algorithm, HashingAlgorithm, OnboardingConfig, SecretKeyString}; -// use sd_crypto::{Error, Protected}; -// use serde::Deserialize; -// use specta::Type; -// use std::{path::PathBuf, str::FromStr}; -// use tokio::fs::File; -// use tokio::io::{AsyncReadExt, AsyncWriteExt}; -// use uuid::Uuid; - -// use crate::util::db::write_storedkey_to_db; -// use crate::{invalidate_query, prisma::key}; - -// use super::utils::library; -// use super::{Ctx, R}; - -// #[derive(Type, Deserialize)] -// pub struct KeyAddArgs { -// algorithm: Algorithm, -// hashing_algorithm: HashingAlgorithm, -// key: Protected, -// library_sync: bool, -// automount: bool, -// } - -// #[derive(Type, Deserialize)] -// pub struct UnlockKeyManagerArgs { -// password: Protected, -// secret_key: Protected, -// } - -// #[derive(Type, Deserialize)] -// pub struct RestoreBackupArgs { -// password: Protected, -// secret_key: Protected, -// path: PathBuf, -// } - -// #[derive(Type, Deserialize)] -// pub struct MasterPasswordChangeArgs { -// password: Protected, -// algorithm: Algorithm, -// hashing_algorithm: HashingAlgorithm, -// } - -// #[derive(Type, Deserialize)] -// pub struct AutomountUpdateArgs { -// uuid: Uuid, -// status: bool, -// } - -// pub(crate) fn mount() -> AlphaRouter { -// R.router() -// .procedure("list", { -// R.with2(library()) -// .query(|(_, library), _: ()| async move { Ok(library.key_manager.dump_keystore()) }) -// }) -// // do not unlock the key manager until this route returns true -// .procedure("isUnlocked", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// Ok(library.key_manager.is_unlocked().await) -// }) -// }) -// .procedure("isSetup", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// Ok(!library.db.key().find_many(vec![]).exec().await?.is_empty()) -// }) -// }) -// .procedure("setup", { -// R.with2(library()) -// .mutation(|(_, library), config: OnboardingConfig| async move { -// let root_key = library.key_manager.onboarding(config, library.id).await?; -// write_storedkey_to_db(&library.db, &root_key).await?; -// library -// .key_manager -// .populate_keystore(vec![root_key]) -// .await?; - -// invalidate_query!(library, "keys.isSetup"); -// invalidate_query!(library, "keys.isUnlocked"); - -// Ok(()) -// }) -// }) -// // this is so we can show the key as mounted in the UI -// .procedure("listMounted", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// Ok(library.key_manager.get_mounted_uuids()) -// }) -// }) -// .procedure("getKey", { -// R.with2(library()) -// .query(|(_, library), key_uuid: Uuid| async move { -// Ok(library -// .key_manager -// .get_key(key_uuid) -// .await? -// .expose() -// .clone()) -// }) -// }) -// .procedure("mount", { -// R.with2(library()) -// .mutation(|(_, library), key_uuid: Uuid| async move { -// library.key_manager.mount(key_uuid).await?; -// // we also need to dispatch jobs that automatically decrypt preview media and metadata here -// invalidate_query!(library, "keys.listMounted"); -// Ok(()) -// }) -// }) -// .procedure("getSecretKey", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// if library -// .key_manager -// .keyring_contains_valid_secret_key(library.id) -// .await -// .is_ok() -// { -// Ok(Some( -// library -// .key_manager -// .keyring_retrieve(library.id, SECRET_KEY_IDENTIFIER.to_string()) -// .await? -// .expose() -// .clone(), -// )) -// } else { -// Ok(None) -// } -// }) -// }) -// .procedure("unmount", { -// R.with2(library()) -// .mutation(|(_, library), key_uuid: Uuid| async move { -// library.key_manager.unmount(key_uuid)?; -// // we also need to delete all in-memory decrypted data associated with this key -// invalidate_query!(library, "keys.listMounted"); -// Ok(()) -// }) -// }) -// .procedure("clearMasterPassword", { -// R.with2(library()) -// .mutation(|(_, library), _: ()| async move { -// // This technically clears the root key, but it means the same thing to the frontend -// library.key_manager.clear_root_key().await?; - -// invalidate_query!(library, "keys.isUnlocked"); -// Ok(()) -// }) -// }) -// .procedure("syncKeyToLibrary", { -// R.with2(library()) -// .mutation(|(_, library), key_uuid: Uuid| async move { -// let key = library.key_manager.sync_to_database(key_uuid).await?; - -// // does not check that the key doesn't exist before writing -// write_storedkey_to_db(&library.db, &key).await?; - -// invalidate_query!(library, "keys.list"); -// Ok(()) -// }) -// }) -// .procedure("updateAutomountStatus", { -// R.with2(library()) -// .mutation(|(_, library), args: AutomountUpdateArgs| async move { -// if !library.key_manager.is_memory_only(args.uuid).await? { -// library -// .key_manager -// .change_automount_status(args.uuid, args.status) -// .await?; - -// library -// .db -// .key() -// .update( -// key::uuid::equals(args.uuid.to_string()), -// vec![key::automount::set(args.status)], -// ) -// .exec() -// .await?; - -// invalidate_query!(library, "keys.list"); -// } - -// Ok(()) -// }) -// }) -// .procedure("deleteFromLibrary", { -// R.with2(library()) -// .mutation(|(_, library), key_uuid: Uuid| async move { -// if !library.key_manager.is_memory_only(key_uuid).await? { -// library -// .db -// .key() -// .delete(key::uuid::equals(key_uuid.to_string())) -// .exec() -// .await?; -// } - -// library.key_manager.remove_key(key_uuid).await?; - -// // we also need to delete all in-memory decrypted data associated with this key -// invalidate_query!(library, "keys.list"); -// invalidate_query!(library, "keys.listMounted"); -// invalidate_query!(library, "keys.getDefault"); -// Ok(()) -// }) -// }) -// .procedure("unlockKeyManager", { -// R.with2(library()) -// .mutation(|(_, library), args: UnlockKeyManagerArgs| async move { -// let secret_key = -// (!args.secret_key.expose().is_empty()).then_some(args.secret_key); - -// library -// .key_manager -// .unlock( -// args.password, -// secret_key.map(SecretKeyString), -// library.id, -// || invalidate_query!(library, "keys.isKeyManagerUnlocking"), -// ) -// .await?; - -// invalidate_query!(library, "keys.isUnlocked"); - -// let automount = library -// .db -// .key() -// .find_many(vec![key::automount::equals(true)]) -// .exec() -// .await?; - -// for key in automount { -// library -// .key_manager -// .mount(Uuid::from_str(&key.uuid).map_err(|_| Error::Serialization)?) -// .await?; - -// invalidate_query!(library, "keys.listMounted"); -// } - -// Ok(()) -// }) -// }) -// .procedure("setDefault", { -// R.with2(library()) -// .mutation(|(_, library), key_uuid: Uuid| async move { -// library.key_manager.set_default(key_uuid).await?; - -// library -// .db -// .key() -// .update_many( -// vec![key::default::equals(true)], -// vec![key::default::set(false)], -// ) -// .exec() -// .await?; - -// library -// .db -// .key() -// .update( -// key::uuid::equals(key_uuid.to_string()), -// vec![key::default::set(true)], -// ) -// .exec() -// .await?; - -// invalidate_query!(library, "keys.getDefault"); -// Ok(()) -// }) -// }) -// .procedure("getDefault", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// library.key_manager.get_default().await.ok() -// }) -// }) -// .procedure("isKeyManagerUnlocking", { -// R.with2(library()).query(|(_, library), _: ()| async move { -// library.key_manager.is_unlocking().await.ok() -// }) -// }) -// .procedure("unmountAll", { -// R.with2(library()) -// .mutation(|(_, library), _: ()| async move { -// library.key_manager.empty_keymount(); -// invalidate_query!(library, "keys.listMounted"); -// Ok(()) -// }) -// }) -// .procedure("add", { -// // this also mounts the key -// R.with2(library()) -// .mutation(|(_, library), args: KeyAddArgs| async move { -// // register the key with the keymanager -// let uuid = library -// .key_manager -// .add_to_keystore( -// args.key, -// args.algorithm, -// args.hashing_algorithm, -// !args.library_sync, -// args.automount, -// None, -// ) -// .await?; - -// if args.library_sync { -// write_storedkey_to_db( -// &library.db, -// &library.key_manager.access_keystore(uuid).await?, -// ) -// .await?; - -// if args.automount { -// library -// .db -// .key() -// .update( -// key::uuid::equals(uuid.to_string()), -// vec![key::automount::set(true)], -// ) -// .exec() -// .await?; -// } -// } - -// library.key_manager.mount(uuid).await?; - -// invalidate_query!(library, "keys.list"); -// invalidate_query!(library, "keys.listMounted"); -// Ok(()) -// }) -// }) -// .procedure("backupKeystore", { -// R.with2(library()) -// .mutation(|(_, library), path: PathBuf| async move { -// // dump all stored keys that are in the key manager (maybe these should be taken from prisma as this will include even "non-sync with library" keys) -// let mut stored_keys = library.key_manager.dump_keystore(); - -// // include the verification key at the time of backup -// stored_keys.push(library.key_manager.get_verification_key().await?); - -// // exclude all memory-only keys -// stored_keys.retain(|k| !k.memory_only); - -// let mut output_file = File::create(path).await.map_err(Error::Io)?; -// output_file -// .write_all( -// &serde_json::to_vec(&stored_keys).map_err(|_| Error::Serialization)?, -// ) -// .await -// .map_err(Error::Io)?; -// Ok(()) -// }) -// }) -// .procedure("restoreKeystore", { -// R.with2(library()) -// .mutation(|(_, library), args: RestoreBackupArgs| async move { -// let mut input_file = File::open(args.path).await.map_err(Error::Io)?; - -// let mut backup = Vec::new(); - -// input_file -// .read_to_end(&mut backup) -// .await -// .map_err(Error::Io)?; - -// let stored_keys: Vec = -// serde_json::from_slice(&backup).map_err(|_| Error::Serialization)?; - -// let updated_keys = library -// .key_manager -// .import_keystore_backup( -// args.password, -// SecretKeyString(args.secret_key), -// &stored_keys, -// ) -// .await?; - -// for key in &updated_keys { -// write_storedkey_to_db(&library.db, key).await?; -// } - -// invalidate_query!(library, "keys.list"); -// invalidate_query!(library, "keys.listMounted"); - -// TryInto::::try_into(updated_keys.len()).map_err(|_| { -// rspc::Error::new(ErrorCode::InternalServerError, "integer overflow".into()) -// }) // We convert from `usize` (bigint type) to `u32` (number type) because rspc doesn't support bigints. -// }) -// }) -// .procedure( -// "changeMasterPassword", -// #[allow(clippy::unwrap_used)] // TODO: Jake is fixing this in a Crypto PR -// { -// R.with2(library()).mutation( -// |(_, library), args: MasterPasswordChangeArgs| async move { -// let verification_key = library -// .key_manager -// .change_master_password( -// args.password, -// args.algorithm, -// args.hashing_algorithm, -// library.id, -// ) -// .await?; - -// invalidate_query!(library, "keys.getSecretKey"); - -// // remove old root key if present -// library -// .db -// .key() -// .delete_many(vec![key::key_type::equals( -// serde_json::to_string(&StoredKeyType::Root).unwrap(), -// )]) -// .exec() -// .await?; - -// // write the new verification key -// write_storedkey_to_db(&library.db, &verification_key).await?; - -// Ok(()) -// }, -// ) -// }, -// ) -// } diff --git a/core/src/api/mod.rs b/core/src/api/mod.rs index 9a05264bd..8237afe16 100644 --- a/core/src/api/mod.rs +++ b/core/src/api/mod.rs @@ -24,11 +24,9 @@ use uuid::Uuid; mod auth; mod backups; mod cloud; -// mod categories; mod ephemeral_files; mod files; mod jobs; -mod keys; mod labels; mod libraries; pub mod locations; @@ -200,8 +198,6 @@ pub(crate) fn mount() -> Arc { .merge("volumes.", volumes::mount()) .merge("tags.", tags::mount()) .merge("labels.", labels::mount()) - // .merge("categories.", categories::mount()) - // .merge("keys.", keys::mount()) .merge("locations.", locations::mount()) .merge("ephemeralFiles.", ephemeral_files::mount()) .merge("files.", files::mount()) diff --git a/crates/ai/Cargo.toml b/crates/ai/Cargo.toml index 73e188559..78c7d2076 100644 --- a/crates/ai/Cargo.toml +++ b/crates/ai/Cargo.toml @@ -32,17 +32,16 @@ reqwest = { workspace = true, features = ["native-tls-vendored", "st rmp-serde = { workspace = true } rmpv = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } -tokio-stream = { workspace = true } tracing = { workspace = true } uuid = { workspace = true, features = ["serde", "v4"] } +# Specific AI dependencies # Note: half and ndarray version must be the same as used in ort half = { version = "2.1", features = ['num-traits'] } ndarray = "0.15" -url = '2.5.0' +url = '2.5' # Microsoft does not provide a release for osx-gpu. See: https://github.com/microsoft/onnxruntime/releases # "gpu" means CUDA or TensorRT EP. Thus, the ort crate cannot download them at build time. diff --git a/crates/cloud-api/Cargo.toml b/crates/cloud-api/Cargo.toml index 49c78576a..491b2fe7c 100644 --- a/crates/cloud-api/Cargo.toml +++ b/crates/cloud-api/Cargo.toml @@ -11,9 +11,7 @@ repository.workspace = true sd-p2p = { path = "../p2p" } # Workspace dependencies -base64 = { workspace = true } reqwest = { workspace = true, features = ["native-tls-vendored"] } -rmpv = { workspace = true } rspc = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 4a5e2f305..3305a1222 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -20,6 +20,7 @@ rust-version.workspace = true async-stream = { workspace = true } blake3 = { workspace = true } futures = { workspace = true } +rand = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["io-util", "macros", "rt-multi-thread", "sync"] } @@ -28,20 +29,17 @@ tokio = { workspace = true, features = ["io-util", "macros", "rt-multi-th aead = { version = "0.6.0-rc.0", default-features = false, features = ["stream"] } chacha20poly1305 = "0.11.0-pre.1" cmov = "0.3.1" -# Some deps use this same version, so we just use it too -generic-array = { version = "=0.14.7", features = ["serde", "zeroize"] } -hex = "0.4.3" -rand = "0.9.0-alpha.2" -rand_chacha = "0.9.0-alpha.2" -rand_core = "0.9.0-alpha.2" -serde-big-array = "0.5.1" -serdect = "0.3.0-pre.0" -typenum = "1.17.0" -zeroize = { version = "1.7.0", features = ["aarch64", "derive"] } +generic-array = { version = "=0.14.7", features = ["serde", "zeroize"] } # Update blocked by aead +hex = "0.4.3" +rand_chacha = "0.9.0-alpha.2" +rand_core = "0.9.0-alpha.2" +serdect = "0.3.0-pre.0" +typenum = "1.17" +zeroize = { version = "1.7", features = ["aarch64", "derive"] } [dev-dependencies] -paste = "1.0.14" -tempfile = "3.10.1" +paste = "1.0" +tempfile = "3.10" [[example]] name = "secure_erase" diff --git a/crates/file-ext/Cargo.toml b/crates/file-ext/Cargo.toml index a7b357854..b43b35907 100644 --- a/crates/file-ext/Cargo.toml +++ b/crates/file-ext/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true repository.workspace = true [dependencies] +# Workspace dependencies serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } specta = { workspace = true } diff --git a/crates/images/Cargo.toml b/crates/images/Cargo.toml index b602584da..fa9921a9d 100644 --- a/crates/images/Cargo.toml +++ b/crates/images/Cargo.toml @@ -25,5 +25,5 @@ bincode = { version = "=2.0.0-rc.3", features = ["alloc", "derive"], optional = # Disable defaults for libheif* to avoid bindgen and use pre-compiled headers libheif-rs = { version = "1.0", default-features = false, optional = true } libheif-sys = { version = "2.1", default-features = false, optional = true } -pdfium-render = { version = "0.8.15", features = ["image", "sync", "thread_safe"] } +pdfium-render = { version = "0.8.24", features = ["image", "sync", "thread_safe"] } resvg = "0.43.0" diff --git a/crates/images/src/pdf.rs b/crates/images/src/pdf.rs index d74958cb7..96dcef373 100644 --- a/crates/images/src/pdf.rs +++ b/crates/images/src/pdf.rs @@ -9,10 +9,7 @@ use crate::{ }; use image::DynamicImage; use once_cell::sync::Lazy; -use pdfium_render::{ - color::PdfColor, - prelude::{PdfPageRenderRotation, PdfRenderConfig, Pdfium}, -}; +use pdfium_render::prelude::{PdfColor, PdfPageRenderRotation, PdfRenderConfig, Pdfium}; use tracing::error; // This path must be relative to the running binary diff --git a/crates/media-metadata/Cargo.toml b/crates/media-metadata/Cargo.toml index 672f4c160..0c93f57ed 100644 --- a/crates/media-metadata/Cargo.toml +++ b/crates/media-metadata/Cargo.toml @@ -20,7 +20,6 @@ sd-utils = { path = "../utils" } # Workspace dependencies chrono = { workspace = true, features = ["serde"] } image = { workspace = true } -rand = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } specta = { workspace = true, features = ["chrono"] } diff --git a/crates/p2p/Cargo.toml b/crates/p2p/Cargo.toml index ead36f24f..14d21c20b 100644 --- a/crates/p2p/Cargo.toml +++ b/crates/p2p/Cargo.toml @@ -17,47 +17,33 @@ specta = [] [dependencies] # Workspace dependencies -base64 = { workspace = true } -base91 = { workspace = true } -ed25519-dalek = { workspace = true } -futures = { workspace = true } -pin-project-lite = { workspace = true } -reqwest = { workspace = true } -rmp-serde = { workspace = true } -serde = { workspace = true, features = ["derive"] } -specta = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, features = ["fs", "io-util", "macros", "sync", "time"] } -tokio-stream = { workspace = true, features = ["sync"] } -tokio-util = { workspace = true, features = ["compat"] } -tracing = { workspace = true } -uuid = { workspace = true, features = ["serde"] } +base64 = { workspace = true } +ed25519-dalek = { workspace = true } +futures = { workspace = true } +rmp-serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } +specta = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["fs", "io-util", "macros", "sync", "time"] } +tokio-util = { workspace = true, features = ["compat"] } +tracing = { workspace = true } +uuid = { workspace = true, features = ["serde"] } # Specific P2P dependencies -dns-lookup = "2.0" -flume = "=0.11.0" # Must match version used by `mdns-sd` -futures-core = "0.3.30" +dns-lookup = "2.0" +flume = "=0.11.0" # Must match version used by `mdns-sd` hash_map_diff = "0.2.0" -if-watch = { version = "=3.2.0", features = ["tokio"] } # Override features used by libp2p-quic -libp2p = { version = "=0.53.2", features = [ - "autonat", - "dcutr", - "macros", - "noise", - "quic", - "relay", - "serde", - "tokio", - "yamux" -] } # Update blocked due to custom patch -libp2p-stream = "=0.1.0-alpha" # Update blocked due to custom patch -mdns-sd = "0.11.1" -rand_core = "0.6.4" -sha256 = "1.5.0" -stable-vec = "0.4.1" -streamunordered = "0.5.3" -sync_wrapper = "1.0" -zeroize = { version = "1.8", features = ["derive"] } +if-watch = { version = "=3.2.0", features = ["tokio"] } # Override features used by libp2p-quic +libp2p-stream = "=0.1.0-alpha" # Update blocked due to custom patch +mdns-sd = "0.11.1" +rand_core = "0.6.4" +stable-vec = "0.4.1" +sync_wrapper = "1.0" +zeroize = { version = "1.8", features = ["derive"] } + +[dependencies.libp2p] +features = ["autonat", "dcutr", "macros", "noise", "quic", "relay", "serde", "tokio", "yamux"] +version = "=0.53.2" # Update blocked due to custom patch [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/p2p/crates/block/Cargo.toml b/crates/p2p/crates/block/Cargo.toml index 77f115035..8ac8f61fe 100644 --- a/crates/p2p/crates/block/Cargo.toml +++ b/crates/p2p/crates/block/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true [dependencies] # Spacedrive Sub-crates -sd-p2p = { path = "../../" } sd-p2p-proto = { path = "../proto" } # Workspace dependencies diff --git a/crates/p2p/crates/tunnel/Cargo.toml b/crates/p2p/crates/tunnel/Cargo.toml index bbedd8b1d..ea1af01b6 100644 --- a/crates/p2p/crates/tunnel/Cargo.toml +++ b/crates/p2p/crates/tunnel/Cargo.toml @@ -15,4 +15,3 @@ sd-p2p-proto = { path = "../proto" } # Workspace dependencies thiserror = { workspace = true } tokio = { workspace = true, features = ["io-util"] } -uuid = { workspace = true, features = ["v4"] } diff --git a/crates/prisma-cli/Cargo.toml b/crates/prisma-cli/Cargo.toml index b6594a407..1282cef9c 100644 --- a/crates/prisma-cli/Cargo.toml +++ b/crates/prisma-cli/Cargo.toml @@ -10,5 +10,9 @@ repository.workspace = true # Spacedrive Sub-crates sd-sync-generator = { path = "../sync-generator" } -# Workspace dependencies -prisma-client-rust-cli = { workspace = true } +# Specific prisma-cli dependencies +[dependencies.prisma-client-rust-cli] +default-features = false +features = ["migrations", "specta", "sqlite", "sqlite-create-many"] +git = "https://github.com/brendonovich/prisma-client-rust" +rev = "4f9ef9d38c" diff --git a/crates/prisma/Cargo.toml b/crates/prisma/Cargo.toml index 5910d57e2..b61e4e863 100644 --- a/crates/prisma/Cargo.toml +++ b/crates/prisma/Cargo.toml @@ -13,5 +13,4 @@ prisma-client-rust = { workspace = true } rmp-serde = { workspace = true } rmpv = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } uuid = { workspace = true } diff --git a/crates/sync-generator/Cargo.toml b/crates/sync-generator/Cargo.toml index 7ca3a9ae3..20fde252e 100644 --- a/crates/sync-generator/Cargo.toml +++ b/crates/sync-generator/Cargo.toml @@ -7,9 +7,10 @@ license.workspace = true repository.workspace = true [dependencies] +# Workspace dependencies prisma-client-rust-sdk = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } # Specific Sync Generator dependencies -nom = "7.1.3" +nom = "7.1" diff --git a/interface/app/$libraryId/settings/client/account.tsx b/interface/app/$libraryId/settings/client/account.tsx index 78be76179..89001176d 100644 --- a/interface/app/$libraryId/settings/client/account.tsx +++ b/interface/app/$libraryId/settings/client/account.tsx @@ -42,10 +42,7 @@ const Profile = ({ email, authStore }: { email?: string; authStore: { status: st return ( -
+

@@ -85,14 +82,6 @@ function HostedLocationsPlayground() { locations.refetch(); } }); - const doTheThing = useBridgeMutation('cloud.locations.testing', { - onSuccess() { - toast.success('Uploaded file!'); - }, - onError(err) { - toast.error(err.message); - } - }); useEffect(() => { if (path === '' && locations.data?.[0]) { @@ -100,7 +89,7 @@ function HostedLocationsPlayground() { } }, [path, locations.data]); - const isLoading = createLocation.isLoading || removeLocation.isLoading || doTheThing.isLoading; + const isLoading = createLocation.isLoading || removeLocation.isLoading; return ( <> @@ -152,19 +141,6 @@ function HostedLocationsPlayground() { > Delete -

))} diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 598cf8756..d948acd2c 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -67,7 +67,6 @@ export type Procedures = { { key: "cloud.library.sync", input: LibraryArgs, result: null } | { key: "cloud.locations.create", input: string, result: CloudLocation } | { key: "cloud.locations.remove", input: string, result: CloudLocation } | - { key: "cloud.locations.testing", input: TestingParams, result: null } | { key: "cloud.setApiOrigin", input: string, result: null } | { key: "ephemeralFiles.copyFiles", input: LibraryArgs, result: null } | { key: "ephemeralFiles.createFile", input: LibraryArgs, result: string } | @@ -646,8 +645,6 @@ export type TagUpdateArgs = { id: number; name: string | null; color: string | n export type Target = { Object: number } | { FilePath: number } -export type TestingParams = { id: string; path: string } - export type TextMatch = { contains: string } | { startsWith: string } | { endsWith: string } | { equals: string } /**