ncryptf/
response.rs

1use constant_time_eq::constant_time_eq;
2use dryoc::constants::{
3    CRYPTO_BOX_MACBYTES,
4    CRYPTO_BOX_NONCEBYTES,
5    CRYPTO_BOX_PUBLICKEYBYTES,
6    CRYPTO_BOX_SECRETKEYBYTES,
7    CRYPTO_SIGN_BYTES,
8    CRYPTO_SIGN_PUBLICKEYBYTES
9};
10use dryoc::classic::crypto_box;
11use dryoc::classic::crypto_sign;
12use dryoc::generichash::GenericHash;
13
14use crate::{error::NcryptfError as Error, VERSION_2_HEADER};
15
16/// Response allows for decrypting of a request
17pub struct Response {
18    pub secret_key: Vec<u8>,
19}
20
21impl Response {
22    /// Decrypts a response
23    pub fn decrypt(
24        &self,
25        response: Vec<u8>,
26        public_key: Option<Vec<u8>>,
27        nonce: Option<Vec<u8>>,
28    ) -> Result<String, Error> {
29        // Extract the nonce if one isn't provided
30        let n = match nonce {
31            Some(nonce) => nonce,
32            None => response.get(4..28).unwrap().to_vec(),
33        };
34
35        return self.decrypt_body(response, public_key, n.clone());
36    }
37
38    fn decrypt_body(
39        &self,
40        response: Vec<u8>,
41        public_key: Option<Vec<u8>>,
42        nonce: Vec<u8>,
43    ) -> Result<String, Error> {
44        if nonce.len() != (CRYPTO_BOX_NONCEBYTES as usize) {
45            return Err(Error::InvalidArgument(format!(
46                "Nonce should be {} bytes",
47                CRYPTO_BOX_NONCEBYTES
48            )));
49        }
50
51        let r = response.clone();
52        match Self::get_version(r) {
53            Ok(version) => match version {
54                2 => return self.decrypt_v2(response, nonce),
55                _ => return self.decrypt_v1(response, public_key.unwrap(), nonce),
56            },
57            Err(error) => return Err(error),
58        };
59    }
60
61    fn decrypt_v1(
62        &self,
63        response: Vec<u8>,
64        public_key: Vec<u8>,
65        nonce: Vec<u8>,
66    ) -> Result<String, Error> {
67        if public_key.len() != (CRYPTO_BOX_PUBLICKEYBYTES as usize) {
68            return Err(Error::InvalidArgument(format!(
69                "Public key should be {} bytes",
70                CRYPTO_BOX_NONCEBYTES
71            )));
72        }
73
74        if response.len() < (CRYPTO_BOX_MACBYTES as usize) {
75            return Err(Error::InvalidArgument(format!(
76                "Response is too short to be decrypted"
77            )));
78        }
79
80        let sk: [u8; CRYPTO_BOX_SECRETKEYBYTES as usize] =
81            self.secret_key.clone().try_into().unwrap();
82        let pk: [u8; CRYPTO_BOX_PUBLICKEYBYTES as usize] = public_key.try_into().unwrap();
83        let n: [u8; CRYPTO_BOX_NONCEBYTES as usize] = nonce.try_into().unwrap();
84
85        let mut message = vec![0u8; response.len() - CRYPTO_BOX_MACBYTES as usize];
86        crypto_box::crypto_box_open_easy(
87            &mut message,
88            &response,
89            &n,
90            &pk,
91            &sk,
92        ).map_err(|_| Error::DecryptError)?;
93
94        let string = String::from_utf8(message).map_err(|_| Error::DecryptError)?;
95        return Ok(string);
96    }
97
98    fn decrypt_v2(&self, response: Vec<u8>, nonce: Vec<u8>) -> Result<String, Error> {
99        let length = response.len();
100        if length < 236 {
101            return Err(Error::InvalidArgument(format!(
102                "Response length is too short for a v2 response."
103            )));
104        }
105
106        let payload = response.get(0..length - 64).unwrap().to_vec();
107        let checksum = response.get(length - 64..length).unwrap().to_vec();
108
109        let s: &[u8; CRYPTO_BOX_NONCEBYTES as usize] = &nonce.clone().try_into().unwrap();
110        let input = payload.clone();
111        
112        let hash: [u8; 64] = GenericHash::hash(&input, Some(s))
113            .map_err(|_| Error::DecryptError)?;
114
115        // Verify that the checksum hasn't been tampered with
116        if !constant_time_eq(&checksum, &hash) {
117            return Err(Error::DecryptError);
118        }
119
120        let public_key = Self::get_public_key_from_response(response.clone()).unwrap();
121        let payload_len = payload.len();
122        let signature = payload.get(payload_len - 64..payload_len).unwrap().to_vec();
123        let signature_public_key = Self::get_signing_public_key_from_response(response).unwrap();
124        let body = payload.get(60..payload_len - 96).unwrap().to_vec();
125
126        let decrypted = self.decrypt_v1(body, public_key, nonce.clone())?;
127
128        Self::is_signature_valid(decrypted.clone(), signature, signature_public_key)?;
129
130        return Ok(decrypted);
131    }
132
133    /// Returns true if the signature is valid for the response
134    pub fn is_signature_valid(
135        response: String,
136        signature: Vec<u8>,
137        public_key: Vec<u8>,
138    ) -> Result<bool, Error> {
139        if signature.len() != (CRYPTO_SIGN_BYTES as usize) {
140            return Err(Error::InvalidArgument(format!(
141                "Signature must be {} bytes",
142                CRYPTO_SIGN_BYTES
143            )));
144        }
145
146        if public_key.len() != (CRYPTO_SIGN_PUBLICKEYBYTES as usize) {
147            return Err(Error::InvalidArgument(format!(
148                "Public key must be {} bytes",
149                CRYPTO_SIGN_PUBLICKEYBYTES
150            )));
151        }
152
153        let sig: [u8; CRYPTO_SIGN_BYTES as usize] = signature.try_into().unwrap();
154        let pk: [u8; CRYPTO_SIGN_PUBLICKEYBYTES as usize] = public_key.try_into().unwrap();
155        
156        let result = crypto_sign::crypto_sign_verify_detached(
157            &sig,
158            response.as_bytes(),
159            &pk,
160        );
161
162        match result {
163            Ok(_) => return Ok(true),
164            Err(_) => return Ok(false),
165        };
166    }
167
168    ///  Extracts the public key from a v2 response
169    pub fn get_public_key_from_response(response: Vec<u8>) -> Result<Vec<u8>, Error> {
170        match Self::get_version(response.clone()) {
171            Ok(version) => match version {
172                2 => {
173                    let length = response.len();
174                    if length < 236 {
175                        return Err(Error::InvalidArgument(format!("Message is too short.")));
176                    }
177
178                    return Ok(response.get(28..60).unwrap().to_vec());
179                }
180                _ => {
181                    return Err(Error::InvalidArgument(format!(
182                        "The response provided is not suitable for public key extraction."
183                    )));
184                }
185            },
186            _ => {
187                return Err(Error::InvalidArgument(format!(
188                    "The response provided is not suitable for public key extraction."
189                )));
190            }
191        }
192    }
193
194    /// Extracts the public signing key from a v2 response
195    pub fn get_signing_public_key_from_response(response: Vec<u8>) -> Result<Vec<u8>, Error> {
196        match Self::get_version(response.clone()) {
197            Ok(version) => match version {
198                2 => {
199                    let length = response.len();
200                    if length < 236 {
201                        return Err(Error::InvalidArgument(format!("Message is too short.")));
202                    }
203
204                    return Ok(response
205                        .get(length - 160..(length - 160 + 32))
206                        .unwrap()
207                        .to_vec());
208                }
209                _ => {
210                    return Err(Error::InvalidArgument(format!(
211                        "The response provided is not suitable for public key extraction."
212                    )));
213                }
214            },
215            _ => {
216                return Err(Error::InvalidArgument(format!(
217                    "The response provided is not suitable for public key extraction."
218                )));
219            }
220        }
221    }
222
223    /// Returns the version information from the string
224    pub fn get_version(response: Vec<u8>) -> Result<i32, Error> {
225        if response.len() < 16 {
226            return Err(Error::InvalidArgument(format!(
227                "Message length is too short to determine version"
228            )));
229        }
230
231        match response.get(0..4) {
232            Some(header) => {
233                let s = hex::encode(header.to_vec()).to_string().to_uppercase();
234
235                if s.as_str().eq(VERSION_2_HEADER) {
236                    return Ok(2);
237                }
238
239                return Ok(1);
240            }
241            _ => {
242                return Ok(1);
243            }
244        }
245    }
246
247    /// Creates a response from the secret key
248    pub fn from(secret_key: Vec<u8>) -> Result<Self, Error> {
249        if secret_key.len() != (CRYPTO_BOX_SECRETKEYBYTES as usize) {
250            return Err(Error::InvalidArgument(format!(
251                "Secret key should be {} bytes",
252                CRYPTO_BOX_SECRETKEYBYTES
253            )));
254        }
255
256        return Ok(Response { secret_key });
257    }
258}