Some refactoring

This commit is contained in:
Alex 2022-05-19 14:33:49 +02:00
parent 1ac56a9198
commit 6be90936a1
Signed by: lx
GPG key ID: 0E496D15096376BE
9 changed files with 173 additions and 82 deletions

View file

@ -57,18 +57,16 @@ pub struct Bayou<S: BayouState> {
impl<S: BayouState> Bayou<S> { impl<S: BayouState> Bayou<S> {
pub fn new( pub fn new(
k2v_region: &Region,
s3_region: &Region,
creds: &Credentials, creds: &Credentials,
path: String, path: String,
) -> Result<Self> { ) -> Result<Self> {
let k2v_client = creds.k2v_client(k2v_region)?; let k2v_client = creds.k2v_client()?;
let s3_client = creds.s3_client(s3_region)?; let s3_client = creds.s3_client()?;
Ok(Self { Ok(Self {
bucket: creds.bucket.clone(), bucket: creds.bucket().to_string(),
path, path,
key: creds.master_key.clone(), key: creds.keys.master.clone(),
k2v: k2v_client, k2v: k2v_client,
s3: s3_client, s3: s3_client,
checkpoint: (Timestamp::zero(), S::default()), checkpoint: (Timestamp::zero(), S::default()),

View file

@ -25,10 +25,13 @@ pub struct LoginStaticConfig {
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct LoginStaticUser { pub struct LoginStaticUser {
pub password: String, pub password: String,
pub aws_access_key_id: String, pub aws_access_key_id: String,
pub aws_secret_access_key: String, pub aws_secret_access_key: String,
pub bucket: Option<String>, pub bucket: Option<String>,
pub master_key: Option<String>, pub master_key: Option<String>,
pub secret_key: Option<String>,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]

View file

@ -5,17 +5,22 @@ use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zstd::stream::{decode_all as zstd_decode, encode_all as zstd_encode}; use zstd::stream::{decode_all as zstd_decode, encode_all as zstd_encode};
use sodiumoxide::crypto::secretbox::xsalsa20poly1305::{self, gen_nonce, Nonce, NONCEBYTES}; use sodiumoxide::crypto::secretbox::xsalsa20poly1305 as secretbox;
use sodiumoxide::crypto::box_ as publicbox;
pub use sodiumoxide::crypto::secretbox::xsalsa20poly1305::{gen_key, Key, KEYBYTES}; pub use sodiumoxide::crypto::secretbox::xsalsa20poly1305::{gen_key, Key, KEYBYTES};
pub use sodiumoxide::crypto::box_::{gen_keypair, PublicKey, SecretKey, PUBLICKEYBYTES, SECRETKEYBYTES};
pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> { pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> {
use secretbox::{NONCEBYTES, Nonce};
if cryptoblob.len() < NONCEBYTES { if cryptoblob.len() < NONCEBYTES {
return Err(anyhow!("Cyphertext too short")); return Err(anyhow!("Cyphertext too short"));
} }
// Decrypt -> get Zstd data // Decrypt -> get Zstd data
let nonce = Nonce::from_slice(&cryptoblob[..NONCEBYTES]).unwrap(); let nonce = Nonce::from_slice(&cryptoblob[..NONCEBYTES]).unwrap();
let zstdblob = xsalsa20poly1305::open(&cryptoblob[NONCEBYTES..], &nonce, key) let zstdblob = secretbox::open(&cryptoblob[NONCEBYTES..], &nonce, key)
.map_err(|_| anyhow!("Could not decrypt blob"))?; .map_err(|_| anyhow!("Could not decrypt blob"))?;
// Decompress zstd data // Decompress zstd data
@ -26,13 +31,15 @@ pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> {
} }
pub fn seal(plainblob: &[u8], key: &Key) -> Result<Vec<u8>> { pub fn seal(plainblob: &[u8], key: &Key) -> Result<Vec<u8>> {
use secretbox::{NONCEBYTES, gen_nonce};
// Compress data using zstd // Compress data using zstd
let mut reader = &plainblob[..]; let mut reader = &plainblob[..];
let zstdblob = zstd_encode(&mut reader, 0)?; let zstdblob = zstd_encode(&mut reader, 0)?;
// Encrypt // Encrypt
let nonce = gen_nonce(); let nonce = gen_nonce();
let cryptoblob = xsalsa20poly1305::seal(&zstdblob, &nonce, key); let cryptoblob = secretbox::seal(&zstdblob, &nonce, key);
let mut res = Vec::with_capacity(NONCEBYTES + cryptoblob.len()); let mut res = Vec::with_capacity(NONCEBYTES + cryptoblob.len());
res.extend(nonce.as_ref()); res.extend(nonce.as_ref());

View file

@ -1,5 +1,6 @@
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use rusoto_signature::Region;
use crate::config::*; use crate::config::*;
use crate::login::*; use crate::login::*;
@ -9,7 +10,7 @@ pub struct LdapLoginProvider {
} }
impl LdapLoginProvider { impl LdapLoginProvider {
pub fn new(_config: LoginLdapConfig) -> Result<Self> { pub fn new(_config: LoginLdapConfig, _k2v_region: Region, _s3_region: Region) -> Result<Self> {
unimplemented!() unimplemented!()
} }
} }

View file

@ -9,7 +9,7 @@ use rusoto_credential::{AwsCredentials, StaticProvider};
use rusoto_s3::S3Client; use rusoto_s3::S3Client;
use rusoto_signature::Region; use rusoto_signature::Region;
use crate::cryptoblob::Key as SymmetricKey; use crate::cryptoblob::*;
#[async_trait] #[async_trait]
pub trait LoginProvider { pub trait LoginProvider {
@ -18,14 +18,51 @@ pub trait LoginProvider {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Credentials { pub struct Credentials {
pub storage: StorageCredentials,
pub keys: CryptoKeys,
}
#[derive(Clone, Debug)]
pub struct StorageCredentials {
pub s3_region: Region,
pub k2v_region: Region,
pub aws_access_key_id: String, pub aws_access_key_id: String,
pub aws_secret_access_key: String, pub aws_secret_access_key: String,
pub bucket: String, pub bucket: String,
pub master_key: SymmetricKey,
} }
#[derive(Clone, Debug)]
pub struct CryptoKeys {
// Master key for symmetric encryption of mailbox data
pub master: Key,
// Public/private keypair for encryption of incomming emails
pub secret: SecretKey,
pub public: PublicKey,
}
// ----
impl Credentials { impl Credentials {
pub fn k2v_client(&self, k2v_region: &Region) -> Result<K2vClient> { pub fn k2v_client(&self) -> Result<K2vClient> {
self.storage.k2v_client()
}
pub fn s3_client(&self) -> Result<S3Client> {
self.storage.s3_client()
}
pub fn bucket(&self) -> &str {
self.storage.bucket.as_str()
}
pub fn dump_config(&self) {
println!("aws_access_key_id = \"{}\"", self.storage.aws_access_key_id);
println!("aws_secret_access_key = \"{}\"", self.storage.aws_secret_access_key);
println!("master_key = \"{}\"", base64::encode(&self.keys.master));
println!("secret_key = \"{}\"", base64::encode(&self.keys.secret));
}
}
impl StorageCredentials {
pub fn k2v_client(&self) -> Result<K2vClient> {
let aws_creds = AwsCredentials::new( let aws_creds = AwsCredentials::new(
self.aws_access_key_id.clone(), self.aws_access_key_id.clone(),
self.aws_secret_access_key.clone(), self.aws_secret_access_key.clone(),
@ -34,14 +71,14 @@ impl Credentials {
); );
Ok(K2vClient::new( Ok(K2vClient::new(
k2v_region.clone(), self.k2v_region.clone(),
self.bucket.clone(), self.bucket.clone(),
aws_creds, aws_creds,
None, None,
)?) )?)
} }
pub fn s3_client(&self, s3_region: &Region) -> Result<S3Client> { pub fn s3_client(&self) -> Result<S3Client> {
let aws_creds_provider = StaticProvider::new_minimal( let aws_creds_provider = StaticProvider::new_minimal(
self.aws_access_key_id.clone(), self.aws_access_key_id.clone(),
self.aws_secret_access_key.clone(), self.aws_secret_access_key.clone(),
@ -50,7 +87,34 @@ impl Credentials {
Ok(S3Client::new_with( Ok(S3Client::new_with(
HttpClient::new()?, HttpClient::new()?,
aws_creds_provider, aws_creds_provider,
s3_region.clone(), self.s3_region.clone(),
)) ))
} }
} }
impl CryptoKeys {
pub fn init(storage: &StorageCredentials) -> Result<Self> {
unimplemented!()
}
pub fn init_without_password(storage: &StorageCredentials, master_key: &Key, secret_key: &SecretKey) -> Result<Self> {
unimplemented!()
}
pub fn open(storage: &StorageCredentials, password: &str) -> Result<Self> {
unimplemented!()
}
pub fn open_without_password(storage: &StorageCredentials, master_key: &Key, secret_key: &SecretKey) -> Result<Self> {
unimplemented!()
}
pub fn add_password(&self, storage: &StorageCredentials, password: &str) -> Result<()> {
unimplemented!()
}
pub fn remove_password(&self, storage: &StorageCredentials, password: &str, allow_remove_all: bool) -> Result<()> {
unimplemented!()
}
}

View file

@ -5,21 +5,23 @@ use async_trait::async_trait;
use rusoto_signature::Region; use rusoto_signature::Region;
use crate::config::*; use crate::config::*;
use crate::cryptoblob::Key; use crate::cryptoblob::{Key, SecretKey};
use crate::login::*; use crate::login::*;
pub struct StaticLoginProvider { pub struct StaticLoginProvider {
default_bucket: Option<String>, default_bucket: Option<String>,
users: HashMap<String, LoginStaticUser>, users: HashMap<String, LoginStaticUser>,
k2v_region: Region, k2v_region: Region,
s3_region: Region,
} }
impl StaticLoginProvider { impl StaticLoginProvider {
pub fn new(config: LoginStaticConfig, k2v_region: Region) -> Result<Self> { pub fn new(config: LoginStaticConfig, k2v_region: Region, s3_region: Region) -> Result<Self> {
Ok(Self { Ok(Self {
default_bucket: config.default_bucket, default_bucket: config.default_bucket,
users: config.users, users: config.users,
k2v_region, k2v_region,
s3_region,
}) })
} }
} }
@ -42,18 +44,31 @@ impl LoginProvider for StaticLoginProvider {
"No bucket configured and no default bucket specieid" "No bucket configured and no default bucket specieid"
))?; ))?;
// TODO if master key is not specified, retrieve it from K2V key storage let storage = StorageCredentials {
let master_key_str = u.master_key.as_ref().ok_or(anyhow!( k2v_region: self.k2v_region.clone(),
"Master key must be specified in config file for now, this will change" s3_region: self.s3_region.clone(),
))?;
let master_key = Key::from_slice(&base64::decode(master_key_str)?)
.ok_or(anyhow!("Invalid master key"))?;
Ok(Credentials {
aws_access_key_id: u.aws_access_key_id.clone(), aws_access_key_id: u.aws_access_key_id.clone(),
aws_secret_access_key: u.aws_secret_access_key.clone(), aws_secret_access_key: u.aws_secret_access_key.clone(),
bucket, bucket,
master_key, };
let keys = match (&u.master_key, &u.secret_key) {
(Some(m), Some(s)) => {
let master_key = Key::from_slice(&base64::decode(m)?)
.ok_or(anyhow!("Invalid master key"))?;
let secret_key = SecretKey::from_slice(&base64::decode(m)?)
.ok_or(anyhow!("Invalid secret key"))?;
CryptoKeys::open_without_password(&storage, &master_key, &secret_key)?
}
(None, None) => {
CryptoKeys::open(&storage, password)?
}
_ => bail!("Either both master and secret key or none of them must be specified for user"),
};
Ok(Credentials {
storage,
keys,
}) })
} }
} }

View file

@ -22,19 +22,17 @@ pub struct Mailbox {
impl Mailbox { impl Mailbox {
pub async fn new( pub async fn new(
k2v_region: &Region,
s3_region: &Region,
creds: &Credentials, creds: &Credentials,
name: String, name: String,
) -> Result<Self> { ) -> Result<Self> {
let uid_index = Bayou::<UidIndex>::new(k2v_region, s3_region, creds, name.clone())?; let uid_index = Bayou::<UidIndex>::new(creds, name.clone())?;
Ok(Self { Ok(Self {
bucket: creds.bucket.clone(), bucket: creds.bucket().to_string(),
name, name,
key: creds.master_key.clone(), key: creds.keys.master.clone(),
k2v: creds.k2v_client(&k2v_region)?, k2v: creds.k2v_client()?,
s3: creds.s3_client(&s3_region)?, s3: creds.s3_client()?,
uid_index, uid_index,
}) })
} }

View file

@ -5,6 +5,7 @@ mod login;
mod mailbox; mod mailbox;
mod time; mod time;
mod uidindex; mod uidindex;
mod server;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::sync::Arc; use std::sync::Arc;
@ -14,6 +15,7 @@ use rusoto_signature::Region;
use config::*; use config::*;
use login::{ldap_provider::*, static_provider::*, *}; use login::{ldap_provider::*, static_provider::*, *};
use mailbox::Mailbox; use mailbox::Mailbox;
use server::Server;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -26,53 +28,7 @@ async fn main() {
async fn main2() -> Result<()> { async fn main2() -> Result<()> {
let config = read_config("mailrage.toml".into())?; let config = read_config("mailrage.toml".into())?;
let main = Main::new(config)?; let server = Server::new(config)?;
main.run().await server.run().await
} }
struct Main {
pub s3_region: Region,
pub k2v_region: Region,
pub login_provider: Box<dyn LoginProvider>,
}
impl Main {
fn new(config: Config) -> Result<Arc<Self>> {
let s3_region = Region::Custom {
name: config.s3_region,
endpoint: config.s3_endpoint,
};
let k2v_region = Region::Custom {
name: config.k2v_region,
endpoint: config.k2v_endpoint,
};
let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
{
(Some(st), None) => Box::new(StaticLoginProvider::new(st, k2v_region.clone())?),
(None, Some(ld)) => Box::new(LdapLoginProvider::new(ld)?),
(Some(_), Some(_)) => bail!("A single login provider must be set up in config file"),
(None, None) => bail!("No login provider is set up in config file"),
};
Ok(Arc::new(Self {
s3_region,
k2v_region,
login_provider,
}))
}
async fn run(self: &Arc<Self>) -> Result<()> {
let creds = self.login_provider.login("lx", "plop").await?;
let mut mailbox = Mailbox::new(
&self.k2v_region,
&self.s3_region,
&creds,
"TestMailbox".to_string(),
)
.await?;
mailbox.test().await?;
Ok(())
}
}

49
src/server.rs Normal file
View file

@ -0,0 +1,49 @@
use anyhow::{bail, Result};
use std::sync::Arc;
use rusoto_signature::Region;
use crate::config::*;
use crate::login::{ldap_provider::*, static_provider::*, *};
use crate::mailbox::Mailbox;
pub struct Server {
pub login_provider: Box<dyn LoginProvider>,
}
impl Server {
pub fn new(config: Config) -> Result<Arc<Self>> {
let s3_region = Region::Custom {
name: config.s3_region,
endpoint: config.s3_endpoint,
};
let k2v_region = Region::Custom {
name: config.k2v_region,
endpoint: config.k2v_endpoint,
};
let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
{
(Some(st), None) => Box::new(StaticLoginProvider::new(st, k2v_region, s3_region)?),
(None, Some(ld)) => Box::new(LdapLoginProvider::new(ld, k2v_region, s3_region)?),
(Some(_), Some(_)) => bail!("A single login provider must be set up in config file"),
(None, None) => bail!("No login provider is set up in config file"),
};
Ok(Arc::new(Self {
login_provider,
}))
}
pub async fn run(self: &Arc<Self>) -> Result<()> {
let creds = self.login_provider.login("lx", "plop").await?;
let mut mailbox = Mailbox::new(
&creds,
"TestMailbox".to_string(),
)
.await?;
mailbox.test().await?;
Ok(())
}
}