2022-05-19 10:10:48 +00:00
|
|
|
pub mod ldap_provider;
|
|
|
|
pub mod static_provider;
|
|
|
|
|
2022-06-17 16:39:36 +00:00
|
|
|
use std::sync::Arc;
|
2023-12-13 15:09:01 +00:00
|
|
|
use base64::Engine;
|
2022-05-20 11:05:44 +00:00
|
|
|
|
2022-05-20 11:36:45 +00:00
|
|
|
use anyhow::{anyhow, bail, Context, Result};
|
2022-05-19 10:10:48 +00:00
|
|
|
use async_trait::async_trait;
|
2022-05-20 10:49:53 +00:00
|
|
|
use rand::prelude::*;
|
2022-05-19 10:10:48 +00:00
|
|
|
|
2022-05-19 12:33:49 +00:00
|
|
|
use crate::cryptoblob::*;
|
2023-11-01 14:36:06 +00:00
|
|
|
use crate::storage::*;
|
2022-05-19 10:10:48 +00:00
|
|
|
|
2022-05-23 15:31:53 +00:00
|
|
|
/// The trait LoginProvider defines the interface for a login provider that allows
|
|
|
|
/// to retrieve storage and cryptographic credentials for access to a user account
|
|
|
|
/// from their username and password.
|
2022-05-19 11:54:38 +00:00
|
|
|
#[async_trait]
|
|
|
|
pub trait LoginProvider {
|
2022-05-23 15:31:53 +00:00
|
|
|
/// The login method takes an account's password as an input to decypher
|
|
|
|
/// decryption keys and obtain full access to the user's account.
|
2023-11-01 16:18:58 +00:00
|
|
|
async fn login(&self, username: &str, password: &str) -> Result<Credentials>;
|
2022-05-31 13:30:32 +00:00
|
|
|
/// 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<PublicCredentials>;
|
2022-05-19 11:54:38 +00:00
|
|
|
}
|
|
|
|
|
2022-06-17 16:39:36 +00:00
|
|
|
/// ArcLoginProvider is simply an alias on a structure that is used
|
|
|
|
/// in many places in the code
|
|
|
|
pub type ArcLoginProvider = Arc<dyn LoginProvider + Send + Sync>;
|
|
|
|
|
2022-05-23 15:31:53 +00:00
|
|
|
/// The struct Credentials represent all of the necessary information to interact
|
|
|
|
/// with a user account's data after they are logged in.
|
2022-05-19 10:10:48 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2023-11-01 16:18:58 +00:00
|
|
|
pub struct Credentials {
|
2022-05-23 15:31:53 +00:00
|
|
|
/// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
|
2023-11-02 11:18:43 +00:00
|
|
|
pub storage: Builders,
|
2022-05-23 15:31:53 +00:00
|
|
|
/// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V
|
2022-05-19 12:33:49 +00:00
|
|
|
pub keys: CryptoKeys,
|
|
|
|
}
|
2023-12-13 15:09:01 +00:00
|
|
|
impl Credentials {
|
|
|
|
pub fn row_client(&self) -> Result<RowStore> {
|
|
|
|
Ok(self.storage.row_store()?)
|
|
|
|
}
|
|
|
|
pub fn blob_client(&self) -> Result<BlobStore> {
|
|
|
|
Ok(self.storage.blob_store()?)
|
|
|
|
}
|
|
|
|
}
|
2022-05-19 12:33:49 +00:00
|
|
|
|
2022-05-31 13:30:32 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct PublicCredentials {
|
|
|
|
/// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
|
2023-11-17 11:15:44 +00:00
|
|
|
pub storage: Builders,
|
2022-05-31 13:30:32 +00:00
|
|
|
pub public_key: PublicKey,
|
|
|
|
}
|
|
|
|
|
2023-12-13 15:09:01 +00:00
|
|
|
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<Self> {
|
|
|
|
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<PublicKey> {
|
|
|
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
|
|
|
[ "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<CryptoKeys> {
|
|
|
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
|
|
|
[ "aero", "cryptoroot", "pass", b64blob ] => {
|
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
2023-12-13 17:04:04 +00:00
|
|
|
CryptoKeys::password_open(password, &blob)
|
2023-12-13 15:09:01 +00:00
|
|
|
},
|
|
|
|
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
|
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
|
|
|
CryptoKeys::deserialize(&blob)
|
|
|
|
},
|
2023-12-13 17:04:04 +00:00
|
|
|
[ "aero", "cryptoroot", "incoming", _ ] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
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)),
|
|
|
|
}
|
|
|
|
}
|
2022-05-23 15:31:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The struct CryptoKeys contains the cryptographic keys used to encrypt and decrypt
|
|
|
|
/// data in a user's mailbox.
|
2022-05-19 12:33:49 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct CryptoKeys {
|
2022-05-23 15:31:53 +00:00
|
|
|
/// Master key for symmetric encryption of mailbox data
|
2022-05-19 12:33:49 +00:00
|
|
|
pub master: Key,
|
2022-05-23 15:31:53 +00:00
|
|
|
/// Public/private keypair for encryption of incomming emails (secret part)
|
2022-05-19 12:33:49 +00:00
|
|
|
pub secret: SecretKey,
|
2022-05-23 15:31:53 +00:00
|
|
|
/// Public/private keypair for encryption of incomming emails (public part)
|
2022-05-19 12:33:49 +00:00
|
|
|
pub public: PublicKey,
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----
|
|
|
|
|
2023-11-01 15:45:29 +00:00
|
|
|
|
2023-12-13 15:09:01 +00:00
|
|
|
|
2022-05-19 12:33:49 +00:00
|
|
|
|
|
|
|
impl CryptoKeys {
|
2023-12-13 15:09:01 +00:00
|
|
|
/// Initialize a new cryptography root
|
|
|
|
pub fn init() -> Self {
|
2022-05-20 10:49:53 +00:00
|
|
|
let (public, secret) = gen_keypair();
|
|
|
|
let master = gen_key();
|
2023-12-13 15:09:01 +00:00
|
|
|
CryptoKeys {
|
2022-05-20 10:49:53 +00:00
|
|
|
master,
|
|
|
|
secret,
|
|
|
|
public,
|
|
|
|
}
|
2022-05-20 11:05:44 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 15:09:01 +00:00
|
|
|
// Clear text serialize/deserialize
|
|
|
|
/// Serialize the root as bytes without encryption
|
2022-05-20 10:49:53 +00:00
|
|
|
fn serialize(&self) -> [u8; 64] {
|
|
|
|
let mut res = [0u8; 64];
|
|
|
|
res[..32].copy_from_slice(self.master.as_ref());
|
|
|
|
res[32..].copy_from_slice(self.secret.as_ref());
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
2023-12-13 15:09:01 +00:00
|
|
|
/// Deserialize a clear text crypto root without encryption
|
2022-05-20 10:49:53 +00:00
|
|
|
fn deserialize(bytes: &[u8]) -> Result<Self> {
|
|
|
|
if bytes.len() != 64 {
|
|
|
|
bail!("Invalid length: {}, expected 64", bytes.len());
|
|
|
|
}
|
|
|
|
let master = Key::from_slice(&bytes[..32]).unwrap();
|
|
|
|
let secret = SecretKey::from_slice(&bytes[32..]).unwrap();
|
|
|
|
let public = secret.public_key();
|
|
|
|
Ok(Self {
|
|
|
|
master,
|
|
|
|
secret,
|
|
|
|
public,
|
|
|
|
})
|
|
|
|
}
|
2023-12-13 15:09:01 +00:00
|
|
|
|
|
|
|
// Password sealed keys serialize/deserialize
|
|
|
|
pub fn password_open(password: &str, blob: &[u8]) -> Result<Self> {
|
2023-12-13 17:04:04 +00:00
|
|
|
let _pubkey = &blob[0..32];
|
|
|
|
let kdf_salt = &blob[32..64];
|
|
|
|
let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[64..])?;
|
2023-12-13 15:09:01 +00:00
|
|
|
|
|
|
|
let keys = Self::deserialize(&password_openned)?;
|
|
|
|
Ok(keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn password_seal(&self, password: &str) -> Result<Vec<u8>> {
|
|
|
|
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)
|
|
|
|
}
|
2022-05-20 10:49:53 +00:00
|
|
|
}
|
|
|
|
|
2023-12-06 19:57:25 +00:00
|
|
|
fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result<Key> {
|
|
|
|
Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap())
|
|
|
|
}
|
2022-05-23 15:31:53 +00:00
|
|
|
|
2023-12-06 19:57:25 +00:00
|
|
|
fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8]) -> Result<Vec<u8>> {
|
|
|
|
let password_key = derive_password_key(kdf_salt, password)?;
|
|
|
|
open(encrypted_keys, &password_key)
|
2022-05-23 15:31:53 +00:00
|
|
|
}
|
|
|
|
|
2022-05-20 10:49:53 +00:00
|
|
|
// ---- UTIL ----
|
|
|
|
|
|
|
|
pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec<u8>> {
|
|
|
|
use argon2::{Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version};
|
|
|
|
|
|
|
|
let mut params = ParamsBuilder::new();
|
|
|
|
params
|
|
|
|
.output_len(output_len)
|
|
|
|
.map_err(|e| anyhow!("Invalid output length: {}", e))?;
|
|
|
|
|
|
|
|
let params = params
|
|
|
|
.params()
|
|
|
|
.map_err(|e| anyhow!("Invalid argon2 params: {}", e))?;
|
|
|
|
let argon2 = Argon2::new(Algorithm::default(), Version::default(), params);
|
|
|
|
|
2023-12-13 15:09:01 +00:00
|
|
|
let salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
|
2022-05-20 10:49:53 +00:00
|
|
|
let hash = argon2
|
|
|
|
.hash_password(password, &salt)
|
|
|
|
.map_err(|e| anyhow!("Unable to hash: {}", e))?;
|
|
|
|
|
|
|
|
let hash = hash.hash.ok_or(anyhow!("Missing output"))?;
|
|
|
|
assert!(hash.len() == output_len);
|
|
|
|
Ok(hash.as_bytes().to_vec())
|
|
|
|
}
|