entrouter_universal/
signed_envelope.rs1use crate::{fingerprint_str, UniversalError};
12use base64::{
13 engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
14 Engine,
15};
16use hmac::{Hmac, Mac};
17use serde::{Deserialize, Serialize};
18use sha2::Sha256;
19use std::time::{SystemTime, UNIX_EPOCH};
20
21#[cfg(feature = "compression")]
22use crate::compress::{compress, decompress};
23
24type HmacSha256 = Hmac<Sha256>;
25
26#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
28pub enum SignedEnvelopeMode {
29 Standard,
30 UrlSafe,
31 Compressed,
32 Ttl,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SignedEnvelope {
51 pub d: String,
53 pub f: String,
55 pub sig: String,
57 pub m: SignedEnvelopeMode,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub e: Option<u64>,
62 pub v: u8,
64}
65
66fn hmac_sign(fingerprint: &str, key: &str) -> String {
67 let mut mac = HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC accepts any key length");
68 mac.update(fingerprint.as_bytes());
69 hex::encode(mac.finalize().into_bytes())
70}
71
72fn hmac_verify(fingerprint: &str, key: &str, sig: &str) -> bool {
73 let mut mac = HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC accepts any key length");
74 mac.update(fingerprint.as_bytes());
75 let expected = hex::decode(sig).unwrap_or_default();
76 mac.verify_slice(&expected).is_ok()
77}
78
79impl SignedEnvelope {
80 #[must_use]
84 pub fn wrap(input: &str, key: &str) -> Self {
85 let fp = fingerprint_str(input);
86 Self {
87 d: STANDARD.encode(input.as_bytes()),
88 sig: hmac_sign(&fp, key),
89 f: fp,
90 m: SignedEnvelopeMode::Standard,
91 e: None,
92 v: 3,
93 }
94 }
95
96 #[must_use]
98 pub fn wrap_url_safe(input: &str, key: &str) -> Self {
99 let fp = fingerprint_str(input);
100 Self {
101 d: URL_SAFE_NO_PAD.encode(input.as_bytes()),
102 sig: hmac_sign(&fp, key),
103 f: fp,
104 m: SignedEnvelopeMode::UrlSafe,
105 e: None,
106 v: 3,
107 }
108 }
109
110 #[cfg(feature = "compression")]
112 pub fn wrap_compressed(input: &str, key: &str) -> Result<Self, UniversalError> {
113 let compressed = compress(input.as_bytes())?;
114 let fp = fingerprint_str(input);
115 Ok(Self {
116 d: STANDARD.encode(&compressed),
117 sig: hmac_sign(&fp, key),
118 f: fp,
119 m: SignedEnvelopeMode::Compressed,
120 e: None,
121 v: 3,
122 })
123 }
124
125 #[must_use]
127 pub fn wrap_with_ttl(input: &str, key: &str, ttl_secs: u64) -> Self {
128 let now = SystemTime::now()
129 .duration_since(UNIX_EPOCH)
130 .unwrap_or_default()
131 .as_secs();
132 let fp = fingerprint_str(input);
133 Self {
134 d: STANDARD.encode(input.as_bytes()),
135 sig: hmac_sign(&fp, key),
136 f: fp,
137 m: SignedEnvelopeMode::Ttl,
138 e: Some(now + ttl_secs),
139 v: 3,
140 }
141 }
142
143 pub fn unwrap_verified(&self, key: &str) -> Result<String, UniversalError> {
147 if !hmac_verify(&self.f, key, &self.sig) {
149 return Err(UniversalError::MalformedEnvelope(
150 "HMAC signature invalid -- wrong key or tampered envelope".into(),
151 ));
152 }
153
154 if let Some(expiry) = self.e {
156 let now = SystemTime::now()
157 .duration_since(UNIX_EPOCH)
158 .unwrap_or_default()
159 .as_secs();
160 if now >= expiry {
161 return Err(UniversalError::Expired {
162 expired_at: expiry,
163 now,
164 });
165 }
166 }
167
168 let bytes = match self.m {
170 SignedEnvelopeMode::Standard | SignedEnvelopeMode::Ttl => STANDARD
171 .decode(&self.d)
172 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
173 SignedEnvelopeMode::UrlSafe => URL_SAFE_NO_PAD
174 .decode(&self.d)
175 .map_err(|e| UniversalError::DecodeError(e.to_string()))?,
176 #[cfg(feature = "compression")]
177 SignedEnvelopeMode::Compressed => {
178 let compressed = STANDARD
179 .decode(&self.d)
180 .map_err(|e| UniversalError::DecodeError(e.to_string()))?;
181 decompress(&compressed)?
182 }
183 #[cfg(not(feature = "compression"))]
184 SignedEnvelopeMode::Compressed => {
185 return Err(UniversalError::DecodeError(
186 "compression feature not enabled".to_string(),
187 ))
188 }
189 };
190
191 let decoded =
192 String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))?;
193
194 let actual_fp = fingerprint_str(&decoded);
196 if actual_fp != self.f {
197 return Err(UniversalError::IntegrityViolation {
198 expected: self.f.clone(),
199 actual: actual_fp,
200 });
201 }
202
203 Ok(decoded)
204 }
205
206 pub fn to_json(&self) -> Result<String, UniversalError> {
208 serde_json::to_string(self).map_err(|e| UniversalError::SerializationError(e.to_string()))
209 }
210
211 pub fn from_json(s: &str) -> Result<Self, UniversalError> {
213 serde_json::from_str(s).map_err(|e| UniversalError::SerializationError(e.to_string()))
214 }
215}