2021-03-11 06:20:49 +01:00
|
|
|
use std::convert::TryInto;
|
|
|
|
|
|
|
|
use regex::Regex;
|
|
|
|
use serde::{Deserialize, Serialize};
|
2021-03-15 03:39:25 +01:00
|
|
|
use warp::{Rejection, reply::Reply, reply::Response};
|
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
|
|
|
use super::storage;
|
|
|
|
|
|
|
|
#[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-12 08:55:37 +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-11 06:20:49 +01:00
|
|
|
pub ephemeral_key: String
|
|
|
|
}
|
|
|
|
|
2021-03-14 23:50:29 +01:00
|
|
|
lazy_static::lazy_static! {
|
|
|
|
|
|
|
|
pub static ref PRIVATE_KEY: x25519_dalek::StaticSecret = {
|
|
|
|
let bytes = include_bytes!("../x25519_private_key.pem");
|
|
|
|
return curve25519_parser::parse_openssl_25519_privkey(bytes).unwrap();
|
|
|
|
};
|
|
|
|
|
|
|
|
pub static ref PUBLIC_KEY: x25519_dalek::PublicKey = {
|
|
|
|
let bytes = include_bytes!("../x25519_public_key.pem");
|
|
|
|
return curve25519_parser::parse_openssl_25519_pubkey(bytes).unwrap();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-03-15 03:39:25 +01:00
|
|
|
pub async fn handle_onion_request(blob: warp::hyper::body::Bytes, pool: storage::DatabaseConnectionPool) -> Result<Response, Rejection> {
|
2021-03-12 08:55:37 +01:00
|
|
|
let payload = parse_onion_request_payload(blob).await?;
|
2021-03-15 03:39:25 +01:00
|
|
|
let (plaintext, symmetric_key) = decrypt_onion_request_payload(payload).await?;
|
|
|
|
// 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-16 04:22:33 +01:00
|
|
|
return handle_decrypted_onion_request(&plaintext, &symmetric_key, pool).await;
|
2021-03-15 03:39:25 +01:00
|
|
|
}
|
|
|
|
|
2021-03-16 04:22:33 +01:00
|
|
|
async fn handle_decrypted_onion_request(plaintext: &[u8], symmetric_key: &[u8], pool: storage::DatabaseConnectionPool) -> Result<Response, Rejection> {
|
|
|
|
let json = match String::from_utf8(plaintext.to_vec()) {
|
2021-03-12 03:31:55 +01:00
|
|
|
Ok(json) => json,
|
|
|
|
Err(e) => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Couldn't parse RPC call from JSON due to error: {}.", e);
|
|
|
|
return Err(warp::reject::custom(Error::InvalidOnionRequest));
|
2021-03-12 03:31:55 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let rpc_call = match serde_json::from_str(&json) {
|
|
|
|
Ok(rpc_call) => rpc_call,
|
|
|
|
Err(e) => {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Couldn't parse RPC call from JSON due to error: {}.", e);
|
|
|
|
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
|
|
|
|
let result = rpc::handle_rpc_call(rpc_call, &pool).await
|
|
|
|
// Turn any error that occurred into an HTTP response
|
2021-03-15 03:59:54 +01:00
|
|
|
.or_else(super::errors::into_response)?; // Safe because at this point any error should be caught and turned into an HTTP response (i.e. an OK result)
|
2021-03-15 03:39:25 +01:00
|
|
|
// 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-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 08:55:37 +01:00
|
|
|
async fn parse_onion_request_payload(blob: warp::hyper::body::Bytes) -> 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-11 06:20:49 +01:00
|
|
|
if blob.len() < 4 {
|
|
|
|
println!("Ignoring blob of invalid size.");
|
2021-03-12 08:55:37 +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-03-15 03:39:25 +01:00
|
|
|
println!("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-03-12 08:55:37 +01:00
|
|
|
println!("Couldn't parse onion request payload metadata due to error: {}.", e);
|
|
|
|
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-12 03:21:13 +01:00
|
|
|
let re = Regex::new(r"^[0-9a-fA-F]+$").unwrap();
|
2021-03-11 06:20:49 +01:00
|
|
|
if !re.is_match(&metadata.ephemeral_key) {
|
2021-03-12 08:55:37 +01:00
|
|
|
println!("Ignoring non hex encoded onion request payload ephemeral key.");
|
|
|
|
return Err(warp::reject::custom(Error::InvalidOnionRequest));
|
2021-03-11 06:20:49 +01:00
|
|
|
};
|
|
|
|
// Return
|
2021-03-12 08:55:37 +01:00
|
|
|
return Ok(OnionRequestPayload { ciphertext : ciphertext, metadata : 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.
|
|
|
|
async fn decrypt_onion_request_payload(payload: OnionRequestPayload) -> 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-16 04:22:33 +01:00
|
|
|
let symmetric_key = crypto::get_x25519_symmetric_key(&ephemeral_key, &PRIVATE_KEY).await?;
|
|
|
|
let plaintext = crypto::decrypt_aes_gcm(&payload.ciphertext, &symmetric_key).await?;
|
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 {
|
|
|
|
let error = models::Error { status_code : response.status().as_u16() };
|
|
|
|
bytes = serde_json::to_vec(&error).unwrap();
|
|
|
|
}
|
2021-03-16 04:22:33 +01:00
|
|
|
let ciphertext = crypto::encrypt_aes_gcm(&bytes, symmetric_key).await.unwrap();
|
2021-03-15 03:59:54 +01:00
|
|
|
let json = base64::encode(&ciphertext);
|
|
|
|
return Ok(warp::reply::json(&json).into_response());
|
2021-03-11 06:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Utilities
|
|
|
|
|
|
|
|
fn as_le_u32(array: &[u8; 4]) -> u32 {
|
|
|
|
((array[0] as u32) << 00) +
|
|
|
|
((array[1] as u32) << 08) +
|
|
|
|
((array[2] as u32) << 16) +
|
|
|
|
((array[3] as u32) << 24)
|
|
|
|
}
|