1use hmac::{
2 Hmac, Mac,
3 digest::{FixedOutput, KeyInit, MacMarker, Update},
4};
5use rand::{Rng, distr::Alphanumeric};
6use serde::{Deserialize, Serialize};
7use std::{borrow::Cow, fmt, iter};
8use zino_core::{
9 LazyLock,
10 application::{Agent, Application},
11 crypto::{self, Digest},
12 encoding::base64,
13 extension::TomlTableExt,
14 state::State,
15};
16
17#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct AccessKeyId(String);
20
21impl AccessKeyId {
22 pub fn new() -> Self {
26 let mut rng = rand::rng();
27 let chars: String = iter::repeat(())
28 .map(|_| rng.sample(Alphanumeric))
29 .map(char::from)
30 .take(20)
31 .collect();
32 Self(chars)
33 }
34
35 pub fn with_length(length: u8) -> Self {
39 let mut rng = rand::rng();
40 let chars: String = iter::repeat(())
41 .map(|_| rng.sample(Alphanumeric))
42 .map(char::from)
43 .take(length.into())
44 .collect();
45 Self(chars)
46 }
47
48 #[cfg(feature = "sqids")]
51 #[inline]
52 pub fn encode_sqids(numbers: &[u64]) -> Result<Self, sqids::Error> {
53 SQIDS_GENERATOR.encode(numbers).map(AccessKeyId)
54 }
55
56 #[cfg(feature = "sqids")]
58 #[inline]
59 pub fn decode_sqids(&self) -> Vec<u64> {
60 SQIDS_GENERATOR.decode(self.as_str())
61 }
62
63 #[cfg(feature = "sqids")]
65 #[inline]
66 pub fn encode_uuid(id: &zino_core::Uuid) -> Result<Self, sqids::Error> {
67 let (hi, lo) = id.as_u64_pair();
68 SQIDS_GENERATOR.encode(&[hi, lo]).map(AccessKeyId)
69 }
70
71 #[cfg(feature = "sqids")]
73 #[inline]
74 pub fn decode_uuid(&self) -> Option<zino_core::Uuid> {
75 if let [hi, lo] = SQIDS_GENERATOR.decode(self.as_str()).as_slice() {
76 Some(zino_core::Uuid::from_u64_pair(*hi, *lo))
77 } else {
78 None
79 }
80 }
81
82 #[inline]
84 pub fn as_str(&self) -> &str {
85 self.0.as_str()
86 }
87}
88
89impl fmt::Display for AccessKeyId {
90 #[inline]
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 self.0.fmt(f)
93 }
94}
95
96impl AsRef<[u8]> for AccessKeyId {
97 #[inline]
98 fn as_ref(&self) -> &[u8] {
99 self.0.as_ref()
100 }
101}
102
103impl From<String> for AccessKeyId {
104 #[inline]
105 fn from(s: String) -> Self {
106 Self(s)
107 }
108}
109
110impl From<&str> for AccessKeyId {
111 #[inline]
112 fn from(s: &str) -> Self {
113 Self(s.to_owned())
114 }
115}
116
117impl<'a> From<Cow<'a, str>> for AccessKeyId {
118 #[inline]
119 fn from(s: Cow<'a, str>) -> Self {
120 Self(s.into_owned())
121 }
122}
123
124impl From<AccessKeyId> for String {
125 #[inline]
126 fn from(id: AccessKeyId) -> String {
127 id.0
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct SecretAccessKey(Vec<u8>);
134
135impl SecretAccessKey {
136 #[inline]
138 pub fn new(access_key_id: &AccessKeyId) -> Self {
139 Self::with_key::<Hmac<Digest>>(access_key_id, SECRET_KEY.as_ref())
140 }
141
142 pub fn with_key<H>(access_key_id: &AccessKeyId, key: impl AsRef<[u8]>) -> Self
144 where
145 H: FixedOutput + KeyInit + MacMarker + Update,
146 {
147 fn inner<H>(access_key_id: &AccessKeyId, key: &[u8]) -> SecretAccessKey
148 where
149 H: FixedOutput + KeyInit + MacMarker + Update,
150 {
151 let mut mac = H::new_from_slice(key).expect("HMAC can take key of any size");
152 mac.update(access_key_id.as_ref());
153 SecretAccessKey(mac.finalize().into_bytes().to_vec())
154 }
155 inner::<H>(access_key_id, key.as_ref())
156 }
157
158 #[inline]
160 pub fn as_bytes(&self) -> &[u8] {
161 self.0.as_slice()
162 }
163}
164
165impl fmt::Display for SecretAccessKey {
166 #[inline]
167 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168 base64::encode(self.as_bytes()).fmt(f)
169 }
170}
171
172impl AsRef<[u8]> for SecretAccessKey {
173 #[inline]
174 fn as_ref(&self) -> &[u8] {
175 self.0.as_ref()
176 }
177}
178
179impl From<SecretAccessKey> for Vec<u8> {
180 #[inline]
181 fn from(s: SecretAccessKey) -> Vec<u8> {
182 s.0
183 }
184}
185
186static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
188 let app_config = State::shared().config();
189 let config = app_config.get_table("access-key").unwrap_or(app_config);
190 let checksum: [u8; 32] = config
191 .get_str("checksum")
192 .and_then(|checksum| checksum.as_bytes().try_into().ok())
193 .unwrap_or_else(|| {
194 let secret = config.get_str("secret").unwrap_or_else(|| {
195 tracing::warn!("auto-generated `secret` is used for deriving a secret key");
196 Agent::name()
197 });
198 crypto::digest(secret.as_bytes())
199 });
200 let info = config.get_str("info").unwrap_or("ZINO:ACCESS-KEY");
201 crypto::derive_key(info, &checksum)
202});
203
204#[cfg(feature = "sqids")]
206static SQIDS_GENERATOR: LazyLock<sqids::Sqids> = LazyLock::new(sqids::Sqids::default);