integrationos_domain/domain/access_key/
encrypted_data.rs1use crate::{IntegrationOSError, InternalError};
2use aes::cipher::{generic_array::GenericArray, KeyIvInit, StreamCipher};
3use sha2::{Digest, Sha256};
4
5type Aes256Ctr = ctr::Ctr64BE<aes::Aes256>;
6const HASH_LENGTH: usize = 32;
7pub const IV_LENGTH: usize = 16;
8pub const PASSWORD_LENGTH: usize = 32;
9type HashBuf = [u8; HASH_LENGTH];
10
11const HASH_PREFIX: &str = "\x19Event Signed Message:\n";
12
13#[derive(Debug, Clone, Eq, PartialEq)]
14pub struct EncryptedData {
15 data: Vec<u8>,
16}
17
18impl EncryptedData {
19 pub fn new(data: Vec<u8>) -> Self {
20 Self { data }
21 }
22
23 pub fn verify_and_decrypt(
24 &mut self,
25 password: &[u8; PASSWORD_LENGTH],
26 ) -> Result<&[u8], IntegrationOSError> {
27 if !self.verify_hash(password)? {
28 return Err(InternalError::invalid_argument("Hash does not match", None));
29 }
30
31 self.decrypt(password)
32 }
33
34 pub fn encrypt(
35 mut content: Vec<u8>,
36 iv: &[u8; IV_LENGTH],
37 password: &[u8; PASSWORD_LENGTH],
38 ) -> Result<Vec<u8>, IntegrationOSError> {
39 let mut cipher = Aes256Ctr::new(password.into(), iv.into());
40 cipher
41 .try_apply_keystream(&mut content)
42 .map_err(|e| InternalError::io_err(&format!("Could not encode content: {e}"), None))?;
43 let hash = Self::compute_hash(&content, iv, password)?;
44 content.extend(iv);
45 content.extend(hash);
46 Ok(content)
47 }
48
49 fn decrypt(&mut self, password: &[u8; PASSWORD_LENGTH]) -> Result<&[u8], IntegrationOSError> {
50 let mut cipher = Aes256Ctr::new(password.into(), self.get_iv().into());
51 let content = self.get_content_mut();
52 cipher
53 .try_apply_keystream(content)
54 .map_err(|e| InternalError::io_err(&format!("Could not decode content: {e}"), None))?;
55 Ok(content)
56 }
57
58 fn verify_hash(&self, password: &[u8; PASSWORD_LENGTH]) -> Result<bool, IntegrationOSError> {
59 let actual_hash = Self::compute_hash(self.get_content(), self.get_iv(), password)?;
60 Ok(actual_hash == *self.get_hash())
61 }
62
63 pub fn compute_hash(
64 content: &[u8],
65 iv: &[u8],
66 password: &[u8],
67 ) -> Result<HashBuf, IntegrationOSError> {
68 let message_len = content.len() + iv.len() + password.len();
69 let mut hasher = Sha256::new();
70 hasher.update(HASH_PREFIX);
71 hasher.update(message_len.to_string());
72 hasher.update(content);
73 hasher.update(iv);
74 hasher.update(password);
75 let mut actual_hash = [0u8; HASH_LENGTH];
76 hasher.finalize_into(GenericArray::from_mut_slice(&mut actual_hash));
77 Ok(actual_hash)
78 }
79
80 fn get_iv(&self) -> &[u8] {
81 &self.data[self.data.len() - HASH_LENGTH - IV_LENGTH..self.data.len() - HASH_LENGTH]
82 }
83
84 fn get_hash(&self) -> &[u8] {
85 &self.data[self.data.len() - HASH_LENGTH..]
86 }
87
88 fn get_content_mut(&mut self) -> &mut [u8] {
89 let len = self.data.len();
90 &mut self.data[..len - HASH_LENGTH - IV_LENGTH]
91 }
92
93 fn get_content(&self) -> &[u8] {
94 let len = self.data.len();
95 &self.data[..len - HASH_LENGTH - IV_LENGTH]
96 }
97}
98
99#[cfg(test)]
100mod test {
101
102 use base64ct::{Base64UrlUnpadded, Encoding};
103
104 use super::*;
105
106 const ENCRYPTED_DATA: &str = "Q71YUIZydcgSwJQNOUCHhaTMqmIvslIafF5LluORJfJKydMGELHtYe_ydtBIrVuomEvIMurKaAUqlujQ8xzs4LBOxyf_lJ2unwqyFzk1TnCKBMNyRJybyL9RTBp90BExEwf2WwMtU4FDBUhP2bhWmxm7eQAAAAAAAAAAAAAAAAAAAADPLKD188CZczQr7eWGtyipuCZLZKQ2lBKL3S_R-nEgBA";
107 const INVALID_ENCRYPTED_DATA: &str = "h72AgcpeanqdZgXkeblx4zAygpn1r9Kx5wZDvH67K5QC8pGYCwxUvSsGiIWkAIswPUA6OPOEtGNPB4Je9zpppnnYX8YmbhghsBt7ClICnaOcs2a5i0IAzaUphhYkoz1r8BWLSCi9m0gpEw7tH1JhxK-k5My-7TgjfA";
108
109 const PASSWORD: &[u8; PASSWORD_LENGTH] = b"32KFFT_i4UpkJmyPwY2TGzgHpxfXs7zS";
110 const DECRYPTED_DATA: &str = "\n&build-2e76c839f5fd419db6b34682f4cdff1e\u{12}\u{7}default\u{18}\u{1}\"\u{7}webhook*\nmy-webhook2\u{e}event.received:\u{7}foo.barB\u{7}foo.barJ\u{7}foo.bar";
111
112 #[test]
113 fn test_verify_and_decrypt() {
114 let data = Base64UrlUnpadded::decode_vec(ENCRYPTED_DATA).unwrap();
115 let mut data: EncryptedData = EncryptedData::new(data);
116 let decrypted = data.verify_and_decrypt(PASSWORD).unwrap();
117 assert_eq!(decrypted, DECRYPTED_DATA.as_bytes());
118 }
119
120 #[test]
121 fn test_verify_hash() {
122 let data = Base64UrlUnpadded::decode_vec(ENCRYPTED_DATA).unwrap();
123 let data: EncryptedData = EncryptedData::new(data);
124 assert!(data.verify_hash(PASSWORD).unwrap());
125 }
126
127 #[test]
128 fn test_incorrect_hash() {
129 let data = Base64UrlUnpadded::decode_vec(INVALID_ENCRYPTED_DATA).unwrap();
130 let data: EncryptedData = EncryptedData::new(data);
131 assert!(!data.verify_hash(PASSWORD).unwrap());
132 }
133
134 #[test]
135 fn test_decrypt() {
136 let data = Base64UrlUnpadded::decode_vec(ENCRYPTED_DATA).unwrap();
137 let mut data: EncryptedData = EncryptedData::new(data);
138 let decrypted = data.decrypt(PASSWORD).unwrap();
139 assert_eq!(decrypted, DECRYPTED_DATA.as_bytes());
140 }
141
142 #[test]
143 fn test_incorrect_decrypt() {
144 let data = Base64UrlUnpadded::decode_vec(INVALID_ENCRYPTED_DATA).unwrap();
145 let mut data: EncryptedData = EncryptedData::new(data);
146 let decrypted = data.decrypt(PASSWORD).unwrap();
147 let res = std::str::from_utf8(decrypted);
148 assert!(res.is_err());
149 }
150}