From a1b4865b3f1892acecad18cbe1951f3044249265 Mon Sep 17 00:00:00 2001 From: "Federico Pasqua (eisterman)" Date: Mon, 18 Mar 2024 13:40:34 +0100 Subject: [PATCH] Refactor Certificate management --- .gitignore | 3 +- bonknet_broker/Cargo.toml | 6 + bonknet_broker/src/bin/init_certs_2.rs | 201 +++++++++++++++++++++++++ bonknet_broker/src/certutils.rs | 20 --- bonknet_broker/src/main.rs | 96 +++++------- bonknet_client/Cargo.toml | 3 + bonknet_client/src/client.rs | 61 ++++++++ bonknet_client/src/main.rs | 64 ++++++++ bonknet_server/Cargo.toml | 5 +- bonknet_server/src/bin/server.rs | 51 ++++--- bonknet_server/src/main.rs | 29 ++++ libbonknet/Cargo.toml | 2 + libbonknet/src/cert.rs | 197 ++++++++++++++++++++++++ libbonknet/src/lib.rs | 2 + libbonknet/src/servermsg.rs | 27 +++- 15 files changed, 659 insertions(+), 108 deletions(-) create mode 100644 bonknet_broker/src/bin/init_certs_2.rs delete mode 100644 bonknet_broker/src/certutils.rs create mode 100644 bonknet_client/src/client.rs create mode 100644 bonknet_client/src/main.rs create mode 100644 bonknet_server/src/main.rs create mode 100644 libbonknet/src/cert.rs diff --git a/.gitignore b/.gitignore index f728d17..553f0fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target # Experiments with certificates -certs \ No newline at end of file +certs +certs_pem \ No newline at end of file diff --git a/bonknet_broker/Cargo.toml b/bonknet_broker/Cargo.toml index 3e500dc..470a4f8 100644 --- a/bonknet_broker/Cargo.toml +++ b/bonknet_broker/Cargo.toml @@ -25,7 +25,13 @@ rmp-serde = "1.1.2" rcgen = { version = "0.12.1", features = ["x509-parser"] } rand = "0.8.5" uuid = { version = "1.7.0", features = ["v4", "serde"] } +rustls-pemfile = "2.0.0" +x509-parser = "0.16.0" [[bin]] name = "init_certs" path = "src/bin/init_certs.rs" + +[[bin]] +name = "init_certs_2" +path = "src/bin/init_certs_2.rs" diff --git a/bonknet_broker/src/bin/init_certs_2.rs b/bonknet_broker/src/bin/init_certs_2.rs new file mode 100644 index 0000000..7d1389f --- /dev/null +++ b/bonknet_broker/src/bin/init_certs_2.rs @@ -0,0 +1,201 @@ +use std::fs::File; +use std::io::Write; +use rcgen::{self, BasicConstraints, Certificate, CertificateParams, DnType}; + +fn server_root_cert() -> Certificate { + let subject_alt_names = vec!["hello.world.example".into()]; + let mut certparams = CertificateParams::new(subject_alt_names); + certparams.is_ca = rcgen::IsCa::Ca(BasicConstraints::Unconstrained); + let mut distname = rcgen::DistinguishedName::new(); + distname.push(DnType::OrganizationName, "Eister Corporation"); + distname.push(DnType::CommonName, "Bonknet Server Root Cert CA"); + certparams.distinguished_name = distname; + Certificate::from_params(certparams).unwrap() +} + +fn server_cert() -> Certificate { + let mut params = CertificateParams::new(vec!["entity.other.host".into(), "bonk.server.1".into()]); + params.distinguished_name.push(DnType::CommonName, "Server 1"); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); + params + .extended_key_usages + .push(rcgen::ExtendedKeyUsagePurpose::ClientAuth); + Certificate::from_params(params).unwrap() +} + +fn guestserver_root_cert() -> Certificate { + let subject_alt_names = vec!["hello.world.example".into()]; + let mut certparams = CertificateParams::new(subject_alt_names); + certparams.is_ca = rcgen::IsCa::Ca(BasicConstraints::Unconstrained); + let mut distname = rcgen::DistinguishedName::new(); + distname.push(DnType::OrganizationName, "Eister Corporation"); + distname.push(DnType::CommonName, "Bonknet Guest Server Root Cert CA"); + certparams.distinguished_name = distname; + Certificate::from_params(certparams).unwrap() +} + +fn guestserver_cert() -> Certificate { + let mut params = CertificateParams::new(vec!["entity.other.host".into(), "bonk.guestserver.1".into()]); + params.distinguished_name.push(DnType::CommonName, "Guest Server 1"); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); + params + .extended_key_usages + .push(rcgen::ExtendedKeyUsagePurpose::ClientAuth); + Certificate::from_params(params).unwrap() +} + +fn client_root_cert() -> Certificate { + let subject_alt_names = vec!["hello.world.example".into()]; + let mut certparams = CertificateParams::new(subject_alt_names); + certparams.is_ca = rcgen::IsCa::Ca(BasicConstraints::Unconstrained); + let mut distname = rcgen::DistinguishedName::new(); + distname.push(DnType::OrganizationName, "Eister Corporation"); + distname.push(DnType::CommonName, "Bonknet Client Root Cert CA"); + certparams.distinguished_name = distname; + Certificate::from_params(certparams).unwrap() +} + +fn client_cert() -> Certificate { + let mut params = CertificateParams::new(vec!["entity.other.host".into(), "bonk.client.1".into()]); + params.distinguished_name.push(DnType::CommonName, "Client 1"); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); + params + .extended_key_usages + .push(rcgen::ExtendedKeyUsagePurpose::ClientAuth); + Certificate::from_params(params).unwrap() +} + +fn broker_root_cert() -> Certificate { + let subject_alt_names = vec!["hello.world.example".into()]; + let mut certparams = CertificateParams::new(subject_alt_names); + certparams.is_ca = rcgen::IsCa::Ca(BasicConstraints::Unconstrained); + let mut distname = rcgen::DistinguishedName::new(); + distname.push(DnType::OrganizationName, "Eister Corporation"); + distname.push(DnType::CommonName, "Bonknet Broker Root Cert CA"); + certparams.distinguished_name = distname; + Certificate::from_params(certparams).unwrap() +} + +fn broker_cert() -> Certificate { + let mut params = CertificateParams::new(vec!["entity.other.host".into(), "localhost".into()]); + params.distinguished_name.push(DnType::CommonName, "localhost"); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); + params + .extended_key_usages + .push(rcgen::ExtendedKeyUsagePurpose::ServerAuth); + Certificate::from_params(params).unwrap() +} + +fn main() -> std::io::Result<()> { + // Generate Root CA Certificates + let server_root_cert = server_root_cert(); + let guestserver_root_cert = guestserver_root_cert(); + let client_root_cert = client_root_cert(); + let broker_root_cert = broker_root_cert(); + // Generate Leafs + let server_leaf_cert = server_cert(); + let guestserver_leaf_cert = guestserver_cert(); + let client_leaf_cert = client_cert(); + let broker_leaf_cert = broker_cert(); + // Generate PEMs + // every time you generate one, a new random number is taken, so different cert hashes! + // and we need this PEMs to appear in multiple files + // We don't need this for the pvkey because we generate them only one time for each cert + // IF YOU NEED TO WRITE PVKEY IN MULTIPLE FILES, PLEASE DO IT LIKE THESE LINES FOR THE x509!!! + let server_root_cert_pem = server_root_cert.serialize_pem().unwrap(); + let guestserver_root_cert_pem = guestserver_root_cert.serialize_pem().unwrap(); + let client_root_cert_pem = client_root_cert.serialize_pem().unwrap(); + let broker_root_cert_pem = broker_root_cert.serialize_pem().unwrap(); + let server_leaf_cert_pem = server_leaf_cert.serialize_pem_with_signer(&server_root_cert).unwrap(); + let guestserver_leaf_cert_pem = guestserver_leaf_cert.serialize_pem_with_signer(&guestserver_root_cert).unwrap(); + let client_leaf_cert_pem = client_leaf_cert.serialize_pem_with_signer(&client_root_cert).unwrap(); + let broker_leaf_cert_pem = broker_leaf_cert.serialize_pem_with_signer(&broker_root_cert).unwrap(); + // Root CA PEMs + /* + 1 - CA Cert + 2 - CA Prkey + */ + // Generate Server Root CA PEM + { + let mut pemfile = File::create("certs_pem/server_root_ca.pem")?; + pemfile.write_all(server_root_cert_pem.as_bytes())?; + pemfile.write_all(server_root_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate GuestServer Root CA PEM + { + let mut pemfile = File::create("certs_pem/guestserver_root_ca.pem")?; + pemfile.write_all(guestserver_root_cert_pem.as_bytes())?; + pemfile.write_all(guestserver_root_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate Client Root CA PEM + { + let mut pemfile = File::create("certs_pem/client_root_ca.pem")?; + pemfile.write_all(client_root_cert_pem.as_bytes())?; + pemfile.write_all(client_root_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate Broker Root CA PEM + { + let mut pemfile = File::create("certs_pem/broker_root_ca.pem")?; + pemfile.write_all(broker_root_cert_pem.as_bytes())?; + pemfile.write_all(broker_root_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate Broker CA Cert PEM for Server Authentication + { + let mut pemfile = File::create("certs_pem/broker_root_ca_cert.pem")?; + pemfile.write_all(broker_root_cert_pem.as_bytes())?; + } + // Generate Server Leaf PEM + /* + 1 - Server Leaf Cert + 2 - Server CA Cert chain + 3 - Server Leaf Prkey + */ + { + let mut pemfile = File::create("certs_pem/server.pem")?; + pemfile.write_all(server_leaf_cert_pem.as_bytes())?; + pemfile.write_all(server_root_cert_pem.as_bytes())?; + pemfile.write_all(server_leaf_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate GuestServer Leaf PEM + /* + 1 - GuestServer Leaf Cert + 2 - GuestServer CA Cert chain + 3 - GuestServer Leaf Prkey + */ + { + let mut pemfile = File::create("certs_pem/guestserver.pem")?; + pemfile.write_all(guestserver_leaf_cert_pem.as_bytes())?; + pemfile.write_all(guestserver_root_cert_pem.as_bytes())?; + pemfile.write_all(guestserver_leaf_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate Client Leaf PEM + /* + 1 - Client Leaf Cert + 2 - Client CA Cert chain + 3 - Client Leaf Prkey + */ + { + let mut pemfile = File::create("certs_pem/client.pem")?; + pemfile.write_all(client_leaf_cert_pem.as_bytes())?; + pemfile.write_all(client_root_cert_pem.as_bytes())?; + pemfile.write_all(client_leaf_cert.serialize_private_key_pem().as_bytes())?; + } + // Generate Broker Leaf PEM + /* + 1 - Broker Leaf Cert + 2 - Broker CA Cert + 3 - Broker Leaf Prkey + */ + { + let mut pemfile = File::create("certs_pem/broker.pem")?; + pemfile.write_all(broker_leaf_cert_pem.as_bytes())?; + pemfile.write_all(broker_root_cert_pem.as_bytes())?; + pemfile.write_all(broker_leaf_cert.serialize_private_key_pem().as_bytes())?; + } + println!("Certificates created"); + Ok(()) +} \ No newline at end of file diff --git a/bonknet_broker/src/certutils.rs b/bonknet_broker/src/certutils.rs deleted file mode 100644 index acb2799..0000000 --- a/bonknet_broker/src/certutils.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rcgen::{Certificate, CertificateParams, DnType}; - -pub struct ServerCert { - pub cert: Vec, - pub prkey: Vec, -} - -pub fn generate_server_cert(root_cert: &Certificate, name: &str) -> ServerCert { - let mut params = CertificateParams::new(vec!["entity.other.host".into(), format!("bonk.server.{name}")]); - params.distinguished_name.push(DnType::CommonName, name); - params.use_authority_key_identifier_extension = true; - params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); - params - .extended_key_usages - .push(rcgen::ExtendedKeyUsagePurpose::ClientAuth); - let certificate = Certificate::from_params(params).unwrap(); - let cert = certificate.serialize_der_with_signer(root_cert).unwrap(); - let prkey = certificate.serialize_private_key_der(); - ServerCert { cert, prkey } -} diff --git a/bonknet_broker/src/main.rs b/bonknet_broker/src/main.rs index cd1fe53..a6cca24 100644 --- a/bonknet_broker/src/main.rs +++ b/bonknet_broker/src/main.rs @@ -3,7 +3,6 @@ mod pendingdataconndb; mod servermanager; mod dataconnmanager; mod streamutils; -mod certutils; use servercertdb::*; use pendingdataconndb::*; @@ -12,9 +11,9 @@ use dataconnmanager::*; use streamutils::*; use actix::prelude::*; use std::sync::Arc; -use libbonknet::*; use libbonknet::servermsg::*; use libbonknet::clientmsg::*; +use libbonknet::cert::*; use rustls::{RootCertStore, ServerConfig}; use rustls::server::WebPkiClientVerifier; use actix_tls::accept::rustls_0_22::{Acceptor as RustlsAcceptor, TlsStream}; @@ -24,15 +23,13 @@ use actix_service::ServiceFactoryExt as _; use futures::{SinkExt, StreamExt}; use tokio_util::codec::{Framed, LengthDelimitedCodec}; use tracing::{error, info, warn}; -use rcgen::{Certificate, CertificateParams, KeyPair}; -use rustls::pki_types::CertificateDer; -#[derive(Clone)] struct BrokerContext { - server_root_cert_der: CertificateDer<'static>, - client_root_cert_der: CertificateDer<'static>, - guestserver_root_cert_der: CertificateDer<'static>, + broker_leaf: LeafCertPair<'static>, + client_ca: CACertPair<'static>, + server_ca: CACertPair<'static>, + guestserver_ca: CACertPair<'static>, scdb_addr: Addr, pdcm_addr: Addr, sm_addr: Addr, @@ -44,66 +41,58 @@ async fn main() { // Tracing Subscriber let subscriber = tracing_subscriber::FmtSubscriber::new(); tracing::subscriber::set_global_default(subscriber).unwrap(); - // BROKER CERTS - let broker_root_cert_der = load_cert("certs/broker_root_cert.pem").unwrap(); - let broker_cert_der = load_cert("certs/broker_cert.pem").unwrap(); - let broker_prkey_der = load_prkey("certs/broker_key.pem").unwrap(); - // SERVER ROOT - let server_root_cert_der = load_cert("certs/server_root_cert.pem").unwrap(); - let server_root_prkey_der = load_prkey("certs/server_root_key.pem").unwrap(); - // GUESTSERVER ROOT - let guestserver_root_cert_der = load_cert("certs/guestserver_root_cert.pem").unwrap(); - // CLIENT ROOT - let client_root_cert_der = load_cert("certs/client_root_cert.pem").unwrap(); - // Client Verifier - let mut broker_root_store = RootCertStore::empty(); - broker_root_store.add(server_root_cert_der.clone()).unwrap(); - broker_root_store.add(client_root_cert_der.clone()).unwrap(); - broker_root_store.add(guestserver_root_cert_der.clone()).unwrap(); - let server_verifier = WebPkiClientVerifier::builder(Arc::new(broker_root_store)).build().unwrap(); - // Configure TLS - let server_tlsconfig = ServerConfig::builder() - .with_client_cert_verifier(server_verifier) - .with_single_cert(vec![broker_cert_der.clone(), broker_root_cert_der.clone()], broker_prkey_der.into()) - .unwrap(); - let server_acceptor = RustlsAcceptor::new(server_tlsconfig); - + // Load Identity + let broker_leaf = LeafCertPair::load_from_file("certs_pem/broker.pem").unwrap(); + let client_ca = CACertPair::load_from_file("certs_pem/client_root_ca.pem").unwrap(); + let server_ca = CACertPair::load_from_file("certs_pem/server_root_ca.pem").unwrap(); + let guestserver_ca = CACertPair::load_from_file("certs_pem/guestserver_root_ca.pem").unwrap(); + // Load Actors let scdb_addr = ServerCertDB::new().start(); let dcm_addr = DataConnManager::new().start(); let pdcm_addr = PendingDataConnManager::new(dcm_addr).start(); let sm_addr = ServerManager::new(pdcm_addr.clone()).start(); - - let server_root_prkey = KeyPair::from_der(server_root_prkey_der.secret_pkcs8_der()).unwrap(); - let server_root_cert = Arc::new(Certificate::from_params(CertificateParams::from_ca_cert_der( - &server_root_cert_der, - server_root_prkey - ).unwrap()).unwrap()); - + // Create Context let ctx = Arc::new(BrokerContext { - server_root_cert_der, - client_root_cert_der, - guestserver_root_cert_der, + broker_leaf, + client_ca, + server_ca, + guestserver_ca, scdb_addr, pdcm_addr, sm_addr, }); + // Pki Client Verifier + let mut broker_root_store = RootCertStore::empty(); + broker_root_store.add(ctx.server_ca.cert().clone()).unwrap(); + broker_root_store.add(ctx.client_ca.cert().clone()).unwrap(); + broker_root_store.add(ctx.guestserver_ca.cert().clone()).unwrap(); + let server_verifier = WebPkiClientVerifier::builder(Arc::new(broker_root_store)).build().unwrap(); + // Configure TLS + let server_tlsconfig = ServerConfig::builder() + .with_client_cert_verifier(server_verifier) + .with_single_cert(ctx.broker_leaf.fullchain(), ctx.broker_leaf.clone_key().into()) + .unwrap(); + let server_acceptor = RustlsAcceptor::new(server_tlsconfig); + Server::build() .bind("server-command", ("localhost", 2541), move || { let ctx = Arc::clone(&ctx); - let server_root_cert = Arc::clone(&server_root_cert); // Set up TLS service factory server_acceptor .clone() .map_err(|err| println!("Rustls error: {:?}", err)) .and_then(move |stream: TlsStream| { let ctx = Arc::clone(&ctx); - let server_root_cert = Arc::clone(&server_root_cert); async move { let peer_certs = stream.get_ref().1.peer_certificates().unwrap(); let peer_cert_bytes = peer_certs.first().unwrap().to_vec(); let peer_root_cert_der = peer_certs.last().unwrap().clone(); - if peer_root_cert_der == ctx.server_root_cert_der { + info!("{:?}", &peer_root_cert_der); + info!("{:?}", ctx.server_ca.cert()); + info!("{:?}", ctx.guestserver_ca.cert()); + info!("{:?}", ctx.client_ca.cert()); + if &peer_root_cert_der == ctx.server_ca.cert() { info!("Server connection"); let mut transport = Framed::new(stream, LengthDelimitedCodec::new()); match transport.next().await { @@ -150,12 +139,12 @@ async fn main() { } } info!("Server Task terminated!"); - } else if peer_root_cert_der == ctx.guestserver_root_cert_der { + } else if &peer_root_cert_der == ctx.guestserver_ca.cert() { info!("GuestServer connection"); let codec = LengthDelimitedCodec::new(); let transport = Framed::new(stream, codec); - guestserver_handler(&ctx, transport, &server_root_cert).await; - } else if peer_root_cert_der == ctx.client_root_cert_der { + guestserver_handler(&ctx, transport).await; + } else if &peer_root_cert_der == ctx.client_ca.cert() { info!("Client connection"); let codec = LengthDelimitedCodec::new(); let transport = Framed::new(stream, codec); @@ -244,7 +233,7 @@ async fn server_command_handler(ctx: &BrokerContext, mut transport: TransportStr } } -async fn guestserver_handler(ctx: &BrokerContext, mut transport: TransportStream, server_root_cert: &Certificate) { +async fn guestserver_handler(ctx: &BrokerContext, mut transport: TransportStream) { loop { match transport.next().await { None => { @@ -266,15 +255,12 @@ async fn guestserver_handler(ctx: &BrokerContext, mut transport: TransportStream transport.send(rmp_serde::to_vec(&reply).unwrap().into()).await.unwrap(); break; // Stop reading } else { - let cert = certutils::generate_server_cert(server_root_cert, name.as_str()); + let cert = ctx.server_ca.sign_new_cert(server_leaf_certparams(name.as_str())); ctx.scdb_addr.send(RegisterServer { - cert: cert.cert.clone(), + cert: cert.cert().to_vec(), name, }).await.unwrap().unwrap(); - let reply = ToGuestServerMessage::OkAnnounce { - server_cert: cert.cert, - server_prkey: cert.prkey - }; + let reply = ToGuestServerMessage::make_okannounce(&cert); transport.send(rmp_serde::to_vec(&reply).unwrap().into()).await.unwrap(); } } diff --git a/bonknet_client/Cargo.toml b/bonknet_client/Cargo.toml index ed30a32..03ca213 100644 --- a/bonknet_client/Cargo.toml +++ b/bonknet_client/Cargo.toml @@ -2,6 +2,7 @@ name = "bonknet_client" version = "0.1.0" edition = "2021" +description = "A CLI Client for the Bonknet system" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,3 +18,5 @@ rmp-serde = "1.1.2" tracing = "0.1" tracing-subscriber = "0.3" uuid = { version = "1.7.0", features = ["serde"] } +clap = { version = "4.5.2", features = ["derive"] } +thiserror = "1.0.56" diff --git a/bonknet_client/src/client.rs b/bonknet_client/src/client.rs new file mode 100644 index 0000000..70d9e66 --- /dev/null +++ b/bonknet_client/src/client.rs @@ -0,0 +1,61 @@ +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio_rustls::{TlsConnector}; +use tokio_rustls::client::TlsStream; +use tokio_rustls::rustls::pki_types::ServerName; +use tokio_util::codec::{Framed, LengthDelimitedCodec}; +use futures::{SinkExt, StreamExt}; +use thiserror::Error; +use tokio_rustls::rustls::ClientConfig; +use tracing::{error}; +use libbonknet::clientmsg::{FromClientCommand, ToClientResponse}; + +pub type TransportStream = Framed, LengthDelimitedCodec>; + +#[derive(Error, Debug)] +pub enum SendError { + #[error("Failure during serialization of the msg to send")] + MsgSerializationFailure(rmp_serde::encode::Error), + #[error("Failure during the transport send of the data")] + TransportError(std::io::Error), + #[error("Empty buffer during reply wait")] + EmptyBufferError, + #[error("Generic buffer error during response reading")] + GenericBufferError(std::io::Error), + #[error("Failure during deserialization of the reply msg")] + ReplyDeserializationFailure(rmp_serde::decode::Error), +} + +pub struct BonkClient { + transport: TransportStream, +} + +impl BonkClient { + pub async fn connect(tlsconfig: ClientConfig, dnsname: &str, port: u16) -> tokio::io::Result { + // Load TLS Config + let connector = TlsConnector::from(Arc::new(tlsconfig)); + let stream = TcpStream::connect(format!("{}:{}", dnsname, port)).await?; + let dnsservername = ServerName::try_from(dnsname).unwrap().to_owned(); + let stream = connector.connect(dnsservername, stream).await?; + + let transport = Framed::new(stream, LengthDelimitedCodec::new()); + Ok(BonkClient { transport }) + } + + // TODO: make this private and make single calls for each Bonk interaction? + // The main idea is that the messages need to be inside the API and not ouside as elements of it + pub async fn send(&mut self, msg: &FromClientCommand) -> Result { + let bmsg = rmp_serde::to_vec(msg).map_err(SendError::MsgSerializationFailure)?; + self.transport.send(bmsg.into()).await.map_err(SendError::TransportError)?; + match self.transport.next().await { + None => Err(SendError::EmptyBufferError), + Some(item) => match item { + Ok(buf) => { + let reply: ToClientResponse = rmp_serde::from_slice(&buf).map_err(SendError::ReplyDeserializationFailure)?; + Ok(reply) + } + Err(e) => Err(SendError::GenericBufferError(e)) + } + } + } +} \ No newline at end of file diff --git a/bonknet_client/src/main.rs b/bonknet_client/src/main.rs new file mode 100644 index 0000000..9c9b72f --- /dev/null +++ b/bonknet_client/src/main.rs @@ -0,0 +1,64 @@ +mod client; + +use std::path::PathBuf; +use clap::{Parser, Subcommand}; +use libbonknet::cert::*; +use tracing::{info}; +use libbonknet::clientmsg::FromClientCommand; +use crate::client::BonkClient; + +#[derive(Parser, Debug)] +#[command(version, about)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// connect via ssh to the given remote_id. + Ssh { + remote_id: String, + }, + /// send a file from source to remote. This command uses scp syntax and must contain wildcards to work (see examples). + Scp { + remote_id: String, + source: PathBuf, + dest: PathBuf, + }, + /// get a list of all the servers connected to the broker. + Serverlist { + pattern: Option, + }, +} + +#[tokio::main] +async fn main() -> std::io::Result<()> { + // Tracing Subscriber + let subscriber = tracing_subscriber::FmtSubscriber::new(); + tracing::subscriber::set_global_default(subscriber).unwrap(); + // CLI parsing + let cli = Cli::parse(); + // Load Identity files + let client_ident = LeafCertPair::load_from_file("certs_pem/client.pem").unwrap(); + let broker_root = BrokerRootCerts::load_from_file("certs_pem/broker_root_ca_cert.pem").unwrap(); + let tlsconfig = client_ident.to_tlsclientconfig(&broker_root); + // Execute command + match &cli.command { + Commands::Ssh { remote_id } => { + info!("Run SSH on {remote_id}"); + unimplemented!() + } + Commands::Scp { remote_id, source, dest } => { + info!("Run SCP on {} moving {} to {}", remote_id, source.display(), dest.display()); + unimplemented!() + } + Commands::Serverlist { pattern } => { + info!("Run Clientlist with pattern {:?}", pattern); + let mut client = BonkClient::connect(tlsconfig, "localhost", 2541).await.unwrap(); + let reply = client.send(&FromClientCommand::ServerList).await.unwrap(); + info!("Reply: {:?}", reply); + } + } + Ok(()) +} \ No newline at end of file diff --git a/bonknet_server/Cargo.toml b/bonknet_server/Cargo.toml index 6365333..fad1a50 100644 --- a/bonknet_server/Cargo.toml +++ b/bonknet_server/Cargo.toml @@ -2,6 +2,7 @@ name = "bonknet_server" version = "0.1.0" edition = "2021" +description = "An automated Server for the Bonknet system" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,4 +17,6 @@ rustls-pemfile = "2.0.0" rmp-serde = "1.1.2" tracing = "0.1" tracing-subscriber = "0.3" -uuid = { version = "1.7.0", features = ["serde"] } \ No newline at end of file +uuid = { version = "1.7.0", features = ["serde"] } +clap = { version = "4.5.2", features = ["derive"] } +thiserror = "1.0.56" \ No newline at end of file diff --git a/bonknet_server/src/bin/server.rs b/bonknet_server/src/bin/server.rs index c10cb00..777247e 100644 --- a/bonknet_server/src/bin/server.rs +++ b/bonknet_server/src/bin/server.rs @@ -2,12 +2,13 @@ use std::io::{Error, ErrorKind}; use std::sync::Arc; use futures::{SinkExt, StreamExt}; use tokio::net::TcpStream; -use tokio_rustls::rustls::{ClientConfig, RootCertStore}; -use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName}; +use tokio_rustls::rustls::ClientConfig; +use tokio_rustls::rustls::pki_types::ServerName; use tokio_rustls::TlsConnector; use tokio_util::codec::{Framed, LengthDelimitedCodec}; use libbonknet::*; use libbonknet::servermsg::*; +use libbonknet::cert::*; use uuid::Uuid; use tracing::{error, info}; @@ -94,22 +95,26 @@ async fn main() -> std::io::Result<()> { tracing::subscriber::set_global_default(subscriber).unwrap(); // Server Name let my_name = "cicciopizza"; - // Root certs to verify the server is the right one - let mut broker_root_cert_store = RootCertStore::empty(); - let broker_root_cert_der = load_cert("certs/broker_root_cert.pem").unwrap(); - broker_root_cert_store.add(broker_root_cert_der).unwrap(); - // Public CA that will be used to generate the Server certificate - let root_server_cert = load_cert("certs/server_root_cert.pem").unwrap(); - // Guest CA - let root_guestserver_cert = load_cert("certs/guestserver_root_cert.pem").unwrap(); - // Certificate used to do the first authentication - let guestserver_cert = load_cert("certs/guestserver_cert.pem").unwrap(); - let guestserver_prkey = load_prkey("certs/guestserver_key.pem").unwrap(); + // Load Identity files + let guestserver_ident = LeafCertPair::load_from_file("certs_pem/guestserver.pem").unwrap(); + let broker_root = BrokerRootCerts::load_from_file("certs_pem/broker_root_ca_cert.pem").unwrap(); + // // Root certs to verify the server is the right one + // let mut broker_root_cert_store = RootCertStore::empty(); + // let broker_root_cert_der = load_cert("certs/broker_root_cert.pem").unwrap(); + // broker_root_cert_store.add(broker_root_cert_der).unwrap(); + // // Public CA that will be used to generate the Server certificate + // let root_server_cert = load_cert("certs/server_root_cert.pem").unwrap(); + // // Guest CA + // let root_guestserver_cert = load_cert("certs/guestserver_root_cert.pem").unwrap(); + // // Certificate used to do the first authentication + // let guestserver_cert = load_cert("certs/guestserver_cert.pem").unwrap(); + // let guestserver_prkey = load_prkey("certs/guestserver_key.pem").unwrap(); // Load TLS Config + let guest_cert_chain = guestserver_ident.fullchain(); let tlsconfig = ClientConfig::builder() - .with_root_certificates(broker_root_cert_store.clone()) + .with_root_certificates(broker_root.to_rootcertstore()) // .with_no_client_auth(); - .with_client_auth_cert(vec![guestserver_cert, root_guestserver_cert], guestserver_prkey.into()) + .with_client_auth_cert(guest_cert_chain, guestserver_ident.clone_key().into()) .unwrap(); let connector = TlsConnector::from(Arc::new(tlsconfig)); let dnsname = ServerName::try_from("localhost").unwrap(); @@ -121,24 +126,20 @@ async fn main() -> std::io::Result<()> { let msg = FromGuestServerMessage::Announce { name: my_name.into() }; transport.send(rmp_serde::to_vec(&msg).unwrap().into()).await.unwrap(); // TODO: Remove this two mutable option - let mut myserver_cert: Option = None; - let mut myserver_prkey: Option = None; + let mut myserver_leaf: Option = None; match transport.next().await { None => { panic!("None in the transport"); } Some(item) => match item { Ok(buf) => { - use libbonknet::servermsg::{okannounce_to_cert, ToGuestServerMessage}; use libbonknet::servermsg::ToGuestServerMessage::*; let msg: ToGuestServerMessage = rmp_serde::from_slice(&buf).unwrap(); info!("{:?}", msg); match msg { - OkAnnounce { server_cert, server_prkey } => { + OkAnnounce(payload) => { info!("Ok Announce"); - let (server_cert, server_prkey) = okannounce_to_cert(server_cert, server_prkey); - myserver_cert = Some(server_cert); - myserver_prkey = Some(server_prkey); + myserver_leaf = Some(payload.parse()); } FailedNameAlreadyOccupied => { error!("Failed Announce, name already occupied"); @@ -152,10 +153,10 @@ async fn main() -> std::io::Result<()> { } } transport.close().await.unwrap(); - if let (Some(server_cert), Some(server_prkey)) = (myserver_cert, myserver_prkey) { + if let Some(server_leaf) = myserver_leaf { let tlsconfig = Arc::new(ClientConfig::builder() - .with_root_certificates(broker_root_cert_store) - .with_client_auth_cert(vec![server_cert, root_server_cert], server_prkey.into()) + .with_root_certificates(broker_root.to_rootcertstore()) + .with_client_auth_cert(server_leaf.fullchain(), server_leaf.clone_key().into()) .unwrap()); let connector = TlsConnector::from(Arc::clone(&tlsconfig)); let dnsname = ServerName::try_from("localhost").unwrap(); diff --git a/bonknet_server/src/main.rs b/bonknet_server/src/main.rs new file mode 100644 index 0000000..5def276 --- /dev/null +++ b/bonknet_server/src/main.rs @@ -0,0 +1,29 @@ +use std::io::{Error, ErrorKind}; +use std::sync::Arc; +use futures::{SinkExt, StreamExt}; +use tokio::net::TcpStream; +use tokio_rustls::rustls::{ClientConfig, RootCertStore}; +use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName}; +use tokio_rustls::TlsConnector; +use tokio_util::codec::{Framed, LengthDelimitedCodec}; +use libbonknet::*; +use libbonknet::servermsg::*; +use uuid::Uuid; +use tracing::{error, info}; +use libbonknet::cert::{BrokerRootCerts, LeafCertPair}; + + +#[tokio::main] +async fn main() -> std::io::Result<()> { + // Tracing Subscriber + let subscriber = tracing_subscriber::FmtSubscriber::new(); + tracing::subscriber::set_global_default(subscriber).unwrap(); + // Server Name + // TODO: from config + let my_name = "cicciopizza"; + // Load Identity files + let guestserver_ident = LeafCertPair::load_from_file("certs_pem/guestserver.pem").unwrap(); + let broker_root = BrokerRootCerts::load_from_file("certs_pem/broker_root_ca_cert.pem").unwrap(); + // TODO: ACTOR MODEL per gestione transport in maniera intelligente? + Ok(()) +} \ No newline at end of file diff --git a/libbonknet/Cargo.toml b/libbonknet/Cargo.toml index 6352398..b3e9173 100644 --- a/libbonknet/Cargo.toml +++ b/libbonknet/Cargo.toml @@ -8,5 +8,7 @@ edition = "2021" [dependencies] tokio-rustls = "0.25.0" rustls-pemfile = "2.0.0" +rcgen = { version = "0.12.1", features = ["x509-parser"] } +x509-parser = "0.16.0" serde = { version = "1.0", features = ["derive"] } uuid = { version = "1.7.0", features = ["serde"] } diff --git a/libbonknet/src/cert.rs b/libbonknet/src/cert.rs new file mode 100644 index 0000000..db0fda5 --- /dev/null +++ b/libbonknet/src/cert.rs @@ -0,0 +1,197 @@ +use std::io::{BufReader, Error, ErrorKind}; +use rcgen::{Certificate, CertificateParams, DnType, KeyPair}; +use rustls_pemfile::{Item, read_all, read_one}; +use tokio_rustls::rustls::{ClientConfig, RootCertStore}; +use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; + +pub struct RawCertPair { + pub cert: Vec, + pub prkey: Vec, +} + +// TODO: Eventually consider shifting from CertificateDer to x509_parser::X509Certificate +// for the extra information it can provide runtime +pub struct LeafCertPair<'a> { + cert: CertificateDer<'a>, + ca_chain: Vec>, + prkey: PrivatePkcs8KeyDer<'a>, +} + +impl LeafCertPair<'_> { + pub fn parse<'a>(cert: Vec, ca_chain: Vec>, prkey: Vec) -> LeafCertPair<'a> { + let cert = CertificateDer::from(cert); + let ca_chain = ca_chain.into_iter().map(CertificateDer::from).collect(); + let prkey = PrivatePkcs8KeyDer::from(prkey); + LeafCertPair { + cert, + ca_chain, + prkey, + } + } + + pub fn load_from_file(filename: &str) -> std::io::Result { + let file = std::fs::File::open(filename).unwrap(); + let mut buf = BufReader::new(file); + if let Item::X509Certificate(cert) = read_one(&mut buf).unwrap().unwrap() { + let parsed_cert = x509_parser::parse_x509_certificate(&cert).unwrap().1; + if parsed_cert.is_ca() { + return Err(Error::new(ErrorKind::InvalidInput, "main cert is ca")); + } + let mut ca_chain: Vec = Vec::new(); + for item in read_all(&mut buf) { + match item { + Ok(Item::X509Certificate(c)) => { + let parsed_cert = x509_parser::parse_x509_certificate(&c).unwrap().1; + if !parsed_cert.is_ca() { + return Err(Error::new(ErrorKind::InvalidInput, "chain cert is not ca")); + } + ca_chain.push(c); + }, + Ok(Item::Pkcs8Key(prkey)) => { + return Ok(LeafCertPair { cert, ca_chain, prkey }); + } + _ => { + return Err(Error::new(ErrorKind::InvalidInput, "invalid format")); + } + } + } + Err(Error::new(ErrorKind::InvalidInput, "pkcs8key not found")) + } else { + Err(Error::new(ErrorKind::InvalidInput, "no main x509 cert")) + } + } + + pub fn cert(&self) -> &CertificateDer { + &self.cert + } + + pub fn prkey(&self) -> &PrivatePkcs8KeyDer { + &self.prkey + } + + pub fn clone_key(&self) -> PrivatePkcs8KeyDer<'static> { + self.prkey.clone_key() + } + + pub fn fullchain<'a>(&self) -> Vec> { + let mut chain: Vec = Vec::with_capacity(self.ca_chain.len() + 1); + chain.push(self.cert.clone().into_owned()); + let mut ca_chain = self.ca_chain.clone().into_iter().map(|c| c.into_owned()).collect(); + chain.append(&mut ca_chain); + chain + } + + pub fn to_raw(&self) -> RawCertPair { + let cert = self.cert.to_vec(); + let prkey = self.prkey.secret_pkcs8_der().to_vec(); + RawCertPair { cert, prkey } + } + + pub fn to_tlsclientconfig(&self, broker_root_certs: &BrokerRootCerts) -> ClientConfig { + let broker_root_cert_store = broker_root_certs.to_rootcertstore(); + let cert_chain = self.fullchain(); + ClientConfig::builder() + .with_root_certificates(broker_root_cert_store) + .with_client_auth_cert(cert_chain, self.prkey.clone_key().into()).expect("Invalid Cert chain") + } +} + +pub struct CACertPair<'a> { + cert: CertificateDer<'a>, + ca_chain: Vec>, + prkey: PrivatePkcs8KeyDer<'a>, +} + +impl CACertPair<'_> { + pub fn load_from_file(filename: &str) -> std::io::Result { + let file = std::fs::File::open(filename).unwrap(); + let mut buf = BufReader::new(file); + if let Item::X509Certificate(cert) = read_one(&mut buf).unwrap().unwrap() { + let parsed_cert = x509_parser::parse_x509_certificate(&cert).unwrap().1; + if !parsed_cert.is_ca() { + return Err(Error::new(ErrorKind::InvalidInput, "cert is not ca")); + } + // TODO: Implement ca_chain reading (for now it's unused) + if let Item::Pkcs8Key(prkey) = read_one(&mut buf).unwrap().unwrap() { + Ok(CACertPair { cert, ca_chain: vec![], prkey }) + } else { + Err(Error::new(ErrorKind::InvalidInput, "no ca pkcs8key")) + } + } else { + Err(Error::new(ErrorKind::InvalidInput, "no ca x509 cert")) + } + } + + fn rcgen_keypair(&self) -> KeyPair { + KeyPair::from_der(self.prkey.secret_pkcs8_der()).unwrap() + } + + fn rcgen_certificate(&self) -> Certificate { + Certificate::from_params(CertificateParams::from_ca_cert_der( + &self.cert, + self.rcgen_keypair() + ).unwrap()).unwrap() + } + + pub fn sign_new_cert(&self, params: CertificateParams) -> LeafCertPair { + let root_cert = self.rcgen_certificate(); + let certificate = Certificate::from_params(params).unwrap(); + let b_cert = certificate.serialize_der_with_signer(&root_cert).unwrap(); + let b_prkey = certificate.serialize_private_key_der(); + let cert = CertificateDer::from(b_cert); + let prkey = PrivatePkcs8KeyDer::from(b_prkey); + let mut ca_chain = Vec::with_capacity(self.ca_chain.len() + 1); + ca_chain.push(self.cert.clone()); + ca_chain.extend(self.ca_chain.iter().cloned()); + LeafCertPair { cert, ca_chain, prkey } + } + + pub fn cert(&self) -> &CertificateDer { + &self.cert + } + + pub fn clone_key(&self) -> PrivatePkcs8KeyDer { + self.prkey.clone_key() + } + + pub fn to_raw(&self) -> RawCertPair { + let cert = self.cert.to_vec(); + let prkey = self.prkey.secret_pkcs8_der().to_vec(); + RawCertPair { cert, prkey } + } +} + +pub fn server_leaf_certparams(name: &str) -> CertificateParams { + let mut params = CertificateParams::new(vec!["entity.other.host".into(), format!("bonk.server.{name}")]); + params.distinguished_name.push(DnType::CommonName, name); + params.use_authority_key_identifier_extension = true; + params.key_usages.push(rcgen::KeyUsagePurpose::DigitalSignature); + params.extended_key_usages.push(rcgen::ExtendedKeyUsagePurpose::ClientAuth); + params +} + +pub struct BrokerRootCerts<'a> { + root_cert: CertificateDer<'a> +} + +impl BrokerRootCerts<'_> { + pub fn load_from_file(filename: &str) -> std::io::Result { + let file = std::fs::File::open(filename).unwrap(); + let mut buf = BufReader::new(file); + if let Item::X509Certificate(root_cert) = read_one(&mut buf).unwrap().unwrap() { + Ok(BrokerRootCerts { root_cert }) + } else { + Err(Error::new(ErrorKind::InvalidInput, "no broker root x509 cert")) + } + } + + pub fn to_rootcertstore(&self) -> RootCertStore { + let mut broker_root_cert_store = RootCertStore::empty(); + broker_root_cert_store.add(self.root_cert.clone()).expect("Invalid Broker Root"); + broker_root_cert_store + } + + pub fn certs(&self) -> Vec<&CertificateDer> { + vec![&self.root_cert] + } +} diff --git a/libbonknet/src/lib.rs b/libbonknet/src/lib.rs index d5e8055..fd45f64 100644 --- a/libbonknet/src/lib.rs +++ b/libbonknet/src/lib.rs @@ -1,5 +1,6 @@ pub mod servermsg; pub mod clientmsg; +pub mod cert; use std::io::{BufReader, Error, ErrorKind}; use rustls_pemfile::{Item, read_one}; @@ -28,6 +29,7 @@ pub fn load_prkey(filename: &str) -> std::io::Result { } } +// TODO: move all this inside Client and Server, for example using a DataStreamCmd(ToPeerDataStream) #[derive(Debug, Serialize, Deserialize)] pub enum ToPeerDataStream { // You are now a DataStream, wait the Open message diff --git a/libbonknet/src/servermsg.rs b/libbonknet/src/servermsg.rs index a65e5c1..72555d6 100644 --- a/libbonknet/src/servermsg.rs +++ b/libbonknet/src/servermsg.rs @@ -3,6 +3,7 @@ pub use crate::ToPeerDataStream; use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::cert::LeafCertPair; #[derive(Debug, Serialize, Deserialize)] pub enum FromServerConnTypeMessage { @@ -77,17 +78,31 @@ pub fn okannounce_to_cert<'a>(server_cert: Vec, server_prkey: Vec) -> (C (server_cert, server_prkey) } +#[derive(Debug, Serialize, Deserialize)] +pub struct OkAnnoucePayload { + server_cert: Vec, + ca_chain: Vec>, + server_prkey: Vec, +} + +impl OkAnnoucePayload { + pub fn parse<'a>(self) -> LeafCertPair<'a> { + LeafCertPair::parse(self.server_cert, self.ca_chain, self.server_prkey) + } +} + #[derive(Debug, Serialize, Deserialize)] pub enum ToGuestServerMessage { - OkAnnounce { server_cert: Vec, server_prkey: Vec }, + OkAnnounce(OkAnnoucePayload), FailedNameAlreadyOccupied, } impl ToGuestServerMessage { - pub fn make_okannounce(server_cert: CertificateDer, server_prkey: PrivatePkcs8KeyDer) -> Self { - ToGuestServerMessage::OkAnnounce { - server_cert: server_cert.to_vec(), - server_prkey: server_prkey.secret_pkcs8_der().to_vec() - } + pub fn make_okannounce(server_leaf: &LeafCertPair) -> Self { + ToGuestServerMessage::OkAnnounce(OkAnnoucePayload { + server_cert: server_leaf.cert().to_vec(), + ca_chain: server_leaf.fullchain().into_iter().map(|c| c.to_vec()).collect(), + server_prkey: server_leaf.prkey().secret_pkcs8_der().to_vec(), + }) } }