ncryptf/rocket/json.rs
1use std::{error, fmt, io};
2
3use base64::{engine::general_purpose, Engine as _};
4use crate::shared::EncryptionKey;
5use super::{
6 CacheWrapper, RequestPublicKey, RequestSigningPublicKey
7};
8
9use crate::shared::NCRYPTF_CONTENT_TYPE;
10use anyhow::anyhow;
11use rocket::{
12 data::{FromData, Limits, Outcome},
13 http::{ContentType, Header, Status},
14 response::{self, Responder, Response},
15 Data, Request, State,
16};
17
18use serde::{Deserialize, Serialize};
19
20// Error returned by the [`Json`] guard when JSON deserialization fails.
21#[derive(Debug)]
22pub enum Error<'a> {
23 /// An I/O error occurred while reading the incoming request data.
24 Io(io::Error),
25 /// Parser failure
26 Parse(&'a str, serde_json::error::Error),
27}
28
29impl<'a> fmt::Display for Error<'a> {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Io(err) => write!(f, "i/o error: {}", err),
33 Self::Parse(_, err) => write!(f, "parse error: {}", err),
34 }
35 }
36}
37
38impl<'a> error::Error for Error<'a> {
39 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
40 match self {
41 Self::Io(err) => Some(err),
42 Self::Parse(_, err) => Some(err),
43 }
44 }
45}
46
47/// ncryptf::rocket::Json represents a application/vnd.ncryptf+json, JSON string
48/// The JSON struct supports both serialization and de-serialization to reduce implementation time within your application
49///
50/// ### Usage
51/// Encryption keys and identifiers are stored in Redis. Make sure you have a `rocket_db_pool::Config` setup
52/// for Redis, and added to your rocket figment()
53/// You do not have to setup a RequestGuard for Redis, this just uses the same configuration
54/// ```rust
55/// .merge(("databases.cache", rocket_db_pools::Config {
56/// url: format!("redis://127.0.0.1:6379/"),
57/// min_connections: None,
58/// max_connections: 1024,
59/// connect_timeout: 3,
60/// idle_timeout: None,
61/// }))
62/// ````
63///
64/// Next, create a struct to represent the request data. This struct MUST implement Serialize if you want to return a ncryptf encrypted response
65/// ```rust
66/// use rocket::serde::{Serialize, json::Json};
67///
68/// [derive(Serialize)]
69/// #[serde(crate = "rocket::serde")]
70/// struct TestStruct<'r> {
71/// pub hello: &'r str
72/// }
73/// ```
74///
75/// our request can now be parsed using data tags.
76/// Responses can be automatically converted into an JSON encrypted ncryptf response by returning `ncryptf::rocket::Json<T>`
77///
78/// If your header is application/vnd.ncryptf+json, returning a ncryptf::rocket::Json<T> will return an encrypted response
79/// If the header is an application/json (or anything else), ncryptf::rocket::Json<T> will return a rocket::serde::json::Json<T> equivalent JSON response in plain text
80///
81/// ```rust
82/// #[post("/echo", data="<data>")]
83/// fn echo(data: ncryptf::rocket::Json<TestStruct>) -> ncryptf::rocket::Json<TestStruct> {
84/// // data.0 is your TestStruct
85/// //
86/// return ncryptf::rocket::Json(data.0);
87/// }
88/// ```
89///
90///
91/// nryptf::rocket::Json<T>` supercedes rocket::serde::json::Json<T> for both encrypted messages, and regular JSON. If you intend to use
92/// the `Authorization` trait to authenticate users, you MUST receive and return this for your data type
93#[derive(Debug, Clone)]
94pub struct Json<T>(pub T);
95
96impl<T> Json<T> {
97 /// Returns the underlying serde_json::Value object
98 pub fn into_inner(self) -> T {
99 return self.0;
100 }
101
102 /// Returns a serde_json::Value wrapped as an ncryptf::Json representation
103 pub fn from_value(value: T) -> Self {
104 return Self(value);
105 }
106
107 /// Deserializes the request sting into a raw JSON string
108 pub fn deserialize_req_from_string<'r>(
109 req: &'r Request<'_>,
110 string: String,
111 cache: &CacheWrapper,
112 ) -> Result<String, Error<'r>> {
113 match req.headers().get_one("Content-Type") {
114 Some(h) => {
115 match h {
116 NCRYPTF_CONTENT_TYPE => {
117 // Convert the base64 payload into Vec<u8>
118 let data = general_purpose::STANDARD.decode(string).unwrap();
119
120 // Retrieve the HashID header
121 let hash_id = match req.headers().get_one("X-HashId") {
122 Some(h) => h,
123 None => {
124 return Err(Error::Io(io::Error::new(
125 io::ErrorKind::Other,
126 "Missing client provided hash identifier.",
127 )));
128 }
129 };
130
131 // Retrieve the encryption key from cache
132 let ek = match cache.get(hash_id) {
133 Some(ek) => ek,
134 None => {
135 return Err(Error::Io(io::Error::new(
136 io::ErrorKind::Other,
137 "Encryption key is either invalid, or may have expired.",
138 )));
139 }
140 };
141
142 // Retrieve the secret key
143 let sk = ek.get_box_kp().get_secret_key();
144
145 // Delete the key if it is ephemeral
146 if ek.is_ephemeral() {
147 cache.remove(hash_id);
148 }
149
150 // Decrypt the response, then deserialize the underlying JSON into the requested struct
151 match crate::Response::from(sk) {
152 Ok(response) => {
153 // Pull the public key from the response then store it in the local cache so we can re-use it later
154 // We need this in order to send an encrypted response back since Responder::respond_to
155 // won't let us read the request
156 match crate::Response::get_public_key_from_response(data.clone()) {
157 Ok(cpk) => {
158 req.local_cache(|| {
159 return RequestPublicKey(cpk.clone());
160 });
161 }
162 Err(error) => {
163 return Err(Error::Io(io::Error::new(
164 io::ErrorKind::Other,
165 error.to_string(),
166 )));
167 }
168 };
169
170 // Extract the signature key
171 match crate::Response::get_signing_public_key_from_response(
172 data.clone(),
173 ) {
174 Ok(cpk) => {
175 req.local_cache(|| {
176 return RequestSigningPublicKey(cpk.clone());
177 });
178 }
179 Err(error) => {
180 return Err(Error::Io(io::Error::new(
181 io::ErrorKind::Other,
182 error.to_string(),
183 )));
184 }
185 };
186
187 // Pull the public key and nonce from the headers if they are set.
188 // If they are set they assume priority, and response.decrypt will likely decrypt this as a V1 response
189 let public_key = match req.headers().get_one("X-PubKey") {
190 Some(h) => Some(h.as_bytes().to_vec()),
191 None => None,
192 };
193
194 let nonce = match req.headers().get_one("X-Nonce") {
195 Some(h) => Some(h.as_bytes().to_vec()),
196 None => None,
197 };
198
199 // Decrypt the request
200 match response.decrypt(data.clone(), public_key, nonce) {
201 Ok(msg) => {
202 // Serialize this into a struct, and store the decrypted response in the cache
203 // We'll need this for the Authorization header
204 return Ok(req.local_cache(|| return msg).to_owned());
205 }
206 Err(error) => {
207 return Err(Error::Io(io::Error::new(
208 io::ErrorKind::Other,
209 error.to_string(),
210 )));
211 }
212 };
213 }
214 Err(error) => {
215 return Err(Error::Io(io::Error::new(
216 io::ErrorKind::Other,
217 error.to_string(),
218 )));
219 }
220 };
221 }
222 // If this is a json request, return raw json
223 "json" => {
224 return Ok(req.local_cache(|| return string).to_owned());
225 }
226 // For now, return JSON even if another header was sent.
227 _ => {
228 return Ok(req.local_cache(|| return string).to_owned());
229 }
230 }
231 }
232 // If there isn't an Accept header, also return JSON
233 None => {
234 return Ok(req.local_cache(|| return string).to_owned());
235 }
236 };
237 }
238}
239
240impl<'r, T: Deserialize<'r>> Json<T> {
241 pub fn from_str(s: &'r str) -> Result<Self, Error<'r>> {
242 return serde_json::from_str(s)
243 .map(Json)
244 .map_err(|e| Error::Parse(s, e));
245 }
246
247 pub async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Result<Self, Error<'r>> {
248 parse_body(req, data).await
249 }
250}
251
252#[rocket::async_trait]
253impl<'r, T: Deserialize<'r>> FromData<'r> for Json<T> {
254 type Error = Error<'r>;
255
256 async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
257 match Self::from_data(req, data).await {
258 Ok(value) => Outcome::Success(value),
259 Err(Error::Io(e)) if e.kind() == io::ErrorKind::UnexpectedEof => {
260 Outcome::Error((Status::PayloadTooLarge, Error::Io(e)))
261 }
262 Err(Error::Parse(s, e)) if e.classify() == serde_json::error::Category::Data => {
263 req.local_cache(|| return "".to_string());
264 Outcome::Error((Status::UnprocessableEntity, Error::Parse(s, e)))
265 }
266 Err(e) => Outcome::Error((Status::BadRequest, e)),
267 }
268 }
269}
270
271/// Allows for ncryptf::rocket::Json to be emitted as a responder to reduce code overhead
272/// This responder will handle encrypting the underlying request data correctly for all supported
273/// ncryptf versionn
274impl<'r, T: Serialize> Responder<'r, 'static> for Json<T> {
275 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
276 match respond_to_with_ncryptf(self, Status::Ok, req) {
277 Ok(response) => response,
278 Err(_) => return Err(Status::InternalServerError),
279 }
280 }
281}
282
283/// ncryptf::rocket::JsonResponse is identical to ncryptf::rocket::Json<T> except that is also supports setting
284/// a status code on the response.
285///
286/// You may find this useful for:
287/// - Returning an common JSON structure for parsing in another clients / application
288/// - Returning an error struct if your request is not successful to inform the client on steps they can
289/// perform to resolve the error
290///
291/// The response with JsonResponse<T> will be identical to Json<T>. See Json<T> for more information
292#[derive(Debug, Clone)]
293pub struct JsonResponse<T> {
294 pub status: Status,
295 pub json: Json<T>,
296}
297
298impl<'r, T: Serialize> Responder<'r, 'static> for JsonResponse<T> {
299 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
300 return match respond_to_with_ncryptf(self.json, self.status, req) {
301 Ok(response) => response,
302 Err(_) => return Err(Status::InternalServerError),
303 };
304 }
305}
306
307pub async fn parse_body<'r, T: Deserialize<'r>>(req: &'r Request<'_>, data: Data<'r>) -> Result<Json<T>, Error<'r>> {
308 let limit = req.limits().get("json").unwrap_or(Limits::JSON);
309 let string = match data.open(limit).into_string().await {
310 Ok(s) if s.is_complete() => s.into_inner(),
311 Ok(_) => {
312 let eof = io::ErrorKind::UnexpectedEof;
313 return Err(Error::Io(io::Error::new(eof, "data limit exceeded")));
314 }
315 Err(error) => return Err(Error::Io(error)),
316 };
317
318 // Get the cache from managed state
319 let cache = match req.rocket().state::<CacheWrapper>() {
320 Some(cache) => cache,
321 None => {
322 return Err(Error::Io(io::Error::new(
323 io::ErrorKind::Other,
324 "Cache not found in managed state",
325 )));
326 }
327 };
328
329 match Json::<T>::deserialize_req_from_string(req, string, cache) {
330 Ok(s) => {
331 return Json::<T>::from_str(req.local_cache(|| return s));
332 }
333 Err(error) => return Err(error),
334 };
335}
336
337pub fn respond_to_with_ncryptf<'r, 'a, T: serde::Serialize>(
338 m: Json<T>,
339 status: Status,
340 req: &'r Request<'_>,
341) -> Result<response::Result<'static>, anyhow::Error> {
342 // Handle serialization
343 let message = match serde_json::to_string(&m.0) {
344 Ok(json) => json,
345 Err(_error) => return Err(anyhow!("Could not deserialize message")),
346 };
347
348 match req.headers().get_one("Accept") {
349 Some(accept) => {
350 match accept {
351 NCRYPTF_CONTENT_TYPE => {
352 // Retrieve the client public key
353 let cpk = req.local_cache(|| {
354 return RequestPublicKey(Vec::<u8>::new());
355 });
356
357 let pk: Vec<u8>;
358 // If the cache data is empty, check the header as a fallback
359 if cpk.0.is_empty() {
360 pk = match req.headers().get_one("X-PubKey") {
361 Some(h) => general_purpose::STANDARD.decode(h).unwrap(),
362 // If the header isn't sent, then we have no way to encrypt a response the client can use
363 None =>return Err(anyhow!("Public key is not available on request. Unable to re-encrypt message to client."))
364 };
365 } else {
366 pk = cpk.0.clone();
367 }
368
369 let ek = EncryptionKey::new(false);
370
371 // Create an encryption key then store it in cache
372 // The client can choose to use the new key or ignore it, but we're always going to provide our own for each request
373 let cache = match req.rocket().state::<CacheWrapper>() {
374 Some(cache) => cache,
375 None => return Err(anyhow!("Cache not found in managed state")),
376 };
377
378 cache.set(ek.get_hash_id(), ek.clone());
379
380 let mut request = match crate::Request::from(
381 ek.get_box_kp().get_secret_key(),
382 // todo!() we should pull out the token signature, if it is set and use it
383 ek.get_sign_kp().get_secret_key(),
384 ) {
385 Ok(request) => request,
386 Err(_error) => return Err(anyhow!("Unable to encrypt message")),
387 };
388
389 let content = match request.encrypt(message, pk) {
390 Ok(content) => content,
391 Err(_error) => return Err(anyhow!("Unable to encrypt message")),
392 };
393
394 let d = general_purpose::STANDARD.encode(content);
395
396 let respond_to = match d.respond_to(req) {
397 Ok(s) => s,
398 Err(_) => return Err(anyhow!("Could not send response")),
399 };
400
401 return Ok(Response::build_from(respond_to)
402 .header(ContentType::new("application", "vnd.ncryptf+json"))
403 .header(Header::new(
404 "x-public-key",
405 general_purpose::STANDARD.encode(ek.get_box_kp().get_public_key()),
406 ))
407 .header(Header::new(
408 "x-signature-public-key",
409 general_purpose::STANDARD.encode(ek.get_sign_kp().get_public_key()),
410 ))
411 .header(Header::new(
412 "x-public-key-expiration",
413 ek.expires_at.to_string(),
414 ))
415 .header(Header::new("x-hashid", ek.get_hash_id()))
416 .status(status)
417 .ok());
418 }
419 _ => {
420 let respond_to = match message.respond_to(req) {
421 Ok(s) => s,
422 Err(_) => return Err(anyhow!("Could not send response")),
423 };
424 // Default to a JSON response if the content type is not an ncryptf content type
425 // This is compatible with other implementations of ncryptf
426 return Ok(Response::build_from(respond_to)
427 .header(ContentType::new("application", "json"))
428 .status(status)
429 .ok());
430 }
431 }
432 }
433 None => {
434 let respond_to = match message.respond_to(req) {
435 Ok(s) => s,
436 Err(_) => return Err(anyhow!("Could not send response")),
437 };
438
439 // If an Accept is not defined on the request, return JSON when this struct is requested
440 return Ok(Response::build_from(respond_to)
441 .header(ContentType::new("application", "json"))
442 .status(status)
443 .ok());
444 }
445 }
446}