1use aes::cipher::{KeyIvInit, StreamCipher};
42use base64::{engine::general_purpose::URL_SAFE, Engine as _};
43use std::io::Write;
44use byteorder::{BigEndian, ByteOrder};
45use hmac::{Hmac, Mac};
46use sha2::Sha256;
47use thiserror::Error;
48use time::{Duration, OffsetDateTime};
49
50type HmacSha256 = Hmac<Sha256>;
51type Aes256Ctr64BE = ctr::Ctr64BE<aes::Aes256>;
52
53const UNIX_EPOCH: OffsetDateTime = time::OffsetDateTime::UNIX_EPOCH;
54
55#[derive(Clone, Debug)]
64pub struct Keys {
65 pub encryption_key: [u8; 32],
67 pub integrity_key: [u8; 32],
69}
70
71impl Keys {
72 pub fn new(encryption_key: &[u8], integrity_key: &[u8]) -> Result<Self, CryptoError> {
90 let encryption_key: [u8; 32] = encryption_key
91 .try_into()
92 .map_err(|_| CryptoError::InvalidKey)?;
93 let integrity_key: [u8; 32] = integrity_key
94 .try_into()
95 .map_err(|_| CryptoError::InvalidKey)?;
96
97 Ok(Self {
98 encryption_key,
99 integrity_key,
100 })
101 }
102}
103
104#[derive(Error, Debug)]
106pub enum CryptoError {
107 #[error("invalid key")]
109 InvalidKey,
110 #[error("invalid signature")]
112 InvalidSign,
113 #[error("invalid init vector")]
115 InvalidInitVector,
116 #[error("data too short")]
118 DataTooShort,
119 #[error("payload size mismatch")]
121 PayloadSizeMismatch,
122 #[error("decode error: {0}")]
124 DecodeError(#[from] base64::DecodeError),
125 #[error("io error: {0}")]
127 IoError(#[from] std::io::Error),
128}
129
130pub struct Crypto {
144 pub keys: Keys,
146}
147
148impl Crypto {
149 pub fn new(keys: Keys) -> Self {
160 Self { keys }
161 }
162
163 pub const IV_BASE: usize = 0;
165 pub const IV_SIZE: usize = 16;
167 pub const IV_TIME_OFFSET: usize = 0;
169 pub const IV_TIME_SIZE: usize = 8;
171 pub const IV_SERVER_ID_OFFSET: usize = 8;
173 pub const IV_SERVER_ID_SIZE: usize = 8;
175 pub const SIGNATURE_SIZE: usize = 4;
177 pub const PAYLOAD_BASE: usize = Crypto::IV_BASE + Crypto::IV_SIZE;
179 pub const OVERHEAD_SIZE: usize = Crypto::IV_SIZE + Crypto::SIGNATURE_SIZE;
181
182 #[inline]
194 pub fn decode<T>(&self, data: T) -> Result<Vec<u8>, CryptoError>
195 where
196 T: AsRef<[u8]>,
197 {
198 URL_SAFE
199 .decode(data)
200 .map(|v| v.to_vec())
201 .map_err(|e| e.into())
202 }
203
204 #[inline]
216 pub fn encode<T>(&self, data: T) -> String
217 where
218 T: AsRef<[u8]>,
219 {
220 URL_SAFE.encode(data)
221 }
222
223 #[inline]
241 pub fn decrypt(&self, cipher_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
242 if cipher_data.len() < Self::OVERHEAD_SIZE {
243 return Err(CryptoError::DataTooShort);
244 }
245
246 let mut data = cipher_data.to_vec();
247 let data_size = data.len();
248
249 self.xor_payload(&mut data)?;
250
251 let confirmation_signature = self.hmac_signature(&data)?;
252 let integrity_signature = self.read_i32(&data, data_size - Self::SIGNATURE_SIZE);
253 self.write_i32(
254 &mut data,
255 data_size - Self::SIGNATURE_SIZE,
256 confirmation_signature,
257 );
258
259 if confirmation_signature != integrity_signature {
260 return Err(CryptoError::InvalidSign);
261 }
262
263 Ok(data)
264 }
265
266 #[inline]
279 pub fn encrypt(&self, plain_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
280 if plain_data.len() < Self::OVERHEAD_SIZE {
281 return Err(CryptoError::DataTooShort);
282 }
283
284 let mut data = plain_data.to_vec();
285 let data_size = data.len();
286 let signature = self.hmac_signature(&data)?;
287 self.write_i32(&mut data, data_size - Self::SIGNATURE_SIZE, signature);
288
289 self.xor_payload(&mut data)?;
290
291 Ok(data)
292 }
293
294 #[inline]
311 pub fn package<T>(
312 &self,
313 payload: T,
314 iv: Option<&[u8]>,
315 ) -> Result<String, CryptoError>
316 where
317 T: AsRef<[u8]>,
318 {
319 let mut out = Vec::new();
320 self.package_to(payload, iv, &mut out)?;
321 Ok(String::from_utf8(out).expect("base64 output is valid UTF-8"))
323 }
324
325 #[inline]
342 pub fn unpackage<T>(&self, data: T) -> Result<Vec<u8>, CryptoError>
343 where
344 T: AsRef<[u8]>,
345 {
346 let mut out = Vec::new();
347 self.unpackage_to(data, &mut out)?;
348 Ok(out)
349 }
350
351 #[inline]
371 pub fn package_to<T, W>(
372 &self,
373 payload: T,
374 iv: Option<&[u8]>,
375 out: &mut W,
376 ) -> Result<(), CryptoError>
377 where
378 T: AsRef<[u8]>,
379 W: Write,
380 {
381 let payload = payload.as_ref();
382 let mut pkg = self.init_plain_data(payload.len(), iv)?;
383 self.set_payload(&mut pkg, payload)?;
384 let encrypted = self.encrypt(&pkg)?;
385 out.write_all(URL_SAFE.encode(&encrypted).as_bytes())?;
386 Ok(())
387 }
388
389 #[inline]
408 pub fn unpackage_to<T, W>(
409 &self,
410 data: T,
411 out: &mut W,
412 ) -> Result<(), CryptoError>
413 where
414 T: AsRef<[u8]>,
415 W: Write,
416 {
417 let decoded = self.decode(data)?;
418 let decrypted = self.decrypt(&decoded)?;
419 let payload = self.payload(&decrypted).ok_or(CryptoError::DataTooShort)?;
420 out.write_all(payload)?;
421 Ok(())
422 }
423
424 #[inline]
441 pub fn create_init_vector(&self, timestamp: OffsetDateTime, server_id: i64) -> Vec<u8> {
442 let timestamp = (timestamp.unix_timestamp_nanos() / 1_000) as i64; let mut iv = vec![0; Self::IV_SIZE];
444 self.write_i64(&mut iv, Self::IV_TIME_OFFSET, timestamp);
445 self.write_i64(&mut iv, Self::IV_SERVER_ID_OFFSET, server_id);
446 iv
447 }
448
449 #[inline]
466 pub fn timestamp(&self, data: &[u8]) -> Option<OffsetDateTime> {
467 if data.len() < Self::IV_SIZE {
468 return None;
469 }
470 let ts = self.read_i64(data, Self::IV_BASE + Self::IV_TIME_OFFSET);
471 Some(
472 UNIX_EPOCH
473 .checked_add(Duration::microseconds(ts))
474 .unwrap_or(UNIX_EPOCH),
475 )
476 }
477
478 #[inline]
494 pub fn server_id(&self, data: &[u8]) -> Option<i64> {
495 if data.len() < Self::IV_SIZE {
496 return None;
497 }
498 Some(self.read_i64(data, Self::IV_BASE + Self::IV_SERVER_ID_OFFSET))
499 }
500
501 #[inline]
517 pub fn payload<'a>(&self, data: &'a [u8]) -> Option<&'a [u8]> {
518 if data.len() < Self::OVERHEAD_SIZE {
519 return None;
520 }
521 Some(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE])
522 }
523
524 #[inline]
542 pub fn init_plain_data(
543 &self,
544 payload_size: usize,
545 iv: Option<&[u8]>,
546 ) -> Result<Vec<u8>, CryptoError> {
547 let mut plain_data = vec![0; Self::OVERHEAD_SIZE + payload_size];
548 if let Some(iv) = iv {
549 plain_data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE].copy_from_slice(iv);
550 } else {
551 let now = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000) as i64;
552 self.write_i64(&mut plain_data, Self::IV_TIME_OFFSET, now);
553 self.write_i64(
554 &mut plain_data,
555 Self::IV_SERVER_ID_OFFSET,
556 rand::random::<i64>(),
557 );
558 }
559
560 Ok(plain_data)
561 }
562
563 #[inline]
580 pub fn set_payload(&self, plain_data: &mut [u8], payload: &[u8]) -> Result<(), CryptoError> {
581 if payload.len() != plain_data.len() - Self::OVERHEAD_SIZE {
582 return Err(CryptoError::PayloadSizeMismatch);
583 }
584 plain_data[Self::PAYLOAD_BASE..Self::PAYLOAD_BASE + payload.len()].copy_from_slice(payload);
585 Ok(())
586 }
587
588 #[inline]
589 fn read_i32(&self, data: &[u8], offset: usize) -> i32 {
590 BigEndian::read_i32(&data[offset..offset + 4])
591 }
592
593 #[inline]
594 fn read_i64(&self, data: &[u8], offset: usize) -> i64 {
595 BigEndian::read_i64(&data[offset..offset + 8])
596 }
597
598 #[inline]
599 fn write_i32(&self, data: &mut [u8], offset: usize, value: i32) {
600 BigEndian::write_i32(&mut data[offset..offset + 4], value);
601 }
602
603 #[inline]
604 fn write_i64(&self, data: &mut [u8], offset: usize, value: i64) {
605 BigEndian::write_i64(&mut data[offset..offset + 8], value);
606 }
607
608 #[inline]
609 fn xor_payload(&self, data: &mut [u8]) -> Result<(), CryptoError> {
610 let iv: &[u8; 16] = &data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]
611 .try_into()
612 .map_err(|_| CryptoError::InvalidInitVector)?;
613
614 let mut cipher = Aes256Ctr64BE::new(&self.keys.encryption_key.into(), iv.into());
615 let data_size = data.len();
616 cipher.apply_keystream(&mut data[Self::PAYLOAD_BASE..data_size - Self::SIGNATURE_SIZE]);
617
618 Ok(())
619 }
620
621 #[inline]
622 fn hmac_signature(&self, data: &[u8]) -> Result<i32, CryptoError> {
623 let mut mac = HmacSha256::new_from_slice(&self.keys.integrity_key)
624 .map_err(|_| CryptoError::InvalidKey)?;
625
626 mac.update(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE]);
627 mac.update(&data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]);
628
629 let b = mac.finalize().into_bytes();
630
631 Ok(self.read_i32(&b, 0))
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use base64::prelude::*;
639
640 static TEST_ENCRYPTION_KEY: &str = "sIxwz7yw62yrfoLGt12lIHKuYrK/S5kLuApI2BQe7Ac=";
641 static TEST_INTEGRITY_KEY: &str = "v3fsVcMBMMHYzRhi7SpM0sdqwzvAxM6KPTu9OtVod5I=";
642
643 fn create_keys() -> Keys {
644 Keys::new(
645 &BASE64_STANDARD.decode(TEST_ENCRYPTION_KEY).unwrap(),
646 &BASE64_STANDARD.decode(TEST_INTEGRITY_KEY).unwrap(),
647 )
648 .unwrap()
649 }
650
651 #[test]
652 fn test_decode() {
653 let crypto = Crypto::new(create_keys());
654 let encoded = "aGVsbG8sIHdvcmxk";
655 let decoded = crypto.decode(encoded).unwrap();
656 assert_eq!(decoded, b"hello, world");
657 }
658
659 #[test]
660 fn test_encode() {
661 let crypto = Crypto::new(create_keys());
662 let data = b"hello, world";
663 let encoded = crypto.encode(data);
664 assert_eq!(encoded, "aGVsbG8sIHdvcmxk");
665 }
666
667 #[test]
668 fn test_decrypt() {
669 let crypto = Crypto::new(create_keys());
670 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
671 let iv = crypto.create_init_vector(timestamp, 123456789);
672 let payload = "https://example.com".as_bytes();
673
674 let mut plain_data = crypto.init_plain_data(payload.len(), Some(&iv)).unwrap();
675 crypto.set_payload(&mut plain_data, payload).unwrap();
676 let encrypted_data = crypto.encrypt(&plain_data).unwrap();
677
678 assert_eq!(crypto.timestamp(&iv), Some(timestamp));
679 assert_eq!(crypto.server_id(&iv), Some(123456789));
680 assert_eq!(
681 crypto.payload(&encrypted_data).unwrap().len(),
682 payload.len()
683 );
684 assert_ne!(crypto.payload(&encrypted_data), Some(payload));
685
686 let decrypted_data = crypto.decrypt(&encrypted_data).unwrap();
687 assert_eq!(crypto.timestamp(&decrypted_data), Some(timestamp));
688 assert_eq!(crypto.server_id(&decrypted_data), Some(123456789));
689 assert_eq!(crypto.payload(&decrypted_data), Some(payload));
690
691 let mut encrypted_data_invalid_sign = encrypted_data.clone();
692 crypto.write_i32(
693 &mut encrypted_data_invalid_sign,
694 encrypted_data.len() - Crypto::SIGNATURE_SIZE,
695 123456789,
696 );
697 assert!(matches!(
698 crypto.decrypt(&encrypted_data_invalid_sign),
699 Err(CryptoError::InvalidSign)
700 ));
701 assert_ne!(crypto.payload(&encrypted_data_invalid_sign), Some(payload))
702 }
703
704 #[test]
705 fn test_create_init_vector() {
706 let crypto = Crypto::new(create_keys());
707 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
708 let iv = crypto.create_init_vector(timestamp, 123456789);
709 assert_eq!(iv.len(), Crypto::IV_SIZE);
710 assert_eq!(crypto.read_i64(&iv, Crypto::IV_TIME_OFFSET), 1_000_000);
711 assert_eq!(crypto.read_i64(&iv, Crypto::IV_SERVER_ID_OFFSET), 123456789);
712 assert_eq!(crypto.timestamp(&iv), Some(timestamp));
713 assert_eq!(crypto.server_id(&iv), Some(123456789));
714 }
715
716 #[test]
717 fn test_init_plain_data() {
718 let crypto = Crypto::new(create_keys());
719 let payload = "https://example.com".as_bytes();
720
721 let mut plain_data = crypto.init_plain_data(payload.len(), None).unwrap();
722 crypto.set_payload(&mut plain_data, payload).unwrap();
723
724 assert_eq!(plain_data.len(), Crypto::OVERHEAD_SIZE + payload.len());
725 assert_eq!(crypto.payload(&plain_data), Some(payload));
726 }
727
728 #[test]
729 fn test_init_plain_data_empty_payload() {
730 let crypto = Crypto::new(create_keys());
731 let payload = "".as_bytes();
732
733 let mut plain_data = crypto.init_plain_data(0, None).unwrap();
734 crypto.set_payload(&mut plain_data, payload).unwrap();
735 assert_eq!(crypto.payload(&plain_data), Some(payload));
736 }
737
738 #[test]
739 fn test_package_unpackage() {
740 let crypto = Crypto::new(create_keys());
741 let payload = b"Hello, world!".as_slice();
742
743 let encoded = crypto.package(payload, None).unwrap();
744 assert_ne!(encoded, "");
745
746 let decoded = crypto.unpackage(&encoded).unwrap();
747 assert_eq!(decoded, payload);
748 }
749
750 #[test]
751 fn test_package_unpackage_with_iv() {
752 let crypto = Crypto::new(create_keys());
753 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
754 let iv = crypto.create_init_vector(timestamp, 123456789);
755 let payload = b"https://example.com".as_slice();
756
757 let encoded = crypto.package(payload, Some(&iv)).unwrap();
758
759 let decoded = crypto.decode(encoded.as_bytes()).unwrap();
761 assert_eq!(crypto.timestamp(&decoded), Some(timestamp));
762 assert_eq!(crypto.server_id(&decoded), Some(123456789));
763
764 let recovered = crypto.unpackage(&encoded).unwrap();
765 assert_eq!(recovered, payload);
766 }
767
768 #[test]
769 fn test_package_unpackage_empty_payload() {
770 let crypto = Crypto::new(create_keys());
771 let payload = b"".as_slice();
772
773 let encoded = crypto.package(payload, None).unwrap();
774 let recovered = crypto.unpackage(&encoded).unwrap();
775 assert_eq!(recovered, payload);
776 }
777
778 #[test]
779 fn test_unpackage_tampered_signature() {
780 let crypto = Crypto::new(create_keys());
781 let encoded = crypto.package(b"Hello, world!", None).unwrap();
782
783 let mut bytes = crypto.decode(encoded.as_bytes()).unwrap();
784 let last = bytes.len() - Crypto::SIGNATURE_SIZE;
785 crypto.write_i32(&mut bytes, last, 123456789);
786
787 let tampered = crypto.encode(&bytes);
788 assert!(matches!(
789 crypto.unpackage(&tampered),
790 Err(CryptoError::InvalidSign)
791 ));
792 }
793
794 #[test]
795 fn test_package_to_matches_package() {
796 let crypto = Crypto::new(create_keys());
797 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
798 let iv = crypto.create_init_vector(timestamp, 123456789);
799 let payload = b"https://example.com".as_slice();
800
801 let encoded_alloc = crypto.package(payload, Some(&iv)).unwrap();
803
804 let mut buf = Vec::new();
805 crypto.package_to(payload, Some(&iv), &mut buf).unwrap();
806 assert_eq!(buf, encoded_alloc.as_bytes());
807 }
808
809 #[test]
810 fn test_package_to_unpackage_to_roundtrip() {
811 let crypto = Crypto::new(create_keys());
812 let payload = b"Hello, world!".as_slice();
813
814 let mut enc_buf = Vec::new();
815 crypto.package_to(payload, None, &mut enc_buf).unwrap();
816
817 let mut dec_buf = Vec::new();
818 crypto.unpackage_to(&enc_buf, &mut dec_buf).unwrap();
819 assert_eq!(dec_buf, payload);
820 }
821
822 #[test]
823 fn test_package_to_appends_and_preserves_existing() {
824 let crypto = Crypto::new(create_keys());
825 let payload = b"Hello".as_slice();
826
827 let mut buf = b"prefix".to_vec();
828 let prefix_len = buf.len();
829 crypto.package_to(payload, None, &mut buf).unwrap();
830
831 assert_eq!(&buf[..prefix_len], b"prefix");
833 assert!(buf.len() > prefix_len);
834
835 let mut dec_buf = Vec::new();
836 crypto.unpackage_to(&buf[prefix_len..], &mut dec_buf).unwrap();
837 assert_eq!(dec_buf, payload);
838 }
839
840 #[test]
841 fn test_package_to_empty_payload() {
842 let crypto = Crypto::new(create_keys());
843
844 let mut enc_buf = Vec::new();
845 crypto.package_to(b"", None, &mut enc_buf).unwrap();
846
847 let mut dec_buf = Vec::new();
848 crypto.unpackage_to(&enc_buf, &mut dec_buf).unwrap();
849 assert_eq!(dec_buf, b"");
850 }
851
852 #[test]
853 fn test_unpackage_to_tampered_signature() {
854 let crypto = Crypto::new(create_keys());
855
856 let mut enc_buf = Vec::new();
857 crypto.package_to(b"Hello", None, &mut enc_buf).unwrap();
858
859 let mut raw = crypto.decode(&enc_buf).unwrap();
862 let last = raw.len() - Crypto::SIGNATURE_SIZE;
863 crypto.write_i32(&mut raw, last, 123456789);
864 let tampered = crypto.encode(&raw);
865
866 let mut dec_buf = Vec::new();
867 assert!(matches!(
868 crypto.unpackage_to(tampered.as_bytes(), &mut dec_buf),
869 Err(CryptoError::InvalidSign)
870 ));
871 }
872
873 #[test]
874 fn test_package_to_with_non_vec_writer() {
875 let crypto = Crypto::new(create_keys());
878 let payload = b"Hello, world!".as_slice();
879
880 let mut writer = std::io::BufWriter::new(Vec::<u8>::new());
881 crypto.package_to(payload, None, &mut writer).unwrap();
882 let encoded = writer.into_inner().unwrap();
883
884 let mut dec_buf = Vec::new();
885 crypto.unpackage_to(&encoded, &mut dec_buf).unwrap();
886 assert_eq!(dec_buf, payload);
887 }
888
889 #[test]
890 fn test_unpackage_to_appends_to_existing_buffer() {
891 let crypto = Crypto::new(create_keys());
894 let payload = b"Hello".as_slice();
895 let encoded = crypto.package(payload, None).unwrap();
896
897 let mut buf = b"prefix".to_vec();
898 let prefix_len = buf.len();
899 crypto.unpackage_to(&encoded, &mut buf).unwrap();
900
901 assert_eq!(&buf[..prefix_len], b"prefix");
902 assert_eq!(&buf[prefix_len..], payload);
903 }
904}