Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
DEFAULT_TLS_TIMEOUT, Listener, Listeners, ServerProtocol, TcpListener,
|
||||
tls::{TLS12_VERSION, TLS13_VERSION},
|
||||
};
|
||||
use crate::{
|
||||
Inner,
|
||||
network::{TcpAcceptor, tls::CertificateResolver},
|
||||
};
|
||||
use registry::{
|
||||
schema::{
|
||||
enums::{NetworkListenerProtocol, TlsCipherSuite, TlsVersion},
|
||||
prelude::{ObjectType, SocketAddr},
|
||||
structs::{ClusterListenerGroup, NetworkListener, SystemSettings},
|
||||
},
|
||||
types::{id::ObjectId, map::Map},
|
||||
};
|
||||
use rustls::{
|
||||
ALL_VERSIONS, ServerConfig, SupportedCipherSuite,
|
||||
crypto::aws_lc_rs::{ALL_CIPHER_SUITES, cipher_suite::*, default_provider},
|
||||
};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr as StdSocketAddr},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use store::registry::{RegistryObject, bootstrap::Bootstrap};
|
||||
use tokio::net::TcpSocket;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use types::id::Id;
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
impl Listeners {
|
||||
pub async fn parse(bp: &mut Bootstrap) -> Self {
|
||||
// Parse ACME managers
|
||||
let mut servers = Listeners {
|
||||
span_id_gen: Arc::new(SnowflakeIdGenerator::new()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Parse servers
|
||||
if !bp.registry.is_recovery_mode() {
|
||||
let system = bp.setting_infallible::<SystemSettings>().await;
|
||||
for listener in bp.list_infallible::<NetworkListener>().await {
|
||||
if bp.role.as_ref().is_none_or(|r| match &r.listeners {
|
||||
ClusterListenerGroup::EnableAll => true,
|
||||
ClusterListenerGroup::DisableAll => false,
|
||||
ClusterListenerGroup::EnableSome(group) => {
|
||||
group.listener_ids.iter().any(|id| *id == listener.id.id())
|
||||
}
|
||||
ClusterListenerGroup::DisableSome(group) => {
|
||||
!group.listener_ids.iter().any(|id| *id == listener.id.id())
|
||||
}
|
||||
}) {
|
||||
servers.parse_server(bp, listener, &system);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
servers.parse_server(
|
||||
bp,
|
||||
RegistryObject {
|
||||
id: ObjectId::new(ObjectType::NetworkListener, Id::singleton()),
|
||||
object: NetworkListener {
|
||||
bind: Map::new(vec![
|
||||
SocketAddr::from_str(&format!(
|
||||
"[::]:{}",
|
||||
std::env::var("STALWART_RECOVERY_MODE_PORT")
|
||||
.ok()
|
||||
.and_then(|p| p.parse::<u16>().ok())
|
||||
.unwrap_or(8080)
|
||||
))
|
||||
.unwrap(),
|
||||
]),
|
||||
name: "http-recovery".to_string(),
|
||||
protocol: NetworkListenerProtocol::Http,
|
||||
tls_implicit: false,
|
||||
..Default::default()
|
||||
},
|
||||
revision: 0,
|
||||
},
|
||||
&SystemSettings::default(),
|
||||
);
|
||||
}
|
||||
servers
|
||||
}
|
||||
|
||||
pub fn parse_server(
|
||||
&mut self,
|
||||
bp: &mut Bootstrap,
|
||||
listener: RegistryObject<NetworkListener>,
|
||||
system: &SystemSettings,
|
||||
) {
|
||||
let id = listener.id;
|
||||
let revision = listener.revision;
|
||||
let listener = listener.object;
|
||||
|
||||
// Parse protocol
|
||||
let protocol = match listener.protocol {
|
||||
NetworkListenerProtocol::Smtp => ServerProtocol::Smtp,
|
||||
NetworkListenerProtocol::Lmtp => ServerProtocol::Lmtp,
|
||||
NetworkListenerProtocol::Http => ServerProtocol::Http,
|
||||
NetworkListenerProtocol::Imap => ServerProtocol::Imap,
|
||||
NetworkListenerProtocol::Pop3 => ServerProtocol::Pop3,
|
||||
NetworkListenerProtocol::ManageSieve => ServerProtocol::ManageSieve,
|
||||
};
|
||||
|
||||
// Build listeners
|
||||
let mut listeners = Vec::new();
|
||||
for addr in listener.bind.iter() {
|
||||
// Parse bind address and build socket
|
||||
let mut addr = addr.0;
|
||||
let socket = match if addr.is_ipv4() {
|
||||
TcpSocket::new_v4()
|
||||
} else {
|
||||
TcpSocket::new_v6()
|
||||
} {
|
||||
Ok(socket) => socket,
|
||||
Err(err)
|
||||
if is_ipv6_unsupported(&err)
|
||||
&& addr.is_ipv6()
|
||||
&& addr.ip().is_unspecified() =>
|
||||
{
|
||||
let v4_addr =
|
||||
StdSocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), addr.port());
|
||||
bp.build_warning(
|
||||
id,
|
||||
format!(
|
||||
"IPv6 unavailable on this host ({err}); \
|
||||
falling back from {addr} to {v4_addr}"
|
||||
),
|
||||
);
|
||||
addr = v4_addr;
|
||||
match TcpSocket::new_v4() {
|
||||
Ok(socket) => socket,
|
||||
Err(err) => {
|
||||
bp.build_error(
|
||||
id,
|
||||
format!("Failed to create IPv4 fallback socket: {err}"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
bp.build_error(id, format!("Failed to create socket: {err}"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
if addr.is_ipv6()
|
||||
&& addr.ip().is_unspecified()
|
||||
&& let Err(err) = socket2::SockRef::from(&socket).set_only_v6(false)
|
||||
{
|
||||
bp.build_warning(
|
||||
id,
|
||||
format!(
|
||||
"Failed to disable IPV6_V6ONLY on {addr} ({err}); \
|
||||
IPv4 clients will not be able to connect to this listener"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(err) = socket.set_reuseaddr(listener.socket_reuse_address) {
|
||||
bp.build_error(id, format!("Failed to set SO_REUSEADDR: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
if let Err(err) = socket.set_reuseport(listener.socket_reuse_port) {
|
||||
bp.build_error(id, format!("Failed to set SO_REUSEPORT: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(send_size) = listener.socket_send_buffer_size
|
||||
&& let Err(err) = socket.set_send_buffer_size(send_size as u32)
|
||||
{
|
||||
bp.build_error(id, format!("Failed to set SO_SNDBUF: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(recv_size) = listener.socket_receive_buffer_size
|
||||
&& let Err(err) = socket.set_recv_buffer_size(recv_size as u32)
|
||||
{
|
||||
bp.build_error(id, format!("Failed to set SO_RCVBUF: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(tos) = listener.socket_tos_v4
|
||||
&& let Err(err) = socket.set_tos_v4(tos as u32)
|
||||
{
|
||||
bp.build_error(id, format!("Failed to set IP_TOS: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.push(TcpListener {
|
||||
socket,
|
||||
addr,
|
||||
ttl: listener.socket_ttl.map(|v| v as u32),
|
||||
backlog: listener.socket_backlog.map(|v| v as u32),
|
||||
nodelay: listener.socket_no_delay,
|
||||
});
|
||||
}
|
||||
|
||||
let span_id_gen = self.span_id_gen.clone();
|
||||
|
||||
self.servers.push(Listener {
|
||||
max_connections: listener.max_connections.unwrap_or(system.max_connections),
|
||||
tls_timeout: listener
|
||||
.tls_timeout
|
||||
.map_or(DEFAULT_TLS_TIMEOUT, |timeout| timeout.into_inner()),
|
||||
id: listener.name.clone(),
|
||||
registry_id: id,
|
||||
protocol,
|
||||
listeners,
|
||||
proxy_networks: if !listener.override_proxy_trusted_networks.is_empty() {
|
||||
listener.override_proxy_trusted_networks.as_slice().to_vec()
|
||||
} else {
|
||||
system.proxy_trusted_networks.as_slice().to_vec()
|
||||
},
|
||||
span_id_gen,
|
||||
});
|
||||
self.parsed_listeners.push(RegistryObject {
|
||||
id,
|
||||
object: listener,
|
||||
revision,
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn parse_tcp_acceptors(&mut self, bp: &mut Bootstrap, inner: Arc<Inner>) {
|
||||
let resolver = Arc::new(CertificateResolver::new(inner.clone()));
|
||||
|
||||
for listener in std::mem::take(&mut self.parsed_listeners) {
|
||||
let id = listener.id;
|
||||
let listener = listener.object;
|
||||
|
||||
// Build TLS config
|
||||
let acceptor = if listener.use_tls {
|
||||
// Parse protocol versions
|
||||
let mut tls_v2 = true;
|
||||
let mut tls_v3 = true;
|
||||
|
||||
for disabled in listener.tls_disable_protocols {
|
||||
match disabled {
|
||||
TlsVersion::Tls12 => {
|
||||
tls_v2 = false;
|
||||
}
|
||||
TlsVersion::Tls13 => {
|
||||
tls_v3 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse cipher suites
|
||||
let mut disabled_ciphers: Vec<SupportedCipherSuite> = Vec::new();
|
||||
for disabled in listener.tls_disable_cipher_suites {
|
||||
disabled_ciphers.push(match disabled {
|
||||
TlsCipherSuite::Tls13Aes256GcmSha384 => TLS13_AES_256_GCM_SHA384,
|
||||
TlsCipherSuite::Tls13Aes128GcmSha256 => TLS13_AES_128_GCM_SHA256,
|
||||
TlsCipherSuite::Tls13Chacha20Poly1305Sha256 => {
|
||||
TLS13_CHACHA20_POLY1305_SHA256
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheEcdsaWithAes256GcmSha384 => {
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheEcdsaWithAes128GcmSha256 => {
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheEcdsaWithChacha20Poly1305Sha256 => {
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheRsaWithAes256GcmSha384 => {
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheRsaWithAes128GcmSha256 => {
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
}
|
||||
TlsCipherSuite::TlsEcdheRsaWithChacha20Poly1305Sha256 => {
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Build cert provider
|
||||
let mut provider = default_provider();
|
||||
if !disabled_ciphers.is_empty() {
|
||||
provider.cipher_suites = ALL_CIPHER_SUITES
|
||||
.iter()
|
||||
.filter(|suite| !disabled_ciphers.contains(suite))
|
||||
.copied()
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Build server config
|
||||
let mut server_config = match ServerConfig::builder_with_provider(provider.into())
|
||||
.with_protocol_versions(if tls_v3 == tls_v2 {
|
||||
ALL_VERSIONS
|
||||
} else if tls_v3 {
|
||||
TLS13_VERSION
|
||||
} else {
|
||||
TLS12_VERSION
|
||||
}) {
|
||||
Ok(server_config) => server_config
|
||||
.with_no_client_auth()
|
||||
.with_cert_resolver(resolver.clone()),
|
||||
Err(err) => {
|
||||
bp.build_error(id, format!("Failed to build TLS server config: {err}"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
server_config.ignore_client_order = listener.tls_ignore_client_order;
|
||||
|
||||
// Build acceptor
|
||||
let default_config = Arc::new(server_config);
|
||||
TcpAcceptor::Tls {
|
||||
acceptor: TlsAcceptor::from(default_config.clone()),
|
||||
config: default_config,
|
||||
implicit: listener.tls_implicit,
|
||||
}
|
||||
} else {
|
||||
TcpAcceptor::Plain
|
||||
};
|
||||
|
||||
self.tcp_acceptors.insert(listener.name, acceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ipv6_unsupported(err: &std::io::Error) -> bool {
|
||||
let code = err.raw_os_error();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
matches!(code, Some(libc::EAFNOSUPPORT) | Some(libc::EPROTONOSUPPORT))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
matches!(code, Some(10047) | Some(10043))
|
||||
}
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::network::TcpAcceptor;
|
||||
use ahash::AHashMap;
|
||||
use registry::{
|
||||
schema::structs::NetworkListener,
|
||||
types::{id::ObjectId, ipmask::IpAddrOrMask},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Display, net::SocketAddr, sync::Arc, time::Duration};
|
||||
use store::registry::RegistryObject;
|
||||
use tokio::net::TcpSocket;
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
pub mod listener;
|
||||
pub mod tls;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Listeners {
|
||||
pub servers: Vec<Listener>,
|
||||
pub tcp_acceptors: AHashMap<String, TcpAcceptor>,
|
||||
pub span_id_gen: Arc<SnowflakeIdGenerator>,
|
||||
parsed_listeners: Vec<RegistryObject<NetworkListener>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Listener {
|
||||
pub registry_id: ObjectId,
|
||||
pub id: String,
|
||||
pub protocol: ServerProtocol,
|
||||
pub listeners: Vec<TcpListener>,
|
||||
pub proxy_networks: Vec<IpAddrOrMask>,
|
||||
pub max_connections: u64,
|
||||
pub tls_timeout: Duration,
|
||||
pub span_id_gen: Arc<SnowflakeIdGenerator>,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TLS_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TcpListener {
|
||||
pub socket: TcpSocket,
|
||||
pub addr: SocketAddr,
|
||||
pub backlog: Option<u32>,
|
||||
|
||||
// TCP options
|
||||
pub ttl: Option<u32>,
|
||||
pub nodelay: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub enum ServerProtocol {
|
||||
#[default]
|
||||
Smtp,
|
||||
Lmtp,
|
||||
Imap,
|
||||
Pop3,
|
||||
Http,
|
||||
ManageSieve,
|
||||
}
|
||||
|
||||
impl ServerProtocol {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ServerProtocol::Smtp => "smtp",
|
||||
ServerProtocol::Lmtp => "lmtp",
|
||||
ServerProtocol::Imap => "imap",
|
||||
ServerProtocol::Http => "http",
|
||||
ServerProtocol::Pop3 => "pop3",
|
||||
ServerProtocol::ManageSieve => "managesieve",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerProtocol {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::network::acme::ParsedCert;
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use rcgen::generate_simple_self_signed;
|
||||
use registry::{
|
||||
schema::{
|
||||
prelude::Object,
|
||||
structs::{Certificate, PublicText, SecretText, SystemSettings},
|
||||
},
|
||||
types::{datetime::UTCDateTime, map::Map},
|
||||
};
|
||||
use rustls::{
|
||||
SupportedProtocolVersion,
|
||||
crypto::aws_lc_rs::sign::any_supported_type,
|
||||
sign::CertifiedKey,
|
||||
version::{TLS12, TLS13},
|
||||
};
|
||||
use rustls_pemfile::{Item, certs, read_all};
|
||||
use rustls_pki_types::PrivateKeyDer;
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
use store::{
|
||||
registry::{bootstrap::Bootstrap, write::RegistryWrite},
|
||||
write::now,
|
||||
};
|
||||
|
||||
pub static TLS13_VERSION: &[&SupportedProtocolVersion] = &[&TLS13];
|
||||
pub static TLS12_VERSION: &[&SupportedProtocolVersion] = &[&TLS12];
|
||||
|
||||
pub(crate) async fn parse_certificates(
|
||||
bp: &mut Bootstrap,
|
||||
certificates: &mut AHashMap<Box<str>, Arc<CertifiedKey>>,
|
||||
subject_names: &mut AHashSet<Box<str>>,
|
||||
) {
|
||||
let system = bp.setting_infallible::<SystemSettings>().await;
|
||||
|
||||
// Parse certificates
|
||||
let now = now() as i64;
|
||||
let mut certs_expired = Vec::new();
|
||||
let mut certs_expirations = AHashMap::new();
|
||||
for cert_obj in bp.list_infallible::<Certificate>().await {
|
||||
let obj_id = cert_obj.id;
|
||||
let revision = cert_obj.revision;
|
||||
let mut cert = cert_obj.object;
|
||||
|
||||
let is_file_backed = matches!(cert.certificate, PublicText::File(_))
|
||||
|| matches!(cert.private_key, SecretText::File(_));
|
||||
let mut public = None;
|
||||
let mut refreshed_meta = None;
|
||||
if is_file_backed {
|
||||
let pem = match cert.certificate.value().await {
|
||||
Ok(value) => value.into_owned().into_bytes(),
|
||||
Err(err) => {
|
||||
bp.build_error(obj_id, format!("Failed to obtain certificate value: {err}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match ParsedCert::parse(&pem) {
|
||||
Ok(parsed) => {
|
||||
let not_valid_after =
|
||||
UTCDateTime::from_timestamp(parsed.valid_not_after.timestamp());
|
||||
let not_valid_before =
|
||||
UTCDateTime::from_timestamp(parsed.valid_not_before.timestamp());
|
||||
let sans = Map::new(parsed.sans);
|
||||
if cert.not_valid_after != not_valid_after
|
||||
|| cert.not_valid_before != not_valid_before
|
||||
|| cert.issuer != parsed.issuer
|
||||
|| cert.subject_alternative_names != sans
|
||||
{
|
||||
refreshed_meta =
|
||||
Some((not_valid_after, not_valid_before, parsed.issuer, sans));
|
||||
}
|
||||
public = Some(pem);
|
||||
}
|
||||
Err(err) => {
|
||||
bp.build_error(obj_id, format!("Invalid certificate: {err}"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (not_valid_after, not_valid_before) = match refreshed_meta.as_ref() {
|
||||
Some((after, before, _, _)) => (after.timestamp(), before.timestamp()),
|
||||
None => (
|
||||
cert.not_valid_after.timestamp(),
|
||||
cert.not_valid_before.timestamp(),
|
||||
),
|
||||
};
|
||||
|
||||
if not_valid_after <= now {
|
||||
certs_expired.push((
|
||||
obj_id,
|
||||
cert.subject_alternative_names.clone().into_inner(),
|
||||
Object {
|
||||
inner: cert.into(),
|
||||
revision,
|
||||
},
|
||||
));
|
||||
continue;
|
||||
} else if not_valid_before > now {
|
||||
continue; // Skip certificates that are not yet valid
|
||||
}
|
||||
|
||||
let secret = match cert.private_key.secret().await {
|
||||
Ok(secret) => secret.into_owned().into_bytes(),
|
||||
Err(err) => {
|
||||
bp.build_error(
|
||||
obj_id,
|
||||
format!("Failed to obtain private key secret: {err}"),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let public = match public {
|
||||
Some(public) => public,
|
||||
None => match cert.certificate.value().await {
|
||||
Ok(value) => value.into_owned().into_bytes(),
|
||||
Err(err) => {
|
||||
bp.build_error(obj_id, format!("Failed to obtain certificate value: {err}"));
|
||||
continue;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if let Some((not_valid_after, not_valid_before, issuer, sans)) = refreshed_meta {
|
||||
let old = Object {
|
||||
inner: cert.clone().into(),
|
||||
revision,
|
||||
};
|
||||
cert.not_valid_after = not_valid_after;
|
||||
cert.not_valid_before = not_valid_before;
|
||||
cert.issuer = issuer;
|
||||
cert.subject_alternative_names = sans;
|
||||
let new = Object {
|
||||
inner: cert.clone().into(),
|
||||
revision,
|
||||
};
|
||||
if let Err(err) = bp
|
||||
.registry
|
||||
.write(RegistryWrite::update(obj_id.id(), &new, &old))
|
||||
.await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to refresh TLS certificate metadata in registry.")
|
||||
.caused_by(trc::location!())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add default certificate
|
||||
if system
|
||||
.default_certificate_id
|
||||
.as_ref()
|
||||
.is_some_and(|id| *id == obj_id.id())
|
||||
{
|
||||
cert.subject_alternative_names
|
||||
.push_unchecked("*".to_string());
|
||||
}
|
||||
|
||||
// Ensure that the most up-to-date certificate is used
|
||||
cert.subject_alternative_names.inner_mut().retain(|name| {
|
||||
if certs_expirations
|
||||
.get(name)
|
||||
.is_none_or(|expires| *expires < not_valid_after)
|
||||
{
|
||||
certs_expirations.insert(name.clone(), not_valid_after);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
match build_certified_key(public, secret) {
|
||||
Ok(key) => {
|
||||
// Add certificates
|
||||
let key = Arc::new(key);
|
||||
for name in cert.subject_alternative_names.into_inner() {
|
||||
subject_names.insert(name.as_str().into());
|
||||
certificates.insert(
|
||||
name.strip_prefix("*.")
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| name.into_boxed_str()),
|
||||
key.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
bp.build_error(obj_id, format!("Invalid certificate: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove expired certificates
|
||||
if !certs_expired.is_empty() {
|
||||
for (id, sans, object) in certs_expired {
|
||||
if let Err(err) = bp
|
||||
.registry
|
||||
.write(RegistryWrite::delete_object(id, &object))
|
||||
.await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to delete expired TLS certificate from registry.")
|
||||
.caused_by(trc::location!())
|
||||
);
|
||||
} else {
|
||||
trc::event!(
|
||||
Tls(trc::TlsEvent::ExpiredCertificateRemoved),
|
||||
Details = sans
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_certified_key(
|
||||
cert: Vec<u8>,
|
||||
pk_bytes: Vec<u8>,
|
||||
) -> Result<CertifiedKey, String> {
|
||||
let mut pk = None;
|
||||
for item in read_all(&mut Cursor::new(pk_bytes)) {
|
||||
match item.map_err(|err| format!("Failed to read private key PEM: {err}"))? {
|
||||
Item::Pkcs8Key(key) => {
|
||||
pk = Some(PrivateKeyDer::Pkcs8(key));
|
||||
break;
|
||||
}
|
||||
Item::Pkcs1Key(key) => {
|
||||
pk = Some(PrivateKeyDer::Pkcs1(key));
|
||||
break;
|
||||
}
|
||||
Item::Sec1Key(key) => {
|
||||
pk = Some(PrivateKeyDer::Sec1(key));
|
||||
break;
|
||||
}
|
||||
_ => continue, // Skip certificates, DH params, etc.
|
||||
}
|
||||
}
|
||||
let pk = pk.ok_or_else(|| "No private keys found.".to_string())?;
|
||||
let cert = certs(&mut Cursor::new(cert))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|err| format!("Failed to read certificates: {err}"))?;
|
||||
|
||||
if !cert.is_empty() {
|
||||
Ok(CertifiedKey {
|
||||
cert,
|
||||
key: any_supported_type(&pk)
|
||||
.map_err(|err| format!("Failed to sign certificate: {err}",))?,
|
||||
ocsp: None,
|
||||
})
|
||||
} else {
|
||||
Err("No certificates found.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_self_signed_cert(
|
||||
domains: impl Into<Vec<String>>,
|
||||
) -> Result<CertifiedKey, String> {
|
||||
let domains = domains
|
||||
.into()
|
||||
.into_iter()
|
||||
.map(|domain| {
|
||||
if domain.is_ascii() {
|
||||
domain
|
||||
} else {
|
||||
idna::domain_to_ascii(&domain).unwrap_or(domain)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let rcgen::CertifiedKey { cert, signing_key } = generate_simple_self_signed(domains)
|
||||
.map_err(|err| format!("Failed to generate self-signed certificate: {err}",))?;
|
||||
build_certified_key(
|
||||
cert.pem().into_bytes(),
|
||||
signing_key.serialize_pem().into_bytes(),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user