use hmac::{
digest::{FixedOutput, KeyInit, MacMarker, Update},
Hmac, Mac,
};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt, iter};
use zino_core::{
application::{Agent, Application},
crypto::{self, Digest},
encoding::base64,
extension::TomlTableExt,
state::State,
LazyLock,
};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccessKeyId(String);
impl AccessKeyId {
pub fn new() -> Self {
let mut rng = rand::thread_rng();
let chars: String = iter::repeat(())
.map(|_| rng.sample(Alphanumeric))
.map(char::from)
.take(20)
.collect();
Self(chars)
}
pub fn with_length(length: u8) -> Self {
let mut rng = rand::thread_rng();
let chars: String = iter::repeat(())
.map(|_| rng.sample(Alphanumeric))
.map(char::from)
.take(length.into())
.collect();
Self(chars)
}
#[cfg(feature = "sqids")]
#[inline]
pub fn encode_sqids(numbers: &[u64]) -> Result<Self, sqids::Error> {
SQIDS_GENERATOR.encode(numbers).map(AccessKeyId)
}
#[cfg(feature = "sqids")]
#[inline]
pub fn decode_sqids(&self) -> Vec<u64> {
SQIDS_GENERATOR.decode(self.as_str())
}
#[cfg(feature = "sqids")]
#[inline]
pub fn encode_uuid(id: &zino_core::Uuid) -> Result<Self, sqids::Error> {
let (hi, lo) = id.as_u64_pair();
SQIDS_GENERATOR.encode(&[hi, lo]).map(AccessKeyId)
}
#[cfg(feature = "sqids")]
#[inline]
pub fn decode_uuid(&self) -> Option<zino_core::Uuid> {
if let [hi, lo] = SQIDS_GENERATOR.decode(self.as_str()).as_slice() {
Some(zino_core::Uuid::from_u64_pair(*hi, *lo))
} else {
None
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl fmt::Display for AccessKeyId {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl AsRef<[u8]> for AccessKeyId {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl From<String> for AccessKeyId {
#[inline]
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for AccessKeyId {
#[inline]
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl<'a> From<Cow<'a, str>> for AccessKeyId {
#[inline]
fn from(s: Cow<'a, str>) -> Self {
Self(s.into_owned())
}
}
impl From<AccessKeyId> for String {
#[inline]
fn from(id: AccessKeyId) -> String {
id.0
}
}
#[derive(Debug, Clone)]
pub struct SecretAccessKey(Vec<u8>);
impl SecretAccessKey {
#[inline]
pub fn new(access_key_id: &AccessKeyId) -> Self {
Self::with_key::<Hmac<Digest>>(access_key_id, SECRET_KEY.as_ref())
}
pub fn with_key<H>(access_key_id: &AccessKeyId, key: impl AsRef<[u8]>) -> Self
where
H: FixedOutput + KeyInit + MacMarker + Update,
{
fn inner<H>(access_key_id: &AccessKeyId, key: &[u8]) -> SecretAccessKey
where
H: FixedOutput + KeyInit + MacMarker + Update,
{
let mut mac = H::new_from_slice(key).expect("HMAC can take key of any size");
mac.update(access_key_id.as_ref());
SecretAccessKey(mac.finalize().into_bytes().to_vec())
}
inner::<H>(access_key_id, key.as_ref())
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
}
impl fmt::Display for SecretAccessKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", base64::encode(self.as_bytes()))
}
}
impl AsRef<[u8]> for SecretAccessKey {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl From<SecretAccessKey> for Vec<u8> {
#[inline]
fn from(s: SecretAccessKey) -> Vec<u8> {
s.0
}
}
static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
let app_config = State::shared().config();
let config = app_config.get_table("access-key").unwrap_or(app_config);
let checksum: [u8; 32] = config
.get_str("checksum")
.and_then(|checksum| checksum.as_bytes().try_into().ok())
.unwrap_or_else(|| {
let secret = config.get_str("secret").unwrap_or_else(|| {
tracing::warn!("auto-generated `secret` is used for deriving a secret key");
Agent::name()
});
crypto::digest(secret.as_bytes())
});
let info = config.get_str("info").unwrap_or("ZINO:ACCESS-KEY");
crypto::derive_key(info, &checksum)
});
#[cfg(feature = "sqids")]
static SQIDS_GENERATOR: LazyLock<sqids::Sqids> = LazyLock::new(sqids::Sqids::default);