aboutsummaryrefslogtreecommitdiff
path: root/src/login
diff options
context:
space:
mode:
Diffstat (limited to 'src/login')
-rw-r--r--src/login/demo_provider.rs51
-rw-r--r--src/login/ldap_provider.rs265
-rw-r--r--src/login/mod.rs245
-rw-r--r--src/login/static_provider.rs189
4 files changed, 0 insertions, 750 deletions
diff --git a/src/login/demo_provider.rs b/src/login/demo_provider.rs
deleted file mode 100644
index 11c7d54..0000000
--- a/src/login/demo_provider.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use crate::login::*;
-use crate::storage::*;
-
-pub struct DemoLoginProvider {
- keys: CryptoKeys,
- in_memory_store: in_memory::MemDb,
-}
-
-impl DemoLoginProvider {
- pub fn new() -> Self {
- Self {
- keys: CryptoKeys::init(),
- in_memory_store: in_memory::MemDb::new(),
- }
- }
-}
-
-#[async_trait]
-impl LoginProvider for DemoLoginProvider {
- async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
- tracing::debug!(user=%username, "login");
-
- if username != "alice" {
- bail!("user does not exist");
- }
-
- if password != "hunter2" {
- bail!("wrong password");
- }
-
- let storage = self.in_memory_store.builder("alice").await;
- let keys = self.keys.clone();
-
- Ok(Credentials { storage, keys })
- }
-
- async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
- tracing::debug!(user=%email, "public_login");
- if email != "alice@example.tld" {
- bail!("invalid email address");
- }
-
- let storage = self.in_memory_store.builder("alice").await;
- let public_key = self.keys.public.clone();
-
- Ok(PublicCredentials {
- storage,
- public_key,
- })
- }
-}
diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs
deleted file mode 100644
index 0af5676..0000000
--- a/src/login/ldap_provider.rs
+++ /dev/null
@@ -1,265 +0,0 @@
-use anyhow::Result;
-use async_trait::async_trait;
-use ldap3::{LdapConnAsync, Scope, SearchEntry};
-use log::debug;
-
-use crate::config::*;
-use crate::login::*;
-use crate::storage;
-
-pub struct LdapLoginProvider {
- ldap_server: String,
-
- pre_bind_on_login: bool,
- bind_dn_and_pw: Option<(String, String)>,
-
- search_base: String,
- attrs_to_retrieve: Vec<String>,
- username_attr: String,
- mail_attr: String,
- crypto_root_attr: String,
-
- storage_specific: StorageSpecific,
- in_memory_store: storage::in_memory::MemDb,
- garage_store: storage::garage::GarageRoot,
-}
-
-enum BucketSource {
- Constant(String),
- Attr(String),
-}
-
-enum StorageSpecific {
- InMemory,
- Garage {
- from_config: LdapGarageConfig,
- bucket_source: BucketSource,
- },
-}
-
-impl LdapLoginProvider {
- pub fn new(config: LoginLdapConfig) -> Result<Self> {
- let bind_dn_and_pw = match (config.bind_dn, config.bind_password) {
- (Some(dn), Some(pw)) => Some((dn, pw)),
- (None, None) => None,
- _ => bail!(
- "If either of `bind_dn` or `bind_password` is set, the other must be set as well."
- ),
- };
-
- if config.pre_bind_on_login && bind_dn_and_pw.is_none() {
- bail!("Cannot use `pre_bind_on_login` without setting `bind_dn` and `bind_password`");
- }
-
- let mut attrs_to_retrieve = vec![
- config.username_attr.clone(),
- config.mail_attr.clone(),
- config.crypto_root_attr.clone(),
- ];
-
- // storage specific
- let specific = match config.storage {
- LdapStorage::InMemory => StorageSpecific::InMemory,
- LdapStorage::Garage(grgconf) => {
- attrs_to_retrieve.push(grgconf.aws_access_key_id_attr.clone());
- attrs_to_retrieve.push(grgconf.aws_secret_access_key_attr.clone());
-
- let bucket_source =
- match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) {
- (Some(b), None) => BucketSource::Constant(b),
- (None, Some(a)) => BucketSource::Attr(a),
- _ => bail!("Must set `bucket` or `bucket_attr`, but not both"),
- };
-
- if let BucketSource::Attr(a) = &bucket_source {
- attrs_to_retrieve.push(a.clone());
- }
-
- StorageSpecific::Garage {
- from_config: grgconf,
- bucket_source,
- }
- }
- };
-
- Ok(Self {
- ldap_server: config.ldap_server,
- pre_bind_on_login: config.pre_bind_on_login,
- bind_dn_and_pw,
- search_base: config.search_base,
- attrs_to_retrieve,
- username_attr: config.username_attr,
- mail_attr: config.mail_attr,
- crypto_root_attr: config.crypto_root_attr,
- storage_specific: specific,
- //@FIXME should be created outside of the login provider
- //Login provider should return only a cryptoroot + a storage URI
- //storage URI that should be resolved outside...
- in_memory_store: storage::in_memory::MemDb::new(),
- garage_store: storage::garage::GarageRoot::new()?,
- })
- }
-
- async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result<Builder> {
- let storage: Builder = match &self.storage_specific {
- StorageSpecific::InMemory => {
- self.in_memory_store
- .builder(&get_attr(user, &self.username_attr)?)
- .await
- }
- StorageSpecific::Garage {
- from_config,
- bucket_source,
- } => {
- let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?;
- let aws_secret_access_key =
- get_attr(user, &from_config.aws_secret_access_key_attr)?;
- let bucket = match bucket_source {
- BucketSource::Constant(b) => b.clone(),
- BucketSource::Attr(a) => get_attr(user, &a)?,
- };
-
- self.garage_store.user(storage::garage::GarageConf {
- region: from_config.aws_region.clone(),
- s3_endpoint: from_config.s3_endpoint.clone(),
- k2v_endpoint: from_config.k2v_endpoint.clone(),
- aws_access_key_id,
- aws_secret_access_key,
- bucket,
- })?
- }
- };
-
- Ok(storage)
- }
-}
-
-#[async_trait]
-impl LoginProvider for LdapLoginProvider {
- async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
- check_identifier(username)?;
-
- let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
- ldap3::drive!(conn);
-
- if self.pre_bind_on_login {
- let (dn, pw) = self.bind_dn_and_pw.as_ref().unwrap();
- ldap.simple_bind(dn, pw).await?.success()?;
- }
-
- let (matches, _res) = ldap
- .search(
- &self.search_base,
- Scope::Subtree,
- &format!(
- "(&(objectClass=inetOrgPerson)({}={}))",
- self.username_attr, username
- ),
- &self.attrs_to_retrieve,
- )
- .await?
- .success()?;
-
- if matches.is_empty() {
- bail!("Invalid username");
- }
- if matches.len() > 1 {
- bail!("Invalid username (multiple matching accounts)");
- }
- let user = SearchEntry::construct(matches.into_iter().next().unwrap());
- debug!(
- "Found matching LDAP user for username {}: {}",
- username, user.dn
- );
-
- // Try to login against LDAP server with provided password
- // to check user's password
- ldap.simple_bind(&user.dn, password)
- .await?
- .success()
- .context("Invalid password")?;
- debug!("Ldap login with user name {} successfull", username);
-
- // cryptography
- let crstr = get_attr(&user, &self.crypto_root_attr)?;
- let cr = CryptoRoot(crstr);
- let keys = cr.crypto_keys(password)?;
-
- // storage
- let storage = self.storage_creds_from_ldap_user(&user).await?;
-
- drop(ldap);
-
- Ok(Credentials { storage, keys })
- }
-
- async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
- check_identifier(email)?;
-
- let (dn, pw) = match self.bind_dn_and_pw.as_ref() {
- Some(x) => x,
- None => bail!("Missing bind_dn and bind_password in LDAP login provider config"),
- };
-
- let (conn, mut ldap) = LdapConnAsync::new(&self.ldap_server).await?;
- ldap3::drive!(conn);
- ldap.simple_bind(dn, pw).await?.success()?;
-
- let (matches, _res) = ldap
- .search(
- &self.search_base,
- Scope::Subtree,
- &format!(
- "(&(objectClass=inetOrgPerson)({}={}))",
- self.mail_attr, email
- ),
- &self.attrs_to_retrieve,
- )
- .await?
- .success()?;
-
- if matches.is_empty() {
- bail!("No such user account");
- }
- if matches.len() > 1 {
- bail!("Multiple matching user accounts");
- }
- let user = SearchEntry::construct(matches.into_iter().next().unwrap());
- debug!("Found matching LDAP user for email {}: {}", email, user.dn);
-
- // cryptography
- let crstr = get_attr(&user, &self.crypto_root_attr)?;
- let cr = CryptoRoot(crstr);
- let public_key = cr.public_key()?;
-
- // storage
- let storage = self.storage_creds_from_ldap_user(&user).await?;
- drop(ldap);
-
- Ok(PublicCredentials {
- storage,
- public_key,
- })
- }
-}
-
-fn get_attr(user: &SearchEntry, attr: &str) -> Result<String> {
- Ok(user
- .attrs
- .get(attr)
- .ok_or(anyhow!("Missing attr: {}", attr))?
- .iter()
- .next()
- .ok_or(anyhow!("No value for attr: {}", attr))?
- .clone())
-}
-
-fn check_identifier(id: &str) -> Result<()> {
- let is_ok = id
- .chars()
- .all(|c| c.is_alphanumeric() || "-+_.@".contains(c));
- if !is_ok {
- bail!("Invalid username/email address, must contain only a-z A-Z 0-9 - + _ . @");
- }
- Ok(())
-}
diff --git a/src/login/mod.rs b/src/login/mod.rs
deleted file mode 100644
index 4a1dee1..0000000
--- a/src/login/mod.rs
+++ /dev/null
@@ -1,245 +0,0 @@
-pub mod demo_provider;
-pub mod ldap_provider;
-pub mod static_provider;
-
-use base64::Engine;
-use std::sync::Arc;
-
-use anyhow::{anyhow, bail, Context, Result};
-use async_trait::async_trait;
-use rand::prelude::*;
-
-use crate::cryptoblob::*;
-use crate::storage::*;
-
-/// 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 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>;
-}
-
-/// 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.
-#[derive(Clone, Debug)]
-pub struct Credentials {
- /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
- pub storage: Builder,
- /// The cryptographic keys are used to encrypt and decrypt data stored in S3 and K2V
- pub keys: CryptoKeys,
-}
-
-#[derive(Clone, Debug)]
-pub struct PublicCredentials {
- /// The storage credentials are used to authenticate access to the underlying storage (S3, K2V)
- pub storage: Builder,
- pub public_key: PublicKey,
-}
-
-use serde::{Deserialize, Serialize};
-#[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)?;
- CryptoKeys::password_open(password, &blob)
- }
- ["aero", "cryptoroot", "cleartext", b64blob] => {
- let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
- CryptoKeys::deserialize(&blob)
- }
- ["aero", "cryptoroot", "incoming", _] => {
- 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.
-#[derive(Clone, Debug)]
-pub struct CryptoKeys {
- /// Master key for symmetric encryption of mailbox data
- pub master: Key,
- /// 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,
-}
-
-// ----
-
-impl CryptoKeys {
- /// Initialize a new cryptography root
- pub fn init() -> Self {
- let (public, secret) = gen_keypair();
- let master = gen_key();
- CryptoKeys {
- master,
- secret,
- public,
- }
- }
-
- // Clear text serialize/deserialize
- /// Serialize the root as bytes without encryption
- 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
- }
-
- /// Deserialize a clear text crypto root without encryption
- 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,
- })
- }
-
- // Password sealed keys serialize/deserialize
- pub fn password_open(password: &str, blob: &[u8]) -> Result<Self> {
- let _pubkey = &blob[0..32];
- let kdf_salt = &blob[32..64];
- let password_openned = try_open_encrypted_keys(kdf_salt, password, &blob[64..])?;
-
- 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)
- }
-}
-
-fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result<Key> {
- Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap())
-}
-
-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)
-}
-
-// ---- UTIL ----
-
-pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec<u8>> {
- use argon2::{password_hash, Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version};
-
- let params = ParamsBuilder::new()
- .output_len(output_len)
- .build()
- .map_err(|e| anyhow!("Invalid argon2 params: {}", e))?;
- let argon2 = Argon2::new(Algorithm::default(), Version::default(), params);
-
- let b64_salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
- let valid_salt = password_hash::Salt::from_b64(&b64_salt)
- .map_err(|e| anyhow!("Invalid salt, error {}", e))?;
- let hash = argon2
- .hash_password(password, valid_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())
-}
diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs
deleted file mode 100644
index 79626df..0000000
--- a/src/login/static_provider.rs
+++ /dev/null
@@ -1,189 +0,0 @@
-use std::collections::HashMap;
-use std::path::PathBuf;
-use std::sync::Arc;
-use tokio::signal::unix::{signal, SignalKind};
-use tokio::sync::watch;
-
-use anyhow::{anyhow, bail, Result};
-use async_trait::async_trait;
-
-use crate::config::*;
-use crate::login::*;
-use crate::storage;
-
-pub struct ContextualUserEntry {
- pub username: String,
- pub config: UserEntry,
-}
-
-#[derive(Default)]
-pub struct UserDatabase {
- users: HashMap<String, Arc<ContextualUserEntry>>,
- users_by_email: HashMap<String, Arc<ContextualUserEntry>>,
-}
-
-pub struct StaticLoginProvider {
- user_db: watch::Receiver<UserDatabase>,
- in_memory_store: storage::in_memory::MemDb,
- garage_store: storage::garage::GarageRoot,
-}
-
-pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> {
- let mut stream = signal(SignalKind::user_defined1())
- .expect("failed to install SIGUSR1 signal hander for reload");
-
- loop {
- let ulist: UserList = match read_config(config.clone()) {
- Ok(x) => x,
- Err(e) => {
- tracing::warn!(path=%config.as_path().to_string_lossy(), error=%e, "Unable to load config");
- stream.recv().await;
- continue;
- }
- };
-
- let users = ulist
- .into_iter()
- .map(|(username, config)| {
- (
- username.clone(),
- Arc::new(ContextualUserEntry { username, config }),
- )
- })
- .collect::<HashMap<_, _>>();
-
- let mut users_by_email = HashMap::new();
- for (_, u) in users.iter() {
- for m in u.config.email_addresses.iter() {
- if users_by_email.contains_key(m) {
- tracing::warn!("Several users have the same email address: {}", m);
- stream.recv().await;
- continue;
- }
- users_by_email.insert(m.clone(), u.clone());
- }
- }
-
- tracing::info!("{} users loaded", users.len());
- up.send(UserDatabase {
- users,
- users_by_email,
- })
- .context("update user db config")?;
- stream.recv().await;
- tracing::info!("Received SIGUSR1, reloading");
- }
-}
-
-impl StaticLoginProvider {
- pub async fn new(config: LoginStaticConfig) -> Result<Self> {
- let (tx, mut rx) = watch::channel(UserDatabase::default());
-
- tokio::spawn(update_user_list(config.user_list, tx));
- rx.changed().await?;
-
- Ok(Self {
- user_db: rx,
- in_memory_store: storage::in_memory::MemDb::new(),
- garage_store: storage::garage::GarageRoot::new()?,
- })
- }
-}
-
-#[async_trait]
-impl LoginProvider for StaticLoginProvider {
- async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
- tracing::debug!(user=%username, "login");
- let user = {
- let user_db = self.user_db.borrow();
- match user_db.users.get(username) {
- None => bail!("User {} does not exist", username),
- Some(u) => u.clone(),
- }
- };
-
- tracing::debug!(user=%username, "verify password");
- if !verify_password(password, &user.config.password)? {
- bail!("Wrong password");
- }
-
- tracing::debug!(user=%username, "fetch keys");
- let storage: storage::Builder = match &user.config.storage {
- StaticStorage::InMemory => self.in_memory_store.builder(username).await,
- StaticStorage::Garage(grgconf) => {
- self.garage_store.user(storage::garage::GarageConf {
- region: grgconf.aws_region.clone(),
- k2v_endpoint: grgconf.k2v_endpoint.clone(),
- s3_endpoint: grgconf.s3_endpoint.clone(),
- aws_access_key_id: grgconf.aws_access_key_id.clone(),
- aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
- bucket: grgconf.bucket.clone(),
- })?
- }
- };
-
- let cr = CryptoRoot(user.config.crypto_root.clone());
- let keys = cr.crypto_keys(password)?;
-
- tracing::debug!(user=%username, "logged");
- Ok(Credentials { storage, keys })
- }
-
- async fn public_login(&self, email: &str) -> Result<PublicCredentials> {
- let user = {
- let user_db = self.user_db.borrow();
- match user_db.users_by_email.get(email) {
- None => bail!("Email {} does not exist", email),
- Some(u) => u.clone(),
- }
- };
- tracing::debug!(user=%user.username, "public_login");
-
- let storage: storage::Builder = match &user.config.storage {
- StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await,
- StaticStorage::Garage(grgconf) => {
- self.garage_store.user(storage::garage::GarageConf {
- region: grgconf.aws_region.clone(),
- k2v_endpoint: grgconf.k2v_endpoint.clone(),
- s3_endpoint: grgconf.s3_endpoint.clone(),
- aws_access_key_id: grgconf.aws_access_key_id.clone(),
- aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
- bucket: grgconf.bucket.clone(),
- })?
- }
- };
-
- let cr = CryptoRoot(user.config.crypto_root.clone());
- let public_key = cr.public_key()?;
-
- Ok(PublicCredentials {
- storage,
- public_key,
- })
- }
-}
-
-pub fn hash_password(password: &str) -> Result<String> {
- use argon2::{
- password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
- Argon2,
- };
- let salt = SaltString::generate(&mut OsRng);
- let argon2 = Argon2::default();
- Ok(argon2
- .hash_password(password.as_bytes(), &salt)
- .map_err(|e| anyhow!("Argon2 error: {}", e))?
- .to_string())
-}
-
-pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
- use argon2::{
- password_hash::{PasswordHash, PasswordVerifier},
- Argon2,
- };
- let parsed_hash =
- PasswordHash::new(hash).map_err(|e| anyhow!("Invalid hashed password: {}", e))?;
- Ok(Argon2::default()
- .verify_password(password.as_bytes(), &parsed_hash)
- .is_ok())
-}