diff --git a/Cargo.lock b/Cargo.lock index 4e4c4bcc18..218226bdc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4674,6 +4674,7 @@ dependencies = [ "async-rustls", "async-std", "async-trait", + "base64 0.21.4", "futures", "log", "quinn", @@ -4733,6 +4734,7 @@ dependencies = [ "async-rustls", "async-std", "async-trait", + "base64 0.21.4", "futures", "log", "rustls", diff --git a/Cargo.toml b/Cargo.toml index 157ba72da2..cd8254efc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ async-global-executor = "2.3.1" async-rustls = "0.4.0" async-std = { version = "=1.12.0", default-features = false } # Default features are disabled due to some crates' requirements async-trait = "0.1.60" -base64 = "0.21.0" +base64 = "0.21.4" bincode = "1.3.3" clap = "3.2.23" const_format = "0.2.30" diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index b0857f2caf..d9948e1b90 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -288,11 +288,16 @@ validated_struct::validator! { pub tls: #[derive(Default)] TLSConf { root_ca_certificate: Option, + root_ca_certificate_base64: Option, server_private_key: Option, + server_private_key_base64: Option, server_certificate: Option, + server_certificate_base64: Option, client_auth: Option, client_private_key: Option, + client_private_key_base64 : Option, client_certificate: Option, + client_certificate_base64 : Option, server_name_verification: Option }, pub unixpipe: #[derive(Default)] diff --git a/io/zenoh-links/zenoh-link-quic/Cargo.toml b/io/zenoh-links/zenoh-link-quic/Cargo.toml index 4d9a4e0b3d..1ace2170ee 100644 --- a/io/zenoh-links/zenoh-link-quic/Cargo.toml +++ b/io/zenoh-links/zenoh-link-quic/Cargo.toml @@ -42,3 +42,4 @@ zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } zenoh-sync = { workspace = true } zenoh-util = { workspace = true } +base64 = { workspace = true } diff --git a/io/zenoh-links/zenoh-link-quic/src/lib.rs b/io/zenoh-links/zenoh-link-quic/src/lib.rs index f5449a767e..8725d54e08 100644 --- a/io/zenoh-links/zenoh-link-quic/src/lib.rs +++ b/io/zenoh-links/zenoh-link-quic/src/lib.rs @@ -20,7 +20,8 @@ use async_std::net::ToSocketAddrs; use async_trait::async_trait; use config::{ - TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_FILE, TLS_SERVER_NAME_VERIFICATION, + TLS_ROOT_CA_CERTIFICATE_BASE64, TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_BASE64, + TLS_SERVER_CERTIFICATE_FILE, TLS_SERVER_NAME_VERIFICATION, TLS_SERVER_PRIVATE_KEY_BASE64, TLS_SERVER_PRIVATE_KEY_FILE, }; use std::net::SocketAddr; @@ -31,7 +32,7 @@ use zenoh_protocol::core::{ endpoint::{Address, Parameters}, Locator, }; -use zenoh_result::{bail, ZResult}; +use zenoh_result::{bail, zerror, ZResult}; mod unicast; mod verify; @@ -72,15 +73,46 @@ impl ConfigurationInspector for QuicConfigurator { let mut ps: Vec<(&str, &str)> = vec![]; let c = config.transport().link().tls(); - if let Some(tls_ca_certificate) = c.root_ca_certificate() { - ps.push((TLS_ROOT_CA_CERTIFICATE_FILE, tls_ca_certificate)); + + match (c.root_ca_certificate(), c.root_ca_certificate_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'root_ca_certificate' and 'root_ca_certificate_base64' can be present!") + } + (Some(ca_certificate), None) => { + ps.push((TLS_ROOT_CA_CERTIFICATE_FILE, ca_certificate)); + } + (None, Some(ca_certificate)) => { + ps.push((TLS_ROOT_CA_CERTIFICATE_BASE64, ca_certificate)); + } + _ => {} } - if let Some(tls_server_private_key) = c.server_private_key() { - ps.push((TLS_SERVER_PRIVATE_KEY_FILE, tls_server_private_key)); + + match (c.server_private_key(), c.server_private_key_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'server_private_key' and 'server_private_key_base64' can be present!") + } + (Some(server_private_key), None) => { + ps.push((TLS_SERVER_PRIVATE_KEY_FILE, server_private_key)); + } + (None, Some(server_private_key)) => { + ps.push((TLS_SERVER_PRIVATE_KEY_BASE64, server_private_key)); + } + _ => {} } - if let Some(tls_server_certificate) = c.server_certificate() { - ps.push((TLS_SERVER_CERTIFICATE_FILE, tls_server_certificate)); + + match (c.server_certificate(), c.server_certificate_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'server_certificate' and 'server_certificate_base64' can be present!") + } + (Some(server_certificate), None) => { + ps.push((TLS_SERVER_CERTIFICATE_FILE, server_certificate)); + } + (None, Some(server_certificate)) => { + ps.push((TLS_SERVER_CERTIFICATE_BASE64, server_certificate)); + } + _ => {} } + if let Some(server_name_verification) = c.server_name_verification() { match server_name_verification { true => ps.push((TLS_SERVER_NAME_VERIFICATION, "true")), @@ -111,12 +143,15 @@ zconfigurable! { pub mod config { pub const TLS_ROOT_CA_CERTIFICATE_FILE: &str = "root_ca_certificate_file"; pub const TLS_ROOT_CA_CERTIFICATE_RAW: &str = "root_ca_certificate_raw"; + pub const TLS_ROOT_CA_CERTIFICATE_BASE64: &str = "root_ca_certificate_base64"; pub const TLS_SERVER_PRIVATE_KEY_FILE: &str = "server_private_key_file"; pub const TLS_SERVER_PRIVATE_KEY_RAW: &str = "server_private_key_raw"; + pub const TLS_SERVER_PRIVATE_KEY_BASE64: &str = "server_private_key_base64"; pub const TLS_SERVER_CERTIFICATE_FILE: &str = "tls_server_certificate_file"; pub const TLS_SERVER_CERTIFICATE_RAW: &str = "tls_server_certificate_raw"; + pub const TLS_SERVER_CERTIFICATE_BASE64: &str = "tls_server_certificate_base64"; pub const TLS_SERVER_NAME_VERIFICATION: &str = "server_name_verification"; pub const TLS_SERVER_NAME_VERIFICATION_DEFAULT: &str = "true"; @@ -128,3 +163,11 @@ async fn get_quic_addr(address: &Address<'_>) -> ZResult { None => bail!("Couldn't resolve QUIC locator address: {}", address), } } + +pub fn base64_decode(data: &str) -> ZResult> { + use base64::engine::general_purpose; + use base64::Engine; + Ok(general_purpose::STANDARD + .decode(data) + .map_err(|e| zerror!("Unable to perform base64 decoding: {e:?}"))?) +} diff --git a/io/zenoh-links/zenoh-link-quic/src/unicast.rs b/io/zenoh-links/zenoh-link-quic/src/unicast.rs index dd08a0af27..70bd3ee769 100644 --- a/io/zenoh-links/zenoh-link-quic/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-quic/src/unicast.rs @@ -12,6 +12,7 @@ // ZettaScale Zenoh Team, // +use crate::base64_decode; use crate::{ config::*, get_quic_addr, verify::WebPkiVerifierAnyServerName, ALPN_QUIC_HTTP, QUIC_ACCEPT_THROTTLE_TIME, QUIC_DEFAULT_MTU, QUIC_LOCATOR_PREFIX, @@ -246,6 +247,8 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { // Read the certificates let f = if let Some(value) = epconf.get(TLS_ROOT_CA_CERTIFICATE_RAW) { value.as_bytes().to_vec() + } else if let Some(b64_certificate) = epconf.get(TLS_ROOT_CA_CERTIFICATE_BASE64) { + base64_decode(b64_certificate)? } else if let Some(value) = epconf.get(TLS_ROOT_CA_CERTIFICATE_FILE) { async_std::fs::read(value) .await @@ -334,6 +337,8 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { let f = if let Some(value) = epconf.get(TLS_SERVER_CERTIFICATE_RAW) { value.as_bytes().to_vec() + } else if let Some(b64_certificate) = epconf.get(TLS_SERVER_CERTIFICATE_BASE64) { + base64_decode(b64_certificate)? } else if let Some(value) = epconf.get(TLS_SERVER_CERTIFICATE_FILE) { async_std::fs::read(value) .await @@ -350,6 +355,8 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { // Private keys let f = if let Some(value) = epconf.get(TLS_SERVER_PRIVATE_KEY_RAW) { value.as_bytes().to_vec() + } else if let Some(b64_key) = epconf.get(TLS_SERVER_PRIVATE_KEY_BASE64) { + base64_decode(b64_key)? } else if let Some(value) = epconf.get(TLS_SERVER_PRIVATE_KEY_FILE) { async_std::fs::read(value) .await diff --git a/io/zenoh-links/zenoh-link-tls/Cargo.toml b/io/zenoh-links/zenoh-link-tls/Cargo.toml index dfe2cd3562..129cba5db9 100644 --- a/io/zenoh-links/zenoh-link-tls/Cargo.toml +++ b/io/zenoh-links/zenoh-link-tls/Cargo.toml @@ -41,3 +41,4 @@ zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } zenoh-sync = { workspace = true } zenoh-util = { workspace = true } +base64 = { workspace = true } diff --git a/io/zenoh-links/zenoh-link-tls/src/lib.rs b/io/zenoh-links/zenoh-link-tls/src/lib.rs index 8fb2f899a8..c0dd9cef83 100644 --- a/io/zenoh-links/zenoh-link-tls/src/lib.rs +++ b/io/zenoh-links/zenoh-link-tls/src/lib.rs @@ -21,9 +21,10 @@ use async_rustls::rustls::ServerName; use async_std::net::ToSocketAddrs; use async_trait::async_trait; use config::{ - TLS_CLIENT_AUTH, TLS_CLIENT_CERTIFICATE_FILE, TLS_CLIENT_PRIVATE_KEY_FILE, - TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_FILE, TLS_SERVER_NAME_VERIFICATION, - TLS_SERVER_PRIVATE_KEY_FILE, + TLS_CLIENT_AUTH, TLS_CLIENT_CERTIFICATE_BASE64, TLS_CLIENT_CERTIFICATE_FILE, + TLS_CLIENT_PRIVATE_KEY_BASE64, TLS_CLIENT_PRIVATE_KEY_FILE, TLS_ROOT_CA_CERTIFICATE_BASE64, + TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_BASE64, TLS_SERVER_CERTIFICATE_FILE, + TLS_SERVER_NAME_VERIFICATION, TLS_SERVER_PRIVATE_KEY_BASE_64, TLS_SERVER_PRIVATE_KEY_FILE, }; use std::{convert::TryFrom, net::SocketAddr}; use zenoh_config::Config; @@ -69,27 +70,79 @@ impl ConfigurationInspector for TlsConfigurator { let mut ps: Vec<(&str, &str)> = vec![]; let c = config.transport().link().tls(); - if let Some(ca_certificate) = c.root_ca_certificate() { - ps.push((TLS_ROOT_CA_CERTIFICATE_FILE, ca_certificate)); + + match (c.root_ca_certificate(), c.root_ca_certificate_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'root_ca_certificate' and 'root_ca_certificate_base64' can be present!") + } + (Some(ca_certificate), None) => { + ps.push((TLS_ROOT_CA_CERTIFICATE_FILE, ca_certificate)); + } + (None, Some(ca_certificate)) => { + ps.push((TLS_ROOT_CA_CERTIFICATE_BASE64, ca_certificate)); + } + _ => {} } - if let Some(server_private_key) = c.server_private_key() { - ps.push((TLS_SERVER_PRIVATE_KEY_FILE, server_private_key)); + + match (c.server_private_key(), c.server_private_key_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'server_private_key' and 'server_private_key_base64' can be present!") + } + (Some(server_private_key), None) => { + ps.push((TLS_SERVER_PRIVATE_KEY_FILE, server_private_key)); + } + (None, Some(server_private_key)) => { + ps.push((TLS_SERVER_PRIVATE_KEY_BASE_64, server_private_key)); + } + _ => {} } - if let Some(server_certificate) = c.server_certificate() { - ps.push((TLS_SERVER_CERTIFICATE_FILE, server_certificate)); + + match (c.server_certificate(), c.server_certificate_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'server_certificate' and 'server_certificate_base64' can be present!") + } + (Some(server_certificate), None) => { + ps.push((TLS_SERVER_CERTIFICATE_FILE, server_certificate)); + } + (None, Some(server_certificate)) => { + ps.push((TLS_SERVER_CERTIFICATE_BASE64, server_certificate)); + } + _ => {} } + if let Some(client_auth) = c.client_auth() { match client_auth { true => ps.push((TLS_CLIENT_AUTH, "true")), false => ps.push((TLS_CLIENT_AUTH, "false")), }; } - if let Some(client_private_key) = c.client_private_key() { - ps.push((TLS_CLIENT_PRIVATE_KEY_FILE, client_private_key)); + + match (c.client_private_key(), c.client_private_key_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'client_private_key' and 'client_private_key_base64' can be present!") + } + (Some(client_private_key), None) => { + ps.push((TLS_CLIENT_PRIVATE_KEY_FILE, client_private_key)); + } + (None, Some(client_private_key)) => { + ps.push((TLS_CLIENT_PRIVATE_KEY_BASE64, client_private_key)); + } + _ => {} } - if let Some(client_certificate) = c.client_certificate() { - ps.push((TLS_CLIENT_CERTIFICATE_FILE, client_certificate)); + + match (c.client_private_key(), c.client_private_key_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'client_private_key' and 'client_private_key_base64' can be present!") + } + (Some(client_certificate), None) => { + ps.push((TLS_CLIENT_CERTIFICATE_FILE, client_certificate)); + } + (None, Some(client_certificate)) => { + ps.push((TLS_CLIENT_CERTIFICATE_BASE64, client_certificate)); + } + _ => {} } + if let Some(server_name_verification) = c.server_name_verification() { match server_name_verification { true => ps.push((TLS_SERVER_NAME_VERIFICATION, "true")), @@ -120,18 +173,23 @@ zconfigurable! { pub mod config { pub const TLS_ROOT_CA_CERTIFICATE_FILE: &str = "root_ca_certificate_file"; pub const TLS_ROOT_CA_CERTIFICATE_RAW: &str = "root_ca_certificate_raw"; + pub const TLS_ROOT_CA_CERTIFICATE_BASE64: &str = "root_ca_certificate_base64"; pub const TLS_SERVER_PRIVATE_KEY_FILE: &str = "server_private_key_file"; pub const TLS_SERVER_PRIVATE_KEY_RAW: &str = "server_private_key_raw"; + pub const TLS_SERVER_PRIVATE_KEY_BASE_64: &str = "server_private_key_base64"; pub const TLS_SERVER_CERTIFICATE_FILE: &str = "server_certificate_file"; pub const TLS_SERVER_CERTIFICATE_RAW: &str = "server_certificate_raw"; + pub const TLS_SERVER_CERTIFICATE_BASE64: &str = "server_certificate_base64"; pub const TLS_CLIENT_PRIVATE_KEY_FILE: &str = "client_private_key_file"; pub const TLS_CLIENT_PRIVATE_KEY_RAW: &str = "client_private_key_raw"; + pub const TLS_CLIENT_PRIVATE_KEY_BASE64: &str = "client_private_key_base64"; pub const TLS_CLIENT_CERTIFICATE_FILE: &str = "client_certificate_file"; pub const TLS_CLIENT_CERTIFICATE_RAW: &str = "client_certificate_raw"; + pub const TLS_CLIENT_CERTIFICATE_BASE64: &str = "client_certificate_base64"; pub const TLS_CLIENT_AUTH: &str = "client_auth"; @@ -156,3 +214,11 @@ pub fn get_tls_host<'a>(address: &'a Address<'a>) -> ZResult<&'a str> { pub fn get_tls_server_name(address: &Address<'_>) -> ZResult { Ok(ServerName::try_from(get_tls_host(address)?).map_err(|e| zerror!(e))?) } + +pub fn base64_decode(data: &str) -> ZResult> { + use base64::engine::general_purpose; + use base64::Engine; + Ok(general_purpose::STANDARD + .decode(data) + .map_err(|e| zerror!("Unable to perform base64 decoding: {e:?}"))?) +} diff --git a/io/zenoh-links/zenoh-link-tls/src/unicast.rs b/io/zenoh-links/zenoh-link-tls/src/unicast.rs index ff8d68e6bd..63c6d63b1e 100644 --- a/io/zenoh-links/zenoh-link-tls/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-tls/src/unicast.rs @@ -12,7 +12,7 @@ // ZettaScale Zenoh Team, // use crate::{ - config::*, get_tls_addr, get_tls_host, get_tls_server_name, + base64_decode, config::*, get_tls_addr, get_tls_host, get_tls_server_name, verify::WebPkiVerifierAnyServerName, TLS_ACCEPT_THROTTLE_TIME, TLS_DEFAULT_MTU, TLS_LINGER_TIMEOUT, TLS_LOCATOR_PREFIX, }; @@ -583,6 +583,7 @@ impl TlsServerConfig { config, TLS_SERVER_PRIVATE_KEY_RAW, TLS_SERVER_PRIVATE_KEY_FILE, + TLS_SERVER_PRIVATE_KEY_BASE_64, ) .await } @@ -592,6 +593,7 @@ impl TlsServerConfig { config, TLS_SERVER_CERTIFICATE_RAW, TLS_SERVER_CERTIFICATE_FILE, + TLS_SERVER_CERTIFICATE_BASE64, ) .await } @@ -700,6 +702,7 @@ impl TlsClientConfig { config, TLS_CLIENT_PRIVATE_KEY_RAW, TLS_CLIENT_PRIVATE_KEY_FILE, + TLS_CLIENT_PRIVATE_KEY_BASE64, ) .await } @@ -709,6 +712,7 @@ impl TlsClientConfig { config, TLS_CLIENT_CERTIFICATE_RAW, TLS_CLIENT_CERTIFICATE_FILE, + TLS_CLIENT_CERTIFICATE_BASE64, ) .await } @@ -718,9 +722,12 @@ async fn load_tls_key( config: &Config<'_>, tls_private_key_raw_config_key: &str, tls_private_key_file_config_key: &str, + tls_private_key_base64_config_key: &str, ) -> ZResult> { if let Some(value) = config.get(tls_private_key_raw_config_key) { return Ok(value.as_bytes().to_vec()); + } else if let Some(b64_key) = config.get(tls_private_key_base64_config_key) { + return base64_decode(b64_key); } else if let Some(value) = config.get(tls_private_key_file_config_key) { return Ok(fs::read(value) .await @@ -740,9 +747,12 @@ async fn load_tls_certificate( config: &Config<'_>, tls_certificate_raw_config_key: &str, tls_certificate_file_config_key: &str, + tls_certificate_base64_config_key: &str, ) -> ZResult> { if let Some(value) = config.get(tls_certificate_raw_config_key) { return Ok(value.as_bytes().to_vec()); + } else if let Some(b64_certificate) = config.get(tls_certificate_base64_config_key) { + return base64_decode(b64_certificate); } else if let Some(value) = config.get(tls_certificate_file_config_key) { return Ok(fs::read(value) .await @@ -767,6 +777,21 @@ fn load_trust_anchors(config: &Config<'_>) -> ZResult> { root_cert_store.add_trust_anchors(trust_anchors.into_iter()); return Ok(Some(root_cert_store)); } + if let Some(b64_certificate) = config.get(TLS_ROOT_CA_CERTIFICATE_BASE64) { + let certificate_pem = base64_decode(b64_certificate)?; + let mut pem = BufReader::new(certificate_pem.as_slice()); + let certs = rustls_pemfile::certs(&mut pem)?; + let trust_anchors = certs.iter().map(|cert| { + let ta = TrustAnchor::try_from_cert_der(&cert[..]).unwrap(); + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + }); + root_cert_store.add_trust_anchors(trust_anchors.into_iter()); + return Ok(Some(root_cert_store)); + } if let Some(filename) = config.get(TLS_ROOT_CA_CERTIFICATE_FILE) { let mut pem = BufReader::new(File::open(filename)?); let certs = rustls_pemfile::certs(&mut pem)?;