From 3026b217774a51e01cca1ae584fba8c6398754cc Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 1 Nov 2023 15:36:06 +0100 Subject: integration to login with an enum --- src/login/mod.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 3fab90a..f403bcb 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -15,6 +15,9 @@ use rusoto_credential::{AwsCredentials, StaticProvider}; use rusoto_s3::S3Client; use crate::cryptoblob::*; +use crate::storage::*; +use crate::storage::in_memory::MemTypes; +use crate::storage::garage::GrgTypes; /// The trait LoginProvider defines the interface for a login provider that allows /// to retrieve storage and cryptographic credentials for access to a user account @@ -23,12 +26,17 @@ use crate::cryptoblob::*; pub trait LoginProvider { /// The login method takes an account's password as an input to decypher /// decryption keys and obtain full access to the user's account. - async fn login(&self, username: &str, password: &str) -> Result; + async fn login(&self, username: &str, password: &str) -> Result; /// The public_login method takes an account's email address and returns /// public credentials for adding mails to the user's inbox. async fn public_login(&self, email: &str) -> Result; } +pub enum AnyCredentials { + InMemory(Credentials), + Garage(Credentials), +} + /// ArcLoginProvider is simply an alias on a structure that is used /// in many places in the code pub type ArcLoginProvider = Arc; @@ -36,9 +44,9 @@ pub type ArcLoginProvider = Arc; /// The struct Credentials represent all of the necessary information to interact /// with a user account's data after they are logged in. #[derive(Clone, Debug)] -pub struct Credentials { +pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: StorageCredentials, + pub storage: T::Builder, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } @@ -106,6 +114,7 @@ impl Region { // ---- +/* impl Credentials { pub fn k2v_client(&self) -> Result { self.storage.k2v_client() @@ -116,6 +125,14 @@ impl Credentials { pub fn bucket(&self) -> &str { self.storage.bucket.as_str() } +}*/ +impl From for Credentials { + fn from(ac: AnyCredentials) -> Self { + match ac { + AnyCredentials::InMemory(c) => c, + AnyCredentials::Garage(c) => c, + } + } } impl StorageCredentials { -- cgit v1.2.3 From 8ac3a8ce8ba268a3261e23694b8b62afa6a3ae37 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 1 Nov 2023 16:45:29 +0100 Subject: implement an AnyCredentials --- src/login/mod.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index f403bcb..5bd976e 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -16,8 +16,6 @@ use rusoto_s3::S3Client; use crate::cryptoblob::*; use crate::storage::*; -use crate::storage::in_memory::MemTypes; -use crate::storage::garage::GrgTypes; /// The trait LoginProvider defines the interface for a login provider that allows /// to retrieve storage and cryptographic credentials for access to a user account @@ -32,19 +30,28 @@ pub trait LoginProvider { async fn public_login(&self, email: &str) -> Result; } -pub enum AnyCredentials { - InMemory(Credentials), - Garage(Credentials), -} - /// ArcLoginProvider is simply an alias on a structure that is used /// in many places in the code pub type ArcLoginProvider = Arc; +pub enum AnyCredentials { + InMemory(Credentials), + Garage(Credentials), +} +impl AnyCredentials where X: Sto +{ + fn to_gen(&self) -> Credentials { + match self { + Self::InMemory(u) => u, + Self::Garage(u) => u, + } + } +} + /// The struct Credentials represent all of the necessary information to interact /// with a user account's data after they are logged in. #[derive(Clone, Debug)] -pub struct Credentials { +pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) pub storage: T::Builder, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V @@ -114,7 +121,7 @@ impl Region { // ---- -/* + impl Credentials { pub fn k2v_client(&self) -> Result { self.storage.k2v_client() @@ -125,14 +132,6 @@ impl Credentials { pub fn bucket(&self) -> &str { self.storage.bucket.as_str() } -}*/ -impl From for Credentials { - fn from(ac: AnyCredentials) -> Self { - match ac { - AnyCredentials::InMemory(c) => c, - AnyCredentials::Garage(c) => c, - } - } } impl StorageCredentials { -- cgit v1.2.3 From cf8b9ac28d6813bd589f363ad3659dd215bd7cea Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 1 Nov 2023 17:18:58 +0100 Subject: mask implementation to the rest of the code --- src/login/mod.rs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 5bd976e..e87a17d 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -24,7 +24,7 @@ use crate::storage::*; pub trait LoginProvider { /// The login method takes an account's password as an input to decypher /// decryption keys and obtain full access to the user's account. - async fn login(&self, username: &str, password: &str) -> Result; + async fn login(&self, username: &str, password: &str) -> Result; /// The public_login method takes an account's email address and returns /// public credentials for adding mails to the user's inbox. async fn public_login(&self, email: &str) -> Result; @@ -34,26 +34,12 @@ pub trait LoginProvider { /// in many places in the code pub type ArcLoginProvider = Arc; -pub enum AnyCredentials { - InMemory(Credentials), - Garage(Credentials), -} -impl AnyCredentials where X: Sto -{ - fn to_gen(&self) -> Credentials { - match self { - Self::InMemory(u) => u, - Self::Garage(u) => u, - } - } -} - /// The struct Credentials represent all of the necessary information to interact /// with a user account's data after they are logged in. #[derive(Clone, Debug)] -pub struct Credentials { +pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: T::Builder, + pub storage: AnyEngine, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } -- cgit v1.2.3 From 9aa58194d44fef8b0b916f6c96edd124ce13bf7b Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 2 Nov 2023 10:38:47 +0100 Subject: try dynamic dispatch --- src/login/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index e87a17d..e934112 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -39,7 +39,7 @@ pub type ArcLoginProvider = Arc; #[derive(Clone, Debug)] pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: AnyEngine, + pub storage: Engine, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } -- cgit v1.2.3 From 1f28832deaff3a2319cc88d5a83ffe506b784fc8 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 2 Nov 2023 10:55:40 +0100 Subject: start replacing engine --- src/login/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index e934112..6c948cc 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -109,8 +109,8 @@ impl Region { impl Credentials { - pub fn k2v_client(&self) -> Result { - self.storage.k2v_client() + pub fn k2v_client(&self) -> Result { + self.storage.row.row_store() } pub fn s3_client(&self) -> Result { self.storage.s3_client() -- cgit v1.2.3 From 553ea25f1854706b60ce6f087545968533ef6140 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 2 Nov 2023 11:51:03 +0100 Subject: gradually implement our interface --- src/login/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 6c948cc..afade28 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -109,7 +109,7 @@ impl Region { impl Credentials { - pub fn k2v_client(&self) -> Result { + pub fn k2v_client(&self) -> Result { self.storage.row.row_store() } pub fn s3_client(&self) -> Result { -- cgit v1.2.3 From 3b363b2a7803564231e001c215ab427c99c9435b Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 2 Nov 2023 12:18:43 +0100 Subject: implement equality+cmp for builders based on url --- src/login/mod.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index afade28..f4bf4d2 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -39,7 +39,7 @@ pub type ArcLoginProvider = Arc; #[derive(Clone, Debug)] pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: Engine, + pub storage: Builders, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } @@ -109,14 +109,11 @@ impl Region { impl Credentials { - pub fn k2v_client(&self) -> Result { - self.storage.row.row_store() + pub fn row_client(&self) -> Result { + Ok(self.storage.row_store()?) } - pub fn s3_client(&self) -> Result { - self.storage.s3_client() - } - pub fn bucket(&self) -> &str { - self.storage.bucket.as_str() + pub fn blob_client(&self) -> Result { + Ok(self.storage.blob_store()?) } } -- cgit v1.2.3 From 4a33ac2265dae0e8fd1f7fbaec54ab7120334cbe Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 17 Nov 2023 12:15:44 +0100 Subject: incoming has been fully ported --- src/login/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index f4bf4d2..a150829 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -47,7 +47,7 @@ pub struct Credentials { #[derive(Clone, Debug)] pub struct PublicCredentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: StorageCredentials, + pub storage: Builders, pub public_key: PublicKey, } -- cgit v1.2.3 From 16b38f3197167c344bb522dcfa83292ddb3c1026 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 17 Nov 2023 16:42:25 +0100 Subject: integrate storage choice in config --- src/login/static_provider.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index b9be5a6..378a863 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -7,6 +7,7 @@ use async_trait::async_trait; use crate::config::*; use crate::cryptoblob::{Key, SecretKey}; use crate::login::*; +use crate::storage; pub struct StaticLoginProvider { default_bucket: Option, -- cgit v1.2.3 From 36f4050a40c3ba7b9637a973063b6b5549a2c208 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 17 Nov 2023 18:46:22 +0100 Subject: WIP provider config --- src/login/static_provider.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 378a863..df1432f 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -10,18 +10,13 @@ use crate::login::*; use crate::storage; pub struct StaticLoginProvider { - default_bucket: Option, users: HashMap>, users_by_email: HashMap>, - - k2v_region: Region, - s3_region: Region, } impl StaticLoginProvider { - pub fn new(config: LoginStaticConfig, k2v_region: Region, s3_region: Region) -> Result { + pub fn new(config: LoginStaticConfig) -> Result { let users = config - .users .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); @@ -36,11 +31,8 @@ impl StaticLoginProvider { } Ok(Self { - default_bucket: config.default_bucket, users, users_by_email, - k2v_region, - s3_region, }) } } @@ -59,23 +51,30 @@ impl LoginProvider for StaticLoginProvider { bail!("Wrong password"); } + /* tracing::debug!(user=%username, "fetch bucket"); let bucket = user .bucket .clone() .or_else(|| self.default_bucket.clone()) .ok_or(anyhow!( - "No bucket configured and no default bucket specieid" - ))?; + "No bucket configured and no default bucket specified" + ))?;*/ tracing::debug!(user=%username, "fetch keys"); - let storage = StorageCredentials { + let storage: storage::Builders = match user.storage { + StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), + StaticStorage::Garage(c) => Box::new(storage::garage::GrgCreds {}), + }; + + /* + StorageCredentials { k2v_region: self.k2v_region.clone(), s3_region: self.s3_region.clone(), aws_access_key_id: user.aws_access_key_id.clone(), aws_secret_access_key: user.aws_secret_access_key.clone(), bucket, - }; + };*/ let keys = match (&user.master_key, &user.secret_key) { (Some(m), Some(s)) => { -- cgit v1.2.3 From bd6c3464e609dc76e119457ea583af0f08eeabb4 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 21 Nov 2023 09:04:54 +0100 Subject: remove old storagecredentials --- src/login/mod.rs | 68 -------------------------------------------- src/login/static_provider.rs | 5 ++++ 2 files changed, 5 insertions(+), 68 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index a150829..52d9829 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -51,17 +51,6 @@ pub struct PublicCredentials { pub public_key: PublicKey, } -/// The struct StorageCredentials contains access key to an S3 and K2V bucket -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct StorageCredentials { - pub s3_region: Region, - pub k2v_region: Region, - - pub aws_access_key_id: String, - pub aws_secret_access_key: String, - pub bucket: String, -} - /// The struct UserSecrets represents intermediary secrets that are mixed in with the user's /// password when decrypting the cryptographic keys that are stored in their bucket. /// These secrets should be stored somewhere else (e.g. in the LDAP server or in the @@ -87,24 +76,6 @@ pub struct CryptoKeys { pub public: PublicKey, } -/// A custom S3 region, composed of a region name and endpoint. -/// We use this instead of rusoto_signature::Region so that we can -/// derive Hash and Eq -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Region { - pub name: String, - pub endpoint: String, -} - -impl Region { - pub fn as_rusoto_region(&self) -> rusoto_signature::Region { - rusoto_signature::Region::Custom { - name: self.name.clone(), - endpoint: self.endpoint.clone(), - } - } -} - // ---- @@ -117,45 +88,6 @@ impl Credentials { } } -impl StorageCredentials { - pub fn k2v_client(&self) -> Result { - let aws_creds = AwsCredentials::new( - self.aws_access_key_id.clone(), - self.aws_secret_access_key.clone(), - None, - None, - ); - - Ok(K2vClient::new( - self.k2v_region.as_rusoto_region(), - self.bucket.clone(), - aws_creds, - None, - )?) - } - - pub fn s3_client(&self) -> Result { - let aws_creds_provider = StaticProvider::new_minimal( - self.aws_access_key_id.clone(), - self.aws_secret_access_key.clone(), - ); - - let connector = hyper_rustls::HttpsConnectorBuilder::new() - .with_native_roots() - .https_or_http() - .enable_http1() - .enable_http2() - .build(); - let client = HttpClient::from_connector(connector); - - Ok(S3Client::new_with( - client, - aws_creds_provider, - self.s3_region.as_rusoto_region(), - )) - } -} - impl CryptoKeys { pub async fn init( storage: &StorageCredentials, diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index df1432f..d013c6f 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -106,6 +106,7 @@ impl LoginProvider for StaticLoginProvider { Some(u) => u, }; + /* let bucket = user .bucket .clone() @@ -120,6 +121,10 @@ impl LoginProvider for StaticLoginProvider { aws_access_key_id: user.aws_access_key_id.clone(), aws_secret_access_key: user.aws_secret_access_key.clone(), bucket, + };*/ + let storage: storage::Builders = match user.storage { + StaticStorage::InMemory => X, + StaticStorage::Garage => Y, }; let k2v_client = storage.k2v_client()?; -- cgit v1.2.3 From 6e8b2cfc9ff1abf2b4844884d9ebd807d37bd76e Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 21 Nov 2023 09:56:31 +0100 Subject: rewrite CryptoKeys with Storage abstraction --- src/login/mod.rs | 107 ++++++++++++++----------------------------------------- 1 file changed, 26 insertions(+), 81 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 52d9829..050151d 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -6,13 +6,7 @@ use std::sync::Arc; use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; -use k2v_client::{ - BatchInsertOp, BatchReadOp, CausalValue, CausalityToken, Filter, K2vClient, K2vValue, -}; use rand::prelude::*; -use rusoto_core::HttpClient; -use rusoto_credential::{AwsCredentials, StaticProvider}; -use rusoto_s3::S3Client; use crate::cryptoblob::*; use crate::storage::*; @@ -90,12 +84,12 @@ impl Credentials { impl CryptoKeys { pub async fn init( - storage: &StorageCredentials, + storage: &Builders, user_secrets: &UserSecrets, password: &str, ) -> Result { // Check that salt and public don't exist already - let k2v = storage.k2v_client()?; + let k2v = storage.row_store()?; let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?; // Generate salt for password identifiers @@ -140,12 +134,12 @@ impl CryptoKeys { } pub async fn init_without_password( - storage: &StorageCredentials, + storage: &Builders, master: &Key, secret: &SecretKey, ) -> Result { // Check that salt and public don't exist already - let k2v = storage.k2v_client()?; + let k2v = storage.row_store()?; let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?; // Generate salt for password identifiers @@ -172,7 +166,7 @@ impl CryptoKeys { } pub async fn open( - storage: &StorageCredentials, + storage: &Builders, user_secrets: &UserSecrets, password: &str, ) -> Result { @@ -215,11 +209,11 @@ impl CryptoKeys { } pub async fn open_without_password( - storage: &StorageCredentials, + storage: &Builders, master: &Key, secret: &SecretKey, ) -> Result { - let k2v = storage.k2v_client()?; + let k2v = storage.row_store()?; let (_ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?; // Create CryptoKeys struct from given keys @@ -240,11 +234,11 @@ impl CryptoKeys { pub async fn add_password( &self, - storage: &StorageCredentials, + storage: &Builders, user_secrets: &UserSecrets, password: &str, ) -> Result<()> { - let k2v = storage.k2v_client()?; + let k2v = storage.row_store()?; let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?; // Generate short password digest (= password identity) @@ -289,11 +283,11 @@ impl CryptoKeys { } pub async fn delete_password( - storage: &StorageCredentials, + storage: &Builders, password: &str, allow_delete_all: bool, ) -> Result<()> { - let k2v = storage.k2v_client()?; + let k2v = storage.row_client()?; let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?; // Generate short password digest (= password identity) @@ -322,47 +316,32 @@ impl CryptoKeys { // ---- STORAGE UTIL ---- async fn check_uninitialized( - k2v: &K2vClient, - ) -> Result<(Option, Option)> { + k2v: &RowStore, + ) -> Result<(RowRef, RowRef)> { let params = k2v - .read_batch(&[ - k2v_read_single_key("keys", "salt", true), - k2v_read_single_key("keys", "public", true), - ]) + .select(Selector::List(vec![ + ("keys", "salt"), + ("keys", "public"), + ])) .await .context("ReadBatch for salt and public in check_uninitialized")?; + if params.len() != 2 { bail!( "Invalid response from k2v storage: {:?} (expected two items)", params ); } - if params[0].items.len() > 1 || params[1].items.len() > 1 { - bail!( - "invalid response from k2v storage: {:?} (several items in single_item read)", - params - ); - } - let salt_ct = match params[0].items.iter().next() { - None => None, - Some((_, CausalValue { causality, value })) => { - if value.iter().any(|x| matches!(x, K2vValue::Value(_))) { - bail!("key storage already initialized"); - } - Some(causality.clone()) - } - }; + let salt_ct = params[0].to_ref(); + if params[0].content().iter().any(|x| matches!(x, Alternative::Value(_))) { + bail!("key storage already initialized"); + } - let public_ct = match params[1].items.iter().next() { - None => None, - Some((_, CausalValue { causality, value })) => { - if value.iter().any(|x| matches!(x, K2vValue::Value(_))) { - bail!("key storage already initialized"); - } - Some(causality.clone()) - } - }; + let public_ct = params[1].to_ref(); + if params[1].content().iter().any(|x| matches!(x, Alternative::Value(_))) { + bail!("key storage already initialized"); + } Ok((salt_ct, public_ct)) } @@ -511,37 +490,3 @@ pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result( - partition_key: &'a str, - sort_key: &'a str, - tombstones: bool, -) -> BatchReadOp<'a> { - BatchReadOp { - partition_key, - filter: Filter { - start: Some(sort_key), - end: None, - prefix: None, - limit: None, - reverse: false, - }, - conflicts_only: false, - tombstones, - single_item: true, - } -} - -pub fn k2v_insert_single_key<'a>( - partition_key: &'a str, - sort_key: &'a str, - causality: Option, - value: impl AsRef<[u8]>, -) -> BatchInsertOp<'a> { - BatchInsertOp { - partition_key, - sort_key, - causality, - value: K2vValue::Value(value.as_ref().to_vec()), - } -} -- cgit v1.2.3 From a7c9d554f6523c384cc0a14a789e0c8d9070e605 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 21 Nov 2023 15:09:39 +0100 Subject: fix login mod --- src/login/mod.rs | 138 ++++++++++++++++++------------------------- src/login/static_provider.rs | 19 ------ 2 files changed, 59 insertions(+), 98 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 050151d..216c340 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -1,8 +1,8 @@ pub mod ldap_provider; pub mod static_provider; -use std::collections::BTreeMap; use std::sync::Arc; +use futures::try_join; use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; @@ -122,13 +122,14 @@ impl CryptoKeys { let password_blob = [&kdf_salt[..], &password_sealed].concat(); // Write values to storage - k2v.insert_batch(&[ - k2v_insert_single_key("keys", "salt", salt_ct, ident_salt), - k2v_insert_single_key("keys", "public", public_ct, keys.public), - k2v_insert_single_key("keys", &password_sortkey, None, &password_blob), - ]) - .await - .context("InsertBatch for salt, public, and password")?; + // @FIXME Implement insert batch in the storage API + let (salt, public, passwd) = ( + salt_ct.set_value(&ident_salt), + public_ct.set_value(keys.public.as_ref()), + k2v.row("keys", &password_sortkey).set_value(&password_blob) + ); + try_join!(salt.push(), public.push(), passwd.push()) + .context("InsertBatch for salt, public, and password")?; Ok(keys) } @@ -155,12 +156,13 @@ impl CryptoKeys { }; // Write values to storage - k2v.insert_batch(&[ - k2v_insert_single_key("keys", "salt", salt_ct, ident_salt), - k2v_insert_single_key("keys", "public", public_ct, keys.public), - ]) - .await - .context("InsertBatch for salt and public")?; + // @FIXME implement insert batch in the storage API + let (salt, public) = ( + salt_ct.set_value(&ident_salt), + public_ct.set_value(keys.public.as_ref()), + ); + + try_join!(salt.push(), public.push()).context("InsertBatch for salt and public")?; Ok(keys) } @@ -170,7 +172,7 @@ impl CryptoKeys { user_secrets: &UserSecrets, password: &str, ) -> Result { - let k2v = storage.k2v_client()?; + let k2v = storage.row_store()?; let (ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?; // Generate short password digest (= password identity) @@ -178,20 +180,21 @@ impl CryptoKeys { // Lookup password blob let password_sortkey = format!("password:{}", hex::encode(&ident)); + let password_ref = k2v.row("keys", &password_sortkey); let password_blob = { - let mut val = match k2v.read_item("keys", &password_sortkey).await { - Err(k2v_client::Error::NotFound) => { + let val = match password_ref.fetch().await { + Err(StorageError::NotFound) => { bail!("invalid password") } x => x?, }; - if val.value.len() != 1 { + if val.content().len() != 1 { bail!("multiple values for password in storage"); } - match val.value.pop().unwrap() { - K2vValue::Value(v) => v, - K2vValue::Tombstone => bail!("invalid password"), + match val.content().pop().unwrap() { + Alternative::Value(v) => v, + Alternative::Tombstone => bail!("invalid password"), } }; @@ -258,26 +261,24 @@ impl CryptoKeys { let password_blob = [&kdf_salt[..], &password_sealed].concat(); // List existing passwords to overwrite existing entry if necessary - let ct = match k2v.read_item("keys", &password_sortkey).await { - Err(k2v_client::Error::NotFound) => None, + let pass_key = k2v.row("keys", &password_sortkey); + let passwd = match pass_key.fetch().await { + Err(StorageError::NotFound) => pass_key, v => { let entry = v?; - if entry.value.iter().any(|x| matches!(x, K2vValue::Value(_))) { + if entry.content().iter().any(|x| matches!(x, Alternative::Value(_))) { bail!("password already exists"); } - Some(entry.causality) + entry.to_ref() } }; // Write values to storage - k2v.insert_batch(&[k2v_insert_single_key( - "keys", - &password_sortkey, - ct, - &password_blob, - )]) - .await - .context("InsertBatch for new password")?; + passwd + .set_value(&password_blob) + .push() + .await + .context("InsertBatch for new password")?; Ok(()) } @@ -287,7 +288,7 @@ impl CryptoKeys { password: &str, allow_delete_all: bool, ) -> Result<()> { - let k2v = storage.row_client()?; + let k2v = storage.row_store()?; let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?; // Generate short password digest (= password identity) @@ -299,22 +300,23 @@ impl CryptoKeys { // Check password is there let pw = existing_passwords - .get(&password_sortkey) + .iter() + .map(|x| x.to_ref()) + .find(|x| x.key().1 == &password_sortkey) + //.get(&password_sortkey) .ok_or(anyhow!("password does not exist"))?; if !allow_delete_all && existing_passwords.len() < 2 { bail!("No other password exists, not deleting last password."); } - k2v.delete_item("keys", &password_sortkey, pw.causality.clone()) - .await - .context("DeleteItem for password")?; + pw.rm().await.context("DeleteItem for password")?; Ok(()) } // ---- STORAGE UTIL ---- - + // async fn check_uninitialized( k2v: &RowStore, ) -> Result<(RowRef, RowRef)> { @@ -346,32 +348,29 @@ impl CryptoKeys { Ok((salt_ct, public_ct)) } - pub async fn load_salt_and_public(k2v: &K2vClient) -> Result<([u8; 32], PublicKey)> { - let mut params = k2v - .read_batch(&[ - k2v_read_single_key("keys", "salt", false), - k2v_read_single_key("keys", "public", false), - ]) + pub async fn load_salt_and_public(k2v: &RowStore) -> Result<([u8; 32], PublicKey)> { + let params = k2v + .select(Selector::List(vec![ + ("keys", "salt"), + ("keys", "public"), + ])) .await .context("ReadBatch for salt and public in load_salt_and_public")?; + if params.len() != 2 { bail!( "Invalid response from k2v storage: {:?} (expected two items)", params ); } - if params[0].items.len() != 1 || params[1].items.len() != 1 { + if params[0].content().len() != 1 || params[1].content().len() != 1 { bail!("cryptographic keys not initialized for user"); } // Retrieve salt from given response - let salt_vals = &mut params[0].items.iter_mut().next().unwrap().1.value; - if salt_vals.len() != 1 { - bail!("Multiple values for `salt`"); - } - let salt: Vec = match &mut salt_vals[0] { - K2vValue::Value(v) => std::mem::take(v), - K2vValue::Tombstone => bail!("salt is a tombstone"), + let salt: Vec = match &mut params[0].content().iter_mut().next().unwrap() { + Alternative::Value(v) => std::mem::take(v), + Alternative::Tombstone => bail!("salt is a tombstone"), }; if salt.len() != 32 { bail!("`salt` is not 32 bytes long"); @@ -380,40 +379,21 @@ impl CryptoKeys { salt_constlen.copy_from_slice(&salt); // Retrieve public from given response - let public_vals = &mut params[1].items.iter_mut().next().unwrap().1.value; - if public_vals.len() != 1 { - bail!("Multiple values for `public`"); - } - let public: Vec = match &mut public_vals[0] { - K2vValue::Value(v) => std::mem::take(v), - K2vValue::Tombstone => bail!("public is a tombstone"), + let public: Vec = match &mut params[1].content().iter_mut().next().unwrap() { + Alternative::Value(v) => std::mem::take(v), + Alternative::Tombstone => bail!("public is a tombstone"), }; let public = PublicKey::from_slice(&public).ok_or(anyhow!("Invalid public key length"))?; Ok((salt_constlen, public)) } - async fn list_existing_passwords(k2v: &K2vClient) -> Result> { - let mut res = k2v - .read_batch(&[BatchReadOp { - partition_key: "keys", - filter: Filter { - start: None, - end: None, - prefix: Some("password:"), - limit: None, - reverse: false, - }, - conflicts_only: false, - tombstones: false, - single_item: false, - }]) + async fn list_existing_passwords(k2v: &RowStore) -> Result> { + let res = k2v.select(Selector::Prefix { shard_key: "keys", prefix: "password:" }) .await .context("ReadBatch for prefix password: in list_existing_passwords")?; - if res.len() != 1 { - bail!("unexpected k2v result: {:?}, expected one item", res); - } - Ok(res.pop().unwrap().items) + + Ok(res) } fn serialize(&self) -> [u8; 64] { diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index d013c6f..0e86cff 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -51,31 +51,12 @@ impl LoginProvider for StaticLoginProvider { bail!("Wrong password"); } - /* - tracing::debug!(user=%username, "fetch bucket"); - let bucket = user - .bucket - .clone() - .or_else(|| self.default_bucket.clone()) - .ok_or(anyhow!( - "No bucket configured and no default bucket specified" - ))?;*/ - tracing::debug!(user=%username, "fetch keys"); let storage: storage::Builders = match user.storage { StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), StaticStorage::Garage(c) => Box::new(storage::garage::GrgCreds {}), }; - /* - StorageCredentials { - k2v_region: self.k2v_region.clone(), - s3_region: self.s3_region.clone(), - aws_access_key_id: user.aws_access_key_id.clone(), - aws_secret_access_key: user.aws_secret_access_key.clone(), - bucket, - };*/ - let keys = match (&user.master_key, &user.secret_key) { (Some(m), Some(s)) => { let master_key = -- cgit v1.2.3 From 8cd9801030e24c58621b3bed8723e8a8a4722ef8 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 23 Nov 2023 15:16:44 +0100 Subject: various fixes --- src/login/static_provider.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 0e86cff..bd22060 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -104,11 +104,11 @@ impl LoginProvider for StaticLoginProvider { bucket, };*/ let storage: storage::Builders = match user.storage { - StaticStorage::InMemory => X, - StaticStorage::Garage => Y, + StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), + StaticStorage::Garage(c) => Box::new(storage::garage::GrgCreds {}), }; - let k2v_client = storage.k2v_client()?; + let k2v_client = storage.row_store()?; let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?; Ok(PublicCredentials { -- cgit v1.2.3 From 0722886efbeef3713bd7a671d2c09c8af2bdb6bd Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 23 Nov 2023 17:19:35 +0100 Subject: it compiles! --- src/login/ldap_provider.rs | 90 ++++++++++++++++++++++++++------------------ src/login/static_provider.rs | 38 +++++++++---------- 2 files changed, 71 insertions(+), 57 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 2eeb6d9..561b1c2 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -5,10 +5,9 @@ use log::debug; use crate::config::*; use crate::login::*; +use crate::storage; pub struct LdapLoginProvider { - k2v_region: Region, - s3_region: Region, ldap_server: String, pre_bind_on_login: bool, @@ -19,12 +18,9 @@ pub struct LdapLoginProvider { username_attr: String, mail_attr: String, - aws_access_key_id_attr: String, - aws_secret_access_key_attr: String, + storage_specific: StorageSpecific, user_secret_attr: String, alternate_user_secrets_attr: Option, - - bucket_source: BucketSource, } enum BucketSource { @@ -32,8 +28,13 @@ enum BucketSource { Attr(String), } +enum StorageSpecific { + InMemory, + Garage { from_config: LdapGarageConfig, bucket_source: BucketSource }, +} + impl LdapLoginProvider { - pub fn new(config: LoginLdapConfig, k2v_region: Region, s3_region: Region) -> Result { + pub fn new(config: LoginLdapConfig) -> Result { let bind_dn_and_pw = match (config.bind_dn, config.bind_password) { (Some(dn), Some(pw)) => Some((dn, pw)), (None, None) => None, @@ -42,12 +43,6 @@ impl LdapLoginProvider { ), }; - let bucket_source = match (config.bucket, config.bucket_attr) { - (Some(b), None) => BucketSource::Constant(b), - (None, Some(a)) => BucketSource::Attr(a), - _ => bail!("Must set `bucket` or `bucket_attr`, but not both"), - }; - if config.pre_bind_on_login && bind_dn_and_pw.is_none() { bail!("Cannot use `pre_bind_on_login` without setting `bind_dn` and `bind_password`"); } @@ -55,20 +50,34 @@ impl LdapLoginProvider { let mut attrs_to_retrieve = vec![ config.username_attr.clone(), config.mail_attr.clone(), - config.aws_access_key_id_attr.clone(), - config.aws_secret_access_key_attr.clone(), config.user_secret_attr.clone(), ]; + if let Some(a) = &config.alternate_user_secrets_attr { attrs_to_retrieve.push(a.clone()); } - if let BucketSource::Attr(a) = &bucket_source { - attrs_to_retrieve.push(a.clone()); - } + + // storage specific + let specific = match config.storage { + LdapStorage::InMemory => StorageSpecific::InMemory, + LdapStorage::Garage(grgconf) => { + let bucket_source = match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) { + (Some(b), None) => BucketSource::Constant(b), + (None, Some(a)) => BucketSource::Attr(a), + _ => bail!("Must set `bucket` or `bucket_attr`, but not both"), + }; + + if let BucketSource::Attr(a) = &bucket_source { + attrs_to_retrieve.push(a.clone()); + } + + StorageSpecific::Garage { from_config: grgconf, bucket_source } + }, + }; + + Ok(Self { - k2v_region, - s3_region, ldap_server: config.ldap_server, pre_bind_on_login: config.pre_bind_on_login, bind_dn_and_pw, @@ -76,29 +85,36 @@ impl LdapLoginProvider { attrs_to_retrieve, username_attr: config.username_attr, mail_attr: config.mail_attr, - aws_access_key_id_attr: config.aws_access_key_id_attr, - aws_secret_access_key_attr: config.aws_secret_access_key_attr, + storage_specific: specific, user_secret_attr: config.user_secret_attr, alternate_user_secrets_attr: config.alternate_user_secrets_attr, - bucket_source, }) } - fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { - let aws_access_key_id = get_attr(user, &self.aws_access_key_id_attr)?; - let aws_secret_access_key = get_attr(user, &self.aws_secret_access_key_attr)?; - let bucket = match &self.bucket_source { - BucketSource::Constant(b) => b.clone(), - BucketSource::Attr(a) => get_attr(user, a)?, + fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { + let storage: Builders = match &self.storage_specific { + StorageSpecific::InMemory => Box::new(storage::in_memory::FullMem {}), + StorageSpecific::Garage { from_config, bucket_source } => { + let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?; + let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?; + let bucket = match bucket_source { + BucketSource::Constant(b) => b.clone(), + BucketSource::Attr(a) => get_attr(user, &a)?, + }; + + + Box::new(storage::garage::GrgCreds { + region: from_config.aws_region.clone(), + s3_endpoint: from_config.s3_endpoint.clone(), + k2v_endpoint: from_config.k2v_endpoint.clone(), + aws_access_key_id, + aws_secret_access_key, + bucket + }) + }, }; - Ok(StorageCredentials { - k2v_region: self.k2v_region.clone(), - s3_region: self.s3_region.clone(), - aws_access_key_id, - aws_secret_access_key, - bucket, - }) + Ok(storage) } } @@ -204,7 +220,7 @@ impl LoginProvider for LdapLoginProvider { let storage = self.storage_creds_from_ldap_user(&user)?; drop(ldap); - let k2v_client = storage.k2v_client()?; + let k2v_client = storage.row_store()?; let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?; Ok(PublicCredentials { diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index bd22060..0b726cb 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -52,9 +52,16 @@ impl LoginProvider for StaticLoginProvider { } tracing::debug!(user=%username, "fetch keys"); - let storage: storage::Builders = match user.storage { + let storage: storage::Builders = match &user.storage { StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), - StaticStorage::Garage(c) => Box::new(storage::garage::GrgCreds {}), + StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { + region: grgconf.aws_region.clone(), + k2v_endpoint: grgconf.k2v_endpoint.clone(), + s3_endpoint: grgconf.s3_endpoint.clone(), + aws_access_key_id: grgconf.aws_access_key_id.clone(), + aws_secret_access_key: grgconf.aws_secret_access_key.clone(), + bucket: grgconf.bucket.clone(), + }), }; let keys = match (&user.master_key, &user.secret_key) { @@ -87,25 +94,16 @@ impl LoginProvider for StaticLoginProvider { Some(u) => u, }; - /* - let bucket = user - .bucket - .clone() - .or_else(|| self.default_bucket.clone()) - .ok_or(anyhow!( - "No bucket configured and no default bucket specieid" - ))?; - - let storage = StorageCredentials { - k2v_region: self.k2v_region.clone(), - s3_region: self.s3_region.clone(), - aws_access_key_id: user.aws_access_key_id.clone(), - aws_secret_access_key: user.aws_secret_access_key.clone(), - bucket, - };*/ - let storage: storage::Builders = match user.storage { + let storage: storage::Builders = match &user.storage { StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), - StaticStorage::Garage(c) => Box::new(storage::garage::GrgCreds {}), + StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { + region: grgconf.aws_region.clone(), + k2v_endpoint: grgconf.k2v_endpoint.clone(), + s3_endpoint: grgconf.s3_endpoint.clone(), + aws_access_key_id: grgconf.aws_access_key_id.clone(), + aws_secret_access_key: grgconf.aws_secret_access_key.clone(), + bucket: grgconf.bucket.clone(), + }), }; let k2v_client = storage.row_store()?; -- cgit v1.2.3 From 3ddbce4529225fe004acde3ab2e95261673bafc3 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 6 Dec 2023 20:57:25 +0100 Subject: WIP refactor --- src/login/mod.rs | 43 +++++++++++-------------------------------- src/login/static_provider.rs | 44 +++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 51 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 216c340..a9b9efe 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -45,6 +45,7 @@ pub struct PublicCredentials { pub public_key: PublicKey, } +/* /// The struct UserSecrets represents intermediary secrets that are mixed in with the user's /// password when decrypting the cryptographic keys that are stored in their bucket. /// These secrets should be stored somewhere else (e.g. in the LDAP server or in the @@ -57,6 +58,7 @@ pub struct UserSecrets { /// with old passwords pub alternate_user_secrets: Vec, } +*/ /// The struct CryptoKeys contains the cryptographic keys used to encrypt and decrypt /// data in a user's mailbox. @@ -85,7 +87,6 @@ impl Credentials { impl CryptoKeys { pub async fn init( storage: &Builders, - user_secrets: &UserSecrets, password: &str, ) -> Result { // Check that salt and public don't exist already @@ -113,7 +114,7 @@ impl CryptoKeys { thread_rng().fill(&mut kdf_salt); // Calculate key for password secret box - let password_key = user_secrets.derive_password_key(&kdf_salt, password)?; + let password_key = derive_password_key(&kdf_salt, password)?; // Seal a secret box that contains our crypto keys let password_sealed = seal(&keys.serialize(), &password_key)?; @@ -169,7 +170,6 @@ impl CryptoKeys { pub async fn open( storage: &Builders, - user_secrets: &UserSecrets, password: &str, ) -> Result { let k2v = storage.row_store()?; @@ -200,8 +200,7 @@ impl CryptoKeys { // Try to open blob let kdf_salt = &password_blob[..32]; - let password_openned = - user_secrets.try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?; + let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?; let keys = Self::deserialize(&password_openned)?; if keys.public != expected_public { @@ -238,7 +237,6 @@ impl CryptoKeys { pub async fn add_password( &self, storage: &Builders, - user_secrets: &UserSecrets, password: &str, ) -> Result<()> { let k2v = storage.row_store()?; @@ -252,7 +250,7 @@ impl CryptoKeys { thread_rng().fill(&mut kdf_salt); // Calculate key for password secret box - let password_key = user_secrets.derive_password_key(&kdf_salt, password)?; + let password_key = derive_password_key(&kdf_salt, password)?; // Seal a secret box that contains our crypto keys let password_sealed = seal(&self.serialize(), &password_key)?; @@ -418,32 +416,13 @@ impl CryptoKeys { } } -impl UserSecrets { - fn derive_password_key_with(user_secret: &str, kdf_salt: &[u8], password: &str) -> Result { - let tmp = format!("{}\n\n{}", user_secret, password); - Ok(Key::from_slice(&argon2_kdf(kdf_salt, tmp.as_bytes(), 32)?).unwrap()) - } - - fn derive_password_key(&self, kdf_salt: &[u8], password: &str) -> Result { - Self::derive_password_key_with(&self.user_secret, kdf_salt, password) - } +fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result { + Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap()) +} - fn try_open_encrypted_keys( - &self, - kdf_salt: &[u8], - password: &str, - encrypted_keys: &[u8], - ) -> Result> { - let secrets_to_try = - std::iter::once(&self.user_secret).chain(self.alternate_user_secrets.iter()); - for user_secret in secrets_to_try { - let password_key = Self::derive_password_key_with(user_secret, kdf_salt, password)?; - if let Ok(res) = open(encrypted_keys, &password_key) { - return Ok(res); - } - } - bail!("Unable to decrypt password blob."); - } +fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8]) -> Result> { + let password_key = derive_password_key(kdf_salt, password)?; + open(encrypted_keys, &password_key) } // ---- UTIL ---- diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 0b726cb..3f6a840 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::sync::Arc; +use std::path::PathBuf; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -10,13 +11,28 @@ use crate::login::*; use crate::storage; pub struct StaticLoginProvider { - users: HashMap>, - users_by_email: HashMap>, + user_list: PathBuf, + users: HashMap>, + users_by_email: HashMap>, } impl StaticLoginProvider { pub fn new(config: LoginStaticConfig) -> Result { - let users = config + let mut lp = Self { + user_list: config.user_list, + users: HashMap::new(), + users_by_email: HashMap::new(), + }; + + lp.update_user_list(); + + Ok(lp) + } + + pub fn update_user_list(&mut self) -> Result<()> { + let ulist: UserList = read_config(self.user_list)?; + + let users = ulist .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); @@ -29,11 +45,7 @@ impl StaticLoginProvider { users_by_email.insert(m.clone(), u.clone()); } } - - Ok(Self { - users, - users_by_email, - }) + Ok(()) } } @@ -64,24 +76,18 @@ impl LoginProvider for StaticLoginProvider { }), }; - let keys = match (&user.master_key, &user.secret_key) { - (Some(m), Some(s)) => { + let keys = match user.crypto_root { /*(&user.master_key, &user.secret_key) {*/ + CryptographyRoot::InPlace { master_key: m, secret_key: s } => { let master_key = Key::from_slice(&base64::decode(m)?).ok_or(anyhow!("Invalid master key"))?; let secret_key = SecretKey::from_slice(&base64::decode(s)?) .ok_or(anyhow!("Invalid secret key"))?; CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await? } - (None, None) => { - let user_secrets = UserSecrets { - user_secret: user.user_secret.clone(), - alternate_user_secrets: user.alternate_user_secrets.clone(), - }; - CryptoKeys::open(&storage, &user_secrets, password).await? + CryptographyRoot::PasswordProtected => { + CryptoKeys::open(&storage, password).await? } - _ => bail!( - "Either both master and secret key or none of them must be specified for user" - ), + CryptographyRoot::Keyring => unimplemented!(), }; tracing::debug!(user=%username, "logged"); -- cgit v1.2.3 From cf18eb8afb76a25150c885c6cf525aedcc25facc Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 8 Dec 2023 15:23:50 +0100 Subject: now compile again --- src/login/ldap_provider.rs | 22 +--------------------- src/login/static_provider.rs | 4 ++-- 2 files changed, 3 insertions(+), 23 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 561b1c2..f72b289 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -19,8 +19,6 @@ pub struct LdapLoginProvider { mail_attr: String, storage_specific: StorageSpecific, - user_secret_attr: String, - alternate_user_secrets_attr: Option, } enum BucketSource { @@ -50,13 +48,8 @@ impl LdapLoginProvider { let mut attrs_to_retrieve = vec![ config.username_attr.clone(), config.mail_attr.clone(), - config.user_secret_attr.clone(), ]; - if let Some(a) = &config.alternate_user_secrets_attr { - attrs_to_retrieve.push(a.clone()); - } - // storage specific let specific = match config.storage { LdapStorage::InMemory => StorageSpecific::InMemory, @@ -86,8 +79,6 @@ impl LdapLoginProvider { username_attr: config.username_attr, mail_attr: config.mail_attr, storage_specific: specific, - user_secret_attr: config.user_secret_attr, - alternate_user_secrets_attr: config.alternate_user_secrets_attr, }) } @@ -165,20 +156,9 @@ impl LoginProvider for LdapLoginProvider { debug!("Ldap login with user name {} successfull", username); let storage = self.storage_creds_from_ldap_user(&user)?; - - let user_secret = get_attr(&user, &self.user_secret_attr)?; - let alternate_user_secrets = match &self.alternate_user_secrets_attr { - None => vec![], - Some(a) => user.attrs.get(a).cloned().unwrap_or_default(), - }; - let user_secrets = UserSecrets { - user_secret, - alternate_user_secrets, - }; - drop(ldap); - let keys = CryptoKeys::open(&storage, &user_secrets, password).await?; + let keys = CryptoKeys::open(&storage, password).await?; Ok(Credentials { storage, keys }) } diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 3f6a840..d0a4624 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -30,7 +30,7 @@ impl StaticLoginProvider { } pub fn update_user_list(&mut self) -> Result<()> { - let ulist: UserList = read_config(self.user_list)?; + let ulist: UserList = read_config(self.user_list.clone())?; let users = ulist .into_iter() @@ -76,7 +76,7 @@ impl LoginProvider for StaticLoginProvider { }), }; - let keys = match user.crypto_root { /*(&user.master_key, &user.secret_key) {*/ + let keys = match &user.crypto_root { /*(&user.master_key, &user.secret_key) {*/ CryptographyRoot::InPlace { master_key: m, secret_key: s } => { let master_key = Key::from_slice(&base64::decode(m)?).ok_or(anyhow!("Invalid master key"))?; -- cgit v1.2.3 From 532c99f3d30ab8adc0963f0814ce3151e1b61caf Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 8 Dec 2023 18:13:00 +0100 Subject: rework static login provider --- src/login/static_provider.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index d0a4624..0f6ab3a 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -19,12 +19,17 @@ pub struct StaticLoginProvider { impl StaticLoginProvider { pub fn new(config: LoginStaticConfig) -> Result { let mut lp = Self { - user_list: config.user_list, + user_list: config.user_list.clone(), users: HashMap::new(), users_by_email: HashMap::new(), }; - lp.update_user_list(); + lp + .update_user_list() + .context( + format!( + "failed to read {:?}, make sure it exists and it's correctly formatted", + config.user_list))?; Ok(lp) } @@ -32,17 +37,18 @@ impl StaticLoginProvider { pub fn update_user_list(&mut self) -> Result<()> { let ulist: UserList = read_config(self.user_list.clone())?; - let users = ulist + self.users = ulist .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); - let mut users_by_email = HashMap::new(); - for (_, u) in users.iter() { + + self.users_by_email.clear(); + for (_, u) in self.users.iter() { for m in u.email_addresses.iter() { - if users_by_email.contains_key(m) { + if self.users_by_email.contains_key(m) { bail!("Several users have same email address: {}", m); } - users_by_email.insert(m.clone(), u.clone()); + self.users_by_email.insert(m.clone(), u.clone()); } } Ok(()) -- cgit v1.2.3 From 47e25cd7f710fcd82356377cf48eccf9f65d31cc Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 12 Dec 2023 09:17:59 +0100 Subject: WIP --- src/login/mod.rs | 14 +++++++++++++- src/login/static_provider.rs | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index a9b9efe..f7a81c2 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -169,9 +169,20 @@ impl CryptoKeys { } pub async fn open( - storage: &Builders, password: &str, + root_blob: &str, ) -> Result { + let kdf_salt = &password_blob[..32]; + let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?; + + let keys = Self::deserialize(&password_openned)?; + if keys.public != expected_public { + bail!("Password public key doesn't match stored public key"); + } + + Ok(keys) + + /* let k2v = storage.row_store()?; let (ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?; @@ -208,6 +219,7 @@ impl CryptoKeys { } Ok(keys) + */ } pub async fn open_without_password( diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 0f6ab3a..7fadf2f 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -83,15 +83,15 @@ impl LoginProvider for StaticLoginProvider { }; let keys = match &user.crypto_root { /*(&user.master_key, &user.secret_key) {*/ - CryptographyRoot::InPlace { master_key: m, secret_key: s } => { + CryptographyRoot::ClearText { master_key: m, secret_key: s } => { let master_key = Key::from_slice(&base64::decode(m)?).ok_or(anyhow!("Invalid master key"))?; let secret_key = SecretKey::from_slice(&base64::decode(s)?) .ok_or(anyhow!("Invalid secret key"))?; CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await? } - CryptographyRoot::PasswordProtected => { - CryptoKeys::open(&storage, password).await? + CryptographyRoot::PasswordProtected { root_blob } => { + CryptoKeys::open(password, root_blob).await? } CryptographyRoot::Keyring => unimplemented!(), }; -- cgit v1.2.3 From 064a1077c8c66fe8d3ee71f831c930e1ddfbc34a Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 13 Dec 2023 16:09:01 +0100 Subject: it compiles again! --- src/login/ldap_provider.rs | 20 +- src/login/mod.rs | 452 +++++++++++-------------------------------- src/login/static_provider.rs | 20 +- 3 files changed, 136 insertions(+), 356 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index f72b289..6e94061 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -17,6 +17,7 @@ pub struct LdapLoginProvider { attrs_to_retrieve: Vec, username_attr: String, mail_attr: String, + crypto_root_attr: String, storage_specific: StorageSpecific, } @@ -48,6 +49,7 @@ impl LdapLoginProvider { let mut attrs_to_retrieve = vec![ config.username_attr.clone(), config.mail_attr.clone(), + config.crypto_root_attr.clone(), ]; // storage specific @@ -78,6 +80,7 @@ impl LdapLoginProvider { attrs_to_retrieve, username_attr: config.username_attr, mail_attr: config.mail_attr, + crypto_root_attr: config.crypto_root_attr, storage_specific: specific, }) } @@ -155,10 +158,16 @@ impl LoginProvider for LdapLoginProvider { .context("Invalid password")?; debug!("Ldap login with user name {} successfull", username); + // cryptography + let crstr = get_attr(&user, &self.crypto_root_attr)?; + let cr = CryptoRoot(crstr); + let keys = cr.crypto_keys(password)?; + + // storage let storage = self.storage_creds_from_ldap_user(&user)?; + drop(ldap); - let keys = CryptoKeys::open(&storage, password).await?; Ok(Credentials { storage, keys }) } @@ -197,12 +206,15 @@ impl LoginProvider for LdapLoginProvider { let user = SearchEntry::construct(matches.into_iter().next().unwrap()); debug!("Found matching LDAP user for email {}: {}", email, user.dn); + // cryptography + let crstr = get_attr(&user, &self.crypto_root_attr)?; + let cr = CryptoRoot(crstr); + let public_key = cr.public_key()?; + + // storage let storage = self.storage_creds_from_ldap_user(&user)?; drop(ldap); - let k2v_client = storage.row_store()?; - let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?; - Ok(PublicCredentials { storage, public_key, diff --git a/src/login/mod.rs b/src/login/mod.rs index f7a81c2..3d7a49f 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -2,7 +2,7 @@ pub mod ldap_provider; pub mod static_provider; use std::sync::Arc; -use futures::try_join; +use base64::Engine; use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; @@ -37,6 +37,14 @@ pub struct Credentials { /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } +impl Credentials { + pub fn row_client(&self) -> Result { + Ok(self.storage.row_store()?) + } + pub fn blob_client(&self) -> Result { + Ok(self.storage.blob_store()?) + } +} #[derive(Clone, Debug)] pub struct PublicCredentials { @@ -45,20 +53,81 @@ pub struct PublicCredentials { pub public_key: PublicKey, } -/* -/// The struct UserSecrets represents intermediary secrets that are mixed in with the user's -/// password when decrypting the cryptographic keys that are stored in their bucket. -/// These secrets should be stored somewhere else (e.g. in the LDAP server or in the -/// local config file), as an additionnal authentification factor so that the password -/// isn't enough just alone to decrypt the content of a user's bucket. -pub struct UserSecrets { - /// The main user secret that will be used to encrypt keys when a new password is added - pub user_secret: String, - /// Alternative user secrets that will be tried when decrypting keys that were encrypted - /// with old passwords - pub alternate_user_secrets: Vec, +use serde::{Serialize, Deserialize}; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CryptoRoot(pub String); + +impl CryptoRoot { + pub fn create_pass(password: &str, k: &CryptoKeys) -> Result { + let bytes = k.password_seal(password)?; + let b64 = base64::engine::general_purpose::STANDARD_NO_PAD.encode(bytes); + let cr = format!("aero:cryptoroot:pass:{}", b64); + Ok(Self(cr)) + } + + pub fn create_cleartext(k: &CryptoKeys) -> Self { + let bytes = k.serialize(); + let b64 = base64::engine::general_purpose::STANDARD_NO_PAD.encode(bytes); + let cr = format!("aero:cryptoroot:cleartext:{}", b64); + Self(cr) + } + + pub fn create_incoming(pk: &PublicKey) -> Self { + let bytes: &[u8] = &pk[..]; + let b64 = base64::engine::general_purpose::STANDARD_NO_PAD.encode(bytes); + let cr = format!("aero:cryptoroot:incoming:{}", b64); + Self(cr) + } + + pub fn public_key(&self) -> Result { + match self.0.splitn(4, ':').collect::>()[..] { + [ "aero", "cryptoroot", "pass", b64blob ] => { + let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; + if blob.len() < 32 { + bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); + } + PublicKey::from_slice(&blob[..32]).context("must be a valid public key") + }, + [ "aero", "cryptoroot", "cleartext", b64blob ] => { + let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; + Ok(CryptoKeys::deserialize(&blob)?.public) + }, + [ "aero", "cryptoroot", "incoming", b64blob ] => { + let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; + if blob.len() < 32 { + bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); + } + PublicKey::from_slice(&blob[..32]).context("must be a valid public key") + }, + [ "aero", "cryptoroot", "keyring", _ ] => { + bail!("keyring is not yet implemented!") + }, + _ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)), + } + } + pub fn crypto_keys(&self, password: &str) -> Result { + match self.0.splitn(4, ':').collect::>()[..] { + [ "aero", "cryptoroot", "pass", b64blob ] => { + let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; + if blob.len() < 32 { + bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); + } + CryptoKeys::password_open(password, &blob[32..]) + }, + [ "aero", "cryptoroot", "cleartext", b64blob ] => { + let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; + CryptoKeys::deserialize(&blob) + }, + [ "aero", "cryptoroot", "incoming", b64blob ] => { + bail!("incoming cryptoroot does not contain a crypto key!") + }, + [ "aero", "cryptoroot", "keyring", _ ] =>{ + bail!("keyring is not yet implemented!") + }, + _ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)), + } + } } -*/ /// The struct CryptoKeys contains the cryptographic keys used to encrypt and decrypt /// data in a user's mailbox. @@ -75,337 +144,22 @@ pub struct CryptoKeys { // ---- -impl Credentials { - pub fn row_client(&self) -> Result { - Ok(self.storage.row_store()?) - } - pub fn blob_client(&self) -> Result { - Ok(self.storage.blob_store()?) - } -} + impl CryptoKeys { - pub async fn init( - storage: &Builders, - password: &str, - ) -> Result { - // Check that salt and public don't exist already - let k2v = storage.row_store()?; - let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?; - - // Generate salt for password identifiers - let mut ident_salt = [0u8; 32]; - thread_rng().fill(&mut ident_salt); - - // Generate (public, private) key pair and master key + /// Initialize a new cryptography root + pub fn init() -> Self { let (public, secret) = gen_keypair(); let master = gen_key(); - let keys = CryptoKeys { + CryptoKeys { master, secret, public, - }; - - // Generate short password digest (= password identity) - let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?; - - // Generate salt for KDF - let mut kdf_salt = [0u8; 32]; - thread_rng().fill(&mut kdf_salt); - - // Calculate key for password secret box - let password_key = derive_password_key(&kdf_salt, password)?; - - // Seal a secret box that contains our crypto keys - let password_sealed = seal(&keys.serialize(), &password_key)?; - - let password_sortkey = format!("password:{}", hex::encode(&ident)); - let password_blob = [&kdf_salt[..], &password_sealed].concat(); - - // Write values to storage - // @FIXME Implement insert batch in the storage API - let (salt, public, passwd) = ( - salt_ct.set_value(&ident_salt), - public_ct.set_value(keys.public.as_ref()), - k2v.row("keys", &password_sortkey).set_value(&password_blob) - ); - try_join!(salt.push(), public.push(), passwd.push()) - .context("InsertBatch for salt, public, and password")?; - - Ok(keys) - } - - pub async fn init_without_password( - storage: &Builders, - master: &Key, - secret: &SecretKey, - ) -> Result { - // Check that salt and public don't exist already - let k2v = storage.row_store()?; - let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?; - - // Generate salt for password identifiers - let mut ident_salt = [0u8; 32]; - thread_rng().fill(&mut ident_salt); - - // Create CryptoKeys struct from given keys - let public = secret.public_key(); - let keys = CryptoKeys { - master: master.clone(), - secret: secret.clone(), - public, - }; - - // Write values to storage - // @FIXME implement insert batch in the storage API - let (salt, public) = ( - salt_ct.set_value(&ident_salt), - public_ct.set_value(keys.public.as_ref()), - ); - - try_join!(salt.push(), public.push()).context("InsertBatch for salt and public")?; - - Ok(keys) - } - - pub async fn open( - password: &str, - root_blob: &str, - ) -> Result { - let kdf_salt = &password_blob[..32]; - let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?; - - let keys = Self::deserialize(&password_openned)?; - if keys.public != expected_public { - bail!("Password public key doesn't match stored public key"); - } - - Ok(keys) - - /* - let k2v = storage.row_store()?; - let (ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?; - - // Generate short password digest (= password identity) - let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?; - - // Lookup password blob - let password_sortkey = format!("password:{}", hex::encode(&ident)); - let password_ref = k2v.row("keys", &password_sortkey); - - let password_blob = { - let val = match password_ref.fetch().await { - Err(StorageError::NotFound) => { - bail!("invalid password") - } - x => x?, - }; - if val.content().len() != 1 { - bail!("multiple values for password in storage"); - } - match val.content().pop().unwrap() { - Alternative::Value(v) => v, - Alternative::Tombstone => bail!("invalid password"), - } - }; - - // Try to open blob - let kdf_salt = &password_blob[..32]; - let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?; - - let keys = Self::deserialize(&password_openned)?; - if keys.public != expected_public { - bail!("Password public key doesn't match stored public key"); - } - - Ok(keys) - */ - } - - pub async fn open_without_password( - storage: &Builders, - master: &Key, - secret: &SecretKey, - ) -> Result { - let k2v = storage.row_store()?; - let (_ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?; - - // Create CryptoKeys struct from given keys - let public = secret.public_key(); - let keys = CryptoKeys { - master: master.clone(), - secret: secret.clone(), - public, - }; - - // Check public key matches - if keys.public != expected_public { - bail!("Given public key doesn't match stored public key"); } - - Ok(keys) - } - - pub async fn add_password( - &self, - storage: &Builders, - password: &str, - ) -> Result<()> { - let k2v = storage.row_store()?; - let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?; - - // Generate short password digest (= password identity) - let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?; - - // Generate salt for KDF - let mut kdf_salt = [0u8; 32]; - thread_rng().fill(&mut kdf_salt); - - // Calculate key for password secret box - let password_key = derive_password_key(&kdf_salt, password)?; - - // Seal a secret box that contains our crypto keys - let password_sealed = seal(&self.serialize(), &password_key)?; - - let password_sortkey = format!("password:{}", hex::encode(&ident)); - let password_blob = [&kdf_salt[..], &password_sealed].concat(); - - // List existing passwords to overwrite existing entry if necessary - let pass_key = k2v.row("keys", &password_sortkey); - let passwd = match pass_key.fetch().await { - Err(StorageError::NotFound) => pass_key, - v => { - let entry = v?; - if entry.content().iter().any(|x| matches!(x, Alternative::Value(_))) { - bail!("password already exists"); - } - entry.to_ref() - } - }; - - // Write values to storage - passwd - .set_value(&password_blob) - .push() - .await - .context("InsertBatch for new password")?; - - Ok(()) - } - - pub async fn delete_password( - storage: &Builders, - password: &str, - allow_delete_all: bool, - ) -> Result<()> { - let k2v = storage.row_store()?; - let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?; - - // Generate short password digest (= password identity) - let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?; - let password_sortkey = format!("password:{}", hex::encode(&ident)); - - // List existing passwords - let existing_passwords = Self::list_existing_passwords(&k2v).await?; - - // Check password is there - let pw = existing_passwords - .iter() - .map(|x| x.to_ref()) - .find(|x| x.key().1 == &password_sortkey) - //.get(&password_sortkey) - .ok_or(anyhow!("password does not exist"))?; - - if !allow_delete_all && existing_passwords.len() < 2 { - bail!("No other password exists, not deleting last password."); - } - - pw.rm().await.context("DeleteItem for password")?; - - Ok(()) - } - - // ---- STORAGE UTIL ---- - // - async fn check_uninitialized( - k2v: &RowStore, - ) -> Result<(RowRef, RowRef)> { - let params = k2v - .select(Selector::List(vec![ - ("keys", "salt"), - ("keys", "public"), - ])) - .await - .context("ReadBatch for salt and public in check_uninitialized")?; - - if params.len() != 2 { - bail!( - "Invalid response from k2v storage: {:?} (expected two items)", - params - ); - } - - let salt_ct = params[0].to_ref(); - if params[0].content().iter().any(|x| matches!(x, Alternative::Value(_))) { - bail!("key storage already initialized"); - } - - let public_ct = params[1].to_ref(); - if params[1].content().iter().any(|x| matches!(x, Alternative::Value(_))) { - bail!("key storage already initialized"); - } - - Ok((salt_ct, public_ct)) - } - - pub async fn load_salt_and_public(k2v: &RowStore) -> Result<([u8; 32], PublicKey)> { - let params = k2v - .select(Selector::List(vec![ - ("keys", "salt"), - ("keys", "public"), - ])) - .await - .context("ReadBatch for salt and public in load_salt_and_public")?; - - if params.len() != 2 { - bail!( - "Invalid response from k2v storage: {:?} (expected two items)", - params - ); - } - if params[0].content().len() != 1 || params[1].content().len() != 1 { - bail!("cryptographic keys not initialized for user"); - } - - // Retrieve salt from given response - let salt: Vec = match &mut params[0].content().iter_mut().next().unwrap() { - Alternative::Value(v) => std::mem::take(v), - Alternative::Tombstone => bail!("salt is a tombstone"), - }; - if salt.len() != 32 { - bail!("`salt` is not 32 bytes long"); - } - let mut salt_constlen = [0u8; 32]; - salt_constlen.copy_from_slice(&salt); - - // Retrieve public from given response - let public: Vec = match &mut params[1].content().iter_mut().next().unwrap() { - Alternative::Value(v) => std::mem::take(v), - Alternative::Tombstone => bail!("public is a tombstone"), - }; - let public = PublicKey::from_slice(&public).ok_or(anyhow!("Invalid public key length"))?; - - Ok((salt_constlen, public)) - } - - async fn list_existing_passwords(k2v: &RowStore) -> Result> { - let res = k2v.select(Selector::Prefix { shard_key: "keys", prefix: "password:" }) - .await - .context("ReadBatch for prefix password: in list_existing_passwords")?; - - Ok(res) } + // Clear text serialize/deserialize + /// Serialize the root as bytes without encryption fn serialize(&self) -> [u8; 64] { let mut res = [0u8; 64]; res[..32].copy_from_slice(self.master.as_ref()); @@ -413,6 +167,7 @@ impl CryptoKeys { res } + /// Deserialize a clear text crypto root without encryption fn deserialize(bytes: &[u8]) -> Result { if bytes.len() != 64 { bail!("Invalid length: {}, expected 64", bytes.len()); @@ -426,6 +181,31 @@ impl CryptoKeys { public, }) } + + // Password sealed keys serialize/deserialize + pub fn password_open(password: &str, blob: &[u8]) -> Result { + let kdf_salt = &blob[0..32]; + let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[32..])?; + + let keys = Self::deserialize(&password_openned)?; + Ok(keys) + } + + pub fn password_seal(&self, password: &str) -> Result> { + let mut kdf_salt = [0u8; 32]; + thread_rng().fill(&mut kdf_salt); + + // Calculate key for password secret box + let password_key = derive_password_key(&kdf_salt, password)?; + + // Seal a secret box that contains our crypto keys + let password_sealed = seal(&self.serialize(), &password_key)?; + + // Create blob + let password_blob = [&self.public[..], &kdf_salt[..], &password_sealed].concat(); + + Ok(password_blob) + } } fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result { @@ -452,7 +232,7 @@ pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result { - let master_key = - Key::from_slice(&base64::decode(m)?).ok_or(anyhow!("Invalid master key"))?; - let secret_key = SecretKey::from_slice(&base64::decode(s)?) - .ok_or(anyhow!("Invalid secret key"))?; - CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await? - } - CryptographyRoot::PasswordProtected { root_blob } => { - CryptoKeys::open(password, root_blob).await? - } - CryptographyRoot::Keyring => unimplemented!(), - }; + let cr = CryptoRoot(user.crypto_root); + let keys = cr.crypto_keys(password)?; tracing::debug!(user=%username, "logged"); Ok(Credentials { storage, keys }) @@ -118,8 +106,8 @@ impl LoginProvider for StaticLoginProvider { }), }; - let k2v_client = storage.row_store()?; - let (_, public_key) = CryptoKeys::load_salt_and_public(&k2v_client).await?; + let cr = CryptoRoot(user.crypto_root); + let public_key = cr.public_key()?; Ok(PublicCredentials { storage, -- cgit v1.2.3 From 29561dde41b402362f8baa3d9cd87a07f743b9fd Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 13 Dec 2023 18:04:04 +0100 Subject: CLI tools --- src/login/mod.rs | 12 +++++------- src/login/static_provider.rs | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index 3d7a49f..9e0c437 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -109,16 +109,13 @@ impl CryptoRoot { match self.0.splitn(4, ':').collect::>()[..] { [ "aero", "cryptoroot", "pass", b64blob ] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; - if blob.len() < 32 { - bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); - } - CryptoKeys::password_open(password, &blob[32..]) + CryptoKeys::password_open(password, &blob) }, [ "aero", "cryptoroot", "cleartext", b64blob ] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; CryptoKeys::deserialize(&blob) }, - [ "aero", "cryptoroot", "incoming", b64blob ] => { + [ "aero", "cryptoroot", "incoming", _ ] => { bail!("incoming cryptoroot does not contain a crypto key!") }, [ "aero", "cryptoroot", "keyring", _ ] =>{ @@ -184,8 +181,9 @@ impl CryptoKeys { // Password sealed keys serialize/deserialize pub fn password_open(password: &str, blob: &[u8]) -> Result { - let kdf_salt = &blob[0..32]; - let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[32..])?; + let _pubkey = &blob[0..32]; + let kdf_salt = &blob[32..64]; + let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[64..])?; let keys = Self::deserialize(&password_openned)?; Ok(keys) diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 178d97e..85d55ef 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -81,7 +81,7 @@ impl LoginProvider for StaticLoginProvider { }), }; - let cr = CryptoRoot(user.crypto_root); + let cr = CryptoRoot(user.crypto_root.clone()); let keys = cr.crypto_keys(password)?; tracing::debug!(user=%username, "logged"); @@ -106,7 +106,7 @@ impl LoginProvider for StaticLoginProvider { }), }; - let cr = CryptoRoot(user.crypto_root); + let cr = CryptoRoot(user.crypto_root.clone()); let public_key = cr.public_key()?; Ok(PublicCredentials { -- cgit v1.2.3 From 1f6e64d34e44b8b7bc7247af38bccf3ade86cf0b Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 14 Dec 2023 13:03:04 +0100 Subject: add support for hot reloading --- src/login/static_provider.rs | 73 ++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 29 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 85d55ef..4a8d484 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::sync::Arc; use std::path::PathBuf; +use tokio::sync::watch; +use tokio::signal::unix::{signal, SignalKind}; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -9,48 +11,59 @@ use crate::config::*; use crate::login::*; use crate::storage; -pub struct StaticLoginProvider { - user_list: PathBuf, +#[derive(Default)] +pub struct UserDatabase { users: HashMap>, users_by_email: HashMap>, } -impl StaticLoginProvider { - pub fn new(config: LoginStaticConfig) -> Result { - let mut lp = Self { - user_list: config.user_list.clone(), - users: HashMap::new(), - users_by_email: HashMap::new(), - }; - - lp - .update_user_list() - .context( - format!( - "failed to read {:?}, make sure it exists and it's correctly formatted", - config.user_list))?; +pub struct StaticLoginProvider { + user_db: watch::Receiver, +} - Ok(lp) - } +pub async fn update_user_list(config: PathBuf, up: watch::Sender) -> Result<()> { + let mut stream = signal(SignalKind::user_defined1()).expect("failed to install SIGUSR1 signal hander for reload"); - pub fn update_user_list(&mut self) -> Result<()> { - let ulist: UserList = read_config(self.user_list.clone())?; + loop { + let ulist: UserList = match read_config(config.clone()) { + Ok(x) => x, + Err(e) => { + tracing::warn!(path=%config.as_path().to_string_lossy(), error=%e, "Unable to load config"); + continue; + } + }; - self.users = ulist + let users = ulist .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); - self.users_by_email.clear(); - for (_, u) in self.users.iter() { + let mut users_by_email = HashMap::new(); + for (_, u) in users.iter() { for m in u.email_addresses.iter() { - if self.users_by_email.contains_key(m) { - bail!("Several users have same email address: {}", m); + if users_by_email.contains_key(m) { + tracing::warn!("Several users have the same email address: {}", m); + continue } - self.users_by_email.insert(m.clone(), u.clone()); + users_by_email.insert(m.clone(), u.clone()); } } - Ok(()) + + tracing::info!("{} users loaded", users.len()); + up.send(UserDatabase { users, users_by_email }).context("update user db config")?; + stream.recv().await; + tracing::info!("Received SIGUSR1, reloading"); + } +} + +impl StaticLoginProvider { + pub async fn new(config: LoginStaticConfig) -> Result { + let (tx, mut rx) = watch::channel(UserDatabase::default()); + + tokio::spawn(update_user_list(config.user_list, tx)); + rx.changed().await?; + + Ok(Self { user_db: rx }) } } @@ -58,7 +71,8 @@ impl StaticLoginProvider { impl LoginProvider for StaticLoginProvider { async fn login(&self, username: &str, password: &str) -> Result { tracing::debug!(user=%username, "login"); - let user = match self.users.get(username) { + let user_db = self.user_db.borrow(); + let user = match user_db.users.get(username) { None => bail!("User {} does not exist", username), Some(u) => u, }; @@ -89,7 +103,8 @@ impl LoginProvider for StaticLoginProvider { } async fn public_login(&self, email: &str) -> Result { - let user = match self.users_by_email.get(email) { + let user_db = self.user_db.borrow(); + let user = match user_db.users_by_email.get(email) { None => bail!("No user for email address {}", email), Some(u) => u, }; -- cgit v1.2.3 From 684f4de225c44464abcb6a9cb2ef6dcae90537a8 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sat, 16 Dec 2023 11:13:32 +0100 Subject: new new new storage interface --- src/login/ldap_provider.rs | 4 +++- src/login/static_provider.rs | 27 ++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 6e94061..4e3af3c 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -87,7 +87,9 @@ impl LdapLoginProvider { fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { let storage: Builders = match &self.storage_specific { - StorageSpecific::InMemory => Box::new(storage::in_memory::FullMem {}), + StorageSpecific::InMemory => Box::new(storage::in_memory::FullMem::new( + &get_attr(user, &self.username_attr)? + )), StorageSpecific::Garage { from_config, bucket_source } => { let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?; let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?; diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 4a8d484..788a4c5 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -11,10 +11,15 @@ use crate::config::*; use crate::login::*; use crate::storage; +pub struct ContextualUserEntry { + pub username: String, + pub config: UserEntry, +} + #[derive(Default)] pub struct UserDatabase { - users: HashMap>, - users_by_email: HashMap>, + users: HashMap>, + users_by_email: HashMap>, } pub struct StaticLoginProvider { @@ -35,12 +40,12 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender) let users = ulist .into_iter() - .map(|(k, v)| (k, Arc::new(v))) + .map(|(username, config)| (username.clone() , Arc::new(ContextualUserEntry { username, config }))) .collect::>(); let mut users_by_email = HashMap::new(); for (_, u) in users.iter() { - for m in u.email_addresses.iter() { + for m in u.config.email_addresses.iter() { if users_by_email.contains_key(m) { tracing::warn!("Several users have the same email address: {}", m); continue @@ -78,13 +83,13 @@ impl LoginProvider for StaticLoginProvider { }; tracing::debug!(user=%username, "verify password"); - if !verify_password(password, &user.password)? { + if !verify_password(password, &user.config.password)? { bail!("Wrong password"); } tracing::debug!(user=%username, "fetch keys"); - let storage: storage::Builders = match &user.storage { - StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), + let storage: storage::Builders = match &user.config.storage { + StaticStorage::InMemory => Box::new(storage::in_memory::FullMem::new(username)), StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), @@ -95,7 +100,7 @@ impl LoginProvider for StaticLoginProvider { }), }; - let cr = CryptoRoot(user.crypto_root.clone()); + let cr = CryptoRoot(user.config.crypto_root.clone()); let keys = cr.crypto_keys(password)?; tracing::debug!(user=%username, "logged"); @@ -109,8 +114,8 @@ impl LoginProvider for StaticLoginProvider { Some(u) => u, }; - let storage: storage::Builders = match &user.storage { - StaticStorage::InMemory => Box::new(storage::in_memory::FullMem {}), + let storage: storage::Builders = match &user.config.storage { + StaticStorage::InMemory => Box::new(storage::in_memory::FullMem::new(&user.username)), StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), @@ -121,7 +126,7 @@ impl LoginProvider for StaticLoginProvider { }), }; - let cr = CryptoRoot(user.crypto_root.clone()); + let cr = CryptoRoot(user.config.crypto_root.clone()); let public_key = cr.public_key()?; Ok(PublicCredentials { -- cgit v1.2.3 From 3d41f40dc8cd6bdfa7a9279ab1959564d06eefaf Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 18 Dec 2023 17:09:44 +0100 Subject: Storage trait new implementation --- src/login/ldap_provider.rs | 14 +++++++------- src/login/mod.rs | 12 ++---------- src/login/static_provider.rs | 16 ++++++++-------- 3 files changed, 17 insertions(+), 25 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 4e3af3c..009605d 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -85,11 +85,11 @@ impl LdapLoginProvider { }) } - fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { - let storage: Builders = match &self.storage_specific { - StorageSpecific::InMemory => Box::new(storage::in_memory::FullMem::new( + fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { + let storage: Builder = match &self.storage_specific { + StorageSpecific::InMemory => storage::in_memory::MemBuilder::new( &get_attr(user, &self.username_attr)? - )), + ), StorageSpecific::Garage { from_config, bucket_source } => { let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?; let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?; @@ -99,14 +99,14 @@ impl LdapLoginProvider { }; - Box::new(storage::garage::GrgCreds { + storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: from_config.aws_region.clone(), s3_endpoint: from_config.s3_endpoint.clone(), k2v_endpoint: from_config.k2v_endpoint.clone(), aws_access_key_id, aws_secret_access_key, - bucket - }) + bucket, + })? }, }; diff --git a/src/login/mod.rs b/src/login/mod.rs index 9e0c437..d331522 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -33,23 +33,15 @@ pub type ArcLoginProvider = Arc; #[derive(Clone, Debug)] pub struct Credentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: Builders, + pub storage: Builder, /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V pub keys: CryptoKeys, } -impl Credentials { - pub fn row_client(&self) -> Result { - Ok(self.storage.row_store()?) - } - pub fn blob_client(&self) -> Result { - Ok(self.storage.blob_store()?) - } -} #[derive(Clone, Debug)] pub struct PublicCredentials { /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V) - pub storage: Builders, + pub storage: Builder, pub public_key: PublicKey, } diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 788a4c5..5896f16 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -88,16 +88,16 @@ impl LoginProvider for StaticLoginProvider { } tracing::debug!(user=%username, "fetch keys"); - let storage: storage::Builders = match &user.config.storage { - StaticStorage::InMemory => Box::new(storage::in_memory::FullMem::new(username)), - StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { + let storage: storage::Builder = match &user.config.storage { + StaticStorage::InMemory => storage::in_memory::MemBuilder::new(username), + StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), s3_endpoint: grgconf.s3_endpoint.clone(), aws_access_key_id: grgconf.aws_access_key_id.clone(), aws_secret_access_key: grgconf.aws_secret_access_key.clone(), bucket: grgconf.bucket.clone(), - }), + })?, }; let cr = CryptoRoot(user.config.crypto_root.clone()); @@ -114,16 +114,16 @@ impl LoginProvider for StaticLoginProvider { Some(u) => u, }; - let storage: storage::Builders = match &user.config.storage { - StaticStorage::InMemory => Box::new(storage::in_memory::FullMem::new(&user.username)), - StaticStorage::Garage(grgconf) => Box::new(storage::garage::GrgCreds { + let storage: storage::Builder = match &user.config.storage { + StaticStorage::InMemory => storage::in_memory::MemBuilder::new(&user.username), + StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), s3_endpoint: grgconf.s3_endpoint.clone(), aws_access_key_id: grgconf.aws_access_key_id.clone(), aws_secret_access_key: grgconf.aws_secret_access_key.clone(), bucket: grgconf.bucket.clone(), - }), + })?, }; let cr = CryptoRoot(user.config.crypto_root.clone()); -- cgit v1.2.3 From 2830e62df99dc236cd2ba63a909849cf973d2654 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 20 Dec 2023 13:55:23 +0100 Subject: working in memory storage --- src/login/static_provider.rs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 5896f16..2e2e034 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -24,6 +24,7 @@ pub struct UserDatabase { pub struct StaticLoginProvider { user_db: watch::Receiver, + in_memory_store: tokio::sync::Mutex>>, } pub async fn update_user_list(config: PathBuf, up: watch::Sender) -> Result<()> { @@ -68,7 +69,7 @@ impl StaticLoginProvider { tokio::spawn(update_user_list(config.user_list, tx)); rx.changed().await?; - Ok(Self { user_db: rx }) + Ok(Self { user_db: rx, in_memory_store: tokio::sync::Mutex::new(HashMap::new()) }) } } @@ -76,10 +77,12 @@ impl StaticLoginProvider { impl LoginProvider for StaticLoginProvider { async fn login(&self, username: &str, password: &str) -> Result { tracing::debug!(user=%username, "login"); - let user_db = self.user_db.borrow(); - let user = match user_db.users.get(username) { - None => bail!("User {} does not exist", username), - Some(u) => u, + let user = { + let user_db = self.user_db.borrow(); + match user_db.users.get(username) { + None => bail!("User {} does not exist", username), + Some(u) => u.clone(), + } }; tracing::debug!(user=%username, "verify password"); @@ -89,7 +92,13 @@ impl LoginProvider for StaticLoginProvider { tracing::debug!(user=%username, "fetch keys"); let storage: storage::Builder = match &user.config.storage { - StaticStorage::InMemory => storage::in_memory::MemBuilder::new(username), + StaticStorage::InMemory => { + let mut global_storage = self.in_memory_store.lock().await; + global_storage + .entry(username.to_string()) + .or_insert(storage::in_memory::MemBuilder::new(username)) + .clone() + }, StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), @@ -108,14 +117,23 @@ impl LoginProvider for StaticLoginProvider { } async fn public_login(&self, email: &str) -> Result { - let user_db = self.user_db.borrow(); - let user = match user_db.users_by_email.get(email) { - None => bail!("No user for email address {}", email), - Some(u) => u, + let user = { + let user_db = self.user_db.borrow(); + match user_db.users_by_email.get(email) { + None => bail!("Email {} does not exist", email), + Some(u) => u.clone(), + } }; + tracing::debug!(user=%user.username, "public_login"); let storage: storage::Builder = match &user.config.storage { - StaticStorage::InMemory => storage::in_memory::MemBuilder::new(&user.username), + StaticStorage::InMemory => { + let mut global_storage = self.in_memory_store.lock().await; + global_storage + .entry(user.username.to_string()) + .or_insert(storage::in_memory::MemBuilder::new(&user.username)) + .clone() + }, StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), -- cgit v1.2.3 From a3a9f87d2c1d2f1c01ecba3a00c592e477a6b22b Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 21 Dec 2023 09:32:48 +0100 Subject: avoid infinite loop --- src/login/static_provider.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 2e2e034..76ad6a6 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -35,6 +35,7 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender) Ok(x) => x, Err(e) => { tracing::warn!(path=%config.as_path().to_string_lossy(), error=%e, "Unable to load config"); + stream.recv().await; continue; } }; @@ -49,6 +50,7 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender) for m in u.config.email_addresses.iter() { if users_by_email.contains_key(m) { tracing::warn!("Several users have the same email address: {}", m); + stream.recv().await; continue } users_by_email.insert(m.clone(), u.clone()); -- cgit v1.2.3 From e9aabe8e82e3c3a8190c0224cd1fdf2fc4d2505a Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 21 Dec 2023 15:36:05 +0100 Subject: move storage logic into the storage module --- src/login/static_provider.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) (limited to 'src/login') diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 76ad6a6..b11123c 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -24,7 +24,7 @@ pub struct UserDatabase { pub struct StaticLoginProvider { user_db: watch::Receiver, - in_memory_store: tokio::sync::Mutex>>, + in_memory_store: storage::in_memory::MemDb, } pub async fn update_user_list(config: PathBuf, up: watch::Sender) -> Result<()> { @@ -71,7 +71,7 @@ impl StaticLoginProvider { tokio::spawn(update_user_list(config.user_list, tx)); rx.changed().await?; - Ok(Self { user_db: rx, in_memory_store: tokio::sync::Mutex::new(HashMap::new()) }) + Ok(Self { user_db: rx, in_memory_store: storage::in_memory::MemDb::new() }) } } @@ -94,13 +94,7 @@ impl LoginProvider for StaticLoginProvider { tracing::debug!(user=%username, "fetch keys"); let storage: storage::Builder = match &user.config.storage { - StaticStorage::InMemory => { - let mut global_storage = self.in_memory_store.lock().await; - global_storage - .entry(username.to_string()) - .or_insert(storage::in_memory::MemBuilder::new(username)) - .clone() - }, + StaticStorage::InMemory => self.in_memory_store.builder(username).await, StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), @@ -129,13 +123,7 @@ impl LoginProvider for StaticLoginProvider { tracing::debug!(user=%user.username, "public_login"); let storage: storage::Builder = match &user.config.storage { - StaticStorage::InMemory => { - let mut global_storage = self.in_memory_store.lock().await; - global_storage - .entry(user.username.to_string()) - .or_insert(storage::in_memory::MemBuilder::new(&user.username)) - .clone() - }, + StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await, StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { region: grgconf.aws_region.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(), -- cgit v1.2.3 From e3b11ad1d8249719045329d94f775402aa4ba302 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 21 Dec 2023 16:38:15 +0100 Subject: fix how mem storage is created --- src/login/ldap_provider.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 009605d..a7f56e4 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -20,6 +20,7 @@ pub struct LdapLoginProvider { crypto_root_attr: String, storage_specific: StorageSpecific, + in_memory_store: storage::in_memory::MemDb, } enum BucketSource { @@ -82,14 +83,15 @@ impl LdapLoginProvider { mail_attr: config.mail_attr, crypto_root_attr: config.crypto_root_attr, storage_specific: specific, + in_memory_store: storage::in_memory::MemDb::new(), }) } - fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { + async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { let storage: Builder = match &self.storage_specific { - StorageSpecific::InMemory => storage::in_memory::MemBuilder::new( + StorageSpecific::InMemory => self.in_memory_store.builder( &get_attr(user, &self.username_attr)? - ), + ).await, StorageSpecific::Garage { from_config, bucket_source } => { let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?; let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?; @@ -166,7 +168,7 @@ impl LoginProvider for LdapLoginProvider { let keys = cr.crypto_keys(password)?; // storage - let storage = self.storage_creds_from_ldap_user(&user)?; + let storage = self.storage_creds_from_ldap_user(&user).await?; drop(ldap); @@ -214,7 +216,7 @@ impl LoginProvider for LdapLoginProvider { let public_key = cr.public_key()?; // storage - let storage = self.storage_creds_from_ldap_user(&user)?; + let storage = self.storage_creds_from_ldap_user(&user).await?; drop(ldap); Ok(PublicCredentials { -- cgit v1.2.3 From 4b8b48b48572115b943efdf6356a191871d46a55 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Thu, 21 Dec 2023 20:23:43 +0100 Subject: upgrade argon2, add aws-sdk-s3 --- src/login/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src/login') diff --git a/src/login/mod.rs b/src/login/mod.rs index d331522..3369ac2 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -210,21 +210,18 @@ fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8 // ---- UTIL ---- pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result> { - use argon2::{Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version}; + use argon2::{Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version, password_hash}; - let mut params = ParamsBuilder::new(); - params + let params = ParamsBuilder::new() .output_len(output_len) - .map_err(|e| anyhow!("Invalid output length: {}", e))?; - - let params = params - .params() + .build() .map_err(|e| anyhow!("Invalid argon2 params: {}", e))?; let argon2 = Argon2::new(Algorithm::default(), Version::default(), params); - let salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt); + let b64_salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt); + let valid_salt = password_hash::Salt::from_b64(&b64_salt).map_err(|e| anyhow!("Invalid salt, error {}", e))?; let hash = argon2 - .hash_password(password, &salt) + .hash_password(password, valid_salt) .map_err(|e| anyhow!("Unable to hash: {}", e))?; let hash = hash.hash.ok_or(anyhow!("Missing output"))?; -- cgit v1.2.3 From 7ac24ad913fa081e1bd6f5b042b9da0173dad267 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 27 Dec 2023 14:58:28 +0100 Subject: cargo format --- src/login/ldap_provider.rs | 53 +++++++++++++++++++-------------- src/login/mod.rs | 70 ++++++++++++++++++++++++++------------------ src/login/static_provider.rs | 63 ++++++++++++++++++++++++--------------- 3 files changed, 113 insertions(+), 73 deletions(-) (limited to 'src/login') diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index a7f56e4..81e5879 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -30,7 +30,10 @@ enum BucketSource { enum StorageSpecific { InMemory, - Garage { from_config: LdapGarageConfig, bucket_source: BucketSource }, + Garage { + from_config: LdapGarageConfig, + bucket_source: BucketSource, + }, } impl LdapLoginProvider { @@ -57,22 +60,24 @@ impl LdapLoginProvider { let specific = match config.storage { LdapStorage::InMemory => StorageSpecific::InMemory, LdapStorage::Garage(grgconf) => { - let bucket_source = match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) { - (Some(b), None) => BucketSource::Constant(b), - (None, Some(a)) => BucketSource::Attr(a), - _ => bail!("Must set `bucket` or `bucket_attr`, but not both"), - }; + let bucket_source = + match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) { + (Some(b), None) => BucketSource::Constant(b), + (None, Some(a)) => BucketSource::Attr(a), + _ => bail!("Must set `bucket` or `bucket_attr`, but not both"), + }; if let BucketSource::Attr(a) = &bucket_source { attrs_to_retrieve.push(a.clone()); } - StorageSpecific::Garage { from_config: grgconf, bucket_source } - }, + StorageSpecific::Garage { + from_config: grgconf, + bucket_source, + } + } }; - - Ok(Self { ldap_server: config.ldap_server, pre_bind_on_login: config.pre_bind_on_login, @@ -89,27 +94,32 @@ impl LdapLoginProvider { async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result { let storage: Builder = match &self.storage_specific { - StorageSpecific::InMemory => self.in_memory_store.builder( - &get_attr(user, &self.username_attr)? - ).await, - StorageSpecific::Garage { from_config, bucket_source } => { + StorageSpecific::InMemory => { + self.in_memory_store + .builder(&get_attr(user, &self.username_attr)?) + .await + } + StorageSpecific::Garage { + from_config, + bucket_source, + } => { let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?; - let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?; + let aws_secret_access_key = + get_attr(user, &from_config.aws_secret_access_key_attr)?; let bucket = match bucket_source { BucketSource::Constant(b) => b.clone(), BucketSource::Attr(a) => get_attr(user, &a)?, }; - storage::garage::GarageBuilder::new(storage::garage::GarageConf { - region: from_config.aws_region.clone(), + region: from_config.aws_region.clone(), s3_endpoint: from_config.s3_endpoint.clone(), k2v_endpoint: from_config.k2v_endpoint.clone(), - aws_access_key_id, - aws_secret_access_key, + aws_access_key_id, + aws_secret_access_key, bucket, })? - }, + } }; Ok(storage) @@ -172,7 +182,6 @@ impl LoginProvider for LdapLoginProvider { drop(ldap); - Ok(Credentials { storage, keys }) } @@ -215,7 +224,7 @@ impl LoginProvider for LdapLoginProvider { let cr = CryptoRoot(crstr); let public_key = cr.public_key()?; - // storage + // storage let storage = self.storage_creds_from_ldap_user(&user).await?; drop(ldap); diff --git a/src/login/mod.rs b/src/login/mod.rs index 3369ac2..2926738 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -1,8 +1,8 @@ pub mod ldap_provider; pub mod static_provider; -use std::sync::Arc; use base64::Engine; +use std::sync::Arc; use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; @@ -45,7 +45,7 @@ pub struct PublicCredentials { pub public_key: PublicKey, } -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CryptoRoot(pub String); @@ -73,47 +73,59 @@ impl CryptoRoot { pub fn public_key(&self) -> Result { match self.0.splitn(4, ':').collect::>()[..] { - [ "aero", "cryptoroot", "pass", b64blob ] => { + ["aero", "cryptoroot", "pass", b64blob] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; if blob.len() < 32 { - bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); + bail!( + "Decoded data is {} bytes long, expect at least 32 bytes", + blob.len() + ); } PublicKey::from_slice(&blob[..32]).context("must be a valid public key") - }, - [ "aero", "cryptoroot", "cleartext", b64blob ] => { + } + ["aero", "cryptoroot", "cleartext", b64blob] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; Ok(CryptoKeys::deserialize(&blob)?.public) - }, - [ "aero", "cryptoroot", "incoming", b64blob ] => { + } + ["aero", "cryptoroot", "incoming", b64blob] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; if blob.len() < 32 { - bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len()); + bail!( + "Decoded data is {} bytes long, expect at least 32 bytes", + blob.len() + ); } PublicKey::from_slice(&blob[..32]).context("must be a valid public key") - }, - [ "aero", "cryptoroot", "keyring", _ ] => { + } + ["aero", "cryptoroot", "keyring", _] => { bail!("keyring is not yet implemented!") - }, - _ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)), + } + _ => bail!(format!( + "passed string '{}' is not a valid cryptoroot", + self.0 + )), } } pub fn crypto_keys(&self, password: &str) -> Result { match self.0.splitn(4, ':').collect::>()[..] { - [ "aero", "cryptoroot", "pass", b64blob ] => { + ["aero", "cryptoroot", "pass", b64blob] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; CryptoKeys::password_open(password, &blob) - }, - [ "aero", "cryptoroot", "cleartext", b64blob ] => { + } + ["aero", "cryptoroot", "cleartext", b64blob] => { let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?; CryptoKeys::deserialize(&blob) - }, - [ "aero", "cryptoroot", "incoming", _ ] => { + } + ["aero", "cryptoroot", "incoming", _] => { bail!("incoming cryptoroot does not contain a crypto key!") - }, - [ "aero", "cryptoroot", "keyring", _ ] =>{ + } + ["aero", "cryptoroot", "keyring", _] => { bail!("keyring is not yet implemented!") - }, - _ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)), + } + _ => bail!(format!( + "passed string '{}' is not a valid cryptoroot", + self.0 + )), } } } @@ -132,9 +144,6 @@ pub struct CryptoKeys { // ---- - - - impl CryptoKeys { /// Initialize a new cryptography root pub fn init() -> Self { @@ -202,7 +211,11 @@ fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result { Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap()) } -fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8]) -> Result> { +fn try_open_encrypted_keys( + kdf_salt: &[u8], + password: &str, + encrypted_keys: &[u8], +) -> Result> { let password_key = derive_password_key(kdf_salt, password)?; open(encrypted_keys, &password_key) } @@ -210,7 +223,7 @@ fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8 // ---- UTIL ---- pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result> { - use argon2::{Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version, password_hash}; + use argon2::{password_hash, Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version}; let params = ParamsBuilder::new() .output_len(output_len) @@ -219,7 +232,8 @@ pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result) -> Result<()> { - let mut stream = signal(SignalKind::user_defined1()).expect("failed to install SIGUSR1 signal hander for reload"); + let mut stream = signal(SignalKind::user_defined1()) + .expect("failed to install SIGUSR1 signal hander for reload"); loop { let ulist: UserList = match read_config(config.clone()) { @@ -42,7 +43,12 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender) let users = ulist .into_iter() - .map(|(username, config)| (username.clone() , Arc::new(ContextualUserEntry { username, config }))) + .map(|(username, config)| { + ( + username.clone(), + Arc::new(ContextualUserEntry { username, config }), + ) + }) .collect::>(); let mut users_by_email = HashMap::new(); @@ -51,14 +57,18 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender) if users_by_email.contains_key(m) { tracing::warn!("Several users have the same email address: {}", m); stream.recv().await; - continue + continue; } users_by_email.insert(m.clone(), u.clone()); } } tracing::info!("{} users loaded", users.len()); - up.send(UserDatabase { users, users_by_email }).context("update user db config")?; + up.send(UserDatabase { + users, + users_by_email, + }) + .context("update user db config")?; stream.recv().await; tracing::info!("Received SIGUSR1, reloading"); } @@ -71,7 +81,10 @@ impl StaticLoginProvider { tokio::spawn(update_user_list(config.user_list, tx)); rx.changed().await?; - Ok(Self { user_db: rx, in_memory_store: storage::in_memory::MemDb::new() }) + Ok(Self { + user_db: rx, + in_memory_store: storage::in_memory::MemDb::new(), + }) } } @@ -95,14 +108,16 @@ impl LoginProvider for StaticLoginProvider { tracing::debug!(user=%username, "fetch keys"); let storage: storage::Builder = match &user.config.storage { StaticStorage::InMemory => self.in_memory_store.builder(username).await, - StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { - region: grgconf.aws_region.clone(), - k2v_endpoint: grgconf.k2v_endpoint.clone(), - s3_endpoint: grgconf.s3_endpoint.clone(), - aws_access_key_id: grgconf.aws_access_key_id.clone(), - aws_secret_access_key: grgconf.aws_secret_access_key.clone(), - bucket: grgconf.bucket.clone(), - })?, + StaticStorage::Garage(grgconf) => { + storage::garage::GarageBuilder::new(storage::garage::GarageConf { + region: grgconf.aws_region.clone(), + k2v_endpoint: grgconf.k2v_endpoint.clone(), + s3_endpoint: grgconf.s3_endpoint.clone(), + aws_access_key_id: grgconf.aws_access_key_id.clone(), + aws_secret_access_key: grgconf.aws_secret_access_key.clone(), + bucket: grgconf.bucket.clone(), + })? + } }; let cr = CryptoRoot(user.config.crypto_root.clone()); @@ -124,14 +139,16 @@ impl LoginProvider for StaticLoginProvider { let storage: storage::Builder = match &user.config.storage { StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await, - StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf { - region: grgconf.aws_region.clone(), - k2v_endpoint: grgconf.k2v_endpoint.clone(), - s3_endpoint: grgconf.s3_endpoint.clone(), - aws_access_key_id: grgconf.aws_access_key_id.clone(), - aws_secret_access_key: grgconf.aws_secret_access_key.clone(), - bucket: grgconf.bucket.clone(), - })?, + StaticStorage::Garage(grgconf) => { + storage::garage::GarageBuilder::new(storage::garage::GarageConf { + region: grgconf.aws_region.clone(), + k2v_endpoint: grgconf.k2v_endpoint.clone(), + s3_endpoint: grgconf.s3_endpoint.clone(), + aws_access_key_id: grgconf.aws_access_key_id.clone(), + aws_secret_access_key: grgconf.aws_secret_access_key.clone(), + bucket: grgconf.bucket.clone(), + })? + } }; let cr = CryptoRoot(user.config.crypto_root.clone()); -- cgit v1.2.3