fedimint_server/config/
mod.rs

1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::env;
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use anyhow::{bail, format_err, Context};
7use fedimint_api_client::api::P2PConnectionStatus;
8use fedimint_core::admin_client::ConfigGenParamsConsensus;
9pub use fedimint_core::config::{
10    serde_binary_human_readable, ClientConfig, DkgPeerMessage, FederationId, GlobalClientConfig,
11    JsonWithKind, ModuleInitRegistry, PeerUrl, ServerModuleConfig, ServerModuleConsensusConfig,
12    TypedServerModuleConfig,
13};
14use fedimint_core::core::{ModuleInstanceId, ModuleKind};
15use fedimint_core::envs::is_running_in_test_env;
16use fedimint_core::invite_code::InviteCode;
17use fedimint_core::module::{
18    ApiAuth, ApiVersion, CoreConsensusVersion, MultiApiVersion, PeerHandle,
19    SupportedApiVersionsSummary, SupportedCoreApiVersions, CORE_CONSENSUS_VERSION,
20};
21use fedimint_core::net::peers::{IP2PConnections, Recipient};
22use fedimint_core::task::{sleep, timeout, TaskGroup};
23use fedimint_core::{secp256k1, timing, NumPeersExt, PeerId};
24use fedimint_logging::LOG_NET_PEER_DKG;
25use fedimint_server_core::{DynServerModuleInit, ServerModuleInitRegistry};
26use rand::rngs::OsRng;
27use secp256k1::{PublicKey, Secp256k1, SecretKey};
28use serde::{Deserialize, Serialize};
29use tokio::sync::watch;
30use tokio_rustls::rustls;
31use tracing::{error, info};
32
33use crate::config::api::ConfigGenParamsLocal;
34use crate::config::distributedgen::PeerHandleOps;
35use crate::envs::FM_MAX_CLIENT_CONNECTIONS_ENV;
36use crate::fedimint_core::encoding::Encodable;
37use crate::net::p2p::ReconnectP2PConnections;
38use crate::net::p2p_connector::{dns_sanitize, IP2PConnector, TlsConfig, TlsTcpConnector};
39
40pub mod api;
41pub mod distributedgen;
42pub mod io;
43
44/// The default maximum open connections the API can handle
45pub const DEFAULT_MAX_CLIENT_CONNECTIONS: u32 = 1000;
46
47/// Consensus broadcast settings that result in 3 minutes session time
48const DEFAULT_BROADCAST_ROUND_DELAY_MS: u16 = 50;
49const DEFAULT_BROADCAST_ROUNDS_PER_SESSION: u16 = 3600;
50
51fn default_broadcast_rounds_per_session() -> u16 {
52    DEFAULT_BROADCAST_ROUNDS_PER_SESSION
53}
54
55/// Consensus broadcast settings that result in 10 seconds session time
56const DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS: u16 = 50;
57const DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION: u16 = 200;
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60/// All the serializable configuration for the fedimint server
61pub struct ServerConfig {
62    /// Contains all configuration that needs to be the same for every server
63    pub consensus: ServerConfigConsensus,
64    /// Contains all configuration that is locally configurable and not secret
65    pub local: ServerConfigLocal,
66    /// Contains all configuration that will be encrypted such as private key
67    /// material
68    pub private: ServerConfigPrivate,
69}
70
71impl ServerConfig {
72    pub fn iter_module_instances(
73        &self,
74    ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
75        self.consensus.iter_module_instances()
76    }
77
78    pub(crate) fn supported_api_versions_summary(
79        modules: &BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
80        module_inits: &ServerModuleInitRegistry,
81    ) -> SupportedApiVersionsSummary {
82        SupportedApiVersionsSummary {
83            core: Self::supported_api_versions(),
84            modules: modules
85                .iter()
86                .map(|(&id, config)| {
87                    (
88                        id,
89                        module_inits
90                            .get(&config.kind)
91                            .expect("missing module kind gen")
92                            .supported_api_versions(),
93                    )
94                })
95                .collect(),
96        }
97    }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ServerConfigPrivate {
102    /// Secret API auth string
103    pub api_auth: ApiAuth,
104    /// Secret key for TLS communication, required for peer authentication
105    #[serde(with = "serde_tls_key")]
106    pub tls_key: rustls::PrivateKey,
107    /// Secret key for the atomic broadcast to sign messages
108    pub broadcast_secret_key: SecretKey,
109    /// Secret material from modules
110    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
114pub struct ServerConfigConsensus {
115    /// The version of the binary code running
116    pub code_version: String,
117    /// Agreed on core consensus version
118    pub version: CoreConsensusVersion,
119    /// Public keys for the atomic broadcast to authenticate messages
120    pub broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
121    /// Number of rounds per session.
122    #[serde(default = "default_broadcast_rounds_per_session")]
123    pub broadcast_rounds_per_session: u16,
124    /// Network addresses and names for all peer APIs
125    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
126    /// Certs for TLS communication, required for peer authentication
127    #[serde(with = "serde_tls_cert_map")]
128    pub tls_certs: BTreeMap<PeerId, rustls::Certificate>,
129    /// All configuration that needs to be the same for modules
130    pub modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
131    /// Additional config the federation wants to transmit to the clients
132    pub meta: BTreeMap<String, String>,
133}
134
135// FIXME: (@leonardo) Should this have another field for the expected transport
136// ? (e.g. clearnet/tor/...)
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct ServerConfigLocal {
139    /// Network addresses and names for all p2p connections
140    pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
141    /// Our peer id (generally should not change)
142    pub identity: PeerId,
143    /// How many API connections we will accept
144    pub max_connections: u32,
145    /// Influences the atomic broadcast ordering latency, should be higher than
146    /// the expected latency between peers so everyone can get proposed
147    /// consensus items confirmed. This is only relevant for byzantine
148    /// faults.
149    pub broadcast_round_delay_ms: u16,
150    /// Non-consensus, non-private configuration from modules
151    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
152}
153
154#[derive(Debug, Clone)]
155/// All the parameters necessary for generating the `ServerConfig` during setup
156///
157/// * Guardians can create the parameters using a setup UI or CLI tool
158/// * Used for distributed or trusted config generation
159pub struct ConfigGenParams {
160    pub local: ConfigGenParamsLocal,
161    pub consensus: ConfigGenParamsConsensus,
162}
163
164impl ServerConfigConsensus {
165    pub fn iter_module_instances(
166        &self,
167    ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
168        self.modules.iter().map(|(k, v)| (*k, &v.kind))
169    }
170
171    pub fn to_client_config(
172        &self,
173        module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
174    ) -> Result<ClientConfig, anyhow::Error> {
175        let client = ClientConfig {
176            global: GlobalClientConfig {
177                api_endpoints: self.api_endpoints.clone(),
178                broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
179                consensus_version: self.version,
180                meta: self.meta.clone(),
181            },
182            modules: self
183                .modules
184                .iter()
185                .map(|(k, v)| {
186                    let gen = module_config_gens
187                        .get(&v.kind)
188                        .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
189                    Ok((*k, gen.get_client_config(*k, v)?))
190                })
191                .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
192        };
193        Ok(client)
194    }
195}
196
197impl ServerConfig {
198    /// Api versions supported by this server
199    pub fn supported_api_versions() -> SupportedCoreApiVersions {
200        SupportedCoreApiVersions {
201            core_consensus: CORE_CONSENSUS_VERSION,
202            api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 5 }])
203                .expect("not version conflicts"),
204        }
205    }
206    /// Creates a new config from the results of a trusted or distributed key
207    /// setup
208    pub fn from(
209        params: ConfigGenParams,
210        identity: PeerId,
211        broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
212        broadcast_secret_key: SecretKey,
213        modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
214        code_version_str: String,
215    ) -> Self {
216        let private = ServerConfigPrivate {
217            api_auth: params.local.api_auth.clone(),
218            tls_key: params.local.our_private_key.clone(),
219            broadcast_secret_key,
220            modules: BTreeMap::new(),
221        };
222        let local = ServerConfigLocal {
223            p2p_endpoints: params.p2p_urls(),
224            identity,
225            max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
226            broadcast_round_delay_ms: if is_running_in_test_env() {
227                DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
228            } else {
229                DEFAULT_BROADCAST_ROUND_DELAY_MS
230            },
231            modules: BTreeMap::new(),
232        };
233        let consensus = ServerConfigConsensus {
234            code_version: code_version_str,
235            version: CORE_CONSENSUS_VERSION,
236            broadcast_public_keys,
237            broadcast_rounds_per_session: if is_running_in_test_env() {
238                DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
239            } else {
240                DEFAULT_BROADCAST_ROUNDS_PER_SESSION
241            },
242            api_endpoints: params.api_urls(),
243            tls_certs: params.tls_certs(),
244            modules: BTreeMap::new(),
245            meta: params.consensus.meta,
246        };
247        let mut cfg = Self {
248            consensus,
249            local,
250            private,
251        };
252        cfg.add_modules(modules);
253        cfg
254    }
255
256    pub fn get_invite_code(&self, api_secret: Option<String>) -> InviteCode {
257        InviteCode::new(
258            self.consensus.api_endpoints[&self.local.identity]
259                .url
260                .clone(),
261            self.local.identity,
262            self.calculate_federation_id(),
263            api_secret,
264        )
265    }
266
267    pub fn calculate_federation_id(&self) -> FederationId {
268        FederationId(self.consensus.api_endpoints.consensus_hash())
269    }
270
271    pub fn add_modules(&mut self, modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>) {
272        for (name, config) in modules {
273            let ServerModuleConfig {
274                local,
275                private,
276                consensus,
277            } = config;
278
279            self.local.modules.insert(name, local);
280            self.private.modules.insert(name, private);
281            self.consensus.modules.insert(name, consensus);
282        }
283    }
284
285    /// Constructs a module config by name
286    pub fn get_module_config_typed<T: TypedServerModuleConfig>(
287        &self,
288        id: ModuleInstanceId,
289    ) -> anyhow::Result<T> {
290        let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
291        let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
292        let consensus = self
293            .consensus
294            .modules
295            .get(&id)
296            .ok_or_else(|| format_err!("Module {id} not found"))?
297            .clone();
298        let module = ServerModuleConfig::from(local, private, consensus);
299
300        module.to_typed()
301    }
302    pub fn get_module_id_by_kind(
303        &self,
304        kind: impl Into<ModuleKind>,
305    ) -> anyhow::Result<ModuleInstanceId> {
306        let kind = kind.into();
307        Ok(*self
308            .consensus
309            .modules
310            .iter()
311            .find(|(_, v)| v.kind == kind)
312            .ok_or_else(|| format_err!("Module {kind} not found"))?
313            .0)
314    }
315
316    /// Constructs a module config by id
317    pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
318        let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
319        let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
320        let consensus = self
321            .consensus
322            .modules
323            .get(&id)
324            .ok_or_else(|| format_err!("Module {id} not found"))?
325            .clone();
326        Ok(ServerModuleConfig::from(local, private, consensus))
327    }
328
329    fn get_module_cfg_by_instance_id(
330        json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
331        id: ModuleInstanceId,
332    ) -> anyhow::Result<JsonWithKind> {
333        Ok(json
334            .get(&id)
335            .ok_or_else(|| format_err!("Module {id} not found"))
336            .cloned()?
337            .with_fixed_empty_value())
338    }
339
340    pub fn validate_config(
341        &self,
342        identity: &PeerId,
343        module_config_gens: &ServerModuleInitRegistry,
344    ) -> anyhow::Result<()> {
345        let peers = self.local.p2p_endpoints.clone();
346        let consensus = self.consensus.clone();
347        let private = self.private.clone();
348
349        let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
350
351        if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
352            bail!("Broadcast secret key doesn't match corresponding public key");
353        }
354        if peers.keys().max().copied().map(PeerId::to_usize) != Some(peers.len() - 1) {
355            bail!("Peer ids are not indexed from 0");
356        }
357        if peers.keys().min().copied() != Some(PeerId::from(0)) {
358            bail!("Peer ids are not indexed from 0");
359        }
360
361        for (module_id, module_kind) in &self
362            .consensus
363            .modules
364            .iter()
365            .map(|(id, config)| Ok((*id, config.kind.clone())))
366            .collect::<anyhow::Result<BTreeSet<_>>>()?
367        {
368            module_config_gens
369                .get(module_kind)
370                .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
371                .validate_config(identity, self.get_module_config(*module_id)?)?;
372        }
373
374        Ok(())
375    }
376
377    pub fn trusted_dealer_gen(
378        params: &HashMap<PeerId, ConfigGenParams>,
379        registry: &ServerModuleInitRegistry,
380        code_version_str: &str,
381    ) -> BTreeMap<PeerId, Self> {
382        let peer0 = &params[&PeerId::from(0)];
383
384        let mut broadcast_pks = BTreeMap::new();
385        let mut broadcast_sks = BTreeMap::new();
386        for peer_id in peer0.peer_ids() {
387            let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
388            broadcast_pks.insert(peer_id, broadcast_pk);
389            broadcast_sks.insert(peer_id, broadcast_sk);
390        }
391
392        let modules = peer0.consensus.modules.iter_modules();
393        let module_configs: BTreeMap<_, _> = modules
394            .map(|(module_id, kind, module_params)| {
395                (
396                    module_id,
397                    registry
398                        .get(kind)
399                        .expect("Module not registered")
400                        .trusted_dealer_gen(&peer0.peer_ids(), module_params),
401                )
402            })
403            .collect();
404
405        let server_config: BTreeMap<_, _> = peer0
406            .peer_ids()
407            .iter()
408            .map(|&id| {
409                let config = ServerConfig::from(
410                    params[&id].clone(),
411                    id,
412                    broadcast_pks.clone(),
413                    *broadcast_sks.get(&id).expect("We created this entry"),
414                    module_configs
415                        .iter()
416                        .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
417                        .collect(),
418                    code_version_str.to_string(),
419                );
420                (id, config)
421            })
422            .collect();
423
424        server_config
425    }
426
427    /// Runs the distributed key gen algorithm
428    pub async fn distributed_gen(
429        p2p_bind_addr: SocketAddr,
430        params: &ConfigGenParams,
431        registry: ServerModuleInitRegistry,
432        task_group: &TaskGroup,
433        code_version_str: String,
434    ) -> anyhow::Result<Self> {
435        let _timing /* logs on drop */ = timing::TimeReporter::new("distributed-gen").info();
436
437        // in case we are running by ourselves, avoid DKG
438        if params.peer_ids().len() == 1 {
439            let server = Self::trusted_dealer_gen(
440                &HashMap::from([(params.local.our_id, params.clone())]),
441                &registry,
442                &code_version_str,
443            );
444            return Ok(server[&params.local.our_id].clone());
445        }
446
447        let connector = TlsTcpConnector::new(
448            params.tls_config(),
449            p2p_bind_addr,
450            params
451                .p2p_urls()
452                .into_iter()
453                .map(|(id, peer)| (id, peer.url))
454                .collect(),
455            params.local.our_id,
456        )
457        .into_dyn();
458
459        let mut p2p_status_senders = BTreeMap::new();
460        let mut p2p_status_receivers = BTreeMap::new();
461
462        for peer in connector.peers() {
463            let (p2p_sender, p2p_receiver) = watch::channel(P2PConnectionStatus::Disconnected);
464
465            p2p_status_senders.insert(peer, p2p_sender);
466            p2p_status_receivers.insert(peer, p2p_receiver);
467        }
468
469        let connections = ReconnectP2PConnections::new(
470            params.local.our_id,
471            connector,
472            task_group,
473            Some(p2p_status_senders),
474        )
475        .await
476        .into_dyn();
477
478        while p2p_status_receivers
479            .values()
480            .any(|r| *r.borrow() == P2PConnectionStatus::Disconnected)
481        {
482            info!(
483                target: LOG_NET_PEER_DKG,
484                "Waiting for all p2p connections to open..."
485            );
486
487            sleep(Duration::from_secs(1)).await;
488        }
489
490        info!(
491            target: LOG_NET_PEER_DKG,
492            "Running distributed key generation..."
493        );
494
495        let handle = PeerHandle::new(
496            params.peer_ids().to_num_peers(),
497            params.local.our_id,
498            &connections,
499        );
500
501        let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
502
503        let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
504
505        let mut module_cfgs = BTreeMap::new();
506
507        for (module_id, kind, module_params) in params.consensus.modules.iter_modules() {
508            info!(
509                target: LOG_NET_PEER_DKG,
510                "Running distributed key generation for module of kind {kind}..."
511            );
512
513            let cfg = registry
514                .get(kind)
515                .context("Module of kind {kind} not found")?
516                .distributed_gen(&handle, module_params)
517                .await?;
518
519            module_cfgs.insert(module_id, cfg);
520        }
521
522        info!(
523            target: LOG_NET_PEER_DKG,
524            "Distributed key generation has completed successfully!"
525        );
526
527        connections
528            .send(Recipient::Everyone, DkgPeerMessage::Completed)
529            .await;
530
531        if timeout(Duration::from_secs(30), async {
532            for peer in params
533                .peer_ids()
534                .into_iter()
535                .filter(|p| *p != params.local.our_id)
536            {
537                connections.receive_from_peer(peer).await;
538            }
539        })
540        .await
541        .is_err()
542        {
543            error!(target: LOG_NET_PEER_DKG, "Timeout waiting for dkg completion confirmation");
544        }
545
546        let server = ServerConfig::from(
547            params.clone(),
548            params.local.our_id,
549            broadcast_public_keys,
550            broadcast_sk,
551            module_cfgs,
552            code_version_str,
553        );
554
555        Ok(server)
556    }
557}
558
559impl ServerConfig {
560    pub fn tls_config(&self) -> TlsConfig {
561        TlsConfig {
562            private_key: self.private.tls_key.clone(),
563            certificates: self.consensus.tls_certs.clone(),
564            peer_names: self
565                .local
566                .p2p_endpoints
567                .iter()
568                .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
569                .collect(),
570        }
571    }
572
573    pub fn get_incoming_count(&self) -> u16 {
574        self.local.identity.into()
575    }
576}
577
578impl ConfigGenParams {
579    pub fn peer_ids(&self) -> Vec<PeerId> {
580        self.consensus.peers.keys().copied().collect()
581    }
582
583    pub fn tls_config(&self) -> TlsConfig {
584        TlsConfig {
585            private_key: self.local.our_private_key.clone(),
586            certificates: self.tls_certs(),
587            peer_names: self
588                .p2p_urls()
589                .into_iter()
590                .map(|(id, peer)| (id, peer.name))
591                .collect(),
592        }
593    }
594
595    pub fn tls_certs(&self) -> BTreeMap<PeerId, rustls::Certificate> {
596        self.consensus
597            .peers
598            .iter()
599            .map(|(id, peer)| (*id, peer.cert.clone()))
600            .collect::<BTreeMap<_, _>>()
601    }
602
603    pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
604        self.consensus
605            .peers
606            .iter()
607            .map(|(id, peer)| {
608                (
609                    *id,
610                    PeerUrl {
611                        name: peer.name.clone(),
612                        url: peer.p2p_url.clone(),
613                    },
614                )
615            })
616            .collect::<BTreeMap<_, _>>()
617    }
618
619    pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
620        self.consensus
621            .peers
622            .iter()
623            .map(|(id, peer)| {
624                (
625                    *id,
626                    PeerUrl {
627                        name: peer.name.clone(),
628                        url: peer.api_url.clone(),
629                    },
630                )
631            })
632            .collect::<BTreeMap<_, _>>()
633    }
634}
635
636// TODO: Remove once new config gen UI is written
637pub fn max_connections() -> u32 {
638    env::var(FM_MAX_CLIENT_CONNECTIONS_ENV)
639        .ok()
640        .and_then(|s| s.parse().ok())
641        .unwrap_or(DEFAULT_MAX_CLIENT_CONNECTIONS)
642}
643
644pub fn gen_cert_and_key(
645    name: &str,
646) -> Result<(rustls::Certificate, rustls::PrivateKey), anyhow::Error> {
647    let keypair = rcgen::KeyPair::generate()?;
648    let keypair_ser = keypair.serialize_der();
649    let mut params = rcgen::CertificateParams::new(vec![dns_sanitize(name)])?;
650
651    params.is_ca = rcgen::IsCa::NoCa;
652    params
653        .distinguished_name
654        .push(rcgen::DnType::CommonName, dns_sanitize(name));
655
656    let cert = params.self_signed(&keypair)?;
657
658    Ok((
659        rustls::Certificate(cert.der().to_vec()),
660        rustls::PrivateKey(keypair_ser),
661    ))
662}
663
664mod serde_tls_cert_map {
665    use std::borrow::Cow;
666    use std::collections::BTreeMap;
667
668    use fedimint_core::PeerId;
669    use hex::{FromHex, ToHex};
670    use serde::de::Error;
671    use serde::ser::SerializeMap;
672    use serde::{Deserialize, Deserializer, Serializer};
673    use tokio_rustls::rustls;
674
675    pub fn serialize<S>(
676        certs: &BTreeMap<PeerId, rustls::Certificate>,
677        serializer: S,
678    ) -> Result<S::Ok, S::Error>
679    where
680        S: Serializer,
681    {
682        let mut serializer = serializer.serialize_map(Some(certs.len()))?;
683        for (key, value) in certs {
684            serializer.serialize_key(key)?;
685            let hex_str = value.0.encode_hex::<String>();
686            serializer.serialize_value(&hex_str)?;
687        }
688        serializer.end()
689    }
690
691    pub fn deserialize<'de, D>(
692        deserializer: D,
693    ) -> Result<BTreeMap<PeerId, rustls::Certificate>, D::Error>
694    where
695        D: Deserializer<'de>,
696    {
697        let map: BTreeMap<PeerId, Cow<str>> = Deserialize::deserialize(deserializer)?;
698        let mut certs = BTreeMap::new();
699
700        for (key, value) in map {
701            let cert =
702                rustls::Certificate(Vec::from_hex(value.as_ref()).map_err(D::Error::custom)?);
703            certs.insert(key, cert);
704        }
705        Ok(certs)
706    }
707}
708
709mod serde_tls_key {
710    use std::borrow::Cow;
711
712    use hex::{FromHex, ToHex};
713    use serde::{Deserialize, Deserializer, Serialize, Serializer};
714    use tokio_rustls::rustls;
715
716    pub fn serialize<S>(key: &rustls::PrivateKey, serializer: S) -> Result<S::Ok, S::Error>
717    where
718        S: Serializer,
719    {
720        let hex_str = key.0.encode_hex::<String>();
721        Serialize::serialize(&hex_str, serializer)
722    }
723
724    pub fn deserialize<'de, D>(deserializer: D) -> Result<rustls::PrivateKey, D::Error>
725    where
726        D: Deserializer<'de>,
727    {
728        let hex_str: Cow<str> = Deserialize::deserialize(deserializer)?;
729        let bytes = Vec::from_hex(hex_str.as_ref()).map_err(serde::de::Error::custom)?;
730        Ok(rustls::PrivateKey(bytes))
731    }
732}