diff --git a/Cargo.lock b/Cargo.lock index 4e4c4bcc18..24a474ff18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,6 +3143,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -3943,7 +3953,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "static_assertions", ] @@ -4547,6 +4557,7 @@ dependencies = [ "flume", "json5", "num_cpus", + "secrecy", "serde", "serde_json", "serde_yaml", @@ -4674,6 +4685,7 @@ dependencies = [ "async-rustls", "async-std", "async-trait", + "base64 0.21.4", "futures", "log", "quinn", @@ -4681,6 +4693,7 @@ dependencies = [ "rustls-native-certs", "rustls-pemfile", "rustls-webpki", + "secrecy", "zenoh-config", "zenoh-core", "zenoh-link-commons", @@ -4733,11 +4746,13 @@ dependencies = [ "async-rustls", "async-std", "async-trait", + "base64 0.21.4", "futures", "log", "rustls", "rustls-pemfile", "rustls-webpki", + "secrecy", "webpki-roots", "zenoh-config", "zenoh-core", diff --git a/Cargo.toml b/Cargo.toml index 157ba72da2..a707ab390c 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" @@ -128,6 +128,7 @@ rustls = { version = "0.21.5", features = ["dangerous_configuration"] } rustls-native-certs = "0.6.2" rustls-pemfile = "1.0.2" schemars = "0.8.12" +secrecy = {version = "0.8.0", features = ["serde", "alloc"]} serde = { version = "1.0.154", default-features = false, features = [ "derive", ] } # Default features are disabled due to usage in no_std crates diff --git a/commons/zenoh-config/Cargo.toml b/commons/zenoh-config/Cargo.toml index 3bf62bfdb6..f0189ff3e7 100644 --- a/commons/zenoh-config/Cargo.toml +++ b/commons/zenoh-config/Cargo.toml @@ -35,3 +35,4 @@ zenoh-core = { workspace = true } zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } zenoh-util = { workspace = true } +secrecy = { workspace = true } diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index b0857f2caf..45b9d4a218 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -16,6 +16,7 @@ pub mod defaults; mod include; use include::recursive_include; +use secrecy::{CloneableSecret, DebugSecret, Secret, SerializableSecret, Zeroize}; use serde::{ de::{self, MapAccess, Visitor}, Deserialize, Serialize, @@ -46,6 +47,29 @@ use zenoh_protocol::{ use zenoh_result::{bail, zerror, ZResult}; use zenoh_util::LibLoader; +// Wrappers for secrecy of values +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct SecretString(String); + +impl Deref for SecretString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SerializableSecret for SecretString {} +impl DebugSecret for SecretString {} +impl CloneableSecret for SecretString {} +impl Zeroize for SecretString { + fn zeroize(&mut self) { + self.0 = "".to_string(); + } +} + +pub type SecretValue = Secret; + pub type ValidationFunction = std::sync::Arc< dyn Fn( &str, @@ -293,8 +317,20 @@ validated_struct::validator! { client_auth: Option, client_private_key: Option, client_certificate: Option, - server_name_verification: Option - }, + server_name_verification: Option, + // Skip serializing field because they contain secrets + #[serde(skip_serializing)] + root_ca_certificate_base64: Option, + #[serde(skip_serializing)] + server_private_key_base64: Option, + #[serde(skip_serializing)] + server_certificate_base64: Option, + #[serde(skip_serializing)] + client_private_key_base64 : Option, + #[serde(skip_serializing)] + client_certificate_base64 : Option, + } + , pub unixpipe: #[derive(Default)] UnixPipeConf { file_access_mask: Option diff --git a/io/zenoh-links/zenoh-link-quic/Cargo.toml b/io/zenoh-links/zenoh-link-quic/Cargo.toml index 4d9a4e0b3d..1cc2d82744 100644 --- a/io/zenoh-links/zenoh-link-quic/Cargo.toml +++ b/io/zenoh-links/zenoh-link-quic/Cargo.toml @@ -42,3 +42,5 @@ zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } zenoh-sync = { workspace = true } zenoh-util = { workspace = true } +base64 = { workspace = true } +secrecy = {workspace = true } \ No newline at end of file diff --git a/io/zenoh-links/zenoh-link-quic/src/lib.rs b/io/zenoh-links/zenoh-link-quic/src/lib.rs index f5449a767e..4f268200a2 100644 --- a/io/zenoh-links/zenoh-link-quic/src/lib.rs +++ b/io/zenoh-links/zenoh-link-quic/src/lib.rs @@ -20,9 +20,11 @@ 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 secrecy::ExposeSecret; use std::net::SocketAddr; use zenoh_config::Config; use zenoh_core::zconfigurable; @@ -31,7 +33,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 +74,55 @@ 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.expose_secret(), + )); + } + _ => {} } - 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.expose_secret(), + )); + } + _ => {} } - 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.expose_secret(), + )); + } + _ => {} } + if let Some(server_name_verification) = c.server_name_verification() { match server_name_verification { true => ps.push((TLS_SERVER_NAME_VERIFICATION, "true")), @@ -111,12 +153,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 +173,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..5d047b1160 100644 --- a/io/zenoh-links/zenoh-link-tls/Cargo.toml +++ b/io/zenoh-links/zenoh-link-tls/Cargo.toml @@ -41,3 +41,5 @@ zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } zenoh-sync = { workspace = true } zenoh-util = { workspace = true } +base64 = { workspace = true } +secrecy = {workspace = true } \ No newline at end of file diff --git a/io/zenoh-links/zenoh-link-tls/src/lib.rs b/io/zenoh-links/zenoh-link-tls/src/lib.rs index 8fb2f899a8..9b45b5e68b 100644 --- a/io/zenoh-links/zenoh-link-tls/src/lib.rs +++ b/io/zenoh-links/zenoh-link-tls/src/lib.rs @@ -21,10 +21,12 @@ 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 secrecy::ExposeSecret; use std::{convert::TryFrom, net::SocketAddr}; use zenoh_config::Config; use zenoh_core::zconfigurable; @@ -69,27 +71,94 @@ 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.expose_secret(), + )); + } + _ => {} } - 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.expose_secret(), + )); + } + _ => {} } - 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.expose_secret(), + )); + } + _ => {} } + 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.expose_secret(), + )); + } + _ => {} } - if let Some(client_certificate) = c.client_certificate() { - ps.push((TLS_CLIENT_CERTIFICATE_FILE, client_certificate)); + + match (c.client_certificate(), c.client_certificate_base64()) { + (Some(_), Some(_)) => { + bail!("Only one between 'client_certificate' and 'client_certificate_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.expose_secret(), + )); + } + _ => {} } + if let Some(server_name_verification) = c.server_name_verification() { match server_name_verification { true => ps.push((TLS_SERVER_NAME_VERIFICATION, "true")), @@ -120,18 +189,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 +230,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)?;