pub mod attestation;
pub mod delegation;
pub mod otp;
pub mod seed;
pub mod session;
pub mod token;
pub use joy_crypt::identity::{Keypair as IdentityKeypair, PublicKey};
pub use joy_crypt::kdf::{derive_argon2id as derive_key, generate_salt, DerivedKey, Salt};
use crate::error::JoyError;
pub const MIN_PASSPHRASE_WORDS: usize = 3;
pub fn validate_passphrase(passphrase: &str) -> Result<(), JoyError> {
let word_count = passphrase.split_whitespace().count();
if word_count < MIN_PASSPHRASE_WORDS {
return Err(JoyError::PassphraseTooShort);
}
Ok(())
}
pub struct UnlockedIdentity {
pub keypair: IdentityKeypair,
pub seed: [u8; 32],
}
pub fn unlock_identity(
member: &crate::model::project::Member,
passphrase: &str,
) -> Result<UnlockedIdentity, JoyError> {
let salt_hex = member
.kdf_nonce
.as_ref()
.ok_or_else(|| JoyError::AuthFailed("member has no kdf_nonce".into()))?;
let salt = Salt::from_hex(salt_hex)?;
let verify_hex = member
.verify_key
.as_ref()
.ok_or_else(|| JoyError::AuthFailed("member has no verify_key".into()))?;
let verify_key = PublicKey::from_hex(verify_hex)?;
let (kp, seed_bytes) = if let Some(wrap_hex) = member.seed_wrap_passphrase.as_deref() {
let seed = seed::unwrap_seed_with_passphrase(wrap_hex, passphrase, &salt)?;
let bytes = *seed.as_bytes();
(IdentityKeypair::from_seed(&bytes), bytes)
} else {
let key = derive_key(passphrase, &salt)?;
let bytes = *key.as_bytes();
(IdentityKeypair::from_derived_key(&key), bytes)
};
if kp.public_key() != verify_key {
return Err(JoyError::AuthFailed("incorrect passphrase".into()));
}
Ok(UnlockedIdentity {
keypair: kp,
seed: seed_bytes,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passphrase_too_short() {
assert!(validate_passphrase("").is_err());
assert!(validate_passphrase("one").is_err());
assert!(validate_passphrase("one two").is_err());
}
#[test]
fn passphrase_valid() {
assert!(validate_passphrase("one two three").is_ok());
assert!(validate_passphrase("one two three four five six").is_ok());
assert!(validate_passphrase("a b c d e f g h").is_ok());
}
}