1use crate::errors::{Error, Result, ValidationError};
2use std::fmt;
3use zeroize::Zeroizing;
4#[derive(Clone)]
5pub struct SecretKey {
6 inner: Zeroizing<String>,
7}
8impl SecretKey {
9 pub fn new(raw: String) -> Result<Self> {
10 if raw.is_empty() {
11 return Err(Error::from(ValidationError::invalid_secret_key(
12 "Secret key is empty",
13 )));
14 }
15 if !raw.starts_with("AGE-SECRET-KEY-1") {
16 return Err(Error::from(ValidationError::invalid_secret_key(
17 "Secret key must start with 'AGE-SECRET-KEY-1'",
18 )));
19 }
20 Ok(Self {
21 inner: Zeroizing::new(raw),
22 })
23 }
24 #[must_use]
25 pub fn expose_secret(&self) -> &str {
26 &self.inner
27 }
28}
29impl fmt::Debug for SecretKey {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.debug_struct("SecretKey")
32 .field("value", &"[REDACTED]")
33 .finish()
34 }
35}
36impl fmt::Display for SecretKey {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "[REDACTED]")
39 }
40}
41#[cfg(test)]
42mod tests {
43 use super::*;
44 #[test]
45 fn valid() {
46 let sk = SecretKey::new("AGE-SECRET-KEY-1TEST".into()).unwrap();
47 assert_eq!(sk.expose_secret(), "AGE-SECRET-KEY-1TEST");
48 }
49 #[test]
50 fn debug_redacted() {
51 let sk = SecretKey::new("AGE-SECRET-KEY-1TEST".into()).unwrap();
52 let d = format!("{:?}", sk);
53 assert!(d.contains("[REDACTED]"));
54 assert!(!d.contains("TEST"));
55 }
56}