aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/util/mod.rs
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2023-05-22 15:47:06 -0700
committerbenj <benj@rse8.com>2023-05-22 15:47:06 -0700
commited34a5251f13bbded0aa15719887db4924b351eb (patch)
tree9719d805e915f4483d5db3e5e612e8b4cf5c702c /crates/secd/src/util/mod.rs
parenteb92f823c31a5e702af7005231f0d6915aad3342 (diff)
downloadsecdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.gz
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.bz2
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.lz
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.xz
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.zst
secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.zip
update credential API to include sessions
This change updates the credential API to include sessions as just another credential type. It adds the ApiToken type and enables revocation of credentials. Updates were also made to the Identity API which now includes a list of new credentials added to an Identity. This change also migrates off the hacky ENV configuration paradigm and includes a new config.toml file specified by the SECD_CONFIG_PATH env var. No default is currently provided. Clippy updates and code cleanup.
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(())
}
}