Clean up error handling

This commit is contained in:
Niels Andriesse 2021-03-12 16:40:24 +11:00
parent 1a8e58e389
commit ee1da1b712
9 changed files with 63 additions and 88 deletions

View file

@ -5,11 +5,9 @@ use aes_gcm::aead::{Aead, NewAead, generic_array::GenericArray};
use sha2::Sha256;
use hmac::{Hmac, Mac, NewMac};
type HmacSha256 = Hmac<Sha256>;
use super::errors::Error;
#[derive(Debug)]
pub struct DecryptionError;
impl warp::reject::Reject for DecryptionError { }
type HmacSha256 = Hmac<Sha256>;
// By default the aes-gcm crate will use software implementations of both AES and the POLYVAL universal hash function. When
// targeting modern x86/x86_64 CPUs, use the following RUSTFLAGS to take advantage of high performance AES-NI and CLMUL CPU
@ -21,8 +19,8 @@ const IV_SIZE: usize = 12;
pub async fn get_x25519_symmetric_key(public_key: Vec<u8>, private_key: x25519_dalek::StaticSecret) -> Result<Vec<u8>, warp::reject::Rejection> {
if public_key.len() != 32 {
println!("Couldn't create symmetric key using public key of invalid length.");
return Err(warp::reject::custom(DecryptionError));
println!("Couldn't create symmetric key using public key of invalid length: {}.", hex::encode(public_key));
return Err(warp::reject::custom(Error::DecryptionFailed));
}
let public_key: [u8; 32] = public_key.try_into().unwrap(); // Safe because we know it's a Vec<u8> of length 32
let dalek_public_key = x25519_dalek::PublicKey::from(public_key);
@ -34,8 +32,8 @@ pub async fn get_x25519_symmetric_key(public_key: Vec<u8>, private_key: x25519_d
pub async fn decrypt_aes_gcm(iv_and_ciphertext: Vec<u8>, symmetric_key: Vec<u8>) -> Result<Vec<u8>, warp::reject::Rejection> {
if iv_and_ciphertext.len() < IV_SIZE {
println!("Ignoring ciphertext of invalid size.");
return Err(warp::reject::custom(DecryptionError));
println!("Ignoring ciphertext of invalid size: {}.", iv_and_ciphertext.len());
return Err(warp::reject::custom(Error::DecryptionFailed));
}
let iv: Vec<u8> = iv_and_ciphertext[0..IV_SIZE].try_into().unwrap(); // Safe because we know iv_and_ciphertext has a length of at least IV_SIZE bytes
let ciphertext: Vec<u8> = iv_and_ciphertext[IV_SIZE..].try_into().unwrap(); // Safe because we know iv_and_ciphertext has a length of at least IV_SIZE bytes
@ -44,7 +42,7 @@ pub async fn decrypt_aes_gcm(iv_and_ciphertext: Vec<u8>, symmetric_key: Vec<u8>)
Ok(plaintext) => return Ok(plaintext),
Err(e) => {
println!("Couldn't decrypt ciphertext due to error: {:?}.", e);
return Err(warp::reject::custom(DecryptionError));
return Err(warp::reject::custom(Error::DecryptionFailed));
}
}
}

11
src/errors.rs Normal file
View file

@ -0,0 +1,11 @@
#[derive(Debug)]
pub enum Error {
DecryptionFailed,
DatabaseFailedInternally,
InvalidRequest,
ParsingFailed,
Unauthorized,
ValidationFailed
}
impl warp::reject::Reject for Error { }

View file

@ -2,20 +2,17 @@ use regex::Regex;
use rusqlite::params;
use warp::{Rejection, http::StatusCode, reply::Reply, reply::Response};
use super::errors::Error;
use super::models;
use super::rpc;
use super::storage;
#[derive(Debug)]
pub struct UnauthorizedError;
impl warp::reject::Reject for UnauthorizedError { }
/// Inserts the given `message` into the database if it's valid.
pub async fn insert_message(mut message: models::Message, pool: &storage::DatabaseConnectionPool) -> Result<Response, Rejection> {
// Validate the message
if !message.is_valid() {
println!("Ignoring invalid message.");
return Err(warp::reject::custom(models::ValidationError));
return Err(warp::reject::custom(Error::ValidationFailed));
}
// TODO: Check that the requesting user isn't banned
@ -55,7 +52,7 @@ pub async fn get_messages(options: rpc::QueryOptions, pool: &storage::DatabaseCo
Ok(rows) => rows,
Err(e) => {
println!("Couldn't query database due to error: {:?}.", e);
return Err(warp::reject::custom(storage::DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
let messages: Vec<models::Message> = rows.filter_map(|result| result.ok()).collect();
@ -106,7 +103,7 @@ pub async fn get_deleted_messages(options: rpc::QueryOptions, pool: &storage::Da
Ok(rows) => rows,
Err(e) => {
println!("Couldn't query database due to error: {:?}.", e);
return Err(warp::reject::custom(storage::DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
let ids: Vec<i64> = rows.filter_map(|result| result.ok()).collect();
@ -125,7 +122,7 @@ pub async fn ban(public_key: String, pool: &storage::DatabaseConnectionPool) ->
// Validate the public key
if !is_valid_public_key(&public_key) {
println!("Ignoring ban request for invalid public key.");
return Err(warp::reject::custom(models::ValidationError));
return Err(warp::reject::custom(Error::ValidationFailed));
}
// TODO: Check that the requesting user is a moderator
@ -149,7 +146,7 @@ pub async fn unban(public_key: String, pool: &storage::DatabaseConnectionPool) -
// Validate the public key
if !is_valid_public_key(&public_key) {
println!("Ignoring unban request for invalid public key.");
return Err(warp::reject::custom(models::ValidationError));
return Err(warp::reject::custom(Error::ValidationFailed));
}
// TODO: Check that the requesting user is a moderator
@ -196,7 +193,7 @@ pub async fn get_moderators_vector(pool: &storage::DatabaseConnectionPool) -> Re
Ok(rows) => rows,
Err(e) => {
println!("Couldn't query database due to error: {:?}.", e);
return Err(warp::reject::custom(storage::DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
// Return
@ -220,7 +217,7 @@ pub async fn get_banned_public_keys_vector(pool: &storage::DatabaseConnectionPoo
Ok(rows) => rows,
Err(e) => {
println!("Couldn't query database due to error: {:?}.", e);
return Err(warp::reject::custom(storage::DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
// Return

View file

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use warp::Rejection;
use super::crypto;
use super::errors::Error;
use super::rpc;
use super::storage;
@ -26,32 +27,21 @@ pub struct RpcCall {
pub method: String
}
#[derive(Debug)]
pub struct RequestSizeExceededError;
impl warp::reject::Reject for RequestSizeExceededError { }
#[derive(Debug)]
pub struct ParsingError;
impl warp::reject::Reject for ParsingError { }
pub async fn handle_lsrpc_request(blob: warp::hyper::body::Bytes, pool: storage::DatabaseConnectionPool) -> Result<impl warp::Reply, Rejection> {
if blob.len() > 10 * 1024 * 1024 { // Match storage server
return Err(warp::reject::custom(RequestSizeExceededError));
}
let payload = parse_lsrpc_payload(blob).await?;
let plaintext = decrypt_lsrpc_payload(payload).await?;
let json = match String::from_utf8(plaintext) {
Ok(json) => json,
Err(e) => {
println!("Couldn't parse RPC call from JSON due to error: {:?}.", e);
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
}
};
let rpc_call = match serde_json::from_str(&json) {
Ok(rpc_call) => rpc_call,
Err(e) => {
println!("Couldn't parse RPC call from JSON due to error: {:?}.", e);
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
}
};
return rpc::handle_rpc_call(rpc_call, &pool).await;
@ -61,7 +51,7 @@ async fn parse_lsrpc_payload(blob: warp::hyper::body::Bytes) -> Result<LsrpcPayl
// The encoding of onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 |
if blob.len() < 4 {
println!("Ignoring blob of invalid size.");
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
}
// Extract the different components
// This is safe because we know blob has a length of at least 4 bytes
@ -73,7 +63,7 @@ async fn parse_lsrpc_payload(blob: warp::hyper::body::Bytes) -> Result<LsrpcPayl
Ok(json) => json,
Err(e) => {
println!("Couldn't parse string from bytes due to error: {:?}.", e);
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
}
};
// Parse metadata
@ -81,14 +71,14 @@ async fn parse_lsrpc_payload(blob: warp::hyper::body::Bytes) -> Result<LsrpcPayl
Ok(metadata) => metadata,
Err(e) => {
println!("Couldn't parse LSRPC payload metadata due to error: {:?}.", e);
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
}
};
// Check that the ephemeral public key is valid hex
let re = Regex::new(r"^[0-9a-fA-F]+$").unwrap();
if !re.is_match(&metadata.ephemeral_key) {
println!("Ignoring non hex encoded LSRPC payload ephemeral key.");
return Err(warp::reject::custom(ParsingError));
return Err(warp::reject::custom(Error::ParsingFailed));
};
// Return
return Ok(LsrpcPayload { ciphertext : ciphertext, metadata : metadata });

View file

@ -1,4 +1,5 @@
mod crypto;
mod errors;
mod handlers;
mod lsrpc;
mod models;

View file

@ -1,9 +1,5 @@
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct ValidationError;
impl warp::reject::Reject for ValidationError { }
#[derive(Deserialize, Serialize, Debug)]
pub struct Message {
pub server_id: Option<i64>,

View file

@ -1,10 +1,7 @@
use warp::{Filter, http::StatusCode, Rejection};
use super::crypto;
use super::handlers;
use super::errors::Error;
use super::lsrpc;
use super::models;
use super::rpc;
use super::storage;
/// POST /loki/v3/lsrpc
@ -20,25 +17,15 @@ pub fn lsrpc(
.recover(handle_error);
}
async fn handle_error(e: Rejection) -> Result<impl warp::Reply, Rejection> {
let reply = warp::reply::reply();
if let Some(models::ValidationError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)); // 400
async fn handle_error(e: Rejection) -> Result<StatusCode, Rejection> {
if let Some(error) = e.find::<Error>() {
match error {
Error::DecryptionFailed | Error::InvalidRequest | Error::ParsingFailed
| Error::ValidationFailed => return Ok(StatusCode::BAD_REQUEST),
Error::Unauthorized => return Ok(StatusCode::FORBIDDEN),
Error::DatabaseFailedInternally => return Ok(StatusCode::INTERNAL_SERVER_ERROR)
};
} else {
return Err(e);
}
if let Some(crypto::DecryptionError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)); // 400
}
if let Some(lsrpc::ParsingError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)); // 400
}
if let Some(rpc::InvalidRequestError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)); // 400
}
if let Some(handlers::UnauthorizedError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::FORBIDDEN)); // 403
}
if let Some(storage::DatabaseError) = e.find() {
return Ok(warp::reply::with_status(reply, StatusCode::INTERNAL_SERVER_ERROR)); // 500
}
return Err(e);
}

View file

@ -1,6 +1,7 @@
use serde::Deserialize;
use warp::{Rejection, reply::Response};
use super::errors::Error;
use super::handlers;
use super::lsrpc;
use super::storage;
@ -11,17 +12,13 @@ pub struct QueryOptions {
pub from_server_id: Option<i64>
}
#[derive(Debug)]
pub struct InvalidRequestError;
impl warp::reject::Reject for InvalidRequestError { }
pub async fn handle_rpc_call(rpc_call: lsrpc::RpcCall, pool: &storage::DatabaseConnectionPool) -> Result<Response, Rejection> {
// Check that the endpoint is a valid URI
let uri = match rpc_call.endpoint.parse::<http::Uri>() {
Ok(uri) => uri,
Err(e) => {
println!("Couldn't parse URI from: {:?} due to error: {:?}.", rpc_call.endpoint, e);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
};
// Switch on the HTTP method
@ -31,7 +28,7 @@ pub async fn handle_rpc_call(rpc_call: lsrpc::RpcCall, pool: &storage::DatabaseC
"DELETE" => return handle_delete_request(rpc_call, uri, pool).await,
_ => {
println!("Ignoring RPC call with invalid or unused HTTP method: {:?}.", rpc_call.method);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
}
}
@ -44,7 +41,7 @@ async fn handle_get_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &sto
Ok(query_options) => query_options,
Err(e) => {
println!("Couldn't parse query options from: {:?} due to error: {:?}.", query, e);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
};
}
@ -57,7 +54,7 @@ async fn handle_get_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &sto
"/member_count" => return handlers::get_member_count(pool).await,
_ => {
println!("Ignoring RPC call with invalid or unused endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
}
}
@ -69,7 +66,7 @@ async fn handle_post_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &st
Ok(query_options) => query_options,
Err(e) => {
println!("Couldn't parse message from: {:?} due to error: {:?}.", rpc_call.body, e);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
};
return handlers::insert_message(message, pool).await;
@ -80,7 +77,7 @@ async fn handle_post_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &st
},
_ => {
println!("Ignoring RPC call with invalid or unused endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
}
}
@ -91,13 +88,13 @@ async fn handle_delete_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &
let components: Vec<&str> = uri.path()[1..].split("/").collect(); // Drop the leading slash and split on subsequent slashes
if components.len() != 2 {
println!("Invalid endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
let server_id: i64 = match components[1].parse() {
Ok(server_id) => server_id,
Err(_) => {
println!("Invalid endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
};
return handlers::delete_message(server_id, pool).await;
@ -107,12 +104,12 @@ async fn handle_delete_request(rpc_call: lsrpc::RpcCall, uri: http::Uri, pool: &
let components: Vec<&str> = uri.path()[1..].split("/").collect(); // Drop the leading slash and split on subsequent slashes
if components.len() != 2 {
println!("Invalid endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}
let public_key = components[1].to_string();
return handlers::unban(public_key, pool).await;
}
// Unrecognized endpoint
println!("Ignoring RPC call with invalid or unused endpoint: {:?}.", rpc_call.endpoint);
return Err(warp::reject::custom(InvalidRequestError));
return Err(warp::reject::custom(Error::InvalidRequest));
}

View file

@ -2,13 +2,11 @@ use rusqlite::params;
use r2d2_sqlite::SqliteConnectionManager;
use warp::Rejection;
use super::errors::Error;
pub type DatabaseConnection = r2d2::PooledConnection<SqliteConnectionManager>;
pub type DatabaseConnectionPool = r2d2::Pool<SqliteConnectionManager>;
#[derive(Debug)]
pub struct DatabaseError;
impl warp::reject::Reject for DatabaseError { }
pub const MESSAGES_TABLE: &str = "messages";
pub const DELETED_MESSAGES_TABLE: &str = "deleted_messages";
pub const MODERATORS_TABLE: &str = "moderators";
@ -48,7 +46,7 @@ pub fn create_tables_if_needed(conn: &DatabaseConnection) {
pub fn pool() -> DatabaseConnectionPool {
let db_manager = SqliteConnectionManager::file("database.db");
return r2d2::Pool::new(db_manager).unwrap(); // Force
return r2d2::Pool::new(db_manager).unwrap();
}
pub fn conn(pool: &DatabaseConnectionPool) -> Result<DatabaseConnection, Rejection> {
@ -56,7 +54,7 @@ pub fn conn(pool: &DatabaseConnectionPool) -> Result<DatabaseConnection, Rejecti
Ok(conn) => return Ok(conn),
Err(e) => {
println!("Couldn't get database connection due to error: {:?}.", e);
return Err(warp::reject::custom(DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
}
}
@ -66,7 +64,7 @@ pub fn tx(conn: &mut DatabaseConnection) -> Result<rusqlite::Transaction, Reject
Ok(tx) => return Ok(tx),
Err(e) => {
println!("Couldn't open database transaction due to error: {:?}.", e);
return Err(warp::reject::custom(DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
}
}
@ -77,7 +75,7 @@ pub fn exec(stmt: &str, params: &[&dyn rusqlite::ToSql], tx: &rusqlite::Transact
Ok(count) => return Ok(count),
Err(e) => {
println!("Couldn't execute SQL statement due to error: {:?}.", e);
return Err(warp::reject::custom(DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
}
}
@ -87,7 +85,7 @@ pub fn query<'a>(raw_query: &str, conn: &'a DatabaseConnection) -> Result<rusqli
Ok(query) => return Ok(query),
Err(e) => {
println!("Couldn't create database query due to error: {:?}.", e);
return Err(warp::reject::custom(DatabaseError));
return Err(warp::reject::custom(Error::DatabaseFailedInternally));
}
};
}