diff --git a/config_kme1.json b/config_kme1.json index 0d247a3..39c45cc 100644 --- a/config_kme1.json +++ b/config_kme1.json @@ -9,6 +9,7 @@ "server_cert_path": "certs/zone1/kme1.crt", "server_key_path": "certs/zone1/kme1.key" }, + "debugging_http_interface": "0.0.0.0:8080", "kmes_https_interface": { "listen_address": "0.0.0.0:3001", "ca_client_cert_path": "certs/inter_kmes/root-ca-kme1.crt", diff --git a/src/config/mod.rs b/src/config/mod.rs index fed9bce..5784fbd 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -47,7 +47,9 @@ pub struct ThisKmeConfig { /// # Note you should listen only on secured internal network pub saes_https_interface: SAEsHttpsInterfaceConfig, /// Config for external HTTPS interface for other KMEs - pub kmes_https_interface: KMEsHttpsInterfaceConfig + pub kmes_https_interface: KMEsHttpsInterfaceConfig, + /// Optional HTTP interface to see important debugging events + pub debugging_http_interface: Option, } /// Config for internal HTTPS interface for SAEs (likely secured local network) @@ -123,6 +125,7 @@ mod tests { assert_eq!(config.this_kme_config.kmes_https_interface.ca_client_cert_path, "certs/inter_kmes/root-ca-kme1.crt"); assert_eq!(config.this_kme_config.kmes_https_interface.server_cert_path, "certs/zone1/kme1.crt"); assert_eq!(config.this_kme_config.kmes_https_interface.server_key_path, "certs/zone1/kme1.key"); + assert_eq!(config.this_kme_config.debugging_http_interface, Some("127.0.0.1:8080".to_string())); assert_eq!(config.other_kme_configs.len(), 1); assert_eq!(config.other_kme_configs[0].id, 2); assert_eq!(config.other_kme_configs[0].key_directory_to_watch, "tests/data/raw_keys/kme-1-2"); diff --git a/src/event_subscription/mod.rs b/src/event_subscription/mod.rs new file mode 100644 index 0000000..00f7db8 --- /dev/null +++ b/src/event_subscription/mod.rs @@ -0,0 +1,14 @@ +//! Interface for important event subscribers, supposed to be used for demonstration purpose + +use std::io; + +/// Trait that should be implemented by important event subscribers +/// Only events useful for demonstration purpose will be sent to the subscribers +pub trait ImportantEventSubscriber where Self: Sync + Send { + /// Receive a notification for an important event + /// # Arguments + /// * `message` - The notification message that should be sent to the subscriber + /// # Returns + /// An io::Error if the notification could not be sent, likely because of sync issue, or Ok(()) if the notification was sent successfully + fn notify(&self, message: &str) -> Result<(), io::Error>; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 17a6c8a..9e18eb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub mod routes; pub mod qkd_manager; pub mod config; pub(crate) mod entropy; +pub mod event_subscription; /// Cast a string to an io::Error diff --git a/src/main.rs b/src/main.rs index 796fb48..90bada1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ +use std::sync::Arc; use log::error; use tokio::select; +use qkd_kme_server::event_subscription::ImportantEventSubscriber; use qkd_kme_server::qkd_manager::QkdManager; -use qkd_kme_server::routes::EtsiSaeQkdRoutesV1; +use qkd_kme_server::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; use qkd_kme_server::routes::inter_kmes_routes::InterKMEsRoutes; +use qkd_kme_server::server::auth_https_server::AuthHttpsServer; +use qkd_kme_server::server::log_http_server::LoggingHttpServer; #[tokio::main] async fn main() { @@ -21,29 +25,47 @@ async fn main() { } }; - let sae_https_server = qkd_kme_server::server::Server { - listen_addr: config.this_kme_config.saes_https_interface.listen_address.clone(), - ca_client_cert_path: config.this_kme_config.saes_https_interface.ca_client_cert_path.clone(), - server_cert_path: config.this_kme_config.saes_https_interface.server_cert_path.clone(), - server_key_path: config.this_kme_config.saes_https_interface.server_key_path.clone(), - }; + let sae_https_server = AuthHttpsServer::::new( + &config.this_kme_config.saes_https_interface.listen_address, + &config.this_kme_config.saes_https_interface.ca_client_cert_path, + &config.this_kme_config.saes_https_interface.server_cert_path, + &config.this_kme_config.saes_https_interface.server_key_path + ); - let inter_kme_https_server = qkd_kme_server::server::Server { - listen_addr: config.this_kme_config.kmes_https_interface.listen_address.clone(), - ca_client_cert_path: config.this_kme_config.kmes_https_interface.ca_client_cert_path.clone(), - server_cert_path: config.this_kme_config.kmes_https_interface.server_cert_path.clone(), - server_key_path: config.this_kme_config.kmes_https_interface.server_key_path.clone(), - }; + let inter_kme_https_server = AuthHttpsServer::::new( + &config.this_kme_config.kmes_https_interface.listen_address, + &config.this_kme_config.kmes_https_interface.ca_client_cert_path, + &config.this_kme_config.kmes_https_interface.server_cert_path, + &config.this_kme_config.kmes_https_interface.server_key_path + ); - let qkd_manager = QkdManager::from_config(&config); - let qkd_manager = qkd_manager.unwrap(); + let qkd_manager = QkdManager::from_config(&config).unwrap(); - select! { - x = inter_kme_https_server.run::(&qkd_manager) => { - error!("Error running inter-KMEs HTTPS server: {:?}", x); + match config.this_kme_config.debugging_http_interface { + Some(listen_addr) => { + let logging_http_server = Arc::new(LoggingHttpServer::new(&listen_addr)); + qkd_manager.add_important_event_subscriber(Arc::clone(&logging_http_server) as Arc).unwrap(); + select! { + x = inter_kme_https_server.run(&qkd_manager) => { + error!("Error running inter-KMEs HTTPS server: {:?}", x); + }, + x = sae_https_server.run(&qkd_manager) => { + error!("Error running SAEs HTTPS server: {:?}", x); + }, + x = logging_http_server.run() => { + error!("Error running logging HTTP server: {:?}", x); + } + } }, - x = sae_https_server.run::(&qkd_manager) => { - error!("Error running SAEs HTTPS server: {:?}", x); + None => { + select! { + x = inter_kme_https_server.run(&qkd_manager) => { + error!("Error running inter-KMEs HTTPS server: {:?}", x); + }, + x = sae_https_server.run(&qkd_manager) => { + error!("Error running SAEs HTTPS server: {:?}", x); + } + } } } } \ No newline at end of file diff --git a/src/qkd_manager/key_handler.rs b/src/qkd_manager/key_handler.rs index 0a139f8..f3f3f24 100644 --- a/src/qkd_manager/key_handler.rs +++ b/src/qkd_manager/key_handler.rs @@ -2,6 +2,7 @@ use std::convert::identity; use std::io; +use std::sync::Arc; use uuid::Bytes; use x509_parser::nom::AsBytes; use crate::{io_err, KmeId, qkd_manager, SaeClientCertSerial, SaeId}; @@ -10,6 +11,8 @@ use base64::{engine::general_purpose, Engine as _}; use log::{error, info, warn}; use crate::qkd_manager::http_response_obj::{ResponseQkdKey, ResponseQkdKeysList}; use crate::ensure_prepared_statement_ok; +use crate::export_important_logging_message; +use crate::event_subscription::ImportantEventSubscriber; /// Describes the key handler that will check authentication and manage the QKD keys in the database in a separate thread pub(super) struct KeyHandler { @@ -23,6 +26,8 @@ pub(super) struct KeyHandler { this_kme_id: KmeId, /// Router on classical network, used to connect to other KMEs over unsecure classical network qkd_router: router::QkdRouter, + /// Subscribers to important events, for demonstration purpose + event_notification_subscribers: Vec>, } impl KeyHandler { @@ -48,6 +53,7 @@ impl KeyHandler { })?, this_kme_id, qkd_router: router::QkdRouter::new(), + event_notification_subscribers: vec![], }; // Create the tables if they do not exist key_handler.sqlite_db.execute(DATABASE_INIT_REQ).map_err(|e| { @@ -144,6 +150,9 @@ impl KeyHandler { error!("Error QKD manager sending response"); } } + QkdManagerCommand::AddImportantEventSubscriber(subscriber) => { + self.event_notification_subscribers.push(subscriber); + } } } Err(e) => { @@ -266,6 +275,8 @@ impl KeyHandler { let origin_kme_id = self.this_kme_id; let target_kme_id = self.get_kme_id_from_sae_id(target_sae_id).ok_or(QkdManagerResponse::NotFound)?; + export_important_logging_message!(&self, &format!("[KME {}] SAE {} requested a key to communicate with {}", self.this_kme_id, origin_sae_id, target_sae_id)); + let mut stmt = ensure_prepared_statement_ok!(self.sqlite_db, FETCH_PREINIT_KEY_PREPARED_STATEMENT); stmt.bind((1, target_kme_id)).map_err(|_| { error!("Error binding target KME ID"); @@ -301,6 +312,7 @@ impl KeyHandler { error!("Error activating key on other KME"); qkd_manager_activation_error })?; + export_important_logging_message!(&self, &format!("[KME {}] As SAE {} belongs to KME {}, activating it through inter KMEs network", self.this_kme_id, target_sae_id, target_kme_id)); } self.delete_pre_init_key_with_id(id).map_err(|_| { @@ -451,6 +463,7 @@ impl KeyHandler { error!("Error executing SQL statement"); QkdManagerResponse::Ko })?; + export_important_logging_message!(&self, &format!("[KME {}] Key {} activated between SAEs {} and {}", self.this_kme_id, key_uuid, origin_sae_id, target_sae_id)); Ok(QkdManagerResponse::Ok) } @@ -519,6 +532,8 @@ impl KeyHandler { QkdManagerResponse::Ko })?; + export_important_logging_message!(&self, &format!("[KME {}] SAE {} requested key {} (from {})", self.this_kme_id, current_sae_id, key_uuid, origin_sae_id)); + // Encode the key in base64 Ok(ResponseQkdKey { key_ID: key_uuid, @@ -647,15 +662,49 @@ macro_rules! ensure_prepared_statement_ok { } } +/// Notify all subscribers of an event +/// # Arguments +/// * `key_handler_reference` - The reference to the key handler, like `&self` +/// * `message` - The message to notify, as string slice +#[macro_export] +macro_rules! export_important_logging_message { + ($key_handler_reference:expr, $message:expr) => { + info!("{}", $message); + for subscriber in $key_handler_reference.event_notification_subscribers.iter() { + let _ = subscriber.notify($message); // We ignore the result here + } + } +} + #[cfg(test)] mod tests { + use std::io::Error; + use std::sync::{Arc, Mutex}; use std::thread; + use crate::event_subscription::ImportantEventSubscriber; use crate::qkd_manager::http_response_obj::HttpResponseBody; use crate::qkd_manager::QkdManagerResponse; const CLIENT_CERT_SERIAL_SIZE_BYTES: usize = 20; + struct TestImportantEventSubscriber { + events: Mutex>, + } + impl TestImportantEventSubscriber { + fn new() -> Self { + Self { + events: Mutex::new(Vec::new()), + } + } + } + impl ImportantEventSubscriber for TestImportantEventSubscriber { + fn notify(&self, message: &str) -> Result<(), Error> { + self.events.lock().unwrap().push(message.to_string()); + Ok(()) + } + } + #[test] fn test_get_sae_id_from_certificate() { let (_, command_channel_rx) = crossbeam_channel::unbounded(); @@ -900,17 +949,54 @@ mod tests { key_handler.delete_pre_init_key_with_id(key_id).unwrap(); } + #[test] + fn test_add_important_event_subscriber() { + let (_, command_channel_rx) = crossbeam_channel::unbounded(); + let (response_channel_tx, _) = crossbeam_channel::unbounded(); + let mut key_handler = super::KeyHandler::new(":memory:", command_channel_rx, response_channel_tx, 1).unwrap(); + + let subscriber = Arc::new(TestImportantEventSubscriber::new()); + let subscriber2 = Arc::new(TestImportantEventSubscriber::new()); + + key_handler.event_notification_subscribers.push(Arc::clone(&subscriber) as Arc); + key_handler.event_notification_subscribers.push(Arc::clone(&subscriber2) as Arc); + assert_eq!(key_handler.event_notification_subscribers.len(), 2); + assert_eq!(subscriber.events.lock().unwrap().len(), 0); + assert_eq!(subscriber2.events.lock().unwrap().len(), 0); + + let sae_certificate_serial = vec![0u8; CLIENT_CERT_SERIAL_SIZE_BYTES]; + key_handler.add_sae(1, 1, &Some(sae_certificate_serial.clone())).unwrap(); + key_handler.add_preinit_qkd_key(crate::qkd_manager::PreInitQkdKeyWrapper { + other_kme_id: 1, + key_uuid: *uuid::Uuid::from_bytes([0u8; 16]).as_bytes(), + key: [0u8; crate::QKD_KEY_SIZE_BITS / 8], + }).unwrap(); + key_handler.get_sae_keys(&sae_certificate_serial, 1).unwrap(); + + assert_eq!(subscriber.events.lock().unwrap().len(), 2); + assert_eq!(subscriber2.events.lock().unwrap().len(), 2); + assert_eq!(subscriber.events.lock().unwrap()[0], "[KME 1] SAE 1 requested a key to communicate with 1"); + assert_eq!(subscriber.events.lock().unwrap()[1], "[KME 1] Key 00000000-0000-0000-0000-000000000000 activated between SAEs 1 and 1"); + assert_eq!(subscriber2.events.lock().unwrap()[0], "[KME 1] SAE 1 requested a key to communicate with 1"); + assert_eq!(subscriber2.events.lock().unwrap()[1], "[KME 1] Key 00000000-0000-0000-0000-000000000000 activated between SAEs 1 and 1"); + } + #[test] fn test_run() { let (command_tx, command_channel_rx) = crossbeam_channel::unbounded(); let (response_channel_tx, response_rx) = crossbeam_channel::unbounded(); let mut key_handler = super::KeyHandler::new(":memory:", command_channel_rx, response_channel_tx, 1).unwrap(); + + let subscriber = Arc::new(TestImportantEventSubscriber::new()); + key_handler.event_notification_subscribers.push(Arc::clone(&subscriber) as Arc); + let sae_id = 1; let kme_id = 1; let sae_certificate_serial = vec![0u8; CLIENT_CERT_SERIAL_SIZE_BYTES]; let _ = thread::spawn(move || { key_handler.run(); }); + command_tx.send(super::QkdManagerCommand::AddSae(sae_id, kme_id, Some(sae_certificate_serial.clone()))).unwrap(); let qkd_manager_response = response_rx.recv().unwrap(); assert!(matches!(qkd_manager_response, QkdManagerResponse::Ok)); @@ -933,6 +1019,9 @@ mod tests { let qkd_manager_response = response_rx.recv().unwrap(); assert!(matches!(qkd_manager_response, QkdManagerResponse::NotFound)); + assert_eq!(subscriber.events.lock().unwrap().len(), 1); + assert_eq!(subscriber.events.lock().unwrap()[0], "[KME 1] SAE 1 requested a key to communicate with 1"); + command_tx.send(super::QkdManagerCommand::GetStatus(sae_certificate_serial.clone(), 2)).unwrap(); let qkd_manager_response = response_rx.recv().unwrap(); assert!(matches!(qkd_manager_response, QkdManagerResponse::NotFound)); @@ -982,5 +1071,7 @@ mod tests { true)).unwrap(); let qkd_manager_response = response_rx.recv().unwrap(); assert!(matches!(qkd_manager_response, QkdManagerResponse::Ok)); + + assert_eq!(subscriber.events.lock().unwrap().len(), 1); } } \ No newline at end of file diff --git a/src/qkd_manager/mod.rs b/src/qkd_manager/mod.rs index 99b73da..88d982e 100644 --- a/src/qkd_manager/mod.rs +++ b/src/qkd_manager/mod.rs @@ -14,6 +14,7 @@ use crate::qkd_manager::http_response_obj::ResponseQkdKeysList; use crate::qkd_manager::QkdManagerResponse::TransmissionError; use crate::{io_err, KmeId, QkdEncKey, SaeClientCertSerial, SaeId}; use crate::entropy::{EntropyAccumulator, ShannonEntropyAccumulator}; +use crate::event_subscription::ImportantEventSubscriber; /// QKD manager interface, can be cloned for instance in each request handler task #[derive(Clone)] @@ -279,6 +280,19 @@ impl QkdManager { }).await .map_err(|_| io_err("Async task error"))??) } + + /// ## [demonstration purpose] + /// Add subscriber implementing ImportantEventSubscriber trait, that will receive message for all important events. + /// Events are something like "SAE 1 requested a key for SAE2", or "KME 1 activated a key for SAE 2" + /// # Arguments + /// * `subscriber` - The subscriber to add, must implement ImportantEventSubscriber trait + /// # Returns + /// Ok if the subscriber was added successfully, an error otherwise (likely thread communication error) + pub fn add_important_event_subscriber(&self, subscriber: Arc) -> Result<(), io::Error> { + self.command_tx.send(QkdManagerCommand::AddImportantEventSubscriber(subscriber)) + .map_err(|_| io_err("Error sending command to QKD manager"))?; + Ok(()) + } } /// A Pre-init QKD key, with its origin and target KME IDs @@ -370,6 +384,8 @@ enum QkdManagerCommand { GetKmeIdFromSaeId(SaeId), // SAE id /// Add classical network information to a KME, used to activate keys on it for slave KMEs using "classical channel" AddKmeClassicalNetInfo(KmeId, String, String, String, bool), // KME id + KME address + client auth certificate path + client auth certificate password + should ignore system proxy settings + /// Add subscriber implementing ImportantEventSubscriber trait, that will receive message for all important events (demonstration purpose) + AddImportantEventSubscriber(Arc), } /// All possible responses from the QKD manager @@ -407,7 +423,11 @@ pub enum QkdManagerResponse { #[cfg(test)] mod test { + use std::io::Error; + use std::ops::{Deref, DerefMut}; + use std::sync::{Arc, Mutex}; use serial_test::serial; + use crate::event_subscription::ImportantEventSubscriber; use crate::QkdEncKey; const CLIENT_CERT_SERIAL_SIZE_BYTES: usize = 20; @@ -611,4 +631,42 @@ mod test { assert!(response.is_ok()); assert_eq!(response.unwrap(), super::QkdManagerResponse::Ok); } + + #[tokio::test] + #[serial] + async fn test_add_important_event_subscriber() { + struct TestImportantEventSubscriber { + events: Mutex>, + } + impl TestImportantEventSubscriber { + fn new() -> Self { + Self { + events: Mutex::new(Vec::new()), + } + } + } + impl ImportantEventSubscriber for TestImportantEventSubscriber { + fn notify(&self, message: &str) -> Result<(), Error> { + self.events.lock().unwrap().push(message.to_string()); + Ok(()) + } + } + const SQLITE_DB_PATH: &'static str = ":memory:"; + let qkd_manager = super::QkdManager::new(SQLITE_DB_PATH, 1); + let subscriber = Arc::new(TestImportantEventSubscriber::new()); + let response = qkd_manager.add_important_event_subscriber(Arc::clone(&subscriber) as Arc); + assert!(response.is_ok()); + assert_eq!(subscriber.events.lock().unwrap().deref().len(), 0); + + // Request a key + qkd_manager.add_sae(1, 1, &Some(vec![0; CLIENT_CERT_SERIAL_SIZE_BYTES])).unwrap(); + let key = super::PreInitQkdKeyWrapper::new(1, &[0; crate::QKD_KEY_SIZE_BYTES]).unwrap(); + qkd_manager.add_pre_init_qkd_key(key).unwrap(); + let response = qkd_manager.get_qkd_key(1, &vec![0; CLIENT_CERT_SERIAL_SIZE_BYTES]).await; + assert!(response.is_ok()); + + assert_eq!(subscriber.events.lock().unwrap().deref().len(), 2); + assert_eq!(subscriber.events.lock().unwrap().deref_mut().pop(), Some("[KME 1] Key 7b848ade-8cff-3d54-a9b8-53a215e6ee77 activated between SAEs 1 and 1".to_string())); + assert_eq!(subscriber.events.lock().unwrap().deref_mut().pop(), Some("[KME 1] SAE 1 requested a key to communicate with 1".to_string())); + } } \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 5eb0532..fa608ad 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,23 +1,18 @@ //! Route definitions for the server -mod keys; mod request_context; -mod sae; pub mod inter_kmes_routes; +pub mod sae_zone_routes; use std::convert::Infallible; use request_context::RequestContext; -use hyper::{body, Request, Response, StatusCode}; -use crate::qkd_manager::{http_response_obj, QkdManager}; -use crate::qkd_manager::http_response_obj::HttpResponseBody; +use hyper::{body, Request, Response}; +use crate::qkd_manager::QkdManager; use async_trait::async_trait; use http_body_util::Full; use hyper::body::Bytes; -use hyper::header::CONTENT_TYPE; -use log::error; use rustls_pki_types::CertificateDer; -use crate::RESPONSE_ERROR_FUNCTION; /// Trait representing the routes of the server @@ -35,62 +30,6 @@ pub trait Routes { async fn handle_request(req: Request, client_cert: Option<&CertificateDer>, qkd_manager: QkdManager) -> Result>, Infallible>; } -/// Struct representing the routes of the server for the v1 version of the API -pub struct EtsiSaeQkdRoutesV1 {} - -#[async_trait] -impl Routes for EtsiSaeQkdRoutesV1 { - async fn handle_request(req: Request, client_cert: Option<&CertificateDer>, qkd_manager: QkdManager) -> Result>, Infallible> { - let path = req.uri().path().to_owned(); - - // Create the request context - let rcx = match RequestContext::new(client_cert, qkd_manager) { - Ok(context) => context, - Err(e) => { - error!("Error creating context: {}", e.to_string()); - return Self::internal_server_error(); - } - }; - - // Split the path into segments, eg "/api/v1/keys" -> ["api", "v1", "keys"] - let segments: Vec<&str> = - path.split('/').filter(|s| !s.is_empty()).collect(); - - // If path has less than 3 segments, or the first two segments are not "api" and "v1", return 404 - if segments.len() < 3 || segments[0] != "api" || segments[1] != "v1" { - return Self::not_found(); - } - - // Call the correct handler based on the third segment - match segments[2] { - "keys" => keys::key_handler(&rcx, req, &segments[3..]).await, - "sae" => sae::sae_handler(&rcx, req, &segments[3..]).await, - &_ => Self::not_found(), // Third segment must be "keys" - } - } -} - - -#[allow(dead_code)] -impl EtsiSaeQkdRoutesV1 { - RESPONSE_ERROR_FUNCTION!(internal_server_error, StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"); - RESPONSE_ERROR_FUNCTION!(not_found, StatusCode::NOT_FOUND, "Element not found"); - RESPONSE_ERROR_FUNCTION!(authentication_error, StatusCode::UNAUTHORIZED, "Authentication error"); - RESPONSE_ERROR_FUNCTION!(bad_request, StatusCode::BAD_REQUEST, "Bad request"); - RESPONSE_ERROR_FUNCTION!(gateway_timeout, StatusCode::GATEWAY_TIMEOUT, "Gateway timeout (maybe a remote KME is down)"); - RESPONSE_ERROR_FUNCTION!(precondition_failed, StatusCode::PRECONDITION_FAILED, "A precondition isn't fulfilled, maybe some configuration is missing"); - RESPONSE_ERROR_FUNCTION!(conflict, StatusCode::CONFLICT, "There is a conflict with the requested resource, maybe resource on a remote KME are not synced"); - - /// Creates a HTTP response 200 from a string (likely a JSON) - /// # Arguments - /// * `body` - The body of the response, as a string - /// # Returns - /// A HTTP response with status code 200 and the body as a JSON content type - fn json_response_from_str(body: &str) -> Response> { - const JSON_CONTENT_TYPE: &'static str = "application/json"; - Response::builder().status(StatusCode::OK).header(CONTENT_TYPE, JSON_CONTENT_TYPE).body(Full::new(Bytes::from(String::from(body)))).unwrap() - } -} /// Macro to generate the functions that return the error responses #[allow(non_snake_case)] @@ -106,75 +45,4 @@ macro_rules! RESPONSE_ERROR_FUNCTION { Ok(Response::builder().status($status_code).body(Full::new(Bytes::from(error_body.to_json().unwrap()))).unwrap()) } } -} - -#[cfg(test)] -mod tests { - use http_body_util::BodyExt; - use hyper::StatusCode; - - #[tokio::test] - async fn test_internal_server_error() { - let response = super::EtsiSaeQkdRoutesV1::internal_server_error().unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Internal server error\"\n}"); - } - - #[tokio::test] - async fn test_not_found() { - let response = super::EtsiSaeQkdRoutesV1::not_found().unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Element not found\"\n}"); - } - - #[tokio::test] - async fn test_authentication_error() { - let response = super::EtsiSaeQkdRoutesV1::authentication_error().unwrap(); - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Authentication error\"\n}"); - } - - #[tokio::test] - async fn test_bad_request() { - let response = super::EtsiSaeQkdRoutesV1::bad_request().unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Bad request\"\n}"); - } - - #[tokio::test] - async fn test_gateway_timeout() { - let response = super::EtsiSaeQkdRoutesV1::gateway_timeout().unwrap(); - assert_eq!(response.status(), StatusCode::GATEWAY_TIMEOUT); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Gateway timeout (maybe a remote KME is down)\"\n}"); - } - - #[tokio::test] - async fn test_precondition_failed() { - let response = super::EtsiSaeQkdRoutesV1::precondition_failed().unwrap(); - assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"A precondition isn't fulfilled, maybe some configuration is missing\"\n}"); - } - - #[tokio::test] - async fn test_conflict() { - let response = super::EtsiSaeQkdRoutesV1::conflict().unwrap(); - assert_eq!(response.status(), StatusCode::CONFLICT); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\n \"message\": \"There is a conflict with the requested resource, maybe resource on a remote KME are not synced\"\n}"); - } - - #[tokio::test] - async fn test_json_response_from_str() { - let response = super::EtsiSaeQkdRoutesV1::json_response_from_str("{\"variable\": \"value\"}"); - assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.headers().get("content-type").unwrap(), "application/json"); - let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); - assert_eq!(body.replace("\r", ""), "{\"variable\": \"value\"}"); - } } \ No newline at end of file diff --git a/src/routes/keys/get_key.rs b/src/routes/sae_zone_routes/keys/get_key.rs similarity index 83% rename from src/routes/keys/get_key.rs rename to src/routes/sae_zone_routes/keys/get_key.rs index 6690946..0566d14 100644 --- a/src/routes/keys/get_key.rs +++ b/src/routes/sae_zone_routes/keys/get_key.rs @@ -12,6 +12,7 @@ use log::{error, warn}; use crate::qkd_manager::http_request_obj::RequestListKeysIds; use crate::{ensure_sae_id_format_type, SaeId}; use crate::ensure_client_certificate_serial; +use crate::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; /// Route to get the key status (how many keys are available etc.) from a master SAE @@ -21,7 +22,7 @@ pub(in crate::routes) fn route_get_status(rcx: &RequestContext, _req: Request serial, Err(_) => { - return super::EtsiSaeQkdRoutesV1::authentication_error(); + return EtsiSaeQkdRoutesV1::authentication_error(); } }; @@ -36,17 +37,17 @@ pub(in crate::routes) fn route_get_status(rcx: &RequestContext, _req: Request json, Err(_) => { error!("Error serializing key status"); - return super::EtsiSaeQkdRoutesV1::internal_server_error(); + return EtsiSaeQkdRoutesV1::internal_server_error(); } }; // Return the key status as a response - Ok(crate::routes::EtsiSaeQkdRoutesV1::json_response_from_str(&key_status_json)) + Ok(EtsiSaeQkdRoutesV1::json_response_from_str(&key_status_json)) } QkdManagerResponse::AuthenticationError => { - super::EtsiSaeQkdRoutesV1::authentication_error() + EtsiSaeQkdRoutesV1::authentication_error() } _ => { - super::EtsiSaeQkdRoutesV1::internal_server_error() + EtsiSaeQkdRoutesV1::internal_server_error() } } } @@ -80,29 +81,29 @@ pub(in crate::routes) async fn route_get_key(rcx: & RequestContext<'_>, _req: Re Ok(json) => json, Err(_) => { error!("Error serializing keys"); - return super::EtsiSaeQkdRoutesV1::internal_server_error(); + return EtsiSaeQkdRoutesV1::internal_server_error(); } }; // Return the key(s) as a response - Ok(crate::routes::EtsiSaeQkdRoutesV1::json_response_from_str(&keys_json)) + Ok(EtsiSaeQkdRoutesV1::json_response_from_str(&keys_json)) } QkdManagerResponse::AuthenticationError => { - super::EtsiSaeQkdRoutesV1::authentication_error() + EtsiSaeQkdRoutesV1::authentication_error() } QkdManagerResponse::NotFound => { - super::EtsiSaeQkdRoutesV1::not_found() + EtsiSaeQkdRoutesV1::not_found() } QkdManagerResponse::RemoteKmeCommunicationError => { - super::EtsiSaeQkdRoutesV1::gateway_timeout() + EtsiSaeQkdRoutesV1::gateway_timeout() } QkdManagerResponse::MissingRemoteKmeConfiguration => { - super::EtsiSaeQkdRoutesV1::precondition_failed() + EtsiSaeQkdRoutesV1::precondition_failed() } QkdManagerResponse::RemoteKmeAcceptError => { - super::EtsiSaeQkdRoutesV1::conflict() + EtsiSaeQkdRoutesV1::conflict() } _ => { - super::EtsiSaeQkdRoutesV1::internal_server_error() + EtsiSaeQkdRoutesV1::internal_server_error() } } } @@ -143,7 +144,7 @@ pub(in crate::routes) async fn route_get_key_with_id(rcx: &RequestContext<'_>, r let post_body_bytes = match req.into_body().collect().await { Ok(bytes) => bytes.to_bytes(), Err(_) => { - return super::EtsiSaeQkdRoutesV1::bad_request(); + return EtsiSaeQkdRoutesV1::bad_request(); } }; @@ -151,7 +152,7 @@ pub(in crate::routes) async fn route_get_key_with_id(rcx: &RequestContext<'_>, r let request_list_keys_ids: RequestListKeysIds = match serde_json::from_slice(&post_body_bytes) { Ok(request_list_keys_ids) => request_list_keys_ids, Err(_) => { - return super::EtsiSaeQkdRoutesV1::bad_request(); + return EtsiSaeQkdRoutesV1::bad_request(); } }; @@ -170,20 +171,20 @@ pub(in crate::routes) async fn route_get_key_with_id(rcx: &RequestContext<'_>, r Ok(json) => json, Err(_) => { error!("Error serializing keys"); - return super::EtsiSaeQkdRoutesV1::internal_server_error(); + return EtsiSaeQkdRoutesV1::internal_server_error(); } }; // Return the key(s) as a response - Ok(crate::routes::EtsiSaeQkdRoutesV1::json_response_from_str(&keys_json)) + Ok(EtsiSaeQkdRoutesV1::json_response_from_str(&keys_json)) } QkdManagerResponse::AuthenticationError => { - super::EtsiSaeQkdRoutesV1::authentication_error() + EtsiSaeQkdRoutesV1::authentication_error() } QkdManagerResponse::NotFound => { - super::EtsiSaeQkdRoutesV1::not_found() + EtsiSaeQkdRoutesV1::not_found() } _ => { - super::EtsiSaeQkdRoutesV1::internal_server_error() + EtsiSaeQkdRoutesV1::internal_server_error() } } } diff --git a/src/routes/keys/mod.rs b/src/routes/sae_zone_routes/keys/mod.rs similarity index 81% rename from src/routes/keys/mod.rs rename to src/routes/sae_zone_routes/keys/mod.rs index fc7c753..8d13ccc 100644 --- a/src/routes/keys/mod.rs +++ b/src/routes/sae_zone_routes/keys/mod.rs @@ -4,13 +4,14 @@ use std::convert::Infallible; use http_body_util::Full; use hyper::{body, Request, Response}; use hyper::body::Bytes; -use crate::routes::{EtsiSaeQkdRoutesV1, RequestContext}; +use crate::routes::RequestContext; +use crate::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; mod get_key; mod route_entropy; /// Dispatches the request to the correct function -pub(super) async fn key_handler(rcx: &RequestContext<'_>, req: Request, uri_segments: &[&str]) -> Result>, Infallible> { +pub(in crate::routes) async fn key_handler(rcx: &RequestContext<'_>, req: Request, uri_segments: &[&str]) -> Result>, Infallible> { match (uri_segments, req.method()) { // Get the status of key(s) from a master SAE (how many keys are available etc.) ([slave_sae_id, "status"], &hyper::Method::GET) => get_key::route_get_status(rcx, req, slave_sae_id), diff --git a/src/routes/keys/route_entropy.rs b/src/routes/sae_zone_routes/keys/route_entropy.rs similarity index 80% rename from src/routes/keys/route_entropy.rs rename to src/routes/sae_zone_routes/keys/route_entropy.rs index 2f94827..75dd011 100644 --- a/src/routes/keys/route_entropy.rs +++ b/src/routes/sae_zone_routes/keys/route_entropy.rs @@ -5,6 +5,7 @@ use hyper::body::Bytes; use log::error; use crate::qkd_manager::http_response_obj::HttpResponseBody; use crate::routes::request_context::RequestContext; +use crate::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; pub(in crate::routes) async fn route_get_entropy_total(rcx: &RequestContext<'_>, _req: Request) -> Result>, Infallible> { // Get the total entropy from stored keys @@ -12,7 +13,7 @@ pub(in crate::routes) async fn route_get_entropy_total(rcx: &RequestContext<'_>, Ok(entropy) => entropy, Err(e) => { error!("Error getting total entropy: {}", e.to_string()); - return super::EtsiSaeQkdRoutesV1::internal_server_error(); + return EtsiSaeQkdRoutesV1::internal_server_error(); } }; let total_entropy_response_obj = crate::qkd_manager::http_response_obj::ResponseTotalKeysEntropy { @@ -23,8 +24,8 @@ pub(in crate::routes) async fn route_get_entropy_total(rcx: &RequestContext<'_>, Ok(json) => json, Err(_) => { error!("Error serializing total entropy object"); - return super::EtsiSaeQkdRoutesV1::internal_server_error(); + return EtsiSaeQkdRoutesV1::internal_server_error(); } }; - Ok(crate::routes::EtsiSaeQkdRoutesV1::json_response_from_str(&total_entropy_response_obj_json)) + Ok(EtsiSaeQkdRoutesV1::json_response_from_str(&total_entropy_response_obj_json)) } \ No newline at end of file diff --git a/src/routes/sae_zone_routes/mod.rs b/src/routes/sae_zone_routes/mod.rs new file mode 100644 index 0000000..88736a0 --- /dev/null +++ b/src/routes/sae_zone_routes/mod.rs @@ -0,0 +1,146 @@ +//! Routes for intra private SAE network server, shall be called by internal SAEs, for example to request keys + +use std::convert::Infallible; +use async_trait::async_trait; +use http_body_util::Full; +use hyper::{body, Request, Response, StatusCode}; +use hyper::body::Bytes; +use hyper::header::CONTENT_TYPE; +use log::error; +use rustls_pki_types::CertificateDer; +use crate::qkd_manager::QkdManager; +use crate::RESPONSE_ERROR_FUNCTION; +use crate::qkd_manager::http_response_obj; +use crate::routes::request_context::RequestContext; +use crate::routes::Routes; +use crate::qkd_manager::http_response_obj::HttpResponseBody; + +mod keys; +mod sae; + +/// Struct representing the routes of the server for the v1 version of the API +pub struct EtsiSaeQkdRoutesV1 {} + +#[async_trait] +impl Routes for EtsiSaeQkdRoutesV1 { + async fn handle_request(req: Request, client_cert: Option<&CertificateDer>, qkd_manager: QkdManager) -> Result>, Infallible> { + let path = req.uri().path().to_owned(); + + // Create the request context + let rcx = match RequestContext::new(client_cert, qkd_manager) { + Ok(context) => context, + Err(e) => { + error!("Error creating context: {}", e.to_string()); + return Self::internal_server_error(); + } + }; + + // Split the path into segments, eg "/api/v1/keys" -> ["api", "v1", "keys"] + let segments: Vec<&str> = + path.split('/').filter(|s| !s.is_empty()).collect(); + + // If path has less than 3 segments, or the first two segments are not "api" and "v1", return 404 + if segments.len() < 3 || segments[0] != "api" || segments[1] != "v1" { + return Self::not_found(); + } + + // Call the correct handler based on the third segment + match segments[2] { + "keys" => keys::key_handler(&rcx, req, &segments[3..]).await, + "sae" => sae::sae_handler(&rcx, req, &segments[3..]).await, + &_ => Self::not_found(), // Third segment must be "keys" + } + } +} + +#[allow(dead_code)] +impl EtsiSaeQkdRoutesV1 { + RESPONSE_ERROR_FUNCTION!(internal_server_error, StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"); + RESPONSE_ERROR_FUNCTION!(not_found, StatusCode::NOT_FOUND, "Element not found"); + RESPONSE_ERROR_FUNCTION!(authentication_error, StatusCode::UNAUTHORIZED, "Authentication error"); + RESPONSE_ERROR_FUNCTION!(bad_request, StatusCode::BAD_REQUEST, "Bad request"); + RESPONSE_ERROR_FUNCTION!(gateway_timeout, StatusCode::GATEWAY_TIMEOUT, "Gateway timeout (maybe a remote KME is down)"); + RESPONSE_ERROR_FUNCTION!(precondition_failed, StatusCode::PRECONDITION_FAILED, "A precondition isn't fulfilled, maybe some configuration is missing"); + RESPONSE_ERROR_FUNCTION!(conflict, StatusCode::CONFLICT, "There is a conflict with the requested resource, maybe resource on a remote KME are not synced"); + + /// Creates a HTTP response 200 from a string (likely a JSON) + /// # Arguments + /// * `body` - The body of the response, as a string + /// # Returns + /// A HTTP response with status code 200 and the body as a JSON content type + fn json_response_from_str(body: &str) -> Response> { + const JSON_CONTENT_TYPE: &'static str = "application/json"; + Response::builder().status(StatusCode::OK).header(CONTENT_TYPE, JSON_CONTENT_TYPE).body(Full::new(Bytes::from(String::from(body)))).unwrap() + } +} + +#[cfg(test)] +mod tests { + use http_body_util::BodyExt; + use hyper::StatusCode; + + #[tokio::test] + async fn test_internal_server_error() { + let response = super::EtsiSaeQkdRoutesV1::internal_server_error().unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Internal server error\"\n}"); + } + + #[tokio::test] + async fn test_not_found() { + let response = super::EtsiSaeQkdRoutesV1::not_found().unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Element not found\"\n}"); + } + + #[tokio::test] + async fn test_authentication_error() { + let response = super::EtsiSaeQkdRoutesV1::authentication_error().unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Authentication error\"\n}"); + } + + #[tokio::test] + async fn test_bad_request() { + let response = super::EtsiSaeQkdRoutesV1::bad_request().unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Bad request\"\n}"); + } + + #[tokio::test] + async fn test_gateway_timeout() { + let response = super::EtsiSaeQkdRoutesV1::gateway_timeout().unwrap(); + assert_eq!(response.status(), StatusCode::GATEWAY_TIMEOUT); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"Gateway timeout (maybe a remote KME is down)\"\n}"); + } + + #[tokio::test] + async fn test_precondition_failed() { + let response = super::EtsiSaeQkdRoutesV1::precondition_failed().unwrap(); + assert_eq!(response.status(), StatusCode::PRECONDITION_FAILED); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"A precondition isn't fulfilled, maybe some configuration is missing\"\n}"); + } + + #[tokio::test] + async fn test_conflict() { + let response = super::EtsiSaeQkdRoutesV1::conflict().unwrap(); + assert_eq!(response.status(), StatusCode::CONFLICT); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\n \"message\": \"There is a conflict with the requested resource, maybe resource on a remote KME are not synced\"\n}"); + } + + #[tokio::test] + async fn test_json_response_from_str() { + let response = super::EtsiSaeQkdRoutesV1::json_response_from_str("{\"variable\": \"value\"}"); + assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("content-type").unwrap(), "application/json"); + let body = String::from_utf8(response.into_body().collect().await.unwrap().to_bytes().to_vec()).unwrap(); + assert_eq!(body.replace("\r", ""), "{\"variable\": \"value\"}"); + } +} diff --git a/src/routes/sae/info.rs b/src/routes/sae_zone_routes/sae/info.rs similarity index 84% rename from src/routes/sae/info.rs rename to src/routes/sae_zone_routes/sae/info.rs index 27e4de0..4480b8c 100644 --- a/src/routes/sae/info.rs +++ b/src/routes/sae_zone_routes/sae/info.rs @@ -8,6 +8,7 @@ use hyper::body::Bytes; use log::error; use crate::qkd_manager::http_response_obj::HttpResponseBody; use crate::routes::request_context::RequestContext; +use crate::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; /// Allows a SAE to retrieve its own information /// eg `GET /api/v1/sae/info/me` @@ -16,7 +17,7 @@ pub(in crate::routes) async fn route_get_info_me(rcx: &RequestContext<'_>, _req: let client_cert_serial = match rcx.get_client_certificate_serial_as_raw() { Ok(serial) => serial, Err(_) => { - return crate::routes::EtsiSaeQkdRoutesV1::authentication_error() + return EtsiSaeQkdRoutesV1::authentication_error() } }; // Retrieve the SAE ID from the QKD manager, given the client certificate serial @@ -24,7 +25,7 @@ pub(in crate::routes) async fn route_get_info_me(rcx: &RequestContext<'_>, _req: Ok(sae_info) => sae_info, Err(_) => { // Client certificate serial isn't registered in the QKD manager - return crate::routes::EtsiSaeQkdRoutesV1::not_found() + return EtsiSaeQkdRoutesV1::not_found() } }; @@ -35,12 +36,12 @@ pub(in crate::routes) async fn route_get_info_me(rcx: &RequestContext<'_>, _req: }; match sae_info_response_obj.to_json() { Ok(json) => { - Ok(crate::routes::EtsiSaeQkdRoutesV1::json_response_from_str(&json)) + Ok(EtsiSaeQkdRoutesV1::json_response_from_str(&json)) } Err(_) => { // Error serializing the response object, should never happen error!("Error serializing SAE info"); - crate::routes::EtsiSaeQkdRoutesV1::internal_server_error() + EtsiSaeQkdRoutesV1::internal_server_error() } } } \ No newline at end of file diff --git a/src/routes/sae/mod.rs b/src/routes/sae_zone_routes/sae/mod.rs similarity index 74% rename from src/routes/sae/mod.rs rename to src/routes/sae_zone_routes/sae/mod.rs index 8486035..df27abe 100644 --- a/src/routes/sae/mod.rs +++ b/src/routes/sae_zone_routes/sae/mod.rs @@ -9,7 +9,7 @@ use hyper::body::Bytes; use crate::routes::request_context::RequestContext; /// Dispatches the request to the correct function -pub(super) async fn sae_handler(rcx: &RequestContext<'_>, req: Request, uri_segments: &[&str]) -> Result>, Infallible> { +pub(in crate::routes) async fn sae_handler(rcx: &RequestContext<'_>, req: Request, uri_segments: &[&str]) -> Result>, Infallible> { match (uri_segments, req.method()) { (["info", "me"], &hyper::Method::GET) => info::route_get_info_me(rcx, req).await, _ => super::EtsiSaeQkdRoutesV1::not_found(), diff --git a/src/server/certificates.rs b/src/server/auth_https_server/certificates.rs similarity index 100% rename from src/server/certificates.rs rename to src/server/auth_https_server/certificates.rs diff --git a/src/server/auth_https_server/mod.rs b/src/server/auth_https_server/mod.rs new file mode 100644 index 0000000..3f58902 --- /dev/null +++ b/src/server/auth_https_server/mod.rs @@ -0,0 +1,198 @@ +//! HTTPS server implementation + +mod certificates; + +use hyper; +use rustls; +use tokio; +use tokio_rustls; + +use std::io; +use std::marker::PhantomData; +use std::net::SocketAddr; +use std::sync::Arc; +use hyper::Request; +use hyper::server::conn::http1; +use hyper::service::service_fn; + +use hyper_util::rt::TokioIo; +use log::{error, info, warn}; +use rustls::server::WebPkiClientVerifier; +use rustls::{RootCertStore, ServerConfig}; +use tokio::net::TcpListener; +use tokio_rustls::TlsAcceptor; +use crate::io_err; +use crate::qkd_manager::QkdManager; +use crate::server::auth_https_server::certificates::{load_cert, load_pkey}; + + +/// QKD KME server +/// A KME REST API server that implements the ETSI protocol (cf docs/etsi_qkd_standard_definition.pdf) +/// # Note +/// * SAE clients are authenticated using client certificates, and the server is authenticated using a server certificate +/// * SAE client certificate must be signed by the CA certificate specified in the configuration, and authorized client certificates are discriminated by their serial number +#[derive(Debug)] +pub struct AuthHttpsServer { + phantom: PhantomData, + /// HTTPS listen address, e.g. "0.0.0.0:443" + pub listen_addr: String, + /// Path to the CA certificate used to verify client certificates + /// # Note + /// * The CA certificate must be in CRT format + /// * The client need a certificate signed by this CA to be able to connect to the server + /// * Once connected, client are authenticated using their certificate serial number + pub ca_client_cert_path: String, + /// Path to the server certificate, CRT format + /// # Note + /// * Be aware that your SAE clients will need to trust this certificate + pub server_cert_path: String, + /// Path to the server private key, KEY format + pub server_key_path: String, +} + +impl AuthHttpsServer { + + /// Create a new HTTPS server + /// # Arguments + /// * `listen_addr` - The address to listen for HTTPS connections, e.g. "0.0.0.0:443", or "192.168.1.2:443" for local network (SAE internal network?) + /// * `ca_client_cert_path` - The path to the CA certificate used to verify client certificates + /// * `server_cert_path` - The path to the server certificate + /// * `server_key_path` - The path to the server private key + /// # Parameters + /// * `T` - The type of the routes to use, type implements `crate::routes::Routes` + /// # Returns + /// A new AuthHttpsServer + pub fn new(listen_addr: &str, ca_client_cert_path: &str, server_cert_path: &str, server_key_path: &str) -> AuthHttpsServer { + AuthHttpsServer { + phantom: PhantomData, + listen_addr: listen_addr.to_string(), + ca_client_cert_path: ca_client_cert_path.to_string(), + server_cert_path: server_cert_path.to_string(), + server_key_path: server_key_path.to_string(), + } + } + + /// Run the REST HTTPS server asynchronously + /// # Type parameters + /// * `T` - The type of the routes to use, type implements the REST API routes + /// # Arguments + /// * `qkd_manager` - The QKD manager, needed to store and retrieve QKD keys + /// # Returns + /// An io::Error if the server cannot be started (check the logs for more information) + pub async fn run(&self, qkd_manager: &QkdManager) -> Result<(), io::Error> { + let addr = self.listen_addr.parse::().map_err(|e| { + io_err(&format!( + "invalid listen address {:?}: {:?}", + self.listen_addr, e + )) + })?; + let config = self.get_ssl_config()?; + let tls_acceptor = TlsAcceptor::from(Arc::new(config)); + let socket = TcpListener::bind(&addr).await.map_err(|e| { + io_err(&format!( + "cannot bind to {:?}: {:?}", + self.listen_addr, e + )) + })?; + + loop { + info!("[KME{}] Waiting for incoming connection on port {}...", qkd_manager.kme_id, addr.port()); + let Ok(stream) = tls_acceptor.accept(socket.accept().await?.0).await else { + warn!("Error accepting connection, maybe client certificate is missing?"); + continue; + }; + info!("[KME{}] Received connection from peer {}", qkd_manager.kme_id, stream.get_ref().0.peer_addr().map_err(|_| { + io_err("Error getting peer address") + })?); + let (_, server_session) = stream.get_ref(); + let client_cert = Arc::new(server_session.peer_certificates() + .ok_or(io_err("Error: no client certificate, this is unexpected"))? + .first() + .ok_or(io_err("Error fetching client certificate, this is unexpected"))? + .clone().into_owned()); + + let io = TokioIo::new(stream); + let qkd_manager = qkd_manager.clone(); // Must be cloned to be moved into each new task + + tokio::task::spawn(async move { + let response_service = service_fn(|req: Request| { + let local_client_cert_serial_str = Arc::clone(&client_cert); + let qkd_manager = qkd_manager.clone(); // Must be cloned in each new task + async move { + // Let the route type handle the request + T::handle_request(req, Some(&local_client_cert_serial_str), qkd_manager).await + } + }); + if let Err(err) = http1::Builder::new() + .serve_connection(io, response_service) + .await + { + error!("Error serving connection: {:?}", err); + } + }); + + } + + } + + /// Load the SSL configuration for RusTLS layer + /// # Returns + /// A ServerConfig object, containing the RusTLS SSL configuration + /// # Errors + /// If the configuration cannot be loaded (e.g. invalid certificate file, check the logs for more information) + fn get_ssl_config(&self) -> Result { + // Trusted CA for client certificates + let mut roots = RootCertStore::empty(); + let ca_cert_binding = load_cert(self.ca_client_cert_path.as_str())?; + let ca_cert = ca_cert_binding.first().ok_or(io_err("Invalid client CA certificate file"))?; + roots.add(ca_cert.clone()).map_err(|_| { + io_err("Error adding CA certificate") + })?; + let client_verifier = WebPkiClientVerifier::builder(roots.into()).build().map_err(|_| { + io_err("Error building client verifier") + })?; + + let server_cert = load_cert(self.server_cert_path.as_str())?; + + // We retrieve the first key, as we only support one key per server + let server_key = load_pkey(self.server_key_path.as_str())?.remove(0); + + let config = ServerConfig::builder() + .with_client_cert_verifier(client_verifier) + .with_single_cert(server_cert, server_key) + .map_err(|_| { + io_err("Error building server configuration") + })?; + Ok(config) + } +} + +#[cfg(test)] +mod tests { + use crate::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; + + #[test] + fn test_get_ssl_config() { + const CA_CERT_FILENAME: &'static str = "certs/zone1/CA-zone1.crt"; + const SERVER_CERT_FILENAME: &'static str = "certs/zone1/kme1.crt"; + const SERVER_KEY_FILENAME: &'static str = "certs/zone1/kme1.key"; + let server = super::AuthHttpsServer::::new( + "127.0.0.1:3000", + CA_CERT_FILENAME, + SERVER_CERT_FILENAME, + SERVER_KEY_FILENAME, + ); + let config = server.get_ssl_config(); + assert!(config.is_ok()); + + const CA_CERT_FILE_WRONG_FORMAT: &'static str = "certs/zone1/sae1.pfx"; + let server = super::AuthHttpsServer::::new( + "127.0.0.1:3000", + CA_CERT_FILE_WRONG_FORMAT, + SERVER_CERT_FILENAME, + SERVER_KEY_FILENAME, + ); + let config = server.get_ssl_config(); + assert!(config.is_err()); + } +} \ No newline at end of file diff --git a/src/server/log_http_server/index.html b/src/server/log_http_server/index.html new file mode 100644 index 0000000..4d6b7f7 --- /dev/null +++ b/src/server/log_http_server/index.html @@ -0,0 +1,23 @@ + + + + + List of received events from QKD key exchange + + +

Events

+
+ + + \ No newline at end of file diff --git a/src/server/log_http_server/mod.rs b/src/server/log_http_server/mod.rs new file mode 100644 index 0000000..c1d5fd6 --- /dev/null +++ b/src/server/log_http_server/mod.rs @@ -0,0 +1,149 @@ +//! HTTP server for logging important events, in a demonstration purpose (intended to be visited using a web browser) +//! Events are displayed on index HTML page, and retrieved periodically by AJAX requests from /messages + +use std::convert::Infallible; +use std::io; +use std::ops::Deref; +use std::sync::{Arc, Mutex}; +use http_body_util::Full; +use hyper::{Request, Response, StatusCode}; +use hyper::body::Bytes; +use hyper::header::CONTENT_TYPE; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; +use crate::event_subscription::ImportantEventSubscriber; + +/// HTTP logging server struct +#[derive(Debug)] +pub struct LoggingHttpServer { + /// HTTP listen address, e.g. "0.0.0.0:8080" + pub listen_addr: String, + received_log_messages: Arc>>, +} + +impl LoggingHttpServer { + /// Create a new HTTP logging server + /// # Arguments + /// * `listen_addr` - The address to listen for HTTP connections, e.g. "0.0.0.0:8080" + /// # Returns + /// A new LoggingHttpServer + pub fn new(listen_addr: &str) -> Self { + Self { + listen_addr: listen_addr.to_string(), + received_log_messages: Arc::new(Mutex::new(Vec::new())), + } + } + + /// Run the HTTP server + /// # Returns + /// Never (`!`) in case of success, an io::Error otherwise + pub async fn run(&self) -> Result<(), io::Error> { + let listener = TcpListener::bind(&self.listen_addr).await?; + loop { + let (stream, _) = listener.accept().await?; + + // Use an adapter to access something implementing `tokio::io` traits as if they implement + // `hyper::rt` IO traits. + let io = TokioIo::new(stream); + let received_log_messages = Arc::clone(&self.received_log_messages); + + // Spawn a tokio task to serve multiple connections concurrently + tokio::task::spawn(async move { + // Finally, we bind the incoming connection to our `hello` service + if let Err(err) = http1::Builder::new() + // We could think about passing &self as argument when ! type would be released + .serve_connection(io, service_fn(|incoming_request| Self::handle_incoming_request(incoming_request, Arc::clone(&received_log_messages)))) + .await + { + println!("Error serving connection: {:?}", err); + } + }); + } + } + + /// Called at each incoming request + async fn handle_incoming_request(request: Request, received_log_messages: Arc>>) -> Result>, Infallible> { + const INDEX_HTML_RESPONSE: &str = include_str!("index.html"); + + let response_obj = match request.uri().path() { + "/messages" => { + Self::generate_messages_http_json_response(&received_log_messages) + }, + _ => { + Response::new(Full::new(Bytes::from(INDEX_HTML_RESPONSE.to_string()))) + } + }; + Ok(response_obj) + } + + /// Generates JSON array HTTP response containing all received log messages, or HTTP error status if an error occurred + fn generate_messages_http_json_response(received_log_messages: &Arc>>) -> Response> { + let received_log_messages = match received_log_messages.lock() { + Ok(received_log_messages) => received_log_messages, + Err(_) => { + return Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Full::new(Bytes::from(String::from("Mutex lock error")))).unwrap(); + } + }; + let response_str = match serde_json::to_string(&received_log_messages.deref()) { + Ok(response_str) => response_str, + Err(_) => { + return Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Full::new(Bytes::from(String::from("JSON serialization error")))).unwrap(); + } + }; + Response::builder().status(StatusCode::OK).header(CONTENT_TYPE, "application/json").body(Full::new(Bytes::from(response_str))).unwrap() + } +} + +impl ImportantEventSubscriber for LoggingHttpServer { + /// Add a notification message to be displayed on the HTTP page + /// # Arguments + /// * `message` - The message to be displayed + /// # Returns + /// Result<(), io::Error> - Ok(()) if the message was successfully added, Err(io::Error) otherwise (mutex lock error) + fn notify(&self, message: &str) -> Result<(), io::Error> { + self.received_log_messages + .lock() + .map_err(|_| + io::Error::new(io::ErrorKind::Other, "Mutex lock error" + ))? + .push(message.to_string()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use serial_test::serial; + use crate::event_subscription::ImportantEventSubscriber; + use crate::server::log_http_server::LoggingHttpServer; + + #[tokio::test] + #[serial] + async fn test_logging_http_server() { + const EXPECTED_INDEX_BODY: &'static str = include_str!("index.html"); + + let server = Arc::new(LoggingHttpServer::new("127.0.0.1:8080")); + let server_copy_coroutine = Arc::clone(&server); + tokio::spawn(async move { + server_copy_coroutine.run().await.unwrap(); + }); + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; // To ensure server task is scheduled before client + let get_index_response = reqwest::get("http://127.0.0.1:8080").await.unwrap(); + assert_eq!(get_index_response.status(), 200); + assert_eq!(get_index_response.text().await.unwrap().replace("\r", ""), EXPECTED_INDEX_BODY); + + let get_messages_response = reqwest::get("http://127.0.0.1:8080/messages").await.unwrap(); + assert_eq!(get_messages_response.status(), 200); + assert_eq!(get_messages_response.text().await.unwrap(), "[]"); + + server.notify("Hello").unwrap(); + server.notify("World").unwrap(); + + let get_messages_response = reqwest::get("http://127.0.0.1:8080/messages").await.unwrap(); + assert_eq!(get_messages_response.status(), 200); + assert_eq!(get_messages_response.text().await.unwrap(), "[\"Hello\",\"World\"]"); + } +} \ No newline at end of file diff --git a/src/server/mod.rs b/src/server/mod.rs index 7245b79..7847c6b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,174 +1,4 @@ -//! HTTPS server implementation +//! All external HTTP(s) interfaces, including internal SAEs HTTPS interface, inter-KMEs HTTPS interface and optional debugging HTTP interface -mod certificates; - -use hyper; -use rustls; -use tokio; -use tokio_rustls; - -use std::io; -use std::net::SocketAddr; -use std::sync::Arc; -use hyper::Request; -use hyper::server::conn::http1; -use hyper::service::service_fn; - -use hyper_util::rt::TokioIo; -use log::{error, info, warn}; -use rustls::server::WebPkiClientVerifier; -use rustls::{RootCertStore, ServerConfig}; -use tokio::net::TcpListener; -use tokio_rustls::TlsAcceptor; -use crate::io_err; -use crate::qkd_manager::QkdManager; -use crate::server::certificates::{load_cert, load_pkey}; - - -/// QKD KME server -/// A KME REST API server that implements the ETSI protocol (cf docs/etsi_qkd_standard_definition.pdf) -/// # Note -/// * SAE clients are authenticated using client certificates, and the server is authenticated using a server certificate -/// * SAE client certificate must be signed by the CA certificate specified in the configuration, and authorized client certificates are discriminated by their serial number -#[derive(Debug)] -pub struct Server { - /// HTTPS listen address, e.g. "0.0.0.0:443" - pub listen_addr: String, - /// Path to the CA certificate used to verify client certificates - /// # Note - /// * The CA certificate must be in CRT format - /// * The client need a certificate signed by this CA to be able to connect to the server - /// * Once connected, client are authenticated using their certificate serial number - pub ca_client_cert_path: String, - /// Path to the server certificate, CRT format - /// # Note - /// * Be aware that your SAE clients will need to trust this certificate - pub server_cert_path: String, - /// Path to the server private key, KEY format - pub server_key_path: String, -} - -impl Server { - - /// Run the REST HTTPS server asynchronously - /// # Type parameters - /// * `T` - The type of the routes to use, type implements the REST API routes - /// # Arguments - /// * `qkd_manager` - The QKD manager, needed to store and retrieve QKD keys - /// # Returns - /// An io::Error if the server cannot be started (check the logs for more information) - pub async fn run(&self, qkd_manager: &QkdManager) -> Result<(), io::Error> { - let addr = self.listen_addr.parse::().map_err(|e| { - io_err(&format!( - "invalid listen address {:?}: {:?}", - self.listen_addr, e - )) - })?; - let config = self.get_ssl_config()?; - let tls_acceptor = TlsAcceptor::from(Arc::new(config)); - let socket = TcpListener::bind(&addr).await.map_err(|e| { - io_err(&format!( - "cannot bind to {:?}: {:?}", - self.listen_addr, e - )) - })?; - - loop { - info!("[KME{}] Waiting for incoming connection on port {}...", qkd_manager.kme_id, addr.port()); - let Ok(stream) = tls_acceptor.accept(socket.accept().await?.0).await else { - warn!("Error accepting connection, maybe client certificate is missing?"); - continue; - }; - info!("[KME{}] Received connection from peer {}", qkd_manager.kme_id, stream.get_ref().0.peer_addr().map_err(|_| { - io_err("Error getting peer address") - })?); - let (_, server_session) = stream.get_ref(); - let client_cert = Arc::new(server_session.peer_certificates() - .ok_or(io_err("Error: no client certificate, this is unexpected"))? - .first() - .ok_or(io_err("Error fetching client certificate, this is unexpected"))? - .clone().into_owned()); - - let io = TokioIo::new(stream); - let qkd_manager = qkd_manager.clone(); // Must be cloned to be moved into each new task - - tokio::task::spawn(async move { - let response_service = service_fn(|req: Request| { - let local_client_cert_serial_str = Arc::clone(&client_cert); - let qkd_manager = qkd_manager.clone(); // Must be cloned in each new task - async move { - // Let the route type handle the request - T::handle_request(req, Some(&local_client_cert_serial_str), qkd_manager).await - } - }); - if let Err(err) = http1::Builder::new() - .serve_connection(io, response_service) - .await - { - error!("Error serving connection: {:?}", err); - } - }); - - } - - } - - /// Load the SSL configuration for RusTLS layer - /// # Returns - /// A ServerConfig object, containing the RusTLS SSL configuration - /// # Errors - /// If the configuration cannot be loaded (e.g. invalid certificate file, check the logs for more information) - fn get_ssl_config(&self) -> Result { - // Trusted CA for client certificates - let mut roots = RootCertStore::empty(); - let ca_cert_binding = load_cert(self.ca_client_cert_path.as_str())?; - let ca_cert = ca_cert_binding.first().ok_or(io_err("Invalid client CA certificate file"))?; - roots.add(ca_cert.clone()).map_err(|_| { - io_err("Error adding CA certificate") - })?; - let client_verifier = WebPkiClientVerifier::builder(roots.into()).build().map_err(|_| { - io_err("Error building client verifier") - })?; - - let server_cert = load_cert(self.server_cert_path.as_str())?; - - // We retrieve the first key, as we only support one key per server - let server_key = load_pkey(self.server_key_path.as_str())?.remove(0); - - let config = ServerConfig::builder() - .with_client_cert_verifier(client_verifier) - .with_single_cert(server_cert, server_key) - .map_err(|_| { - io_err("Error building server configuration") - })?; - Ok(config) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_get_ssl_config() { - const CA_CERT_FILENAME: &'static str = "certs/zone1/CA-zone1.crt"; - const SERVER_CERT_FILENAME: &'static str = "certs/zone1/kme1.crt"; - const SERVER_KEY_FILENAME: &'static str = "certs/zone1/kme1.key"; - let server = super::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: CA_CERT_FILENAME.to_string(), - server_cert_path: SERVER_CERT_FILENAME.to_string(), - server_key_path: SERVER_KEY_FILENAME.to_string(), - }; - let config = server.get_ssl_config(); - assert!(config.is_ok()); - - const CA_CERT_FILE_WRONG_FORMAT: &'static str = "certs/zone1/sae1.pfx"; - let server = super::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: CA_CERT_FILE_WRONG_FORMAT.to_string(), - server_cert_path: SERVER_CERT_FILENAME.to_string(), - server_key_path: SERVER_KEY_FILENAME.to_string(), - }; - let config = server.get_ssl_config(); - assert!(config.is_err()); - } -} \ No newline at end of file +pub mod auth_https_server; +pub mod log_http_server; \ No newline at end of file diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 0672506..2250eca 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -7,19 +7,19 @@ use std::io::Read; use std::sync::Arc; use tokio::select; use qkd_kme_server::qkd_manager::{PreInitQkdKeyWrapper, QkdManager}; -use qkd_kme_server::routes::EtsiSaeQkdRoutesV1; +use qkd_kme_server::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; use qkd_kme_server::routes::inter_kmes_routes::InterKMEsRoutes; pub const HOST_PORT: &'static str = "localhost:3000"; pub const REMOTE_KME_HOST_PORT: &'static str = "localhost:4000"; pub fn setup() { - let server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: "certs/zone1/CA-zone1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; + let server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:3000", + "certs/zone1/CA-zone1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); let qkd_manager = QkdManager::new(":memory:", 1); qkd_manager.add_sae(1, @@ -39,7 +39,7 @@ pub fn setup() { ).unwrap(); qkd_manager.add_pre_init_qkd_key(qkd_key_2).unwrap(); - tokio::spawn(async move {server.run::(&qkd_manager).await.unwrap();}); + tokio::spawn(async move {server.run(&qkd_manager).await.unwrap();}); } pub fn setup_cert_auth_reqwest_client() -> reqwest::Client { @@ -84,31 +84,31 @@ pub fn setup_cert_auth_reqwest_bad_client() -> reqwest::Client { } pub fn setup_2_kmes_network() { - let kme1_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: "certs/zone1/CA-zone1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - let kme2_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:4000".to_string(), - ca_client_cert_path: "certs/zone2/CA-zone2.crt".to_string(), - server_cert_path: "certs/zone2/kme2.crt".to_string(), - server_key_path: "certs/zone2/kme2.key".to_string(), - }; - - let kme1_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:3001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - let kme2_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:4001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme2.crt".to_string(), - server_cert_path: "certs/zone2/kme2.crt".to_string(), - server_key_path: "certs/zone2/kme2.key".to_string(), - }; + let kme1_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:3000", + "certs/zone1/CA-zone1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme2_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:4000", + "certs/zone2/CA-zone2.crt", + "certs/zone2/kme2.crt", + "certs/zone2/kme2.key", + ); + + let kme1_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:3001", + "certs/inter_kmes/root-ca-kme1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme2_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:4001", + "certs/inter_kmes/root-ca-kme2.crt", + "certs/zone2/kme2.crt", + "certs/zone2/kme2.key", + ); let kme1_qkd_manager = Arc::new(QkdManager::new(":memory:", 1)); kme1_qkd_manager.add_sae(1, @@ -150,16 +150,16 @@ pub fn setup_2_kmes_network() { tokio::spawn(async move { select! { - x = kme1_internal_sae_server.run::(&kme1_qkd_manager) => { + x = kme1_internal_sae_server.run(&kme1_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme1_external_inter_kmes_server.run::(&kme1_qkd_manager) => { + x = kme1_external_inter_kmes_server.run(&kme1_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, - x = kme2_internal_sae_server.run::(&kme2_qkd_manager) => { + x = kme2_internal_sae_server.run(&kme2_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme2_external_inter_kmes_server.run::(&kme2_qkd_manager) => { + x = kme2_external_inter_kmes_server.run(&kme2_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, } @@ -167,31 +167,31 @@ pub fn setup_2_kmes_network() { } pub fn setup_2_kmes_network_keys_not_sync() { - let kme1_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: "certs/zone1/CA-zone1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - let kme2_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:4000".to_string(), - ca_client_cert_path: "certs/zone2/CA-zone2.crt".to_string(), - server_cert_path: "certs/zone2/kme2.crt".to_string(), - server_key_path: "certs/zone2/kme2.key".to_string(), - }; - - let kme1_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:3001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - let kme2_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:4001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme2.crt".to_string(), - server_cert_path: "certs/zone2/kme2.crt".to_string(), - server_key_path: "certs/zone2/kme2.key".to_string(), - }; + let kme1_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:3000", + "certs/zone1/CA-zone1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme2_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:4000", + "certs/zone2/CA-zone2.crt", + "certs/zone2/kme2.crt", + "certs/zone2/kme2.key", + ); + + let kme1_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:3001", + "certs/inter_kmes/root-ca-kme1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme2_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:4001", + "certs/inter_kmes/root-ca-kme2.crt", + "certs/zone2/kme2.crt", + "certs/zone2/kme2.key", + ); let kme1_qkd_manager = Arc::new(QkdManager::new(":memory:", 1)); kme1_qkd_manager.add_sae(1, @@ -226,16 +226,16 @@ pub fn setup_2_kmes_network_keys_not_sync() { tokio::spawn(async move { select! { - x = kme1_internal_sae_server.run::(&kme1_qkd_manager) => { + x = kme1_internal_sae_server.run(&kme1_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme1_external_inter_kmes_server.run::(&kme1_qkd_manager) => { + x = kme1_external_inter_kmes_server.run(&kme1_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, - x = kme2_internal_sae_server.run::(&kme2_qkd_manager) => { + x = kme2_internal_sae_server.run(&kme2_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme2_external_inter_kmes_server.run::(&kme2_qkd_manager) => { + x = kme2_external_inter_kmes_server.run(&kme2_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, } @@ -243,19 +243,18 @@ pub fn setup_2_kmes_network_keys_not_sync() { } pub fn setup_2_kmes_network_1_kme_down() { - let kme1_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: "certs/zone1/CA-zone1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - - let kme1_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:3001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; + let kme1_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:3000", + "certs/zone1/CA-zone1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme1_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:3001", + "certs/inter_kmes/root-ca-kme1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); let kme1_qkd_manager = Arc::new(QkdManager::new(":memory:", 1)); kme1_qkd_manager.add_sae(1, @@ -279,10 +278,10 @@ pub fn setup_2_kmes_network_1_kme_down() { tokio::spawn(async move { select! { - x = kme1_internal_sae_server.run::(&kme1_qkd_manager) => { + x = kme1_internal_sae_server.run(&kme1_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme1_external_inter_kmes_server.run::(&kme1_qkd_manager) => { + x = kme1_external_inter_kmes_server.run(&kme1_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, } @@ -290,19 +289,18 @@ pub fn setup_2_kmes_network_1_kme_down() { } pub fn setup_2_kmes_network_missing_conf() { - let kme1_internal_sae_server = qkd_kme_server::server::Server { - listen_addr: "127.0.0.1:3000".to_string(), - ca_client_cert_path: "certs/zone1/CA-zone1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; - - let kme1_external_inter_kmes_server = qkd_kme_server::server::Server { - listen_addr: "0.0.0.0:3001".to_string(), - ca_client_cert_path: "certs/inter_kmes/root-ca-kme1.crt".to_string(), - server_cert_path: "certs/zone1/kme1.crt".to_string(), - server_key_path: "certs/zone1/kme1.key".to_string(), - }; + let kme1_internal_sae_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "127.0.0.1:3000", + "certs/zone1/CA-zone1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); + let kme1_external_inter_kmes_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + "0.0.0.0:3001", + "certs/inter_kmes/root-ca-kme1.crt", + "certs/zone1/kme1.crt", + "certs/zone1/kme1.key", + ); let kme1_qkd_manager = Arc::new(QkdManager::new(":memory:", 1)); kme1_qkd_manager.add_sae(1, @@ -325,10 +323,10 @@ pub fn setup_2_kmes_network_missing_conf() { tokio::spawn(async move { select! { - x = kme1_internal_sae_server.run::(&kme1_qkd_manager) => { + x = kme1_internal_sae_server.run(&kme1_qkd_manager) => { eprintln!("Error running internal SAE server: {:?}", x); }, - x = kme1_external_inter_kmes_server.run::(&kme1_qkd_manager) => { + x = kme1_external_inter_kmes_server.run(&kme1_qkd_manager) => { eprintln!("Error running external inter-KMEs server: {:?}", x); }, } diff --git a/tests/config_parsing.rs b/tests/config_parsing.rs index e2c8f1c..7d130fb 100644 --- a/tests/config_parsing.rs +++ b/tests/config_parsing.rs @@ -1,11 +1,14 @@ +use std::sync::Arc; use const_format::concatcp; use log::error; use reqwest::header::CONTENT_TYPE; use serial_test::serial; use tokio::select; +use qkd_kme_server::event_subscription::ImportantEventSubscriber; use qkd_kme_server::qkd_manager::QkdManager; -use qkd_kme_server::routes::EtsiSaeQkdRoutesV1; +use qkd_kme_server::routes::sae_zone_routes::EtsiSaeQkdRoutesV1; use qkd_kme_server::routes::inter_kmes_routes::InterKMEsRoutes; +use qkd_kme_server::server::log_http_server::LoggingHttpServer; mod common; @@ -21,6 +24,12 @@ async fn test_key_transfer_from_file_config() { const INIT_POST_KEY_REQUEST_URL_2: &'static str = concatcp!("https://", common::REMOTE_KME_HOST_PORT ,"/api/v1/keys/1/enc_keys"); const REMOTE_DEC_KEYS_REQUEST_URL: &'static str = concatcp!("https://", common::REMOTE_KME_HOST_PORT ,"/api/v1/keys/1/dec_keys"); const REMOTE_DEC_KEYS_REQUEST_URL_2: &'static str = concatcp!("https://", common::HOST_PORT ,"/api/v1/keys/3/dec_keys"); + const LOG_DEMO_REQUEST_URL_INDEX: &'static str = concatcp!("http://localhost:8080"); + const LOG_DEMO_REQUEST_URL_JSON_DATA: &'static str = concatcp!("http://localhost:8080/messages"); + + const LOG_MESSAGE_STEP_1: &'static str = "[]"; + const LOG_MESSAGE_STEP_2: &'static str = "[\"[KME 1] SAE 1 requested a key to communicate with 3\",\"[KME 1] As SAE 3 belongs to KME 2, activating it through inter KMEs network\",\"[KME 1] Key 2ae3e385-4e51-7458-b1c1-69066a4cb6d7 activated between SAEs 1 and 3\"]"; + const LOG_MESSAGE_STEP_3: &'static str = "[\"[KME 1] SAE 1 requested a key to communicate with 3\",\"[KME 1] As SAE 3 belongs to KME 2, activating it through inter KMEs network\",\"[KME 1] Key 2ae3e385-4e51-7458-b1c1-69066a4cb6d7 activated between SAEs 1 and 3\",\"[KME 1] Key 9768257a-1c59-d255-a93d-d4bb1b693651 activated between SAEs 3 and 1\",\"[KME 1] SAE 1 requested key 9768257a-1c59-d255-a93d-d4bb1b693651 (from 3)\"]"; tokio::spawn(async move { launch_kme_from_config_file(CONFIG_FILE_PATH_KME1).await; @@ -31,6 +40,13 @@ async fn test_key_transfer_from_file_config() { let sae1_reqwest_client = common::setup_cert_auth_reqwest_client(); let sae2_reqwest_client = common::setup_cert_auth_reqwest_client_remote_kme(); + let log_demo_reqwest_client = reqwest::Client::new(); + + let log_index_response = log_demo_reqwest_client.get(LOG_DEMO_REQUEST_URL_INDEX).send().await.unwrap(); + assert_eq!(log_index_response.status(), 200); + let log_data_response = log_demo_reqwest_client.get(LOG_DEMO_REQUEST_URL_JSON_DATA).send().await.unwrap(); + assert_eq!(log_data_response.status(), 200); + assert_eq!(log_data_response.text().await.unwrap(), LOG_MESSAGE_STEP_1); let post_key_response = sae1_reqwest_client.post(INIT_POST_KEY_REQUEST_URL).send().await; assert!(post_key_response.is_ok()); @@ -39,6 +55,10 @@ async fn test_key_transfer_from_file_config() { const EXPECTED_INIT_KEY_RESPONSE_BODY: &'static str = "{\n \"keys\": [\n {\n \"key_ID\": \"2ae3e385-4e51-7458-b1c1-69066a4cb6d7\",\n \"key\": \"m0gAbsCqIwYgM2HMOcc8nkh6nhZG3EBAxuL6rgas1FU=\"\n }\n ]\n}"; assert_eq!(post_key_response.text().await.unwrap().replace("\r", ""), EXPECTED_INIT_KEY_RESPONSE_BODY); + let log_data_response = log_demo_reqwest_client.get(LOG_DEMO_REQUEST_URL_JSON_DATA).send().await.unwrap(); + assert_eq!(log_data_response.status(), 200); + assert_eq!(log_data_response.text().await.unwrap(), LOG_MESSAGE_STEP_2); + const REMOTE_DEC_KEYS_REQ_BODY: &'static str = "{\n\"key_IDs\": [{\"key_ID\": \"2ae3e385-4e51-7458-b1c1-69066a4cb6d7\"}]\n}"; let req_key_remote_response = sae2_reqwest_client.post(REMOTE_DEC_KEYS_REQUEST_URL).header(CONTENT_TYPE, "application/json").body(REMOTE_DEC_KEYS_REQ_BODY).send().await; assert!(req_key_remote_response.is_ok()); @@ -61,35 +81,58 @@ async fn test_key_transfer_from_file_config() { assert_eq!(req_key_remote_response.status(), 200); const EXPECTED_BODY_DEC_KEY_2: &'static str = "{\n \"keys\": [\n {\n \"key_ID\": \"9768257a-1c59-d255-a93d-d4bb1b693651\",\n \"key\": \"zNK/zOIUDAFyuKRM0dSJLLZVYaDTuhzhAIACBgWABfY=\"\n }\n ]\n}"; assert_eq!(req_key_remote_response.text().await.unwrap().replace("\r", ""), EXPECTED_BODY_DEC_KEY_2); + + let log_data_response = log_demo_reqwest_client.get(LOG_DEMO_REQUEST_URL_JSON_DATA).send().await.unwrap(); + assert_eq!(log_data_response.status(), 200); + assert_eq!(log_data_response.text().await.unwrap(), LOG_MESSAGE_STEP_3); } // Quite similar to program's main function async fn launch_kme_from_config_file(config_file_path: &str) { let config = qkd_kme_server::config::Config::from_json_path(config_file_path).unwrap(); - let sae_https_server = qkd_kme_server::server::Server { - listen_addr: config.this_kme_config.saes_https_interface.listen_address.clone(), - ca_client_cert_path: config.this_kme_config.saes_https_interface.ca_client_cert_path.clone(), - server_cert_path: config.this_kme_config.saes_https_interface.server_cert_path.clone(), - server_key_path: config.this_kme_config.saes_https_interface.server_key_path.clone(), - }; + let sae_https_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + &config.this_kme_config.saes_https_interface.listen_address, + &config.this_kme_config.saes_https_interface.ca_client_cert_path, + &config.this_kme_config.saes_https_interface.server_cert_path, + &config.this_kme_config.saes_https_interface.server_key_path, + ); - let inter_kme_https_server = qkd_kme_server::server::Server { - listen_addr: config.this_kme_config.kmes_https_interface.listen_address.clone(), - ca_client_cert_path: config.this_kme_config.kmes_https_interface.ca_client_cert_path.clone(), - server_cert_path: config.this_kme_config.kmes_https_interface.server_cert_path.clone(), - server_key_path: config.this_kme_config.kmes_https_interface.server_key_path.clone(), - }; + let inter_kme_https_server = qkd_kme_server::server::auth_https_server::AuthHttpsServer::::new( + &config.this_kme_config.kmes_https_interface.listen_address, + &config.this_kme_config.kmes_https_interface.ca_client_cert_path, + &config.this_kme_config.kmes_https_interface.server_cert_path, + &config.this_kme_config.kmes_https_interface.server_key_path, + ); let qkd_manager = QkdManager::from_config(&config); let qkd_manager = qkd_manager.unwrap(); - select! { - x = inter_kme_https_server.run::(&qkd_manager) => { - error!("Error running inter-KMEs HTTPS server: {:?}", x); + match config.this_kme_config.debugging_http_interface { + Some(listen_addr) => { + let logging_http_server = Arc::new(LoggingHttpServer::new(&listen_addr)); + qkd_manager.add_important_event_subscriber(Arc::clone(&logging_http_server) as Arc).unwrap(); + select! { + x = inter_kme_https_server.run(&qkd_manager) => { + error!("Error running inter-KMEs HTTPS server: {:?}", x); + }, + x = sae_https_server.run(&qkd_manager) => { + error!("Error running SAEs HTTPS server: {:?}", x); + }, + x = logging_http_server.run() => { + error!("Error running logging HTTP server: {:?}", x); + } + } }, - x = sae_https_server.run::(&qkd_manager) => { - error!("Error running SAEs HTTPS server: {:?}", x); + None => { + select! { + x = inter_kme_https_server.run(&qkd_manager) => { + error!("Error running inter-KMEs HTTPS server: {:?}", x); + }, + x = sae_https_server.run(&qkd_manager) => { + error!("Error running SAEs HTTPS server: {:?}", x); + } + } } } } \ No newline at end of file diff --git a/tests/data/test_kme_config.json b/tests/data/test_kme_config.json index 43edcb0..560088c 100644 --- a/tests/data/test_kme_config.json +++ b/tests/data/test_kme_config.json @@ -14,7 +14,8 @@ "ca_client_cert_path": "certs/inter_kmes/root-ca-kme1.crt", "server_cert_path": "certs/zone1/kme1.crt", "server_key_path": "certs/zone1/kme1.key" - } + }, + "debugging_http_interface": "127.0.0.1:8080" }, "other_kmes": [ {