1use anyhow::{Context, Result};
18use base64::{engine::general_purpose, Engine as _};
19use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
20use hmac::{Hmac, Mac};
21use serde::{Deserialize, Serialize};
22use sha2::Sha256;
23use std::time::{SystemTime, UNIX_EPOCH};
24
25type HmacSha256 = Hmac<Sha256>;
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SigningConfig {
30 pub enabled: bool,
32
33 pub algorithm: SigningAlgorithm,
35
36 pub hmac_secret: Option<String>,
38
39 pub ed25519_private_key: Option<String>,
41
42 pub require_signatures: bool,
44
45 pub grace_period_seconds: u64,
47
48 pub include_timestamp: bool,
50
51 pub max_clock_skew_seconds: u64,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
57#[serde(rename_all = "snake_case")]
58pub enum SigningAlgorithm {
59 HmacSha256,
60 Ed25519,
61}
62
63impl std::fmt::Display for SigningAlgorithm {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 match self {
66 Self::HmacSha256 => write!(f, "hmac-sha256"),
67 Self::Ed25519 => write!(f, "ed25519"),
68 }
69 }
70}
71
72impl Default for SigningConfig {
73 fn default() -> Self {
74 Self {
75 enabled: false,
76 algorithm: SigningAlgorithm::HmacSha256,
77 hmac_secret: None,
78 ed25519_private_key: None,
79 require_signatures: false,
80 grace_period_seconds: 86400, include_timestamp: true,
82 max_clock_skew_seconds: 300, }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct MessageSignature {
90 pub algorithm: SigningAlgorithm,
92
93 pub signature: String,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub timestamp: Option<u64>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub key_id: Option<String>,
103}
104
105pub struct SigningManager {
107 config: SigningConfig,
108 hmac_key: Option<Vec<u8>>,
109 signing_key: Option<SigningKey>,
110 verifying_key: Option<VerifyingKey>,
111 start_time: SystemTime,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct SignedMessage {
117 pub message: serde_json::Value,
119
120 pub signature: MessageSignature,
122}
123
124impl SigningManager {
125 pub fn new(config: SigningConfig) -> Result<Self> {
127 let mut hmac_key = None;
128 let mut signing_key = None;
129 let mut verifying_key = None;
130
131 if config.enabled {
132 match config.algorithm {
133 SigningAlgorithm::HmacSha256 => {
134 if let Some(secret) = &config.hmac_secret {
135 let key = general_purpose::STANDARD
136 .decode(secret)
137 .context("Invalid HMAC secret base64")?;
138 if key.len() < 32 {
139 anyhow::bail!("HMAC secret must be at least 32 bytes");
140 }
141 hmac_key = Some(key);
142 } else {
143 anyhow::bail!("HMAC secret required when HMAC-SHA256 is enabled");
144 }
145 },
146 SigningAlgorithm::Ed25519 => {
147 if let Some(private_key) = &config.ed25519_private_key {
148 let key_bytes = general_purpose::STANDARD
149 .decode(private_key)
150 .context("Invalid Ed25519 private key base64")?;
151
152 if key_bytes.len() != 32 {
153 anyhow::bail!("Ed25519 private key must be exactly 32 bytes");
154 }
155
156 let key_array: [u8; 32] = match key_bytes.try_into() {
157 Ok(arr) => arr,
158 Err(_) => anyhow::bail!("Failed to convert key bytes to array"),
159 };
160 let secret = SigningKey::from_bytes(&key_array);
161 verifying_key = Some(secret.verifying_key());
162 signing_key = Some(secret);
163 } else {
164 anyhow::bail!("Ed25519 private key required when Ed25519 is enabled");
165 }
166 },
167 }
168 }
169
170 Ok(Self {
171 config,
172 hmac_key,
173 signing_key,
174 verifying_key,
175 start_time: SystemTime::now(),
176 })
177 }
178
179 pub fn sign_message(&self, message: &serde_json::Value) -> Result<SignedMessage> {
181 if !self.config.enabled {
182 anyhow::bail!("Message signing is not enabled");
183 }
184
185 let timestamp = if self.config.include_timestamp {
186 Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs())
187 } else {
188 None
189 };
190
191 let canonical = self.canonicalize_message(message, timestamp)?;
193
194 let signature_value = match self.config.algorithm {
195 SigningAlgorithm::HmacSha256 => {
196 let key = self
197 .hmac_key
198 .as_ref()
199 .ok_or_else(|| anyhow::anyhow!("HMAC key not initialized"))?;
200
201 let mut mac = HmacSha256::new_from_slice(key)?;
202 mac.update(canonical.as_bytes());
203 let result = mac.finalize();
204
205 general_purpose::STANDARD.encode(result.into_bytes())
206 },
207 SigningAlgorithm::Ed25519 => {
208 let signing_key = self
209 .signing_key
210 .as_ref()
211 .ok_or_else(|| anyhow::anyhow!("Ed25519 key not initialized"))?;
212
213 let signature = signing_key.sign(canonical.as_bytes());
214 general_purpose::STANDARD.encode(signature.to_bytes())
215 },
216 };
217
218 Ok(SignedMessage {
219 message: message.clone(),
220 signature: MessageSignature {
221 algorithm: self.config.algorithm.clone(),
222 signature: signature_value,
223 timestamp,
224 key_id: None, },
226 })
227 }
228
229 pub fn verify_message(&self, signed: &SignedMessage) -> Result<()> {
231 if !self.config.enabled {
232 return Ok(());
234 }
235
236 if !self.config.require_signatures {
238 let elapsed = SystemTime::now().duration_since(self.start_time)?.as_secs();
239
240 if elapsed < self.config.grace_period_seconds {
241 return Ok(());
243 }
244 }
245
246 if let Some(timestamp) = signed.signature.timestamp {
248 let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
249
250 let time_diff = if timestamp > now {
251 timestamp - now
252 } else {
253 now - timestamp
254 };
255
256 if time_diff > self.config.max_clock_skew_seconds {
257 anyhow::bail!("Message timestamp outside acceptable range");
258 }
259 }
260
261 if signed.signature.algorithm != self.config.algorithm {
263 anyhow::bail!("Signature algorithm mismatch");
264 }
265
266 let canonical = self.canonicalize_message(&signed.message, signed.signature.timestamp)?;
268
269 match signed.signature.algorithm {
271 SigningAlgorithm::HmacSha256 => {
272 let key = self
273 .hmac_key
274 .as_ref()
275 .ok_or_else(|| anyhow::anyhow!("HMAC key not initialized"))?;
276
277 let mut mac = HmacSha256::new_from_slice(key)?;
278 mac.update(canonical.as_bytes());
279
280 let expected = general_purpose::STANDARD.decode(&signed.signature.signature)?;
281
282 mac.verify_slice(&expected)
283 .map_err(|_| anyhow::anyhow!("HMAC verification failed"))?;
284 },
285 SigningAlgorithm::Ed25519 => {
286 let verifying_key = self
287 .verifying_key
288 .as_ref()
289 .ok_or_else(|| anyhow::anyhow!("Ed25519 verifying key not initialized"))?;
290
291 let signature_bytes =
292 general_purpose::STANDARD.decode(&signed.signature.signature)?;
293
294 let signature = Signature::from_slice(&signature_bytes)
295 .context("Invalid Ed25519 signature format")?;
296
297 verifying_key
298 .verify(canonical.as_bytes(), &signature)
299 .map_err(|_| anyhow::anyhow!("Ed25519 verification failed"))?;
300 },
301 }
302
303 Ok(())
304 }
305
306 fn canonicalize_message(
308 &self,
309 message: &serde_json::Value,
310 timestamp: Option<u64>,
311 ) -> Result<String> {
312 let mut canonical = serde_json::to_string(message)?;
314
315 if let Some(ts) = timestamp {
316 canonical.push_str(&format!("|timestamp:{ts}"));
317 }
318
319 Ok(canonical)
320 }
321
322 pub fn extract_signature(authorization: &str) -> Option<MessageSignature> {
324 if !authorization.starts_with("Signature ") {
326 return None;
327 }
328
329 let parts = authorization.trim_start_matches("Signature ");
330 let mut algorithm = None;
331 let mut signature = None;
332 let mut timestamp = None;
333 let mut key_id = None;
334
335 for part in parts.split(',') {
336 let kv: Vec<&str> = part.trim().splitn(2, '=').collect();
337 if kv.len() != 2 {
338 continue;
339 }
340
341 match kv[0] {
342 "algorithm" => {
343 algorithm = match kv[1] {
344 "hmac-sha256" => Some(SigningAlgorithm::HmacSha256),
345 "ed25519" => Some(SigningAlgorithm::Ed25519),
346 _ => None,
347 };
348 },
349 "signature" => signature = Some(kv[1].to_string()),
350 "timestamp" => timestamp = kv[1].parse().ok(),
351 "keyid" => key_id = Some(kv[1].to_string()),
352 _ => {},
353 }
354 }
355
356 match (algorithm, signature) {
357 (Some(alg), Some(sig)) => Some(MessageSignature {
358 algorithm: alg,
359 signature: sig,
360 timestamp,
361 key_id,
362 }),
363 _ => None,
364 }
365 }
366
367 pub fn create_auth_header(signature: &MessageSignature) -> String {
369 let mut parts = vec![
370 format!(
371 "algorithm={}",
372 match signature.algorithm {
373 SigningAlgorithm::HmacSha256 => "hmac-sha256",
374 SigningAlgorithm::Ed25519 => "ed25519",
375 }
376 ),
377 format!("signature={}", signature.signature),
378 ];
379
380 if let Some(ts) = signature.timestamp {
381 parts.push(format!("timestamp={ts}"));
382 }
383
384 if let Some(kid) = &signature.key_id {
385 parts.push(format!("keyid={kid}"));
386 }
387
388 format!("Signature {}", parts.join(","))
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_hmac_signing() {
398 let config = SigningConfig {
399 enabled: true,
400 algorithm: SigningAlgorithm::HmacSha256,
401 hmac_secret: Some(
402 general_purpose::STANDARD.encode(b"FAKE-TEST-KEY-DO-NOT-USE-IN-PROD-32b"),
403 ),
404 include_timestamp: false,
405 require_signatures: true,
406 grace_period_seconds: 0, ..Default::default()
408 };
409
410 let manager = SigningManager::new(config).unwrap();
411 let message = serde_json::json!({"method": "test", "params": {}});
412
413 let signed = manager.sign_message(&message).unwrap();
414 assert_eq!(signed.signature.algorithm, SigningAlgorithm::HmacSha256);
415
416 manager.verify_message(&signed).unwrap();
418
419 let mut tampered = signed.clone();
421 tampered.message = serde_json::json!({"method": "tampered", "params": {}});
422 assert!(manager.verify_message(&tampered).is_err());
423 }
424
425 #[test]
426 fn test_ed25519_signing() {
427 use rand::{rngs::OsRng, RngCore};
428
429 let mut secret_key_bytes = [0u8; 32];
431 OsRng.fill_bytes(&mut secret_key_bytes);
432 let signing_key = SigningKey::from_bytes(&secret_key_bytes);
433 let private_key_base64 = general_purpose::STANDARD.encode(signing_key.to_bytes());
434
435 let config = SigningConfig {
436 enabled: true,
437 algorithm: SigningAlgorithm::Ed25519,
438 ed25519_private_key: Some(private_key_base64),
439 include_timestamp: true,
440 ..Default::default()
441 };
442
443 let manager = SigningManager::new(config).unwrap();
444 let message = serde_json::json!({"method": "test", "params": {}});
445
446 let signed = manager.sign_message(&message).unwrap();
447 assert_eq!(signed.signature.algorithm, SigningAlgorithm::Ed25519);
448 assert!(signed.signature.timestamp.is_some());
449
450 manager.verify_message(&signed).unwrap();
452 }
453
454 #[test]
455 fn test_signature_extraction() {
456 let auth = "Signature algorithm=hmac-sha256,signature=abc123,timestamp=1234567890";
457 let sig = SigningManager::extract_signature(auth).unwrap();
458
459 assert_eq!(sig.algorithm, SigningAlgorithm::HmacSha256);
460 assert_eq!(sig.signature, "abc123");
461 assert_eq!(sig.timestamp, Some(1234567890));
462 }
463}