1use crate::sanitize::TaskValue;
47use serde::{Deserialize, Serialize};
48use std::fmt;
49use uuid::Uuid;
50
51const SHA256_BLOCK_BYTES: usize = 64;
57
58pub const SHA256_DIGEST_BYTES: usize = 32;
60
61const SHA256_K: [u32; 64] = [
64 0x428a_2f98,
65 0x7137_4491,
66 0xb5c0_fbcf,
67 0xe9b5_dba5,
68 0x3956_c25b,
69 0x59f1_11f1,
70 0x923f_82a4,
71 0xab1c_5ed5,
72 0xd807_aa98,
73 0x1283_5b01,
74 0x2431_85be,
75 0x550c_7dc3,
76 0x72be_5d74,
77 0x80de_b1fe,
78 0x9bdc_06a7,
79 0xc19b_f174,
80 0xe49b_69c1,
81 0xefbe_4786,
82 0x0fc1_9dc6,
83 0x240c_a1cc,
84 0x2de9_2c6f,
85 0x4a74_84aa,
86 0x5cb0_a9dc,
87 0x76f9_88da,
88 0x983e_5152,
89 0xa831_c66d,
90 0xb003_27c8,
91 0xbf59_7fc7,
92 0xc6e0_0bf3,
93 0xd5a7_9147,
94 0x06ca_6351,
95 0x1429_2967,
96 0x27b7_0a85,
97 0x2e1b_2138,
98 0x4d2c_6dfc,
99 0x5338_0d13,
100 0x650a_7354,
101 0x766a_0abb,
102 0x81c2_c92e,
103 0x9272_2c85,
104 0xa2bf_e8a1,
105 0xa81a_664b,
106 0xc24b_8b70,
107 0xc76c_51a3,
108 0xd192_e819,
109 0xd699_0624,
110 0xf40e_3585,
111 0x106a_a070,
112 0x19a4_c116,
113 0x1e37_6c08,
114 0x2748_774c,
115 0x34b0_bcb5,
116 0x391c_0cb3,
117 0x4ed8_aa4a,
118 0x5b9c_ca4f,
119 0x682e_6ff3,
120 0x748f_82ee,
121 0x78a5_636f,
122 0x84c8_7814,
123 0x8cc7_0208,
124 0x90be_fffa,
125 0xa450_6ceb,
126 0xbef9_a3f7,
127 0xc671_78f2,
128];
129
130const SHA256_H0: [u32; 8] = [
133 0x6a09_e667,
134 0xbb67_ae85,
135 0x3c6e_f372,
136 0xa54f_f53a,
137 0x510e_527f,
138 0x9b05_688c,
139 0x1f83_d9ab,
140 0x5be0_cd19,
141];
142
143#[derive(Clone)]
149pub struct Sha256 {
150 state: [u32; 8],
151 buffer: [u8; SHA256_BLOCK_BYTES],
152 buffer_len: usize,
153 total_len: u64,
154}
155
156impl Default for Sha256 {
157 #[inline]
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl fmt::Debug for Sha256 {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 f.debug_struct("Sha256")
167 .field("total_len", &self.total_len)
168 .finish_non_exhaustive()
169 }
170}
171
172impl Sha256 {
173 #[inline]
175 #[must_use]
176 pub const fn new() -> Self {
177 Self {
178 state: SHA256_H0,
179 buffer: [0u8; SHA256_BLOCK_BYTES],
180 buffer_len: 0,
181 total_len: 0,
182 }
183 }
184
185 pub fn update(&mut self, mut data: &[u8]) {
187 self.total_len = self.total_len.wrapping_add(data.len() as u64);
188
189 if self.buffer_len > 0 {
191 let need = SHA256_BLOCK_BYTES - self.buffer_len;
192 let take = need.min(data.len());
193 self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&data[..take]);
194 self.buffer_len += take;
195 data = &data[take..];
196
197 if self.buffer_len == SHA256_BLOCK_BYTES {
198 let block = self.buffer;
199 Self::compress(&mut self.state, &block);
200 self.buffer_len = 0;
201 }
202 }
203
204 while data.len() >= SHA256_BLOCK_BYTES {
206 let mut block = [0u8; SHA256_BLOCK_BYTES];
207 block.copy_from_slice(&data[..SHA256_BLOCK_BYTES]);
208 Self::compress(&mut self.state, &block);
209 data = &data[SHA256_BLOCK_BYTES..];
210 }
211
212 if !data.is_empty() {
214 self.buffer[..data.len()].copy_from_slice(data);
215 self.buffer_len = data.len();
216 }
217 }
218
219 #[must_use]
221 pub fn finalize(mut self) -> [u8; SHA256_DIGEST_BYTES] {
222 let bit_len = self.total_len.wrapping_mul(8);
223
224 self.update_byte(0x80);
226
227 while self.buffer_len != 56 {
230 self.update_byte(0x00);
231 }
232
233 let len_bytes = bit_len.to_be_bytes();
235 for b in len_bytes {
236 self.update_byte(b);
237 }
238
239 debug_assert_eq!(self.buffer_len, 0, "buffer must be flushed after padding");
240
241 let mut out = [0u8; SHA256_DIGEST_BYTES];
242 for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
243 chunk.copy_from_slice(&word.to_be_bytes());
244 }
245 out
246 }
247
248 #[must_use]
250 pub fn digest(data: &[u8]) -> [u8; SHA256_DIGEST_BYTES] {
251 let mut h = Self::new();
252 h.update(data);
253 h.finalize()
254 }
255
256 #[inline]
259 fn update_byte(&mut self, byte: u8) {
260 self.buffer[self.buffer_len] = byte;
261 self.buffer_len += 1;
262 if self.buffer_len == SHA256_BLOCK_BYTES {
263 let block = self.buffer;
264 Self::compress(&mut self.state, &block);
265 self.buffer_len = 0;
266 }
267 }
268
269 #[allow(clippy::many_single_char_names)]
271 fn compress(state: &mut [u32; 8], block: &[u8; SHA256_BLOCK_BYTES]) {
272 let mut w = [0u32; 64];
273 for (i, word) in w.iter_mut().enumerate().take(16) {
274 let j = i * 4;
275 *word = u32::from_be_bytes([block[j], block[j + 1], block[j + 2], block[j + 3]]);
276 }
277 for i in 16..64 {
278 let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
279 let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
280 w[i] = w[i - 16]
281 .wrapping_add(s0)
282 .wrapping_add(w[i - 7])
283 .wrapping_add(s1);
284 }
285
286 let mut a = state[0];
287 let mut b = state[1];
288 let mut c = state[2];
289 let mut d = state[3];
290 let mut e = state[4];
291 let mut f = state[5];
292 let mut g = state[6];
293 let mut h = state[7];
294
295 for i in 0..64 {
296 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
297 let ch = (e & f) ^ ((!e) & g);
298 let temp1 = h
299 .wrapping_add(s1)
300 .wrapping_add(ch)
301 .wrapping_add(SHA256_K[i])
302 .wrapping_add(w[i]);
303 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
304 let maj = (a & b) ^ (a & c) ^ (b & c);
305 let temp2 = s0.wrapping_add(maj);
306
307 h = g;
308 g = f;
309 f = e;
310 e = d.wrapping_add(temp1);
311 d = c;
312 c = b;
313 b = a;
314 a = temp1.wrapping_add(temp2);
315 }
316
317 state[0] = state[0].wrapping_add(a);
318 state[1] = state[1].wrapping_add(b);
319 state[2] = state[2].wrapping_add(c);
320 state[3] = state[3].wrapping_add(d);
321 state[4] = state[4].wrapping_add(e);
322 state[5] = state[5].wrapping_add(f);
323 state[6] = state[6].wrapping_add(g);
324 state[7] = state[7].wrapping_add(h);
325 }
326}
327
328const HMAC_IPAD: u8 = 0x36;
334const HMAC_OPAD: u8 = 0x5c;
335
336#[derive(Clone)]
341pub struct HmacSha256 {
342 inner: Sha256,
343 outer_key: [u8; SHA256_BLOCK_BYTES],
344}
345
346impl fmt::Debug for HmacSha256 {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.debug_struct("HmacSha256").finish_non_exhaustive()
350 }
351}
352
353impl HmacSha256 {
354 #[must_use]
359 pub fn new(key: &[u8]) -> Self {
360 let mut block_key = [0u8; SHA256_BLOCK_BYTES];
361 if key.len() > SHA256_BLOCK_BYTES {
362 let digest = Sha256::digest(key);
363 block_key[..SHA256_DIGEST_BYTES].copy_from_slice(&digest);
364 } else {
365 block_key[..key.len()].copy_from_slice(key);
366 }
367
368 let mut inner_key = [0u8; SHA256_BLOCK_BYTES];
369 let mut outer_key = [0u8; SHA256_BLOCK_BYTES];
370 for i in 0..SHA256_BLOCK_BYTES {
371 inner_key[i] = block_key[i] ^ HMAC_IPAD;
372 outer_key[i] = block_key[i] ^ HMAC_OPAD;
373 }
374
375 let mut inner = Sha256::new();
376 inner.update(&inner_key);
377
378 block_key.iter_mut().for_each(|b| *b = 0);
380 inner_key.iter_mut().for_each(|b| *b = 0);
381
382 Self { inner, outer_key }
383 }
384
385 #[inline]
387 pub fn update(&mut self, data: &[u8]) {
388 self.inner.update(data);
389 }
390
391 #[must_use]
393 pub fn finalize(self) -> [u8; SHA256_DIGEST_BYTES] {
394 let inner_digest = self.inner.finalize();
395 let mut outer = Sha256::new();
396 outer.update(&self.outer_key);
397 outer.update(&inner_digest);
398 outer.finalize()
399 }
400
401 #[must_use]
403 pub fn mac(key: &[u8], data: &[u8]) -> [u8; SHA256_DIGEST_BYTES] {
404 let mut h = Self::new(key);
405 h.update(data);
406 h.finalize()
407 }
408}
409
410#[must_use]
416pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
417 if a.len() != b.len() {
418 return false;
419 }
420 let mut diff: u8 = 0;
421 for (x, y) in a.iter().zip(b.iter()) {
422 diff |= x ^ y;
423 }
424 diff == 0
425}
426
427#[must_use]
433pub fn to_hex(bytes: &[u8]) -> String {
434 const LUT: &[u8; 16] = b"0123456789abcdef";
435 let mut out = String::with_capacity(bytes.len() * 2);
436 for &b in bytes {
437 out.push(LUT[(b >> 4) as usize] as char);
438 out.push(LUT[(b & 0x0f) as usize] as char);
439 }
440 out
441}
442
443pub fn from_hex(s: &str) -> Result<Vec<u8>, SignatureError> {
450 let bytes = s.as_bytes();
451 if !bytes.len().is_multiple_of(2) {
452 return Err(SignatureError::MalformedSignature(
453 "hex string has odd length".to_string(),
454 ));
455 }
456 let mut out = Vec::with_capacity(bytes.len() / 2);
457 let mut i = 0;
458 while i < bytes.len() {
459 let hi = hex_val(bytes[i])?;
460 let lo = hex_val(bytes[i + 1])?;
461 out.push((hi << 4) | lo);
462 i += 2;
463 }
464 Ok(out)
465}
466
467#[inline]
468fn hex_val(c: u8) -> Result<u8, SignatureError> {
469 match c {
470 b'0'..=b'9' => Ok(c - b'0'),
471 b'a'..=b'f' => Ok(c - b'a' + 10),
472 b'A'..=b'F' => Ok(c - b'A' + 10),
473 _ => Err(SignatureError::MalformedSignature(format!(
474 "invalid hex character: {:?}",
475 c as char
476 ))),
477 }
478}
479
480#[derive(Debug, Clone, PartialEq, Eq)]
486pub enum SignatureError {
487 Mismatch,
490
491 MissingSignature,
493
494 MalformedSignature(String),
497
498 UnsupportedAlgorithm(String),
500}
501
502impl fmt::Display for SignatureError {
503 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 match self {
505 SignatureError::Mismatch => {
506 write!(f, "task signature verification failed: MAC mismatch")
507 }
508 SignatureError::MissingSignature => {
509 write!(f, "task message is unsigned but a signature is required")
510 }
511 SignatureError::MalformedSignature(why) => {
512 write!(f, "malformed task signature: {why}")
513 }
514 SignatureError::UnsupportedAlgorithm(alg) => {
515 write!(f, "unsupported task signature algorithm: {alg}")
516 }
517 }
518 }
519}
520
521impl std::error::Error for SignatureError {}
522
523impl From<SignatureError> for crate::CelersError {
524 fn from(err: SignatureError) -> Self {
525 crate::CelersError::Other(format!("signature error: {err}"))
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Default)]
540pub struct SignedFields {
541 pub id: Uuid,
543 pub name: String,
545 pub args: Vec<TaskValue>,
547 pub kwargs: Vec<(String, TaskValue)>,
549}
550
551impl SignedFields {
552 #[must_use]
554 pub fn new(id: Uuid, name: impl Into<String>) -> Self {
555 Self {
556 id,
557 name: name.into(),
558 args: Vec::new(),
559 kwargs: Vec::new(),
560 }
561 }
562
563 #[must_use]
565 pub fn with_args(mut self, args: Vec<TaskValue>) -> Self {
566 self.args = args;
567 self
568 }
569
570 #[must_use]
572 pub fn with_arg(mut self, value: TaskValue) -> Self {
573 self.args.push(value);
574 self
575 }
576
577 #[must_use]
579 pub fn with_kwargs(mut self, kwargs: Vec<(String, TaskValue)>) -> Self {
580 self.kwargs = kwargs;
581 self
582 }
583
584 #[must_use]
586 pub fn with_kwarg(mut self, key: impl Into<String>, value: TaskValue) -> Self {
587 self.kwargs.push((key.into(), value));
588 self
589 }
590
591 #[must_use]
599 pub fn canonical_bytes(&self) -> Vec<u8> {
600 let mut out = Vec::new();
601
602 out.extend_from_slice(b"celers.task.sig.v1");
605
606 write_field(&mut out, b'I', self.id.as_bytes());
608
609 write_field(&mut out, b'N', self.name.as_bytes());
611
612 write_len(&mut out, b'A', self.args.len() as u64);
614 for arg in &self.args {
615 let encoded = canonical_value_bytes(arg);
616 write_field(&mut out, b'a', &encoded);
617 }
618
619 let mut sorted: Vec<&(String, TaskValue)> = self.kwargs.iter().collect();
621 sorted.sort_by(|x, y| x.0.cmp(&y.0));
622 write_len(&mut out, b'K', sorted.len() as u64);
623 for (key, value) in sorted {
624 write_field(&mut out, b'k', key.as_bytes());
625 let encoded = canonical_value_bytes(value);
626 write_field(&mut out, b'v', &encoded);
627 }
628
629 out
630 }
631}
632
633fn write_field(out: &mut Vec<u8>, tag: u8, bytes: &[u8]) {
635 out.push(tag);
636 out.extend_from_slice(&(bytes.len() as u64).to_be_bytes());
637 out.extend_from_slice(bytes);
638}
639
640fn write_len(out: &mut Vec<u8>, tag: u8, value: u64) {
642 out.push(tag);
643 out.extend_from_slice(&value.to_be_bytes());
644}
645
646fn canonical_value_bytes(value: &TaskValue) -> Vec<u8> {
653 let mut out = Vec::new();
654 match value {
655 TaskValue::Null => out.push(b'0'),
656 TaskValue::Bool(b) => {
657 out.push(b'b');
658 out.push(u8::from(*b));
659 }
660 TaskValue::Int(i) => {
661 out.push(b'i');
662 out.extend_from_slice(&i.to_be_bytes());
663 }
664 TaskValue::UInt(u) => {
665 out.push(b'u');
666 out.extend_from_slice(&u.to_be_bytes());
667 }
668 TaskValue::Float(f) => {
669 out.push(b'f');
670 let bits = if f.is_nan() {
673 f64::NAN.to_bits()
674 } else if *f == 0.0 {
675 0.0_f64.to_bits()
676 } else {
677 f.to_bits()
678 };
679 out.extend_from_slice(&bits.to_be_bytes());
680 }
681 TaskValue::String(s) => {
682 out.push(b's');
683 out.extend_from_slice(&(s.len() as u64).to_be_bytes());
684 out.extend_from_slice(s.as_bytes());
685 }
686 TaskValue::Bytes(bytes) => {
687 out.push(b'B');
688 out.extend_from_slice(&(bytes.len() as u64).to_be_bytes());
689 out.extend_from_slice(bytes);
690 }
691 TaskValue::Array(items) => {
692 out.push(b'L');
693 out.extend_from_slice(&(items.len() as u64).to_be_bytes());
694 for item in items {
695 let encoded = canonical_value_bytes(item);
696 out.extend_from_slice(&(encoded.len() as u64).to_be_bytes());
697 out.extend_from_slice(&encoded);
698 }
699 }
700 TaskValue::Object(entries) => {
701 out.push(b'O');
702 let mut sorted: Vec<&(String, TaskValue)> = entries.iter().collect();
703 sorted.sort_by(|x, y| x.0.cmp(&y.0));
704 out.extend_from_slice(&(sorted.len() as u64).to_be_bytes());
705 for (k, v) in sorted {
706 out.extend_from_slice(&(k.len() as u64).to_be_bytes());
707 out.extend_from_slice(k.as_bytes());
708 let encoded = canonical_value_bytes(v);
709 out.extend_from_slice(&(encoded.len() as u64).to_be_bytes());
710 out.extend_from_slice(&encoded);
711 }
712 }
713 }
714 out
715}
716
717#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
724pub enum SignatureAlgorithm {
725 #[serde(rename = "HMAC-SHA256")]
727 HmacSha256,
728}
729
730impl SignatureAlgorithm {
731 #[must_use]
733 pub const fn as_str(self) -> &'static str {
734 match self {
735 SignatureAlgorithm::HmacSha256 => "HMAC-SHA256",
736 }
737 }
738}
739
740impl fmt::Display for SignatureAlgorithm {
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 f.write_str(self.as_str())
743 }
744}
745
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
752pub struct TaskSignature {
753 pub algorithm: SignatureAlgorithm,
755 pub tag: String,
757}
758
759impl TaskSignature {
760 #[must_use]
762 pub fn hmac_sha256(tag: &[u8]) -> Self {
763 Self {
764 algorithm: SignatureAlgorithm::HmacSha256,
765 tag: to_hex(tag),
766 }
767 }
768
769 pub fn tag_bytes(&self) -> Result<Vec<u8>, SignatureError> {
775 from_hex(&self.tag)
776 }
777}
778
779impl fmt::Display for TaskSignature {
780 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781 write!(f, "{}:{}", self.algorithm.as_str(), self.tag)
782 }
783}
784
785#[derive(Clone)]
795pub struct TaskSigner {
796 key: Vec<u8>,
797}
798
799impl fmt::Debug for TaskSigner {
800 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801 f.debug_struct("TaskSigner")
803 .field("key_len", &self.key.len())
804 .finish()
805 }
806}
807
808impl TaskSigner {
809 #[must_use]
814 pub fn new(key: impl AsRef<[u8]>) -> Self {
815 Self {
816 key: key.as_ref().to_vec(),
817 }
818 }
819
820 #[must_use]
822 pub fn mac(&self, fields: &SignedFields) -> [u8; SHA256_DIGEST_BYTES] {
823 HmacSha256::mac(&self.key, &fields.canonical_bytes())
824 }
825
826 #[must_use]
828 pub fn sign(&self, fields: &SignedFields) -> TaskSignature {
829 TaskSignature::hmac_sha256(&self.mac(fields))
830 }
831
832 pub fn verify(
845 &self,
846 fields: &SignedFields,
847 signature: &TaskSignature,
848 ) -> Result<(), SignatureError> {
849 if signature.algorithm != SignatureAlgorithm::HmacSha256 {
850 return Err(SignatureError::UnsupportedAlgorithm(
851 signature.algorithm.as_str().to_string(),
852 ));
853 }
854
855 let provided = signature.tag_bytes()?;
856 if provided.len() != SHA256_DIGEST_BYTES {
857 return Err(SignatureError::MalformedSignature(format!(
858 "expected {SHA256_DIGEST_BYTES}-byte tag, got {} bytes",
859 provided.len()
860 )));
861 }
862
863 let expected = self.mac(fields);
864 if constant_time_eq(&expected, &provided) {
865 Ok(())
866 } else {
867 Err(SignatureError::Mismatch)
868 }
869 }
870
871 pub fn verify_optional(
882 &self,
883 fields: &SignedFields,
884 signature: Option<&TaskSignature>,
885 ) -> Result<(), SignatureError> {
886 match signature {
887 Some(sig) => self.verify(fields, sig),
888 None => Err(SignatureError::MissingSignature),
889 }
890 }
891
892 #[must_use]
894 pub fn is_valid(&self, fields: &SignedFields, signature: &TaskSignature) -> bool {
895 self.verify(fields, signature).is_ok()
896 }
897}
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902
903 #[test]
906 fn sha256_empty() {
907 let d = Sha256::digest(b"");
908 assert_eq!(
909 to_hex(&d),
910 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
911 );
912 }
913
914 #[test]
915 fn sha256_abc() {
916 let d = Sha256::digest(b"abc");
917 assert_eq!(
918 to_hex(&d),
919 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
920 );
921 }
922
923 #[test]
924 fn sha256_two_block() {
925 let d = Sha256::digest(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
926 assert_eq!(
927 to_hex(&d),
928 "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
929 );
930 }
931
932 #[test]
933 fn sha256_million_a() {
934 let mut h = Sha256::new();
936 let chunk = vec![b'a'; 1000];
937 for _ in 0..1000 {
938 h.update(&chunk);
939 }
940 let d = h.finalize();
941 assert_eq!(
942 to_hex(&d),
943 "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"
944 );
945 }
946
947 #[test]
948 fn sha256_streaming_matches_oneshot() {
949 let data = b"the quick brown fox jumps over the lazy dog";
950 let oneshot = Sha256::digest(data);
951 let mut h = Sha256::new();
952 for byte in data {
953 h.update(std::slice::from_ref(byte));
954 }
955 assert_eq!(oneshot, h.finalize());
956 }
957
958 #[test]
961 fn hmac_rfc4231_case1() {
962 let key = [0x0b_u8; 20];
963 let data = b"Hi There";
964 let mac = HmacSha256::mac(&key, data);
965 assert_eq!(
966 to_hex(&mac),
967 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
968 );
969 }
970
971 #[test]
972 fn hmac_rfc4231_case2() {
973 let key = b"Jefe";
974 let data = b"what do ya want for nothing?";
975 let mac = HmacSha256::mac(key, data);
976 assert_eq!(
977 to_hex(&mac),
978 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
979 );
980 }
981
982 #[test]
983 fn hmac_rfc4231_case3() {
984 let key = [0xaa_u8; 20];
985 let data = [0xdd_u8; 50];
986 let mac = HmacSha256::mac(&key, &data);
987 assert_eq!(
988 to_hex(&mac),
989 "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
990 );
991 }
992
993 #[test]
994 fn hmac_rfc4231_case6_long_key() {
995 let key = [0xaa_u8; 131];
997 let data = b"Test Using Larger Than Block-Size Key - Hash Key First";
998 let mac = HmacSha256::mac(&key, data);
999 assert_eq!(
1000 to_hex(&mac),
1001 "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
1002 );
1003 }
1004
1005 #[test]
1008 fn hex_roundtrip() {
1009 let bytes = [0x00, 0x0f, 0xa5, 0xff, 0x10];
1010 let s = to_hex(&bytes);
1011 assert_eq!(s, "000fa5ff10");
1012 assert_eq!(from_hex(&s).unwrap(), bytes);
1013 assert_eq!(
1015 from_hex("00Fa5Fff10").unwrap(),
1016 [0x00, 0xfa, 0x5f, 0xff, 0x10]
1017 );
1018 }
1019
1020 #[test]
1021 fn hex_rejects_bad_input() {
1022 assert!(from_hex("abc").is_err()); assert!(from_hex("zz").is_err()); }
1025
1026 #[test]
1027 fn constant_time_eq_works() {
1028 assert!(constant_time_eq(b"abcd", b"abcd"));
1029 assert!(!constant_time_eq(b"abcd", b"abce"));
1030 assert!(!constant_time_eq(b"abc", b"abcd"));
1031 }
1032
1033 fn sample_fields() -> SignedFields {
1036 SignedFields::new(Uuid::from_u128(0x1234_5678_9abc_def0), "tasks.add")
1037 .with_args(vec![TaskValue::from(2_i64), TaskValue::from(3_i64)])
1038 .with_kwarg("note", TaskValue::from("hello"))
1039 .with_kwarg("flag", TaskValue::from(true))
1040 }
1041
1042 #[test]
1043 fn sign_verify_roundtrip() {
1044 let signer = TaskSigner::new(b"shared-secret-key-shared-secret!");
1045 let fields = sample_fields();
1046 let sig = signer.sign(&fields);
1047 assert_eq!(sig.algorithm, SignatureAlgorithm::HmacSha256);
1048 assert!(signer.verify(&fields, &sig).is_ok());
1049 assert!(signer.is_valid(&fields, &sig));
1050 }
1051
1052 #[test]
1053 fn kwarg_order_does_not_matter() {
1054 let signer = TaskSigner::new(b"key");
1055 let a = SignedFields::new(Uuid::nil(), "t")
1056 .with_kwarg("a", TaskValue::from(1_i64))
1057 .with_kwarg("b", TaskValue::from(2_i64));
1058 let b = SignedFields::new(Uuid::nil(), "t")
1059 .with_kwarg("b", TaskValue::from(2_i64))
1060 .with_kwarg("a", TaskValue::from(1_i64));
1061 assert_eq!(signer.mac(&a), signer.mac(&b));
1062 }
1063
1064 #[test]
1065 fn tamper_args_detected() {
1066 let signer = TaskSigner::new(b"key");
1067 let fields = sample_fields();
1068 let sig = signer.sign(&fields);
1069
1070 let tampered = SignedFields::new(fields.id, &fields.name)
1071 .with_args(vec![TaskValue::from(2_i64), TaskValue::from(4_i64)])
1072 .with_kwarg("note", TaskValue::from("hello"))
1073 .with_kwarg("flag", TaskValue::from(true));
1074 assert_eq!(
1075 signer.verify(&tampered, &sig),
1076 Err(SignatureError::Mismatch)
1077 );
1078 }
1079
1080 #[test]
1081 fn tamper_name_detected() {
1082 let signer = TaskSigner::new(b"key");
1083 let fields = sample_fields();
1084 let sig = signer.sign(&fields);
1085 let mut tampered = fields.clone();
1086 tampered.name = "tasks.subtract".to_string();
1087 assert!(signer.verify(&tampered, &sig).is_err());
1088 }
1089
1090 #[test]
1091 fn tamper_id_detected() {
1092 let signer = TaskSigner::new(b"key");
1093 let fields = sample_fields();
1094 let sig = signer.sign(&fields);
1095 let mut tampered = fields.clone();
1096 tampered.id = Uuid::from_u128(0xdead_beef);
1097 assert!(signer.verify(&tampered, &sig).is_err());
1098 }
1099
1100 #[test]
1101 fn tamper_kwarg_value_detected() {
1102 let signer = TaskSigner::new(b"key");
1103 let fields = sample_fields();
1104 let sig = signer.sign(&fields);
1105 let tampered = SignedFields::new(fields.id, &fields.name)
1106 .with_args(fields.args.clone())
1107 .with_kwarg("note", TaskValue::from("HELLO"))
1108 .with_kwarg("flag", TaskValue::from(true));
1109 assert!(signer.verify(&tampered, &sig).is_err());
1110 }
1111
1112 #[test]
1113 fn wrong_key_detected() {
1114 let signer = TaskSigner::new(b"key-one");
1115 let other = TaskSigner::new(b"key-two");
1116 let fields = sample_fields();
1117 let sig = signer.sign(&fields);
1118 assert_eq!(other.verify(&fields, &sig), Err(SignatureError::Mismatch));
1119 }
1120
1121 #[test]
1122 fn unsigned_rejected() {
1123 let signer = TaskSigner::new(b"key");
1124 let fields = sample_fields();
1125 assert_eq!(
1126 signer.verify_optional(&fields, None),
1127 Err(SignatureError::MissingSignature)
1128 );
1129 let sig = signer.sign(&fields);
1131 assert!(signer.verify_optional(&fields, Some(&sig)).is_ok());
1132 }
1133
1134 #[test]
1135 fn malformed_signature_rejected() {
1136 let signer = TaskSigner::new(b"key");
1137 let fields = sample_fields();
1138
1139 let bad_hex = TaskSignature {
1140 algorithm: SignatureAlgorithm::HmacSha256,
1141 tag: "nothex!!".to_string(),
1142 };
1143 assert!(matches!(
1144 signer.verify(&fields, &bad_hex),
1145 Err(SignatureError::MalformedSignature(_))
1146 ));
1147
1148 let wrong_len = TaskSignature {
1149 algorithm: SignatureAlgorithm::HmacSha256,
1150 tag: "aabb".to_string(),
1151 };
1152 assert!(matches!(
1153 signer.verify(&fields, &wrong_len),
1154 Err(SignatureError::MalformedSignature(_))
1155 ));
1156 }
1157
1158 #[test]
1159 fn distinct_types_do_not_collide() {
1160 let signer = TaskSigner::new(b"key");
1162 let as_int = SignedFields::new(Uuid::nil(), "t").with_arg(TaskValue::from(1_i64));
1163 let as_str = SignedFields::new(Uuid::nil(), "t").with_arg(TaskValue::from("1"));
1164 let as_bool = SignedFields::new(Uuid::nil(), "t").with_arg(TaskValue::from(true));
1165 let m_int = signer.mac(&as_int);
1166 let m_str = signer.mac(&as_str);
1167 let m_bool = signer.mac(&as_bool);
1168 assert_ne!(m_int, m_str);
1169 assert_ne!(m_int, m_bool);
1170 assert_ne!(m_str, m_bool);
1171 }
1172
1173 #[test]
1174 fn arg_boundary_not_ambiguous() {
1175 let signer = TaskSigner::new(b"key");
1177 let split = SignedFields::new(Uuid::nil(), "t")
1178 .with_args(vec![TaskValue::from("a"), TaskValue::from("b")]);
1179 let joined = SignedFields::new(Uuid::nil(), "t").with_arg(TaskValue::from("ab"));
1180 assert_ne!(signer.mac(&split), signer.mac(&joined));
1181 }
1182
1183 #[test]
1184 fn signature_serde_roundtrip() {
1185 let signer = TaskSigner::new(b"key");
1186 let fields = sample_fields();
1187 let sig = signer.sign(&fields);
1188 let json = serde_json::to_string(&sig).expect("serialize");
1189 let restored: TaskSignature = serde_json::from_str(&json).expect("deserialize");
1190 assert_eq!(sig, restored);
1191 assert!(signer.verify(&fields, &restored).is_ok());
1192 }
1193
1194 #[test]
1195 fn signature_display_and_debug_do_not_leak_key() {
1196 let signer = TaskSigner::new(b"top-secret-key-material-here!!!!");
1197 let dbg = format!("{signer:?}");
1198 assert!(!dbg.contains("top-secret"));
1199 assert!(dbg.contains("key_len"));
1200 }
1201}