1use base32::{self, Alphabet};
81
82use constant_time_eq::constant_time_eq;
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum SecretParseError {
87 ParseBase32,
89}
90
91impl std::error::Error for SecretParseError {}
92
93impl std::fmt::Display for SecretParseError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 SecretParseError::ParseBase32 => write!(f, "Could not decode base32 secret."),
97 }
98 }
99}
100
101impl std::error::Error for Secret {}
102
103#[derive(Debug, Clone, Eq)]
105#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
106pub enum Secret {
107 Raw(Vec<u8>),
109 Encoded(String),
111}
112
113impl PartialEq for Secret {
114 fn eq(&self, other: &Self) -> bool {
117 constant_time_eq(&self.to_bytes().unwrap(), &other.to_bytes().unwrap())
118 }
119}
120
121#[cfg(feature = "gen_secret")]
122#[cfg_attr(docsrs, doc(cfg(feature = "gen_secret")))]
123impl Default for Secret {
124 fn default() -> Self {
125 Secret::generate_secret()
126 }
127}
128
129impl Secret {
130 pub fn to_bytes(&self) -> Result<Vec<u8>, SecretParseError> {
132 match self {
133 Secret::Raw(s) => Ok(s.to_vec()),
134 Secret::Encoded(s) => match base32::decode(Alphabet::Rfc4648 { padding: false }, s) {
135 Some(bytes) => Ok(bytes),
136 None => Err(SecretParseError::ParseBase32),
137 },
138 }
139 }
140
141 pub fn to_raw(&self) -> Result<Self, SecretParseError> {
143 match self {
144 Secret::Raw(_) => Ok(self.clone()),
145 Secret::Encoded(s) => match base32::decode(Alphabet::Rfc4648 { padding: false }, s) {
146 Some(buf) => Ok(Secret::Raw(buf)),
147 None => Err(SecretParseError::ParseBase32),
148 },
149 }
150 }
151
152 pub fn to_encoded(&self) -> Self {
154 match self {
155 Secret::Raw(s) => {
156 Secret::Encoded(base32::encode(Alphabet::Rfc4648 { padding: false }, s))
157 }
158 Secret::Encoded(_) => self.clone(),
159 }
160 }
161
162 #[cfg(feature = "gen_secret")]
170 #[cfg_attr(docsrs, doc(cfg(feature = "gen_secret")))]
171 pub fn generate_secret() -> Secret {
172 use rand::Rng;
173
174 let mut rng = rand::rng();
175 let mut secret: [u8; 20] = Default::default();
176 rng.fill(&mut secret[..]);
177 Secret::Raw(secret.to_vec())
178 }
179}
180
181impl std::fmt::Display for Secret {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 Secret::Raw(bytes) => {
185 for b in bytes {
186 write!(f, "{:02x}", b)?;
187 }
188 Ok(())
189 }
190 Secret::Encoded(s) => write!(f, "{}", s),
191 }
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::Secret;
198
199 const BASE32: &str = "OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG";
200 const BYTES: [u8; 23] = [
201 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x65,
202 0x63, 0x72, 0x65, 0x74, 0x2d, 0x31, 0x32, 0x33,
203 ];
204 const BYTES_DISPLAY: &str = "706c61696e2d737472696e672d7365637265742d313233";
205
206 #[test]
207 fn secret_display() {
208 let base32_str = String::from(BASE32);
209 let secret_raw = Secret::Raw(BYTES.to_vec());
210 let secret_base32 = Secret::Encoded(base32_str);
211 println!("{}", secret_raw);
212 assert_eq!(secret_raw.to_string(), BYTES_DISPLAY.to_string());
213 assert_eq!(secret_base32.to_string(), BASE32.to_string());
214 }
215
216 #[test]
217 fn secret_convert_base32_raw() {
218 let base32_str = String::from(BASE32);
219 let secret_raw = Secret::Raw(BYTES.to_vec());
220 let secret_base32 = Secret::Encoded(base32_str);
221
222 assert_eq!(&secret_raw.to_encoded(), &secret_base32);
223 assert_eq!(&secret_raw.to_raw().unwrap(), &secret_raw);
224
225 assert_eq!(&secret_base32.to_raw().unwrap(), &secret_raw);
226 assert_eq!(&secret_base32.to_encoded(), &secret_base32);
227 }
228
229 #[test]
230 fn secret_as_bytes() {
231 let base32_str = String::from(BASE32);
232 assert_eq!(
233 Secret::Raw(BYTES.to_vec()).to_bytes().unwrap(),
234 BYTES.to_vec()
235 );
236 assert_eq!(
237 Secret::Encoded(base32_str).to_bytes().unwrap(),
238 BYTES.to_vec()
239 );
240 }
241
242 #[test]
243 fn secret_from_string() {
244 let raw: Secret = Secret::Raw("TestSecretSuperSecret".as_bytes().to_vec());
245 let encoded: Secret = Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string());
246 assert_eq!(raw.to_encoded(), encoded);
247 assert_eq!(raw, encoded.to_raw().unwrap());
248 }
249
250 #[test]
251 #[cfg(feature = "gen_secret")]
252 fn secret_gen_secret() {
253 let sec = Secret::generate_secret();
254
255 assert!(matches!(sec, Secret::Raw(_)));
256 assert_eq!(sec.to_bytes().unwrap().len(), 20);
257 }
258
259 #[test]
260 #[cfg(feature = "gen_secret")]
261 fn secret_gen_default() {
262 let sec = Secret::default();
263
264 assert!(matches!(sec, Secret::Raw(_)));
265 assert_eq!(sec.to_bytes().unwrap().len(), 20);
266 }
267
268 #[test]
269 #[cfg(feature = "gen_secret")]
270 fn secret_empty() {
271 let non_ascii = vec![240, 159, 146, 150];
272 let sec = Secret::Encoded(std::str::from_utf8(&non_ascii).unwrap().to_owned());
273
274 let to_r = sec.to_raw();
275
276 assert!(to_r.is_err());
277
278 let to_b = sec.to_bytes();
279
280 assert!(to_b.is_err());
281 }
282}