aerogramme/src/login/mod.rs

464 lines
16 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-11-21 14:09:39 +00:00
use futures::try_join;
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,
}
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-06 19:57:25 +00:00
/*
/// The struct UserSecrets represents intermediary secrets that are mixed in with the user's
/// password when decrypting the cryptographic keys that are stored in their bucket.
/// These secrets should be stored somewhere else (e.g. in the LDAP server or in the
/// local config file), as an additionnal authentification factor so that the password
/// isn't enough just alone to decrypt the content of a user's bucket.
pub struct UserSecrets {
/// The main user secret that will be used to encrypt keys when a new password is added
pub user_secret: String,
/// Alternative user secrets that will be tried when decrypting keys that were encrypted
/// with old passwords
pub alternate_user_secrets: Vec<String>,
}
2023-12-06 19:57:25 +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 {
/// 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
2022-05-19 11:54:38 +00:00
impl Credentials {
pub fn row_client(&self) -> Result<RowStore> {
Ok(self.storage.row_store()?)
2022-05-19 12:33:49 +00:00
}
pub fn blob_client(&self) -> Result<BlobStore> {
Ok(self.storage.blob_store()?)
2022-05-19 12:33:49 +00:00
}
}
impl CryptoKeys {
pub async fn init(
storage: &Builders,
password: &str,
) -> Result<Self> {
2022-05-20 10:49:53 +00:00
// Check that salt and public don't exist already
let k2v = storage.row_store()?;
2022-05-20 19:35:50 +00:00
let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?;
2022-05-20 10:49:53 +00:00
// Generate salt for password identifiers
let mut ident_salt = [0u8; 32];
thread_rng().fill(&mut ident_salt);
// Generate (public, private) key pair and master key
let (public, secret) = gen_keypair();
let master = gen_key();
let keys = CryptoKeys {
master,
secret,
public,
};
// Generate short password digest (= password identity)
let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?;
// Generate salt for KDF
let mut kdf_salt = [0u8; 32];
thread_rng().fill(&mut kdf_salt);
// Calculate key for password secret box
2023-12-06 19:57:25 +00:00
let password_key = derive_password_key(&kdf_salt, password)?;
2022-05-20 10:49:53 +00:00
// Seal a secret box that contains our crypto keys
let password_sealed = seal(&keys.serialize(), &password_key)?;
let password_sortkey = format!("password:{}", hex::encode(&ident));
let password_blob = [&kdf_salt[..], &password_sealed].concat();
// Write values to storage
2023-11-21 14:09:39 +00:00
// @FIXME Implement insert batch in the storage API
let (salt, public, passwd) = (
salt_ct.set_value(&ident_salt),
public_ct.set_value(keys.public.as_ref()),
k2v.row("keys", &password_sortkey).set_value(&password_blob)
);
try_join!(salt.push(), public.push(), passwd.push())
.context("InsertBatch for salt, public, and password")?;
2022-05-20 10:49:53 +00:00
Ok(keys)
2022-05-19 12:33:49 +00:00
}
2022-05-19 13:14:36 +00:00
pub async fn init_without_password(
storage: &Builders,
2022-05-20 10:49:53 +00:00
master: &Key,
secret: &SecretKey,
2022-05-19 13:14:36 +00:00
) -> Result<Self> {
2022-05-20 10:49:53 +00:00
// Check that salt and public don't exist already
let k2v = storage.row_store()?;
2022-05-20 19:35:50 +00:00
let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?;
2022-05-20 10:49:53 +00:00
// Generate salt for password identifiers
let mut ident_salt = [0u8; 32];
thread_rng().fill(&mut ident_salt);
// Create CryptoKeys struct from given keys
let public = secret.public_key();
let keys = CryptoKeys {
master: master.clone(),
secret: secret.clone(),
public,
};
// Write values to storage
2023-11-21 14:09:39 +00:00
// @FIXME implement insert batch in the storage API
let (salt, public) = (
salt_ct.set_value(&ident_salt),
public_ct.set_value(keys.public.as_ref()),
);
try_join!(salt.push(), public.push()).context("InsertBatch for salt and public")?;
2022-05-20 10:49:53 +00:00
Ok(keys)
2022-05-19 12:33:49 +00:00
}
pub async fn open(
password: &str,
2023-12-12 08:17:59 +00:00
root_blob: &str,
) -> Result<Self> {
2023-12-12 08:17:59 +00:00
let kdf_salt = &password_blob[..32];
let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?;
let keys = Self::deserialize(&password_openned)?;
if keys.public != expected_public {
bail!("Password public key doesn't match stored public key");
}
Ok(keys)
/*
2023-11-21 14:09:39 +00:00
let k2v = storage.row_store()?;
2022-05-20 10:49:53 +00:00
let (ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?;
// Generate short password digest (= password identity)
let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?;
// Lookup password blob
let password_sortkey = format!("password:{}", hex::encode(&ident));
2023-11-21 14:09:39 +00:00
let password_ref = k2v.row("keys", &password_sortkey);
2022-05-20 10:49:53 +00:00
let password_blob = {
2023-11-21 14:09:39 +00:00
let val = match password_ref.fetch().await {
Err(StorageError::NotFound) => {
2022-05-20 19:35:50 +00:00
bail!("invalid password")
2022-05-20 19:02:53 +00:00
}
x => x?,
};
2023-11-21 14:09:39 +00:00
if val.content().len() != 1 {
2022-05-20 19:02:53 +00:00
bail!("multiple values for password in storage");
2022-05-20 10:49:53 +00:00
}
2023-11-21 14:09:39 +00:00
match val.content().pop().unwrap() {
Alternative::Value(v) => v,
Alternative::Tombstone => bail!("invalid password"),
2022-05-20 10:49:53 +00:00
}
};
// Try to open blob
let kdf_salt = &password_blob[..32];
2023-12-06 19:57:25 +00:00
let password_openned = try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?;
2022-05-20 10:49:53 +00:00
let keys = Self::deserialize(&password_openned)?;
if keys.public != expected_public {
bail!("Password public key doesn't match stored public key");
}
Ok(keys)
2023-12-12 08:17:59 +00:00
*/
2022-05-19 12:33:49 +00:00
}
2022-05-19 13:14:36 +00:00
pub async fn open_without_password(
storage: &Builders,
2022-05-20 10:49:53 +00:00
master: &Key,
secret: &SecretKey,
2022-05-19 13:14:36 +00:00
) -> Result<Self> {
let k2v = storage.row_store()?;
2022-05-20 10:49:53 +00:00
let (_ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?;
// Create CryptoKeys struct from given keys
let public = secret.public_key();
let keys = CryptoKeys {
master: master.clone(),
secret: secret.clone(),
public,
};
// Check public key matches
if keys.public != expected_public {
bail!("Given public key doesn't match stored public key");
}
Ok(keys)
2022-05-19 12:33:49 +00:00
}
pub async fn add_password(
&self,
storage: &Builders,
password: &str,
) -> Result<()> {
let k2v = storage.row_store()?;
let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?;
// Generate short password digest (= password identity)
let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?;
// Generate salt for KDF
let mut kdf_salt = [0u8; 32];
thread_rng().fill(&mut kdf_salt);
// Calculate key for password secret box
2023-12-06 19:57:25 +00:00
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)?;
let password_sortkey = format!("password:{}", hex::encode(&ident));
let password_blob = [&kdf_salt[..], &password_sealed].concat();
// List existing passwords to overwrite existing entry if necessary
2023-11-21 14:09:39 +00:00
let pass_key = k2v.row("keys", &password_sortkey);
let passwd = match pass_key.fetch().await {
Err(StorageError::NotFound) => pass_key,
2022-05-20 19:35:50 +00:00
v => {
let entry = v?;
2023-11-21 14:09:39 +00:00
if entry.content().iter().any(|x| matches!(x, Alternative::Value(_))) {
2022-05-20 19:35:50 +00:00
bail!("password already exists");
}
2023-11-21 14:09:39 +00:00
entry.to_ref()
}
};
// Write values to storage
2023-11-21 14:09:39 +00:00
passwd
.set_value(&password_blob)
.push()
.await
.context("InsertBatch for new password")?;
Ok(())
2022-05-19 12:33:49 +00:00
}
2022-05-19 13:14:36 +00:00
pub async fn delete_password(
storage: &Builders,
2022-05-19 13:14:36 +00:00
password: &str,
allow_delete_all: bool,
) -> Result<()> {
2023-11-21 14:09:39 +00:00
let k2v = storage.row_store()?;
let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?;
// Generate short password digest (= password identity)
let ident = argon2_kdf(&ident_salt, password.as_bytes(), 16)?;
let password_sortkey = format!("password:{}", hex::encode(&ident));
// List existing passwords
let existing_passwords = Self::list_existing_passwords(&k2v).await?;
// Check password is there
let pw = existing_passwords
2023-11-21 14:09:39 +00:00
.iter()
.map(|x| x.to_ref())
.find(|x| x.key().1 == &password_sortkey)
//.get(&password_sortkey)
.ok_or(anyhow!("password does not exist"))?;
if !allow_delete_all && existing_passwords.len() < 2 {
bail!("No other password exists, not deleting last password.");
}
2023-11-21 14:09:39 +00:00
pw.rm().await.context("DeleteItem for password")?;
Ok(())
2022-05-19 12:33:49 +00:00
}
2022-05-20 10:49:53 +00:00
// ---- STORAGE UTIL ----
2023-11-21 14:09:39 +00:00
//
2022-05-20 19:35:50 +00:00
async fn check_uninitialized(
k2v: &RowStore,
) -> Result<(RowRef, RowRef)> {
2022-05-20 10:49:53 +00:00
let params = k2v
.select(Selector::List(vec![
("keys", "salt"),
("keys", "public"),
]))
2022-05-20 11:36:45 +00:00
.await
.context("ReadBatch for salt and public in check_uninitialized")?;
2022-05-20 10:49:53 +00:00
if params.len() != 2 {
bail!(
"Invalid response from k2v storage: {:?} (expected two items)",
params
);
}
let salt_ct = params[0].to_ref();
if params[0].content().iter().any(|x| matches!(x, Alternative::Value(_))) {
bail!("key storage already initialized");
}
2022-05-20 19:35:50 +00:00
let public_ct = params[1].to_ref();
if params[1].content().iter().any(|x| matches!(x, Alternative::Value(_))) {
bail!("key storage already initialized");
}
2022-05-20 19:35:50 +00:00
Ok((salt_ct, public_ct))
2022-05-20 10:49:53 +00:00
}
2023-11-21 14:09:39 +00:00
pub async fn load_salt_and_public(k2v: &RowStore) -> Result<([u8; 32], PublicKey)> {
let params = k2v
.select(Selector::List(vec![
("keys", "salt"),
("keys", "public"),
]))
2022-05-20 11:36:45 +00:00
.await
.context("ReadBatch for salt and public in load_salt_and_public")?;
2023-11-21 14:09:39 +00:00
2022-05-20 10:49:53 +00:00
if params.len() != 2 {
bail!(
"Invalid response from k2v storage: {:?} (expected two items)",
params
);
}
2023-11-21 14:09:39 +00:00
if params[0].content().len() != 1 || params[1].content().len() != 1 {
2022-05-20 19:35:50 +00:00
bail!("cryptographic keys not initialized for user");
2022-05-20 10:49:53 +00:00
}
// Retrieve salt from given response
2023-11-21 14:09:39 +00:00
let salt: Vec<u8> = match &mut params[0].content().iter_mut().next().unwrap() {
Alternative::Value(v) => std::mem::take(v),
Alternative::Tombstone => bail!("salt is a tombstone"),
2022-05-20 10:49:53 +00:00
};
if salt.len() != 32 {
bail!("`salt` is not 32 bytes long");
}
let mut salt_constlen = [0u8; 32];
salt_constlen.copy_from_slice(&salt);
// Retrieve public from given response
2023-11-21 14:09:39 +00:00
let public: Vec<u8> = match &mut params[1].content().iter_mut().next().unwrap() {
Alternative::Value(v) => std::mem::take(v),
Alternative::Tombstone => bail!("public is a tombstone"),
2022-05-20 10:49:53 +00:00
};
let public = PublicKey::from_slice(&public).ok_or(anyhow!("Invalid public key length"))?;
Ok((salt_constlen, public))
}
2023-11-21 14:09:39 +00:00
async fn list_existing_passwords(k2v: &RowStore) -> Result<Vec<RowValue>> {
let res = k2v.select(Selector::Prefix { shard_key: "keys", prefix: "password:" })
2022-05-20 11:36:45 +00:00
.await
.context("ReadBatch for prefix password: in list_existing_passwords")?;
2023-11-21 14:09:39 +00:00
Ok(res)
}
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
}
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-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);
2022-05-20 11:36:45 +00:00
let salt = base64::encode_config(salt, base64::STANDARD_NO_PAD);
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())
}