aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/util/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/secd/src/util/mod.rs')
-rw-r--r--crates/secd/src/util/mod.rs275
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(())
}
}