From 424f0faf192bc90a3dc6bd1c6d0a29ca84e6769b Mon Sep 17 00:00:00 2001 From: Philipp Eder Date: Tue, 31 Oct 2023 13:45:43 +0100 Subject: [PATCH] WIP: Add: client authorization on endpoints To prevent that a registered client can see results or scan of another client a differentiation factor is introduces. The scans and results will now be stored as an u64->information and the key is either calculated by the used client certificate or, when openvasd is started without client certifactes, by the used API key. When client certificates and the API key is configured than the client certificates are getting used. When neither is configured that differntiation feature is turned off as there is no identifier available. Additionally it can be turned off via the configuration option TBD. --- rust/.gitignore | 2 + rust/examples/openvasd/config.example.toml | 2 + .../server_certificates.sh | 122 +++++----- rust/openvasd/Cargo.toml | 2 +- rust/openvasd/src/config.rs | 6 +- rust/openvasd/src/controller/context.rs | 3 +- rust/openvasd/src/controller/entry.rs | 28 ++- rust/openvasd/src/controller/mod.rs | 43 ++-- rust/openvasd/src/main.rs | 117 ++++++++-- rust/openvasd/src/tls.rs | 214 ++++++++++++++---- rust/openvasd/tests/authorization.rs | 0 .../openvasd/tests/data/generate-test-keys.sh | 73 ++++++ rust/openvasd/tests/data/openssl.cnf | 25 ++ 13 files changed, 480 insertions(+), 157 deletions(-) create mode 100644 rust/openvasd/tests/authorization.rs create mode 100644 rust/openvasd/tests/data/generate-test-keys.sh create mode 100644 rust/openvasd/tests/data/openssl.cnf diff --git a/rust/.gitignore b/rust/.gitignore index a2ef7f4edf..d4661763be 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1,2 +1,4 @@ target/ libsrc/ +*.rsa +*.cert diff --git a/rust/examples/openvasd/config.example.toml b/rust/examples/openvasd/config.example.toml index 4a8df6d761..1b5920a52b 100644 --- a/rust/examples/openvasd/config.example.toml +++ b/rust/examples/openvasd/config.example.toml @@ -1,6 +1,8 @@ [feed] # path to the openvas feed. This is required for the /vts endpoint. path = "/var/lib/openvas/plugins" +# disables or enables the signnature check +signature_check = true [feed.check_interval] # how often the feed should be checked for updates diff --git a/rust/examples/tls/Self-Signed mTLS Method/server_certificates.sh b/rust/examples/tls/Self-Signed mTLS Method/server_certificates.sh index 3da90b334c..dab9756e0a 100644 --- a/rust/examples/tls/Self-Signed mTLS Method/server_certificates.sh +++ b/rust/examples/tls/Self-Signed mTLS Method/server_certificates.sh @@ -3,57 +3,71 @@ # # SPDX-License-Identifier: GPL-2.0-or-later -set -xe - -openssl req -nodes \ - -x509 \ - -days 3650 \ - -newkey rsa:4096 \ - -keyout ca.key \ - -out ca.cert \ - -sha256 \ - -batch \ - -subj "/CN=ponytown RSA CA" - -openssl req -nodes \ - -newkey rsa:3072 \ - -keyout inter.key \ - -out inter.req \ - -sha256 \ - -batch \ - -subj "/CN=ponytown RSA level 2 intermediate" - -openssl req -nodes \ - -newkey rsa:2048 \ - -keyout end.key \ - -out end.req \ - -sha256 \ - -batch \ - -subj "/CN=testserver.com" - -openssl rsa \ - -in end.key \ - -out server.rsa - -openssl x509 -req \ - -in inter.req \ - -out inter.cert \ - -CA ca.cert \ - -CAkey ca.key \ - -sha256 \ - -days 3650 \ - -set_serial 123 \ - -extensions v3_inter -extfile ../openssl.cnf - -openssl x509 -req \ - -in end.req \ - -out end.cert \ - -CA inter.cert \ - -CAkey inter.key \ - -sha256 \ - -days 2000 \ - -set_serial 456 \ - -extensions v3_end -extfile ../openssl.cnf - -cat end.cert inter.cert ca.cert > server.pem -rm *.key *.cert *.req +set -e +generate_certificates() +{ + out="$1" + name="$2" + printf "generating $out for $name\t" + openssl req -nodes \ + -x509 \ + -days 3650 \ + -newkey rsa:4096 \ + -keyout ca.key \ + -out ca.cert \ + -sha256 \ + -batch \ + -subj "/CN=$name RSA CA" + + openssl req -nodes \ + -newkey rsa:3072 \ + -keyout inter.key \ + -out inter.req \ + -sha256 \ + -batch \ + -subj "/CN=$name RSA level 2 intermediate" + + openssl req -nodes \ + -newkey rsa:2048 \ + -keyout end.key \ + -out end.req \ + -sha256 \ + -batch \ + -subj "/CN=testserver.com" + + openssl rsa \ + -in end.key \ + -out $out.rsa + + openssl x509 -req \ + -in inter.req \ + -out inter.cert \ + -CA ca.cert \ + -CAkey ca.key \ + -sha256 \ + -days 3650 \ + -set_serial 123 \ + -extensions v3_inter -extfile openssl.cnf + + openssl x509 -req \ + -in end.req \ + -out end.cert \ + -CA inter.cert \ + -CAkey inter.key \ + -sha256 \ + -days 2000 \ + -set_serial 456 \ + -extensions v3_end -extfile openssl.cnf + + cat end.cert inter.cert ca.cert > $out.pem + rm *.key *.cert *.req + printf "done\n" +} + +generate_certificates "server" "ponytown" +generate_certificates "client1" "onehause" +generate_certificates "client2" "zweidorf" +mkdir -p clients/certificates +mkdir -p clients/keys +mv client*.pem clients/certificates/ +mv client*.rsa clients/keys/ diff --git a/rust/openvasd/Cargo.toml b/rust/openvasd/Cargo.toml index 40fdd9ccec..11cec1e0ff 100644 --- a/rust/openvasd/Cargo.toml +++ b/rust/openvasd/Cargo.toml @@ -20,7 +20,7 @@ serde_json = "1.0.96" serde = { version = "1.0.163", features = ["derive"] } uuid = {version = "1", features = ["v4", "fast-rng", "serde"]} hyper-rustls = "0.24.0" -rustls = "0.21.1" +rustls = {version = "0.21.1", features = ["secret_extraction", "dangerous_configuration"]} tokio-rustls = "0.24.0" futures-util = "0.3.28" rustls-pemfile = "1.0.2" diff --git a/rust/openvasd/src/config.rs b/rust/openvasd/src/config.rs index 1547d687db..5b2fe2265d 100644 --- a/rust/openvasd/src/config.rs +++ b/rust/openvasd/src/config.rs @@ -188,8 +188,9 @@ impl Config { where P: AsRef + std::fmt::Display, { - let config = std::fs::read_to_string(path).unwrap_or_default(); - toml::from_str(&config).unwrap_or_default() + println!("loading {path} ..."); + let config = std::fs::read_to_string(path).unwrap(); + toml::from_str(&config).unwrap() } pub fn load() -> Self { @@ -330,6 +331,7 @@ impl Config { let mut config = match cmds.get_one::("config") { Some(path) => Self::from_file(path), None => { + println!("no config provided"); if let Some(config) = Self::load_user() { config } else { diff --git a/rust/openvasd/src/controller/context.rs b/rust/openvasd/src/controller/context.rs index 50a1e389df..a68e64aa95 100644 --- a/rust/openvasd/src/controller/context.rs +++ b/rust/openvasd/src/controller/context.rs @@ -100,7 +100,8 @@ impl ContextBuilder { if let Some(fp) = self.feed_config.as_ref() { let loader = nasl_interpreter::FSPluginLoader::new(fp.path.clone()); let dispatcher: DefaultDispatcher = DefaultDispatcher::default(); - let version = feed::version(&loader, &dispatcher).unwrap(); + let version = + feed::version(&loader, &dispatcher).unwrap_or_else(|_| String::from("UNDEFINED")); self.response.set_feed_version(&version); } self diff --git a/rust/openvasd/src/controller/entry.rs b/rust/openvasd/src/controller/entry.rs index 4ba9a1faa9..86414cce65 100644 --- a/rust/openvasd/src/controller/entry.rs +++ b/rust/openvasd/src/controller/entry.rs @@ -6,12 +6,18 @@ //! //! All known paths must be handled in the entrypoint function. -use std::{fmt::Display, sync::Arc}; +use std::{ + fmt::Display, + sync::{Arc, RwLock}, +}; -use super::context::Context; +use super::{context::Context, ClientIdentifier}; use hyper::{Body, Method, Request, Response}; -use crate::scan::{self, Error, ScanDeleter, ScanStarter, ScanStopper}; +use crate::{ + scan::{self, Error, ScanDeleter, ScanStarter, ScanStopper}, + tls::TlsStream, +}; enum HealthOpts { /// Ready @@ -60,7 +66,7 @@ impl KnownPaths { Some("alive") => KnownPaths::Health(HealthOpts::Alive), Some("started") => KnownPaths::Health(HealthOpts::Started), _ => KnownPaths::Unknown, - } + }, _ => { tracing::trace!("Unknown path: {path}"); KnownPaths::Unknown @@ -95,6 +101,7 @@ impl Display for KnownPaths { pub async fn entrypoint<'a, S, DB>( req: Request, ctx: Arc>, + num: Arc>, ) -> Result, Error> where S: ScanStarter @@ -108,11 +115,12 @@ where { use KnownPaths::*; // on head requests we just return an empty response without checking the api key - tracing::trace!( - "{} {}:{:?}", + tracing::warn!( + "{} {}:{:?} -> {:?}", req.method(), req.uri().path(), - req.uri().query() + req.uri().query(), + num.read().unwrap(), ); if req.method() == Method::HEAD { return Ok(ctx.response.empty(hyper::StatusCode::OK)); @@ -133,9 +141,9 @@ where } match (req.method(), kp) { - (&Method::GET, Health(HealthOpts::Alive)) | - (&Method::GET, Health(HealthOpts::Started)) => - Ok(ctx.response.empty(hyper::StatusCode::OK)), + (&Method::GET, Health(HealthOpts::Alive)) | (&Method::GET, Health(HealthOpts::Started)) => { + Ok(ctx.response.empty(hyper::StatusCode::OK)) + } (&Method::GET, Health(HealthOpts::Ready)) => { let oids = ctx.db.oids().await?; if oids.count() == 0 { diff --git a/rust/openvasd/src/controller/mod.rs b/rust/openvasd/src/controller/mod.rs index 6e9ddb7fe7..0b9fa3dc01 100644 --- a/rust/openvasd/src/controller/mod.rs +++ b/rust/openvasd/src/controller/mod.rs @@ -7,6 +7,8 @@ mod entry; pub mod feed; pub mod results; +use std::default; + use crate::scan::{ScanDeleter, ScanResultFetcher, ScanStarter, ScanStopper}; pub use context::{Context, ContextBuilder, NoOpScanner}; pub use entry::entrypoint; @@ -20,29 +22,28 @@ pub(crate) fn quit_on_poison() -> T { /// Combines all traits needed for a scanner. pub trait Scanner: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {} -impl Scanner for T where T: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {} - -macro_rules! make_svc { - ($controller:expr) => {{ - // start background service - use std::sync::Arc; - - tokio::spawn(crate::controller::results::fetch(Arc::clone(&$controller))); - tokio::spawn(crate::controller::feed::fetch(Arc::clone(&$controller))); - - use hyper::service::{make_service_fn, service_fn}; - make_service_fn(|_conn| { - let controller = Arc::clone($controller); - async { - Ok::<_, crate::scan::Error>(service_fn(move |req| { - crate::controller::entrypoint(req, Arc::clone(&controller)) - })) - } - }) - }}; +/// Contains information about an authorization model of a connection (e.g. mtls) +#[derive(Default, Debug)] +pub enum ClientIdentifier { + /// When there in no information available + #[default] + Unknown, + /// Contains a hashed number of an identifier + /// + /// openvasd uses the identifier as a key for results. This key is usually calculated by an + /// subject of a known client certificate. Based on that we don't need more information. + Known(u64), +} +/// Is used to transfer the information if there is an identifier present within the connection +pub trait ClientInformationRetriever { + /// Gets the identifier + /// + /// Based on the concurrent nature, the actual information is boxed within a Arc and locked for + /// concurrent read write accesses. + fn client_identifier(&self) -> &std::sync::Arc>; } -pub(crate) use make_svc; +impl Scanner for T where T: ScanStarter + ScanStopper + ScanDeleter + ScanResultFetcher {} #[cfg(test)] mod tests { diff --git a/rust/openvasd/src/main.rs b/rust/openvasd/src/main.rs index ac8cd86c66..1e4059e08c 100644 --- a/rust/openvasd/src/main.rs +++ b/rust/openvasd/src/main.rs @@ -12,6 +12,55 @@ mod scan; mod storage; mod tls; +async fn serve<'a, DB, I>( + db: DB, + config: config::Config, + inc: I, +) -> Result<(), Box> +where + I: hyper::server::accept::Accept, + I::Error: Into>, + I::Conn: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static, + DB: crate::storage::Storage + std::marker::Send + 'static + std::marker::Sync, +{ + // let scanner = scan::OSPDWrapper::new(config.ospd.socket.clone(), config.ospd.read_timeout); + // let rc = config.ospd.result_check_interval; + // let fc = ( + // config.feed.path.clone(), + // config.feed.check_interval, + // config.feed.signature_check, + // ); + // let ctx = controller::ContextBuilder::new() + // .result_config(rc) + // .feed_config(fc) + // .scanner(scanner) + // .api_key(config.endpoints.key.clone()) + // .enable_get_scans(config.endpoints.enable_get_scans) + // .storage(db) + // .build(); + // let controller = std::sync::Arc::new(ctx); + // let make_svc = { + // use std::sync::Arc; + + // tokio::spawn(crate::controller::results::fetch(Arc::clone(&controller))); + // tokio::spawn(crate::controller::feed::fetch(Arc::clone(&controller))); + + // use hyper::service::{make_service_fn, service_fn}; + // make_service_fn(|_conn| { + // let controller = Arc::clone(&controller); + // async { + // Ok::<_, crate::scan::Error>(service_fn(move |req| { + // crate::controller::entrypoint(req, Arc::clone(&controller)) + // })) + // } + // }) + // }; + // // TODO: change to return server + // let server = hyper::Server::builder(inc).serve(make_svc); + // server.await?; + Ok(()) +} + pub async fn run<'a, DB>( db: DB, config: config::Config, @@ -19,37 +68,56 @@ pub async fn run<'a, DB>( where DB: crate::storage::Storage + std::marker::Send + 'static + std::marker::Sync, { - let scanner = scan::OSPDWrapper::new(config.ospd.socket.clone(), config.ospd.read_timeout); - let rc = config.ospd.result_check_interval; - let fc = ( - config.feed.path.clone(), - config.feed.check_interval, - config.feed.signature_check, - ); - let ctx = controller::ContextBuilder::new() - .result_config(rc) - .feed_config(fc) - .scanner(scanner) - .api_key(config.endpoints.key.clone()) - .enable_get_scans(config.endpoints.enable_get_scans) - .storage(db) - .build(); - let controller = std::sync::Arc::new(ctx); let addr = config.listener.address; let incoming = hyper::server::conn::AddrIncoming::bind(&addr)?; let addr = incoming.local_addr(); - - if let Some(tlsc) = tls::tls_config(&config)? { - tracing::trace!("TLS enabled"); - let make_svc = crate::controller::make_svc!(&controller); - let server = hyper::Server::builder(tls::TlsAcceptor::new(tlsc, incoming)).serve(make_svc); + if let Some((roots, certs, key)) = tls::tls_config(&config)? { tracing::info!("listening on https://{}", addr); + let inc = tls::TlsAcceptor::new(roots, certs, key, incoming); + let scanner = scan::OSPDWrapper::new(config.ospd.socket.clone(), config.ospd.read_timeout); + let rc = config.ospd.result_check_interval; + let fc = ( + config.feed.path.clone(), + config.feed.check_interval, + config.feed.signature_check, + ); + let ctx = controller::ContextBuilder::new() + .result_config(rc) + .feed_config(fc) + .scanner(scanner) + .api_key(config.endpoints.key.clone()) + .enable_get_scans(config.endpoints.enable_get_scans) + .storage(db) + .build(); + let controller = std::sync::Arc::new(ctx); + let make_svc = { + use std::sync::Arc; + + tokio::spawn(crate::controller::results::fetch(Arc::clone(&controller))); + tokio::spawn(crate::controller::feed::fetch(Arc::clone(&controller))); + + use hyper::service::{make_service_fn, service_fn}; + make_service_fn(|conn: &crate::tls::TlsStream| { + let controller = Arc::clone(&controller); + let num = Arc::clone(&conn.client_identifier); + tracing::warn!("mmmmmooooeeeepppp23"); + async { + Ok::<_, crate::scan::Error>(service_fn(move |req| { + crate::controller::entrypoint( + req, + Arc::clone(&controller), + Arc::clone(&num), + ) + })) + } + }) + }; + // TODO: change to return server + let server = hyper::Server::builder(inc).serve(make_svc); server.await?; } else { - let make_svc = crate::controller::make_svc!(&controller); - let server = hyper::Server::builder(incoming).serve(make_svc); tracing::info!("listening on http://{}", addr); - server.await?; + serve(db, config, incoming).await?; } Ok(()) } @@ -60,6 +128,7 @@ async fn main() -> Result<(), Box> { let filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) .parse_lossy(config.log.level.clone()); + tracing::debug!("config: {:?}", config); tracing_subscriber::fmt().with_env_filter(filter).init(); if !config.ospd.socket.exists() { tracing::warn!("OSPD socket {} does not exist. Some commands will not work until the socket is created!", config.ospd.socket.display()); diff --git a/rust/openvasd/src/tls.rs b/rust/openvasd/src/tls.rs index 46e0663758..7f7de4c325 100644 --- a/rust/openvasd/src/tls.rs +++ b/rust/openvasd/src/tls.rs @@ -29,17 +29,22 @@ use core::task::{Context, Poll}; use futures_util::{ready, Future}; use hyper::server::accept::Accept; use hyper::server::conn::{AddrIncoming, AddrStream}; -use rustls::server::{AllowAnyAuthenticatedClient, NoClientAuth}; +use rustls::server::{ + AllowAnyAnonymousOrAuthenticatedClient, AllowAnyAuthenticatedClient, ClientCertVerifier, + NoClientAuth, +}; use rustls::RootCertStore; use rustls_pemfile::{read_one, Item}; use std::path::{Path, PathBuf}; use std::pin::Pin; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::{fs, io}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_rustls::rustls::ServerConfig; +use crate::controller::{ClientIdentifier, ClientInformationRetriever}; + enum State { Handshaking(tokio_rustls::Accept), Streaming(tokio_rustls::server::TlsStream), @@ -53,17 +58,38 @@ enum State { /// On streaming the connection will be read/written. pub struct TlsStream { state: State, + pub client_identifier: Arc>, } impl TlsStream { - fn new(stream: AddrStream, config: Arc) -> TlsStream { + fn new( + stream: AddrStream, + + roots: RootCertStore, + certs: Vec, + key: rustls::PrivateKey, + ) -> TlsStream { + let client_identifier = Arc::new(RwLock::new(ClientIdentifier::default())); + let inner = AllowAnyAuthenticatedClient::new(roots); + let verifier = SnitchAuthenticatedClients::new(inner, client_identifier.clone()).boxed(); + let config: Arc = Arc::new(server_config(verifier, certs, key).unwrap()); + let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream); TlsStream { state: State::Handshaking(accept), + client_identifier, } } } +impl ClientInformationRetriever for TlsStream { + fn client_identifier( + &self, + ) -> &std::sync::Arc> { + &self.client_identifier + } +} + impl AsyncRead for TlsStream { fn poll_read( self: Pin<&mut Self>, @@ -74,7 +100,10 @@ impl AsyncRead for TlsStream { match pin.state { State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) { Ok(mut stream) => { + tracing::warn!("mmmmmooooeeeepppp"); let result = Pin::new(&mut stream).poll_read(cx, buf); + // let mut num = pin.num.write().unwrap(); + // *num = 42; pin.state = State::Streaming(stream); result } @@ -122,13 +151,25 @@ impl AsyncWrite for TlsStream { /// Handles the actual tls connection based on the given address and config. pub struct TlsAcceptor { - config: Arc, + roots: RootCertStore, + certs: Vec, + key: rustls::PrivateKey, incoming: AddrIncoming, } impl TlsAcceptor { - pub fn new(config: Arc, incoming: AddrIncoming) -> TlsAcceptor { - TlsAcceptor { config, incoming } + pub fn new( + roots: RootCertStore, + certs: Vec, + key: rustls::PrivateKey, + incoming: AddrIncoming, + ) -> TlsAcceptor { + TlsAcceptor { + roots, + certs, + key, + incoming, + } } } @@ -142,13 +183,99 @@ impl Accept for TlsAcceptor { ) -> Poll>> { let pin = self.get_mut(); match ready!(Pin::new(&mut pin.incoming).poll_accept(cx)) { - Some(Ok(sock)) => Poll::Ready(Some(Ok(TlsStream::new(sock, pin.config.clone())))), + Some(Ok(sock)) => Poll::Ready(Some(Ok(TlsStream::new( + sock, + pin.roots.clone(), + pin.certs.clone(), + pin.key.clone(), + )))), Some(Err(e)) => Poll::Ready(Some(Err(e))), None => Poll::Ready(None), } } } +struct SnitchAuthenticatedClients { + inner: AllowAnyAuthenticatedClient, + client_identifier: Arc>, +} + +impl SnitchAuthenticatedClients { + /// Construct a new `AllowAnyAnonymousOrAuthenticatedClient`. + /// + /// `roots` is the list of trust anchors to use for certificate validation. + pub fn new( + inner: AllowAnyAuthenticatedClient, + client_identifier: Arc>, + ) -> Self { + Self { + inner, + client_identifier, + } + } + + /// Update the verifier to validate client certificates against the provided DER format + /// unparsed certificate revocation lists (CRLs). + pub fn with_crls( + self, + crls: impl IntoIterator, + client_identifier: Arc>, + ) -> Result { + Ok(Self { + inner: self.inner.with_crls(crls)?, + client_identifier, + }) + } + + /// Wrap this verifier in an [`Arc`] and coerce it to `dyn ClientCertVerifier` + #[inline(always)] + pub fn boxed(self) -> Arc { + // This function is needed because `ClientCertVerifier` is only reachable if the + // `dangerous_configuration` feature is enabled, which makes coercing hard to outside users + Arc::new(self) + } +} + +impl rustls::server::ClientCertVerifier for SnitchAuthenticatedClients { + fn offer_client_auth(&self) -> bool { + self.inner.offer_client_auth() + } + + fn client_auth_mandatory(&self) -> bool { + false + } + + fn client_auth_root_subjects(&self) -> &[rustls::DistinguishedName] { + self.inner.client_auth_root_subjects() + } + + fn verify_client_cert( + &self, + end_entity: &rustls::Certificate, + intermediates: &[rustls::Certificate], + now: std::time::SystemTime, + ) -> Result { + tracing::warn!("verify client cerrrrrrrrttttt"); + match self + .inner + .verify_client_cert(end_entity, intermediates, now) + { + Ok(r) => { + let mut ci = self.client_identifier.write().unwrap(); + let ehm: u64 = end_entity + .0 + .iter() + .map(|x| *x as u64) + .reduce(|a, b| a + b) + .unwrap_or_default(); + tracing::warn!("narg: {ehm}"); + *ci = ClientIdentifier::Known(ehm); + Ok(r) + } + Err(_) => todo!(), + } + } +} /// Creates a rustls ServerConfig based on the given config. /// /// When the tls certificate cannot be loaded it will return None. @@ -157,15 +284,23 @@ impl Accept for TlsAcceptor { /// client authentication. pub fn tls_config( config: &crate::config::Config, -) -> Result>, Box> { +) -> Result< + Option<(RootCertStore, Vec, rustls::PrivateKey)>, + Box, +> { if let Some(certs_path) = &config.tls.certs { match load_certs(certs_path) { Ok(certs) => { if let Some(key_path) = &config.tls.key { let key = load_private_key(key_path)?; - let verifier = { - if let Some(client_certs_dir) = &config.tls.client_certs { - let client_certs: Vec = std::fs::read_dir(client_certs_dir)? + let client_certs = config + .clone() + .tls + .client_certs + .map(|p| { + let client_certs: Vec = std::fs::read_dir(p) + // TODO create proper return statement + .unwrap() .filter_map(|entry| { let entry = entry.ok()?; let file_type = entry.file_type().ok()?; @@ -178,39 +313,16 @@ pub fn tls_config( } }) .collect(); - if client_certs.is_empty() { - tracing::info!( - "no client certs found, starting without certificate based client auth" - ); - NoClientAuth::boxed() - } else { - tracing::info!( - "client certs found, starting with certificate based client auth" - ); - let mut client_auth_roots = RootCertStore::empty(); - for root in client_certs.iter().flat_map(load_certs).flatten() { - client_auth_roots.add(&root)?; - } - AllowAnyAuthenticatedClient::new(client_auth_roots).boxed() - } - } else { - tracing::info!( - "no client certs found, starting without certificate based client auth" - ); - NoClientAuth::boxed() - } - }; - - let mut cfg = rustls::ServerConfig::builder() - .with_safe_defaults() - //.with_client_cert_verifier() - .with_client_cert_verifier(verifier) - .with_single_cert(certs, key) - .map_err(|e| error(format!("{}", e)))?; - // Configure ALPN to accept HTTP/2, HTTP/1.1, and HTTP/1.0 in that order. - cfg.alpn_protocols = - vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; - Ok(Some(std::sync::Arc::new(cfg))) + client_certs + }) + .unwrap_or_default(); + + let mut roots = RootCertStore::empty(); + for root in client_certs.iter().flat_map(load_certs).flatten() { + roots.add(&root)?; + } + + Ok(Some((roots, certs, key))) } else { Err(error("TLS enabled, but private key is missing".to_string()).into()) } @@ -223,6 +335,20 @@ pub fn tls_config( } } +fn server_config( + verifier: Arc, + certs: Vec, + key: rustls::PrivateKey, +) -> Result> { + let mut cfg = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(verifier) + .with_single_cert(certs, key) + .map_err(|e| error(format!("{}", e)))?; + cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; + Ok(cfg) +} + fn error(err: String) -> io::Error { io::Error::new(io::ErrorKind::Other, err) } diff --git a/rust/openvasd/tests/authorization.rs b/rust/openvasd/tests/authorization.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rust/openvasd/tests/data/generate-test-keys.sh b/rust/openvasd/tests/data/generate-test-keys.sh new file mode 100644 index 0000000000..35a6849384 --- /dev/null +++ b/rust/openvasd/tests/data/generate-test-keys.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2023 Greenbone AG +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set -e +generate_certificates() +{ + out="$1" + name="$2" + openssl req -nodes \ + -x509 \ + -days 3650 \ + -newkey rsa:4096 \ + -keyout ca.key \ + -out ca.cert \ + -sha256 \ + -batch \ + -subj "/CN=$name RSA CA" + + openssl req -nodes \ + -newkey rsa:3072 \ + -keyout inter.key \ + -out inter.req \ + -sha256 \ + -batch \ + -subj "/CN=$name RSA level 2 intermediate" + + openssl req -nodes \ + -newkey rsa:2048 \ + -keyout end.key \ + -out end.req \ + -sha256 \ + -batch \ + -subj "/CN=testserver.com" + + openssl rsa \ + -in end.key \ + -out $out.rsa + + openssl x509 -req \ + -in inter.req \ + -out inter.cert \ + -CA ca.cert \ + -CAkey ca.key \ + -sha256 \ + -days 3650 \ + -set_serial 123 \ + -extensions v3_inter -extfile openssl.cnf + + openssl x509 -req \ + -in end.req \ + -out end.cert \ + -CA inter.cert \ + -CAkey inter.key \ + -sha256 \ + -days 2000 \ + -set_serial 456 \ + -extensions v3_end -extfile openssl.cnf + + cat end.cert inter.cert ca.cert > $out.pem + rm *.key *.cert *.req + printf "generated $out\n" +} + +generate_certificates "server" "ponytown" +generate_certificates "client1" "onehause" +generate_certificates "client2" "zweidorf" +mkdir -p clients/certificates +mkdir -p clients/keys +mv client*.pem clients/certificates/ +mv client*.rsa clients/keys/ + diff --git a/rust/openvasd/tests/data/openssl.cnf b/rust/openvasd/tests/data/openssl.cnf new file mode 100644 index 0000000000..d0701c4328 --- /dev/null +++ b/rust/openvasd/tests/data/openssl.cnf @@ -0,0 +1,25 @@ + +[ v3_end ] +basicConstraints = critical,CA:false +keyUsage = nonRepudiation, digitalSignature +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +subjectAltName = @alt_names + +[ v3_client ] +basicConstraints = critical,CA:false +keyUsage = nonRepudiation, digitalSignature +extendedKeyUsage = critical, clientAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[ v3_inter ] +subjectKeyIdentifier = hash +extendedKeyUsage = critical, serverAuth, clientAuth +basicConstraints = CA:true +keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign + +[ alt_names ] +DNS.1 = localhost.localdomain +DNS.2 = localhost4.localdomain4 +DNS.3 = localhost