diff options
Diffstat (limited to '')
| -rw-r--r-- | crates/secd/src/util/mod.rs | 275 |
1 files changed, 233 insertions, 42 deletions
diff --git a/crates/secd/src/util/mod.rs b/crates/secd/src/util/mod.rs index 8676f26..fb984d1 100644 --- a/crates/secd/src/util/mod.rs +++ b/crates/secd/src/util/mod.rs @@ -3,12 +3,14 @@ pub(crate) mod from; use self::crypter::{Crypter, CrypterError}; use crate::{ - AddressType, Credential, CredentialType, IdentityId, SecdError, Session, SESSION_DURATION, - SESSION_SIZE_BYTES, + AddressType, Credential, CredentialType, IdentityId, SecdError, API_TOKEN_SIZE_BYTES, + CREDENTIAL_PUBLIC_PART_BYTES, SESSION_DURATION, SESSION_SIZE_BYTES, }; +use base64::{engine::general_purpose, Engine as _}; use sha2::{Digest, Sha256}; use std::str::from_utf8; use time::OffsetDateTime; +use uuid::Uuid; pub(crate) fn hash(i: &[u8]) -> Vec<u8> { let mut hasher = Sha256::new(); @@ -16,76 +18,265 @@ pub(crate) fn hash(i: &[u8]) -> Vec<u8> { hasher.finalize().to_vec() } +pub trait ErrorContext<T, E: std::error::Error> { + fn ctx(self, err: &str) -> Result<T, E>; +} + +impl<T, E: std::error::Error> ErrorContext<T, E> for Result<T, E> { + fn ctx(self, err: &str) -> Result<T, E> { + self.map_err(|e| { + log::error!("{} [{}]", err, e.to_string()); + e + }) + } +} + impl AddressType { pub fn get_value(&self) -> Option<String> { match &self { - AddressType::Email { email_address } => { - email_address.as_ref().map(|a| a.to_string().clone()) - } + AddressType::Email { email_address } => email_address.as_ref().map(|a| a.to_string()), AddressType::Sms { phone_number } => phone_number.as_ref().cloned(), } } } -impl Session { - pub(crate) fn new(identity_id: IdentityId) -> Result<Self, SecdError> { - let token = (0..SESSION_SIZE_BYTES) +impl CredentialType { + pub fn new_api_token() -> Result<Self, SecdError> { + let public = general_purpose::URL_SAFE_NO_PAD.encode( + (0..CREDENTIAL_PUBLIC_PART_BYTES) + .map(|_| rand::random::<u8>()) + .collect::<Vec<u8>>(), + ); + + let private = general_purpose::URL_SAFE_NO_PAD.encode( + (0..API_TOKEN_SIZE_BYTES) + .map(|_| rand::random::<u8>()) + .collect::<Vec<u8>>(), + ); + + Ok(CredentialType::ApiToken { public, private }) + } + + pub fn session_from_str(token: &str) -> Result<Self, SecdError> { + let decoded = general_purpose::URL_SAFE_NO_PAD + .decode(token) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + + let key = + general_purpose::URL_SAFE_NO_PAD.encode(&decoded[0..CREDENTIAL_PUBLIC_PART_BYTES]); + let secret = + general_purpose::URL_SAFE_NO_PAD.encode(&decoded[CREDENTIAL_PUBLIC_PART_BYTES..]); + + Ok(CredentialType::Session { key, secret }) + } + + pub fn api_token_from_str(token: &str) -> Result<Self, SecdError> { + let decoded = general_purpose::URL_SAFE_NO_PAD + .decode(token) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + + let public = + general_purpose::URL_SAFE_NO_PAD.encode(&decoded[0..CREDENTIAL_PUBLIC_PART_BYTES]); + let private = + general_purpose::URL_SAFE_NO_PAD.encode(&decoded[CREDENTIAL_PUBLIC_PART_BYTES..]); + + Ok(CredentialType::ApiToken { public, private }) + } + + pub fn session_token(&self) -> Result<String, SecdError> { + match self { + CredentialType::Session { key, secret } => { + let key_bytes = general_purpose::URL_SAFE_NO_PAD + .decode(key) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + let secret_bytes = general_purpose::URL_SAFE_NO_PAD + .decode(secret) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + + let mut input = key_bytes; + input.extend(secret_bytes); + + Ok(general_purpose::URL_SAFE_NO_PAD.encode(input)) + } + _ => Err(SecdError::InvalidCredential), + } + .ctx("the credential type is not a session") + } + + pub fn api_token(&self) -> Result<String, SecdError> { + match self { + CredentialType::ApiToken { public, private } => { + let public_bytes = general_purpose::URL_SAFE_NO_PAD + .decode(public) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + let private_bytes = general_purpose::URL_SAFE_NO_PAD + .decode(private) + .map_err(|e| SecdError::DecodeError(e.to_string()))?; + + let mut input = public_bytes; + input.extend(private_bytes); + + Ok(general_purpose::URL_SAFE_NO_PAD.encode(input)) + } + _ => Err(SecdError::InvalidCredential), + } + .ctx("the credential type is not an api token") + } +} + +impl Credential { + pub fn new_session(identity_id: IdentityId) -> Result<Self, SecdError> { + let key = (0..CREDENTIAL_PUBLIC_PART_BYTES) + .map(|_| rand::random::<u8>()) + .collect::<Vec<u8>>(); + + let secret = (0..SESSION_SIZE_BYTES) .map(|_| rand::random::<u8>()) .collect::<Vec<u8>>(); + let now = OffsetDateTime::now_utc(); - Ok(Session { + Ok(Credential { + id: Uuid::new_v4(), identity_id, - token, + t: CredentialType::Session { + key: general_purpose::URL_SAFE_NO_PAD.encode(key), + secret: general_purpose::URL_SAFE_NO_PAD.encode(secret), + }, created_at: now, - expired_at: now - .checked_add(time::Duration::new(SESSION_DURATION, 0)) - .ok_or(SecdError::Todo)?, - revoked_at: None, + revoked_at: Some( + now.checked_add(time::Duration::new(SESSION_DURATION, 0)) + .ok_or(SecdError::Todo)?, + ), + deleted_at: None, }) } -} -impl Credential { - pub(crate) fn encrypt(&mut self, crypter: &Crypter) -> Result<(), SecdError> { - Ok(match self.t { - CredentialType::Passphrase { - key: _, - ref mut value, + pub fn encrypt(&mut self, crypter: &Crypter) -> Result<(), SecdError> { + match self.t { + CredentialType::ApiToken { + ref mut private, .. } => { + *private = hex::encode(crypter.encrypt(private.as_bytes())?); + } + CredentialType::Passphrase { ref mut value, .. } => { *value = hex::encode(crypter.encrypt(value.as_bytes())?); } - _ => {} - }) + CredentialType::Session { ref mut secret, .. } => { + *secret = hex::encode(crypter.encrypt(secret.as_bytes())?); + } + }; + Ok(()) } - pub(crate) fn decrypt(&mut self, crypter: &Crypter) -> Result<(), SecdError> { - Ok(match self.t { - CredentialType::Passphrase { - key: _, - ref mut value, + pub fn decrypt(&mut self, crypter: &Crypter) -> Result<(), SecdError> { + match self.t { + CredentialType::ApiToken { + ref mut private, .. } => { - *value = from_utf8( + *private = from_utf8( &crypter.decrypt( - &hex::decode(value.clone()) - .map_err(|e| CrypterError::DecodeError(e.to_string()))?, + &hex::decode(private.clone()) + .map_err(|e| CrypterError::Decode(e.to_string()))?, )?, ) - .map_err(|e| CrypterError::DecodeError(e.to_string()))? + .map_err(|e| CrypterError::Decode(e.to_string()))? .to_string() } - _ => {} - }) + CredentialType::Passphrase { ref mut value, .. } => { + *value = from_utf8(&crypter.decrypt( + &hex::decode(value.clone()).map_err(|e| CrypterError::Decode(e.to_string()))?, + )?) + .map_err(|e| CrypterError::Decode(e.to_string()))? + .to_string() + } + CredentialType::Session { ref mut secret, .. } => { + *secret = from_utf8( + &crypter.decrypt( + &hex::decode(secret.clone()) + .map_err(|e| CrypterError::Decode(e.to_string()))?, + )?, + ) + .map_err(|e| CrypterError::Decode(e.to_string()))? + .to_string() + } + }; + Ok(()) } - pub(crate) fn hash(&mut self, crypter: &Crypter) -> Result<(), SecdError> { - Ok(match self.t { - CredentialType::Passphrase { - key: _, - ref mut value, + pub fn hash(&mut self, crypter: &Crypter) -> Result<(), SecdError> { + match self.t { + CredentialType::ApiToken { + ref mut private, .. } => { - *value = crypter.hash(value.as_bytes())?; + *private = crypter.weak_hash(private.as_bytes())?; } - _ => {} - }) + CredentialType::Passphrase { ref mut value, .. } => { + *value = crypter.hash(value.as_bytes(), None)?; + } + CredentialType::Session { ref mut secret, .. } => { + *secret = crypter.weak_hash(secret.as_bytes())?; + } + }; + Ok(()) + } + + pub fn hash_compare( + &mut self, + plaintext: &CredentialType, + crypter: &Crypter, + ) -> Result<(), SecdError> { + match (&self.t, plaintext) { + ( + CredentialType::ApiToken { + public: current_public, + private: current_private, + }, + CredentialType::ApiToken { + public: plaintext_public, + private: plaintext_private, + }, + ) => { + let plaintext_hash = crypter.weak_hash(plaintext_private.as_bytes())?; + if plaintext_public != current_public || &plaintext_hash != current_private { + return Err(SecdError::InvalidCredential); + } + } + ( + CredentialType::Passphrase { + key: current_key, + value: current_value, + }, + CredentialType::Passphrase { + key: plaintext_key, + value: plaintext_value, + }, + ) => { + let plaintext_hash = + crypter.hash(plaintext_value.as_bytes(), Some(current_value))?; + if plaintext_key != current_key || &plaintext_hash != current_value { + return Err(SecdError::InvalidCredential); + } + } + ( + CredentialType::Session { + key: current_key, + secret: current_secret, + }, + CredentialType::Session { + key: plaintext_key, + secret: plaintext_secret, + }, + ) => { + let plaintext_hash = crypter.weak_hash(plaintext_secret.as_bytes())?; + if plaintext_key != current_key || &plaintext_hash != current_secret { + return Err(SecdError::InvalidCredential); + } + } + _ => panic!( + "unreachable 78cfff7c-5493-42c5-add7-044241b3d713 [different credential types]" + ), + } + + Ok(()) } } |
