session-open-group-server/src/onion_requests.rs

132 lines
5.4 KiB
Rust
Raw Normal View History

use std::convert::TryInto;
2021-04-01 00:55:47 +02:00
use log::warn;
use serde::{Deserialize, Serialize};
2021-03-25 00:56:16 +01:00
use warp::{http::StatusCode, reply::Reply, reply::Response, Rejection};
use super::crypto;
2021-03-12 06:40:24 +01:00
use super::errors::Error;
use super::models;
2021-03-12 05:11:12 +01:00
use super::rpc;
#[derive(Deserialize, Serialize, Debug)]
2021-03-12 08:55:37 +01:00
struct OnionRequestPayload {
pub ciphertext: Vec<u8>,
2021-10-08 07:56:27 +02:00
pub metadata: OnionRequestPayloadMetadata,
}
#[derive(Deserialize, Serialize, Debug)]
2021-03-12 08:55:37 +01:00
struct OnionRequestPayloadMetadata {
2021-10-08 07:56:27 +02:00
pub ephemeral_key: String,
}
2021-03-18 05:04:56 +01:00
pub async fn handle_onion_request(blob: warp::hyper::body::Bytes) -> Result<Response, Rejection> {
2021-03-31 06:12:32 +02:00
let payload = parse_onion_request_payload(blob)?;
let (plaintext, symmetric_key) = decrypt_onion_request_payload(payload)?;
// From this point on we can wrap any error that occurs in a HTTP response that's encrypted
// with the given symmetric key, so that the error that occurred is propagated back to the
// client that made the onion request.
//
// If an error occurred before this point we'll have responded to the Service Node with a
// unsuccessful status code, which it'll have propagated back to the client as a "Loki server
// error" (i.e. the actual error is hidden from the client that made the onion request). This
// is unfortunate but cannot be solved without fundamentally changing how onion requests work.
2021-03-18 05:04:56 +01:00
return handle_decrypted_onion_request(&plaintext, &symmetric_key).await;
}
2021-03-25 00:56:16 +01:00
async fn handle_decrypted_onion_request(
plaintext: &[u8],
2021-10-08 07:56:27 +02:00
symmetric_key: &[u8],
) -> Result<Response, Rejection> {
2021-03-23 01:13:32 +01:00
let rpc_call = match serde_json::from_slice(plaintext) {
2021-03-12 03:31:55 +01:00
Ok(rpc_call) => rpc_call,
Err(e) => {
warn!("Couldn't parse RPC call from JSON: {}.", e);
2021-03-12 08:55:37 +01:00
return Err(warp::reject::custom(Error::InvalidOnionRequest));
2021-03-12 03:31:55 +01:00
}
};
// Perform the RPC call
2021-03-25 00:56:16 +01:00
let result = rpc::handle_rpc_call(rpc_call)
2021-04-01 01:32:25 +02:00
.await
// Turn any error that occurred into an HTTP response
// Unwrapping is safe because at this point any error should be caught and turned into an
// HTTP response (i.e. an OK result)
2021-03-25 01:04:11 +01:00
.or_else(super::errors::into_response)?;
// Encrypt the HTTP response so that it's propagated back to the client that made the onion
// request
return encrypt_response(result, symmetric_key).await;
}
2021-03-31 06:12:32 +02:00
fn parse_onion_request_payload(
2021-10-08 07:56:27 +02:00
blob: warp::hyper::body::Bytes,
2021-03-25 00:56:16 +01:00
) -> Result<OnionRequestPayload, Rejection> {
// The encoding of an onion request looks like:
//
// | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 |
2021-03-25 00:56:16 +01:00
if blob.len() < 4 {
2021-04-01 00:55:47 +02:00
warn!("Ignoring blob of invalid size.");
2021-03-25 00:56:16 +01:00
return Err(warp::reject::custom(Error::InvalidOnionRequest));
}
// Extract the different components
2021-03-12 03:21:13 +01:00
// This is safe because we know blob has a length of at least 4 bytes
2021-05-14 06:22:33 +02:00
let size = u32::from_le_bytes(blob[0..4].try_into().unwrap()) as usize;
2021-06-29 02:14:19 +02:00
if blob.len() < 4 + size {
warn!("Ignoring blob of invalid size.");
return Err(warp::reject::custom(Error::InvalidOnionRequest));
}
let ciphertext: Vec<u8> = blob[4..(4 + size)].try_into().unwrap();
let utf8_json: Vec<u8> = blob[(4 + size)..].try_into().unwrap();
// Parse JSON
let json = match String::from_utf8(utf8_json) {
Ok(json) => json,
Err(e) => {
warn!("Couldn't parse onion request payload metadata: {}.", e);
2021-03-12 08:55:37 +01:00
return Err(warp::reject::custom(Error::InvalidOnionRequest));
}
};
// Parse metadata
2021-03-12 08:55:37 +01:00
let metadata: OnionRequestPayloadMetadata = match serde_json::from_str(&json) {
Ok(metadata) => metadata,
Err(e) => {
warn!("Couldn't parse onion request payload metadata: {}.", e);
2021-03-12 08:55:37 +01:00
return Err(warp::reject::custom(Error::InvalidOnionRequest));
}
};
// Check that the ephemeral public key is valid hex
2021-03-25 00:56:16 +01:00
if hex::decode(&metadata.ephemeral_key).is_err() {
2021-04-01 00:55:47 +02:00
warn!("Ignoring non hex encoded onion request payload ephemeral key.");
2021-03-25 00:56:16 +01:00
return Err(warp::reject::custom(Error::InvalidOnionRequest));
};
// Return
2021-03-16 04:33:55 +01:00
return Ok(OnionRequestPayload { ciphertext, metadata });
}
/// Returns the decrypted `payload.ciphertext` plus the `symmetric_key` that was used for
/// decryption if successful.
2021-03-31 06:12:32 +02:00
fn decrypt_onion_request_payload(
2021-10-08 07:56:27 +02:00
payload: OnionRequestPayload,
2021-03-25 00:56:16 +01:00
) -> Result<(Vec<u8>, Vec<u8>), Rejection> {
2021-03-12 02:39:35 +01:00
let ephemeral_key = hex::decode(payload.metadata.ephemeral_key).unwrap(); // Safe because it was validated in the parsing step
2021-03-31 02:23:45 +02:00
let symmetric_key = crypto::get_x25519_symmetric_key(&ephemeral_key, &crypto::PRIVATE_KEY)?;
let plaintext = crypto::decrypt_aes_gcm(&payload.ciphertext, &symmetric_key)?;
return Ok((plaintext, symmetric_key));
}
2021-03-16 04:22:33 +01:00
async fn encrypt_response(response: Response, symmetric_key: &[u8]) -> Result<Response, Rejection> {
let bytes: Vec<u8>;
if response.status().is_success() {
let (_, body) = response.into_parts();
bytes = warp::hyper::body::to_bytes(body).await.unwrap().to_vec();
} else {
2021-03-25 00:56:16 +01:00
let error = models::StatusCode { status_code: response.status().as_u16() };
bytes = serde_json::to_vec(&error).unwrap();
}
2021-03-31 02:23:45 +02:00
let ciphertext = crypto::encrypt_aes_gcm(&bytes, symmetric_key).unwrap();
2021-03-15 03:59:54 +01:00
let json = base64::encode(&ciphertext);
2021-03-25 00:56:16 +01:00
let response =
warp::http::Response::builder().status(StatusCode::OK.as_u16()).body(json).into_response();
2021-03-23 03:04:38 +01:00
return Ok(response);
}