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

142 lines
6.2 KiB
Rust
Raw Normal View History

use std::convert::TryInto;
use regex::Regex;
use serde::{Deserialize, Serialize};
use warp::{Rejection, reply::Reply, reply::Response};
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;
use super::storage;
#[derive(Deserialize, Serialize, Debug)]
2021-03-12 08:55:37 +01:00
struct OnionRequestPayload {
pub ciphertext: Vec<u8>,
2021-03-12 08:55:37 +01:00
pub metadata: OnionRequestPayloadMetadata
}
#[derive(Deserialize, Serialize, Debug)]
2021-03-12 08:55:37 +01:00
struct OnionRequestPayloadMetadata {
pub ephemeral_key: String
}
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();
};
}
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?;
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-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
}
};
// 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)
// 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-12 08:55:37 +01:00
async fn parse_onion_request_payload(blob: warp::hyper::body::Bytes) -> Result<OnionRequestPayload, Rejection> {
// The encoding of an onion request looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 |
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));
}
// 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
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) => {
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));
}
};
// Parse metadata
2021-03-12 08:55:37 +01:00
let metadata: OnionRequestPayloadMetadata = match serde_json::from_str(&json) {
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));
}
};
// 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();
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));
};
// Return
2021-03-12 08:55:37 +01:00
return Ok(OnionRequestPayload { ciphertext : ciphertext, metadata : metadata });
}
/// 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?;
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 {
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());
}
// 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)
}