diff options
-rw-r--r-- | src/bayou.rs | 10 | ||||
-rw-r--r-- | src/config.rs | 3 | ||||
-rw-r--r-- | src/cryptoblob.rs | 13 | ||||
-rw-r--r-- | src/login/ldap_provider.rs | 3 | ||||
-rw-r--r-- | src/login/mod.rs | 76 | ||||
-rw-r--r-- | src/login/static_provider.rs | 37 | ||||
-rw-r--r-- | src/mailbox.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 52 | ||||
-rw-r--r-- | src/server.rs | 49 |
9 files changed, 173 insertions, 82 deletions
diff --git a/src/bayou.rs b/src/bayou.rs index 97281cd..acca1e5 100644 --- a/src/bayou.rs +++ b/src/bayou.rs @@ -57,18 +57,16 @@ pub struct Bayou<S: BayouState> { impl<S: BayouState> Bayou<S> { pub fn new( - k2v_region: &Region, - s3_region: &Region, creds: &Credentials, path: String, ) -> Result<Self> { - let k2v_client = creds.k2v_client(k2v_region)?; - let s3_client = creds.s3_client(s3_region)?; + let k2v_client = creds.k2v_client()?; + let s3_client = creds.s3_client()?; Ok(Self { - bucket: creds.bucket.clone(), + bucket: creds.bucket().to_string(), path, - key: creds.master_key.clone(), + key: creds.keys.master.clone(), k2v: k2v_client, s3: s3_client, checkpoint: (Timestamp::zero(), S::default()), diff --git a/src/config.rs b/src/config.rs index 47db90d..d756d6e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,10 +25,13 @@ pub struct LoginStaticConfig { #[derive(Deserialize, Debug, Clone)] pub struct LoginStaticUser { pub password: String, + pub aws_access_key_id: String, pub aws_secret_access_key: String, pub bucket: Option<String>, + pub master_key: Option<String>, + pub secret_key: Option<String>, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/cryptoblob.rs b/src/cryptoblob.rs index 6e51dbb..ad05521 100644 --- a/src/cryptoblob.rs +++ b/src/cryptoblob.rs @@ -5,17 +5,22 @@ use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; 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::box_::{gen_keypair, PublicKey, SecretKey, PUBLICKEYBYTES, SECRETKEYBYTES}; pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> { + use secretbox::{NONCEBYTES, Nonce}; + if cryptoblob.len() < NONCEBYTES { return Err(anyhow!("Cyphertext too short")); } // Decrypt -> get Zstd data 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"))?; // 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>> { + use secretbox::{NONCEBYTES, gen_nonce}; + // Compress data using zstd let mut reader = &plainblob[..]; let zstdblob = zstd_encode(&mut reader, 0)?; // Encrypt 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()); res.extend(nonce.as_ref()); diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs index 6108e6e..54ddbd5 100644 --- a/src/login/ldap_provider.rs +++ b/src/login/ldap_provider.rs @@ -1,5 +1,6 @@ use anyhow::Result; use async_trait::async_trait; +use rusoto_signature::Region; use crate::config::*; use crate::login::*; @@ -9,7 +10,7 @@ pub struct LdapLoginProvider { } impl LdapLoginProvider { - pub fn new(_config: LoginLdapConfig) -> Result<Self> { + pub fn new(_config: LoginLdapConfig, _k2v_region: Region, _s3_region: Region) -> Result<Self> { unimplemented!() } } diff --git a/src/login/mod.rs b/src/login/mod.rs index 0845371..4022962 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -9,7 +9,7 @@ use rusoto_credential::{AwsCredentials, StaticProvider}; use rusoto_s3::S3Client; use rusoto_signature::Region; -use crate::cryptoblob::Key as SymmetricKey; +use crate::cryptoblob::*; #[async_trait] pub trait LoginProvider { @@ -18,14 +18,51 @@ pub trait LoginProvider { #[derive(Clone, Debug)] 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_secret_access_key: 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 { - 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( self.aws_access_key_id.clone(), self.aws_secret_access_key.clone(), @@ -34,14 +71,14 @@ impl Credentials { ); Ok(K2vClient::new( - k2v_region.clone(), + self.k2v_region.clone(), self.bucket.clone(), aws_creds, 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( self.aws_access_key_id.clone(), self.aws_secret_access_key.clone(), @@ -50,7 +87,34 @@ impl Credentials { Ok(S3Client::new_with( HttpClient::new()?, 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!() + } +} + diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs index 037948a..d7d791a 100644 --- a/src/login/static_provider.rs +++ b/src/login/static_provider.rs @@ -5,21 +5,23 @@ use async_trait::async_trait; use rusoto_signature::Region; use crate::config::*; -use crate::cryptoblob::Key; +use crate::cryptoblob::{Key, SecretKey}; use crate::login::*; pub struct StaticLoginProvider { default_bucket: Option<String>, users: HashMap<String, LoginStaticUser>, k2v_region: Region, + s3_region: Region, } 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 { default_bucket: config.default_bucket, users: config.users, k2v_region, + s3_region, }) } } @@ -42,18 +44,31 @@ impl LoginProvider for StaticLoginProvider { "No bucket configured and no default bucket specieid" ))?; - // TODO if master key is not specified, retrieve it from K2V key storage - let master_key_str = u.master_key.as_ref().ok_or(anyhow!( - "Master key must be specified in config file for now, this will change" - ))?; - let master_key = Key::from_slice(&base64::decode(master_key_str)?) - .ok_or(anyhow!("Invalid master key"))?; - - Ok(Credentials { + let storage = StorageCredentials { + k2v_region: self.k2v_region.clone(), + s3_region: self.s3_region.clone(), aws_access_key_id: u.aws_access_key_id.clone(), aws_secret_access_key: u.aws_secret_access_key.clone(), 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, }) } } diff --git a/src/mailbox.rs b/src/mailbox.rs index d997691..44b9f95 100644 --- a/src/mailbox.rs +++ b/src/mailbox.rs @@ -22,19 +22,17 @@ pub struct Mailbox { impl Mailbox { pub async fn new( - k2v_region: &Region, - s3_region: &Region, creds: &Credentials, name: String, ) -> 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 { - bucket: creds.bucket.clone(), + bucket: creds.bucket().to_string(), name, - key: creds.master_key.clone(), - k2v: creds.k2v_client(&k2v_region)?, - s3: creds.s3_client(&s3_region)?, + key: creds.keys.master.clone(), + k2v: creds.k2v_client()?, + s3: creds.s3_client()?, uid_index, }) } diff --git a/src/main.rs b/src/main.rs index 40dfbb8..ca66137 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod login; mod mailbox; mod time; mod uidindex; +mod server; use anyhow::{bail, Result}; use std::sync::Arc; @@ -14,6 +15,7 @@ use rusoto_signature::Region; use config::*; use login::{ldap_provider::*, static_provider::*, *}; use mailbox::Mailbox; +use server::Server; #[tokio::main] async fn main() { @@ -26,53 +28,7 @@ async fn main() { async fn main2() -> Result<()> { let config = read_config("mailrage.toml".into())?; - let main = Main::new(config)?; - main.run().await + let server = Server::new(config)?; + 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(()) - } -} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..4c628d6 --- /dev/null +++ b/src/server.rs @@ -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(()) + } +} |