2021-03-11 06:20:49 +01:00
|
|
|
use std::convert::TryInto;
|
|
|
|
|
2021-04-01 00:55:47 +02:00
|
|
|
use log::warn;
|
2021-03-11 06:20:49 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-03-25 00:56:16 +01:00
|
|
|
use warp::{http::StatusCode, reply::Reply, reply::Response, Rejection};
|
2021-03-11 06:20:49 +01:00
|
|
|
|
|
|
|
use super::crypto;
|
2021-03-12 06:40:24 +01:00
|
|
|
use super::errors::Error;
|
2021-03-15 03:39:25 +01:00
|
|
|
use super::models;
|
2021-03-12 05:11:12 +01:00
|
|
|
use super::rpc;
|
2021-03-11 06:20:49 +01:00
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
2021-03-12 08:55:37 +01:00
|
|
|
struct OnionRequestPayload {
|
2021-03-11 06:20:49 +01:00
|
|
|
pub ciphertext: Vec<u8>,
|
2021-03-25 01:38:06 +01:00
|
|
|
pub metadata: OnionRequestPayloadMetadata,
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
2021-03-12 08:55:37 +01:00
|
|
|
struct OnionRequestPayloadMetadata {
|
2021-03-25 01:38:06 +01:00
|
|
|
pub ephemeral_key: String,
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
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)?;
|
2021-03-15 03:39:25 +01:00
|
|
|
// 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-15 03:39:25 +01:00
|
|
|
}
|
|
|
|
|
2021-03-25 00:56:16 +01:00
|
|
|
async fn handle_decrypted_onion_request(
|
2021-03-25 01:38:06 +01:00
|
|
|
plaintext: &[u8], symmetric_key: &[u8],
|
2021-03-25 00:56:16 +01:00
|
|
|
) -> 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) => {
|
2021-04-01 00:55:47 +02:00
|
|
|
warn!("Couldn't parse RPC call from JSON due to error: {}.", e);
|
2021-03-12 08:55:37 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidOnionRequest));
|
2021-03-12 03:31:55 +01:00
|
|
|
}
|
|
|
|
};
|
2021-03-15 03:39:25 +01:00
|
|
|
// Perform the RPC call
|
2021-03-25 00:56:16 +01:00
|
|
|
let result = rpc::handle_rpc_call(rpc_call)
|
2021-03-15 03:39:25 +01:00
|
|
|
// Turn any error that occurred into an HTTP response
|
2021-03-25 01:04:11 +01:00
|
|
|
// Unwrapping is safe because at this point any error should be caught and turned into an HTTP response (i.e. an OK result)
|
|
|
|
.or_else(super::errors::into_response)?;
|
|
|
|
// Encrypt the HTTP response so that it's propagated back to the client that made the onion request
|
2021-03-15 03:39:25 +01:00
|
|
|
return encrypt_response(result, symmetric_key).await;
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
2021-03-31 06:12:32 +02:00
|
|
|
fn parse_onion_request_payload(
|
2021-03-25 01:38:06 +01:00
|
|
|
blob: warp::hyper::body::Bytes,
|
2021-03-25 00:56:16 +01:00
|
|
|
) -> Result<OnionRequestPayload, Rejection> {
|
2021-03-15 03:39:25 +01:00
|
|
|
// 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));
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
// 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-03-11 06:20:49 +01:00
|
|
|
let size = as_le_u32(&blob[0..4].try_into().unwrap()) as usize;
|
|
|
|
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) => {
|
2021-04-01 00:55:47 +02:00
|
|
|
warn!("Couldn't parse onion request payload metadata due to error: {}.", e);
|
2021-03-12 08:55:37 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidOnionRequest));
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// Parse metadata
|
2021-03-12 08:55:37 +01:00
|
|
|
let metadata: OnionRequestPayloadMetadata = match serde_json::from_str(&json) {
|
2021-03-11 06:20:49 +01:00
|
|
|
Ok(metadata) => metadata,
|
|
|
|
Err(e) => {
|
2021-04-01 00:55:47 +02:00
|
|
|
warn!("Couldn't parse onion request payload metadata due to error: {}.", e);
|
2021-03-12 08:55:37 +01:00
|
|
|
return Err(warp::reject::custom(Error::InvalidOnionRequest));
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// 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));
|
2021-03-11 06:20:49 +01:00
|
|
|
};
|
|
|
|
// Return
|
2021-03-16 04:33:55 +01:00
|
|
|
return Ok(OnionRequestPayload { ciphertext, metadata });
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
2021-03-15 03:39:25 +01:00
|
|
|
/// 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-03-25 01:38:06 +01: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)?;
|
2021-03-15 03:39:25 +01:00
|
|
|
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> {
|
2021-03-15 03:39:25 +01:00
|
|
|
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() };
|
2021-03-15 03:39:25 +01:00
|
|
|
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);
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Utilities
|
|
|
|
|
|
|
|
fn as_le_u32(array: &[u8; 4]) -> u32 {
|
2021-03-25 00:56:16 +01:00
|
|
|
((array[0] as u32) << 00)
|
|
|
|
+ ((array[1] as u32) << 08)
|
|
|
|
+ ((array[2] as u32) << 16)
|
|
|
|
+ ((array[3] as u32) << 24)
|
|
|
|
}
|