aerogramme/src/login/mod.rs

244 lines
8.9 KiB
Rust
Raw Normal View History

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: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
/// 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 {
/// 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<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>;
/// 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)]
pub struct Credentials {
/// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
pub storage: Builders,
/// 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)?;
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.
2022-05-19 12:33:49 +00:00
#[derive(Clone, Debug)]
pub struct CryptoKeys {
/// Master key for symmetric encryption of mailbox data
2022-05-19 12:33:49 +00:00
pub master: Key,
/// Public/private keypair for encryption of incomming emails (secret part)
2022-05-19 12:33:49 +00:00
pub secret: SecretKey,
/// 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,
}
}
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> {
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<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())
}
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-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())
}