aboutsummaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-05-23 17:31:53 +0200
committerAlex Auvolat <alex@adnab.me>2022-05-23 17:31:53 +0200
commitcb9b64a184470c7f332eb2c20bf64d53e84406f1 (patch)
tree992ae5d5cc8c22d7e9a4974028f914e9b3ba50ce /src/login
parent378cbd76d0ce97e24941a81ff57c210c0342bd01 (diff)
downloadaerogramme-cb9b64a184470c7f332eb2c20bf64d53e84406f1.tar.gz
aerogramme-cb9b64a184470c7f332eb2c20bf64d53e84406f1.zip
Add user secret in mix to encrypt keys with password
Diffstat (limited to 'src/login')
-rw-r--r--src/login/mod.rs88
-rw-r--r--src/login/static_provider.rs6
2 files changed, 81 insertions, 13 deletions
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 75a39f8..1ee0007 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -16,17 +16,27 @@ use rusoto_signature::Region;
use crate::cryptoblob::*;
+/// 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.
#[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>;
}
+/// The struct Credentials represent all of the necessary information to interact
+/// with a user account's data after they are logged in.
#[derive(Clone, Debug)]
pub struct Credentials {
+ /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
pub storage: StorageCredentials,
+ /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V
pub keys: CryptoKeys,
}
+/// The struct StorageCredentials contains access key to an S3 and K2V bucket
#[derive(Clone, Debug)]
pub struct StorageCredentials {
pub s3_region: Region,
@@ -37,12 +47,28 @@ pub struct StorageCredentials {
pub bucket: String,
}
+/// 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>,
+}
+
+/// The struct CryptoKeys contains the cryptographic keys used to encrypt and decrypt
+/// data in a user's mailbox.
#[derive(Clone, Debug)]
pub struct CryptoKeys {
- // Master key for symmetric encryption of mailbox data
+ /// Master key for symmetric encryption of mailbox data
pub master: Key,
- // Public/private keypair for encryption of incomming emails
+ /// Public/private keypair for encryption of incomming emails (secret part)
pub secret: SecretKey,
+ /// Public/private keypair for encryption of incomming emails (public part)
pub public: PublicKey,
}
@@ -92,7 +118,11 @@ impl StorageCredentials {
}
impl CryptoKeys {
- pub async fn init(storage: &StorageCredentials, password: &str) -> Result<Self> {
+ pub async fn init(
+ storage: &StorageCredentials,
+ user_secrets: &UserSecrets,
+ password: &str,
+ ) -> Result<Self> {
// Check that salt and public don't exist already
let k2v = storage.k2v_client()?;
let (salt_ct, public_ct) = Self::check_uninitialized(&k2v).await?;
@@ -118,8 +148,7 @@ impl CryptoKeys {
thread_rng().fill(&mut kdf_salt);
// Calculate key for password secret box
- let password_key =
- Key::from_slice(&argon2_kdf(&kdf_salt, password.as_bytes(), 32)?).unwrap();
+ let password_key = user_secrets.derive_password_key(&kdf_salt, password)?;
// Seal a secret box that contains our crypto keys
let password_sealed = seal(&keys.serialize(), &password_key)?;
@@ -171,7 +200,11 @@ impl CryptoKeys {
Ok(keys)
}
- pub async fn open(storage: &StorageCredentials, password: &str) -> Result<Self> {
+ pub async fn open(
+ storage: &StorageCredentials,
+ user_secrets: &UserSecrets,
+ password: &str,
+ ) -> Result<Self> {
let k2v = storage.k2v_client()?;
let (ident_salt, expected_public) = Self::load_salt_and_public(&k2v).await?;
@@ -199,9 +232,8 @@ impl CryptoKeys {
// Try to open blob
let kdf_salt = &password_blob[..32];
- let password_key =
- Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap();
- let password_openned = open(&password_blob[32..], &password_key)?;
+ let password_openned =
+ user_secrets.try_open_encrypted_keys(&kdf_salt, password, &password_blob[32..])?;
let keys = Self::deserialize(&password_openned)?;
if keys.public != expected_public {
@@ -235,7 +267,12 @@ impl CryptoKeys {
Ok(keys)
}
- pub async fn add_password(&self, storage: &StorageCredentials, password: &str) -> Result<()> {
+ pub async fn add_password(
+ &self,
+ storage: &StorageCredentials,
+ user_secrets: &UserSecrets,
+ password: &str,
+ ) -> Result<()> {
let k2v = storage.k2v_client()?;
let (ident_salt, _public) = Self::load_salt_and_public(&k2v).await?;
@@ -247,8 +284,7 @@ impl CryptoKeys {
thread_rng().fill(&mut kdf_salt);
// Calculate key for password secret box
- let password_key =
- Key::from_slice(&argon2_kdf(&kdf_salt, password.as_bytes(), 32)?).unwrap();
+ let password_key = user_secrets.derive_password_key(&kdf_salt, password)?;
// Seal a secret box that contains our crypto keys
let password_sealed = seal(&self.serialize(), &password_key)?;
@@ -453,6 +489,34 @@ impl CryptoKeys {
}
}
+impl UserSecrets {
+ fn derive_password_key_with(user_secret: &str, kdf_salt: &[u8], password: &str) -> Result<Key> {
+ let tmp = format!("{}\n\n{}", user_secret, password);
+ Ok(Key::from_slice(&argon2_kdf(&kdf_salt, tmp.as_bytes(), 32)?).unwrap())
+ }
+
+ fn derive_password_key(&self, kdf_salt: &[u8], password: &str) -> Result<Key> {
+ Self::derive_password_key_with(&self.user_secret, kdf_salt, password)
+ }
+
+ fn try_open_encrypted_keys(
+ &self,
+ kdf_salt: &[u8],
+ password: &str,
+ encrypted_keys: &[u8],
+ ) -> Result<Vec<u8>> {
+ let secrets_to_try =
+ std::iter::once(&self.user_secret).chain(self.alternate_user_secrets.iter());
+ for user_secret in secrets_to_try {
+ let password_key = Self::derive_password_key_with(user_secret, kdf_salt, password)?;
+ if let Ok(res) = open(encrypted_keys, &password_key) {
+ return Ok(res);
+ }
+ }
+ bail!("Unable to decrypt password blob.");
+ }
+}
+
// ---- UTIL ----
pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec<u8>> {
diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs
index fb8ec68..cc6ffb6 100644
--- a/src/login/static_provider.rs
+++ b/src/login/static_provider.rs
@@ -60,7 +60,11 @@ impl LoginProvider for StaticLoginProvider {
CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await?
}
(None, None) => {
- CryptoKeys::open(&storage, password).await?
+ let user_secrets = UserSecrets {
+ user_secret: u.user_secret.clone(),
+ alternate_user_secrets: u.alternate_user_secrets.clone(),
+ };
+ CryptoKeys::open(&storage, &user_secrets, password).await?
}
_ => bail!("Either both master and secret key or none of them must be specified for user"),
};