2023-12-29 16:16:41 +00:00
|
|
|
pub mod demo_provider;
|
2022-05-19 10:10:48 +00:00
|
|
|
pub mod ldap_provider;
|
|
|
|
pub mod static_provider;
|
|
|
|
|
2023-12-27 13:58:28 +00:00
|
|
|
use std::sync::Arc;
|
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;
|
2024-03-08 07:17:03 +00:00
|
|
|
use base64::Engine;
|
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-12-18 16:09:44 +00:00
|
|
|
pub storage: Builder,
|
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,
|
|
|
|
}
|
|
|
|
|
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-12-18 16:09:44 +00:00
|
|
|
pub storage: Builder,
|
2022-05-31 13:30:32 +00:00
|
|
|
pub public_key: PublicKey,
|
|
|
|
}
|
|
|
|
|
2023-12-27 13:58:28 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-12-13 15:09:01 +00:00
|
|
|
#[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>>()[..] {
|
2023-12-27 13:58:28 +00:00
|
|
|
["aero", "cryptoroot", "pass", b64blob] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
|
|
|
if blob.len() < 32 {
|
2023-12-27 13:58:28 +00:00
|
|
|
bail!(
|
|
|
|
"Decoded data is {} bytes long, expect at least 32 bytes",
|
|
|
|
blob.len()
|
|
|
|
);
|
2023-12-13 15:09:01 +00:00
|
|
|
}
|
|
|
|
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "cleartext", b64blob] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
|
|
|
Ok(CryptoKeys::deserialize(&blob)?.public)
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "incoming", b64blob] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
|
|
|
if blob.len() < 32 {
|
2023-12-27 13:58:28 +00:00
|
|
|
bail!(
|
|
|
|
"Decoded data is {} bytes long, expect at least 32 bytes",
|
|
|
|
blob.len()
|
|
|
|
);
|
2023-12-13 15:09:01 +00:00
|
|
|
}
|
|
|
|
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "keyring", _] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
bail!("keyring is not yet implemented!")
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
_ => bail!(format!(
|
|
|
|
"passed string '{}' is not a valid cryptoroot",
|
|
|
|
self.0
|
|
|
|
)),
|
2023-12-13 15:09:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn crypto_keys(&self, password: &str) -> Result<CryptoKeys> {
|
|
|
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
2023-12-27 13:58:28 +00:00
|
|
|
["aero", "cryptoroot", "pass", b64blob] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
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-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "cleartext", b64blob] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
|
|
|
CryptoKeys::deserialize(&blob)
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "incoming", _] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
bail!("incoming cryptoroot does not contain a crypto key!")
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
["aero", "cryptoroot", "keyring", _] => {
|
2023-12-13 15:09:01 +00:00
|
|
|
bail!("keyring is not yet implemented!")
|
2023-12-27 13:58:28 +00:00
|
|
|
}
|
|
|
|
_ => bail!(format!(
|
|
|
|
"passed string '{}' is not a valid cryptoroot",
|
|
|
|
self.0
|
|
|
|
)),
|
2023-12-13 15:09:01 +00:00
|
|
|
}
|
|
|
|
}
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----
|
|
|
|
|
|
|
|
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-27 13:58:28 +00:00
|
|
|
fn try_open_encrypted_keys(
|
|
|
|
kdf_salt: &[u8],
|
|
|
|
password: &str,
|
|
|
|
encrypted_keys: &[u8],
|
|
|
|
) -> Result<Vec<u8>> {
|
2023-12-06 19:57:25 +00:00
|
|
|
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>> {
|
2023-12-27 13:58:28 +00:00
|
|
|
use argon2::{password_hash, Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version};
|
2022-05-20 10:49:53 +00:00
|
|
|
|
2023-12-21 19:23:43 +00:00
|
|
|
let params = ParamsBuilder::new()
|
2022-05-20 10:49:53 +00:00
|
|
|
.output_len(output_len)
|
2023-12-21 19:23:43 +00:00
|
|
|
.build()
|
2022-05-20 10:49:53 +00:00
|
|
|
.map_err(|e| anyhow!("Invalid argon2 params: {}", e))?;
|
|
|
|
let argon2 = Argon2::new(Algorithm::default(), Version::default(), params);
|
|
|
|
|
2023-12-21 19:23:43 +00:00
|
|
|
let b64_salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
|
2023-12-27 13:58:28 +00:00
|
|
|
let valid_salt = password_hash::Salt::from_b64(&b64_salt)
|
|
|
|
.map_err(|e| anyhow!("Invalid salt, error {}", e))?;
|
2022-05-20 10:49:53 +00:00
|
|
|
let hash = argon2
|
2023-12-21 19:23:43 +00:00
|
|
|
.hash_password(password, valid_salt)
|
2022-05-20 10:49:53 +00:00
|
|
|
.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())
|
|
|
|
}
|