diff options
Diffstat (limited to 'src/crypto.rs')
-rw-r--r-- | src/crypto.rs | 139 |
1 files changed, 104 insertions, 35 deletions
diff --git a/src/crypto.rs b/src/crypto.rs index 617bd7a..7fba9cd 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -15,15 +15,23 @@ use scrypt::scrypt; use serde::{Deserialize, Serialize}; use sha2::Sha256; +use crate::{ + serde::as_hex, + types::{AccountResetID, KeyFetchID, PasswordChangeID, SessionID}, +}; + const NAMESPACE: &[u8] = b"identity.mozilla.com/picl/v1/"; +pub fn random_bytes<const N: usize>() -> [u8; N] { + let mut result = [0; N]; + rand::rngs::OsRng.fill_bytes(&mut result); + result +} + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub struct SecretBytes<const N: usize>(pub [u8; N]); -#[derive(Clone, PartialEq, Eq)] -pub struct TokenID(pub [u8; 32]); - impl<const N: usize> Debug for SecretBytes<N> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fmt.write_fmt(format_args!("SecretBytes {{ raw: {} }}", hex::encode(&self.0))) @@ -73,7 +81,7 @@ mod from_hkdf { mod private { pub trait Seal {} impl<const N: usize> Seal for super::super::SecretBytes<N> {} - impl Seal for super::super::TokenID {} + impl<const N: usize> Seal for [u8; N] {} impl<L: Seal, R: Seal> Seal for (L, R) {} } @@ -90,11 +98,11 @@ mod from_hkdf { } } - impl FromHkdf for super::TokenID { - const SIZE: usize = 32; + impl<const N: usize> FromHkdf for [u8; N] { + const SIZE: usize = N; fn from_hkdf(bytes: &[u8]) -> Self { #[allow(clippy::expect_used)] - Self(bytes.try_into().expect("hkdf failed")) + bytes.try_into().expect("hkdf failed") } } @@ -166,29 +174,59 @@ impl StretchedPW { } } -pub struct SessionCredentials { - pub token_id: TokenID, +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) struct SessionToken(#[serde(with = "as_hex")] [u8; 32]); + +impl Debug for SessionToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SessionToken").field(&hex::encode(self.0)).finish() + } +} + +impl SessionToken { + pub fn generate() -> Self { + Self(random_bytes()) + } +} + +pub(crate) struct SessionCredentials { + pub token_id: SessionID, pub req_hmac_key: SecretBytes<32>, } impl SessionCredentials { - pub fn derive(seed: &SecretBytes<32>) -> Self { + pub fn derive_from(seed: &SessionToken) -> Self { let (token_id, req_hmac_key) = from_hkdf(&seed.0, &[NAMESPACE, b"sessionToken"]); - Self { token_id, req_hmac_key } + Self { token_id: SessionID(token_id), req_hmac_key } + } +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) struct KeyFetchToken(#[serde(with = "as_hex")] [u8; 32]); + +impl Debug for KeyFetchToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("KeyFetchToken").field(&hex::encode(self.0)).finish() + } +} + +impl KeyFetchToken { + pub fn generate() -> Self { + Self(random_bytes()) } } -pub struct KeyFetchReq { - pub token_id: TokenID, +pub(crate) struct KeyFetchReq { + pub token_id: KeyFetchID, pub req_hmac_key: SecretBytes<32>, key_request_key: SecretBytes<32>, } impl KeyFetchReq { - pub fn from_token(key_fetch_token: &SecretBytes<32>) -> Self { + pub fn derive_from(key_fetch_token: &KeyFetchToken) -> Self { let (token_id, (req_hmac_key, key_request_key)) = from_hkdf(&key_fetch_token.0, &[NAMESPACE, b"keyFetchToken"]); - Self { token_id, req_hmac_key, key_request_key } + Self { token_id: KeyFetchID(token_id), req_hmac_key, key_request_key } } pub fn derive_resp(&self) -> KeyFetchResp { @@ -243,32 +281,62 @@ impl WrappedKeyBundle { } } -pub struct PasswordChangeReq { - pub token_id: TokenID, +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) struct PasswordChangeToken(#[serde(with = "as_hex")] [u8; 32]); + +impl Debug for PasswordChangeToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PasswordChangeToken").field(&hex::encode(self.0)).finish() + } +} + +impl PasswordChangeToken { + pub fn generate() -> Self { + Self(random_bytes()) + } +} + +pub(crate) struct PasswordChangeReq { + pub token_id: PasswordChangeID, pub req_hmac_key: SecretBytes<32>, } impl PasswordChangeReq { - pub fn from_change_token(token: &SecretBytes<32>) -> Self { + pub fn derive_from_change_token(token: &PasswordChangeToken) -> Self { let (token_id, req_hmac_key) = from_hkdf(&token.0, &[NAMESPACE, b"passwordChangeToken"]); - Self { token_id, req_hmac_key } + Self { token_id: PasswordChangeID(token_id), req_hmac_key } } - pub fn from_forgot_token(token: &SecretBytes<32>) -> Self { + pub fn derive_from_forgot_token(token: &PasswordChangeToken) -> Self { let (token_id, req_hmac_key) = from_hkdf(&token.0, &[NAMESPACE, b"passwordForgotToken"]); - Self { token_id, req_hmac_key } + Self { token_id: PasswordChangeID(token_id), req_hmac_key } + } +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) struct AccountResetToken(#[serde(with = "as_hex")] [u8; 32]); + +impl Debug for AccountResetToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AccountResetToken").field(&hex::encode(self.0)).finish() + } +} + +impl AccountResetToken { + pub fn generate() -> Self { + Self(random_bytes()) } } -pub struct AccountResetReq { - pub token_id: TokenID, +pub(crate) struct AccountResetReq { + pub token_id: AccountResetID, pub req_hmac_key: SecretBytes<32>, } impl AccountResetReq { - pub fn from_token(token: &SecretBytes<32>) -> Self { + pub fn derive_from(token: &AccountResetToken) -> Self { let (token_id, req_hmac_key) = from_hkdf(&token.0, &[NAMESPACE, b"accountResetToken"]); - Self { token_id, req_hmac_key } + Self { token_id: AccountResetID(token_id), req_hmac_key } } } @@ -278,7 +346,8 @@ mod test { use password_hash::{Output, SaltString}; use crate::crypto::{ - AccountResetReq, KeyBundle, KeyFetchReq, PasswordChangeReq, SessionCredentials, + AccountResetReq, AccountResetToken, KeyBundle, KeyFetchReq, KeyFetchToken, + PasswordChangeReq, PasswordChangeToken, SessionCredentials, SessionToken, }; use super::{AuthPW, SecretBytes}; @@ -291,7 +360,7 @@ mod test { #[test] fn test_derive_session() { - let creds = SessionCredentials::derive(&SecretBytes(hex!( + let creds = SessionCredentials::derive_from(&SessionToken(hex!( "a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf" ))); assert_eq!( @@ -306,9 +375,9 @@ mod test { #[test] fn test_key_fetch() { - let key_fetch = KeyFetchReq::from_token(&shex!( + let key_fetch = KeyFetchReq::derive_from(&KeyFetchToken(hex!( "8081828384858687 88898a8b8c8d8e8f 9091929394959697 98999a9b9c9d9e9f" - )); + ))); assert_eq!( key_fetch.token_id.0, hex!("3d0a7c02a15a62a2882f76e39b6494b500c022a8816e048625a495718998ba60") @@ -404,9 +473,9 @@ mod test { #[test] fn test_password_change() { - let req = PasswordChangeReq::from_change_token(&shex!( + let req = PasswordChangeReq::derive_from_change_token(&PasswordChangeToken(hex!( "0000000000000000 0000000000000000 0000000000000000 0000000000000000" - )); + ))); assert_eq!( req.token_id.0, hex!("5a9f93f66c26fd1c 1ea9826fafc422e9 4b9c9f833cd2bfa5 da18c8d3317224aa") @@ -419,9 +488,9 @@ mod test { #[test] fn test_password_forgot() { - let req = PasswordChangeReq::from_forgot_token(&shex!( + let req = PasswordChangeReq::derive_from_forgot_token(&PasswordChangeToken(hex!( "0000000000000000 0000000000000000 0000000000000000 0000000000000000" - )); + ))); assert_eq!( req.token_id.0, hex!("570e79050fd157a9 b8e7d7d6f88a3f67 e36207c5dfabe7d8 a80994502a624e07") @@ -434,9 +503,9 @@ mod test { #[test] fn test_account_reset() { - let req = AccountResetReq::from_token(&shex!( + let req = AccountResetReq::derive_from(&AccountResetToken(hex!( "0000000000000000 0000000000000000 0000000000000000 0000000000000000" - )); + ))); assert_eq!( req.token_id.0, hex!("8ade842449ab0285 e7b22de9d428cd5b 3c38ea0aa78e2956 a6a69ec66818d864") |