Skip to main content

pubhubs/servers/config/
core.rs

1//! Configuration (files)
2use core::fmt::Debug;
3use std::ops::{Deref, DerefMut};
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context as _, Result};
7use url::Url;
8
9use crate::misc::serde_ext::bytes_wrapper::{B64, B64UU};
10use crate::servers::{for_all_servers, server::Server as _};
11use crate::{
12    api::{self},
13    attr,
14    common::{elgamal, kem},
15    hub,
16    misc::{jwt, serde_ext, time_ext},
17    servers::yivi,
18};
19
20use super::host_aliases::{HostAliases, UrlPwa};
21use super::log::LogConfig;
22
23/// Configuration for one, or several, of the PubHubs servers
24///
25/// Also used for the `pubhubs admin` cli command.  In that case only `phc_url` needs to be set.
26#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
27#[serde(deny_unknown_fields)]
28pub struct Config {
29    /// URL of the PubHubs Central server.
30    ///
31    /// Any information on the other servers that can be stored at PHC is stored at PHC.
32    pub phc_url: UrlPwa,
33
34    /// Configure logging.  Overwrites what's passed through `RUST_LOG`.
35    #[serde(default)]
36    pub log: Option<LogConfig>,
37
38    #[serde(skip)]
39    pub(crate) preparation_state: PreparationState,
40
41    /// Specify abbreviations for an IP address that are only valid in this configuration file.
42    ///
43    /// Any [UrlPwa] that contains one of the aliases as host name exactly (so no subdomain)
44    /// will be modified to have as host name the associated IP address.
45    #[serde(default)]
46    pub host_aliases: HostAliases,
47
48    /// Path with respect to which relative paths are interpretted.
49    #[serde(default)]
50    pub wd: PathBuf,
51
52    /// Configuration to run PubHubs Central
53    pub phc: Option<ServerConfig<phc::ExtraConfig>>,
54
55    /// Configuration to run the Transcryptor
56    pub transcryptor: Option<ServerConfig<transcryptor::ExtraConfig>>,
57
58    /// Configuration to run the Authentication Server
59    pub auths: Option<ServerConfig<auths::ExtraConfig>>,
60}
61
62/// Represents the level of preparation of a [Config] instance.
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub(crate) enum PreparationState {
65    /// State after loading config file from disk.
66    #[default]
67    Unprepared,
68
69    /// [`Config::preliminary_prep`] has been called.
70    Preliminary,
71
72    /// [`Config`] is completely prepared after [`PrepareConfig::prepare`] has been called on it.
73    Complete,
74}
75
76/// Configuration for one server.  Derefs to `ServerSpecific`..
77#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
78#[serde(deny_unknown_fields)]
79pub struct ServerConfig<ServerSpecific> {
80    /// Port to bind this server to
81    #[serde(default)]
82    pub port: u16,
83
84    /// Ip addresses to bind to
85    #[serde(default = "default_ips")]
86    pub ips: Box<[std::net::IpAddr]>,
87
88    /// Deprecated; consumed and ignored on read, omitted on write.
89    // `skip_serializing` rather than `skip`: an old config may still carry the `bind_to` key, and
90    // `skip` would not consume it — which `deny_unknown_fields` then rejects as an unknown field.
91    #[serde(default, skip_serializing)]
92    pub bind_to: serde::de::IgnoredAny,
93
94    /// Random string used by this server to identify itself.  Randomly generated if not set.
95    /// May be set manually when multiple instances of the same server are used.
96    pub self_check_code: Option<String>,
97
98    /// Key used to sign JSON web tokens generated by this server.
99    /// If `None`, one is generated automatically (which is not suitable for production.)
100    ///
101    /// Generate using `cargo run tools generate signing-key`.
102    pub signing_key: Option<api::SigningKeyBytes>,
103
104    /// Deprecated, superseded by [`signing_key`](Self::signing_key); accepted (and ignored) so that
105    /// existing config files still carrying the old ed25519 `jwt_key` continue to load.  Omitted on
106    /// write.
107    #[serde(default, skip_serializing)]
108    pub jwt_key: serde::de::IgnoredAny,
109
110    /// Secret seed used only to derive this server's own non-permanent local secrets.
111    ///
112    /// Formerly an ElGamal private key that was also published (as `enc_key`) and used to establish
113    /// inter-server shared secrets; both of those roles now belong to the post-quantum KEM, so a
114    /// zero placeholder is published in its stead and only the local-seed role remains.
115    ///
116    /// If `None`, one is generated automatically (which is not suitable for production).
117    pub enc_key: Option<B64>,
118
119    /// Symmetric (HMAC) key, hex-encoded, that authenticates requests to the admin endpoints; the
120    /// same secret signs (admin cli) and verifies (server).
121    /// If `None`, one is generated automatically and printed to the log.
122    pub admin_key: Option<serde_ext::bytes_wrapper::B16>,
123
124    /// If the server needs an object store, use this one.
125    pub object_store: Option<ObjectStoreConfig>,
126
127    /// What version (if any) to claim this pubhubs binary is running.
128    /// Uses [`crate::servers::version()`] by default.  Should only be used for
129    /// troubleshooting/debugging.
130    #[serde(default = "default_version")]
131    pub version: Option<String>,
132
133    #[serde(flatten)]
134    /// Can be accessed via [`Deref`].
135    extra: ServerSpecific,
136}
137
138impl<X> Deref for ServerConfig<X> {
139    type Target = X;
140
141    fn deref(&self) -> &X {
142        &self.extra
143    }
144}
145
146impl<X> DerefMut for ServerConfig<X> {
147    fn deref_mut(&mut self) -> &mut X {
148        &mut self.extra
149    }
150}
151
152fn default_version() -> Option<String> {
153    crate::servers::version().map(str::to_string)
154}
155
156fn default_ips() -> Box<[std::net::IpAddr]> {
157    Box::new([
158        // Bind :: first, as this may already bind 0.0.0.0 too;
159        // if 0.0.0.0 is bound first a separate service will be started for ::.
160        std::net::Ipv6Addr::UNSPECIFIED.into(), // ::
161        std::net::Ipv4Addr::UNSPECIFIED.into(), // 0.0.0.0
162    ])
163}
164
165impl<'a, X> std::net::ToSocketAddrs for &'a ServerConfig<X> {
166    type Iter = Box<dyn Iterator<Item = std::net::SocketAddr> + 'a>;
167
168    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
169        Ok(Box::new(self.ips.iter().map(|ip| (*ip, self.port).into())))
170    }
171}
172
173impl Config {
174    /// Loads [Config] from `path` and generates random values.
175    ///
176    /// Returns [None] if there's no file there.
177    pub fn load_from_path(path: &Path) -> Result<Option<Self>> {
178        // NOTE: the toml crate does not have a `from_reader` like `serde_json` does
179        let mut res: Self = toml::from_str(&match std::fs::read_to_string(path) {
180            Ok(contents) => contents,
181            Err(e) => match e.kind() {
182                std::io::ErrorKind::NotFound => return Ok(None),
183                _ => {
184                    return Err(e)
185                        .with_context(|| format!("could not open config file {}", path.display()));
186                }
187            },
188        })
189        .with_context(|| format!("could not parse config file {}", path.display()))?;
190
191        if res.wd.as_os_str().is_empty() {
192            res.wd = path
193                .canonicalize()
194                .with_context(|| format!("failed to canonicalize path {}", path.display()))?
195                .parent()
196                .expect("did not expect a configuration file without a parent directory")
197                .into();
198        }
199
200        if !res.wd.is_absolute() {
201            anyhow::bail!(
202                "if you specify a working directory (`wd` in {}) it must be absolute",
203                path.display()
204            );
205        }
206
207        res.preliminary_prep()?;
208
209        log::info!(
210            "loaded config file from {};  interpretting relative paths in {}",
211            path.display(),
212            res.wd.display()
213        );
214
215        Ok(Some(res))
216    }
217
218    pub fn preliminary_prep(&mut self) -> Result<()> {
219        anyhow::ensure!(
220            self.preparation_state == PreparationState::Unprepared,
221            "configuration already (partially) prepared: {:?}",
222            self.preparation_state
223        );
224
225        self.host_aliases.resolve_all()?;
226        self.host_aliases.dealias(&mut self.phc_url);
227
228        self.preparation_state = PreparationState::Preliminary;
229
230        Ok(())
231    }
232
233    /// Clones this configuration and strips out everything that's not needed to run
234    /// the specified server.  Also generated any random values not yet set.
235    pub async fn prepare_for(&self, server: crate::servers::Name) -> Result<Self> {
236        anyhow::ensure!(
237            self.preparation_state == PreparationState::Preliminary,
238            "configuration not in the correct preparation state"
239        );
240
241        // destruct to make sure we consider every field of Config
242        let Self {
243            log: _,
244            host_aliases,
245            phc_url,
246            wd,
247            preparation_state,
248            phc: _,
249            transcryptor: _,
250            auths: _,
251        } = self;
252
253        let mut config: Config = Config {
254            log: None, // <- servers do not read this
255            host_aliases: host_aliases.clone(),
256            phc_url: phc_url.clone(),
257            wd: wd.clone(),
258            preparation_state: *preparation_state,
259            phc: None,
260            transcryptor: None,
261            auths: None,
262        };
263
264        macro_rules! clone_only_server {
265            ($server:ident) => {
266                if crate::servers::$server::Server::NAME == server {
267                    assert!(self.$server.is_some());
268                    config.$server.clone_from(&self.$server);
269                }
270            };
271        }
272
273        for_all_servers!(clone_only_server);
274
275        config.prepare().await?;
276
277        Ok(config)
278    }
279
280    /// Prepares [`Config`] to be run; used by [`Config::prepare_for`].
281    pub async fn prepare(&mut self) -> anyhow::Result<()> {
282        let pcc = Pcc::new(actix_web::dev::Extensions::new());
283
284        PrepareConfig::prepare(self, pcc).await
285    }
286
287    /// Creates a new [Config] from the current one by updating a specific part
288    pub fn json_updated(&self, pointer: &str, new_value: serde_json::Value) -> Result<Self> {
289        let mut json_config: serde_json::Value =
290            serde_json::to_value(self).context("failed to serialize config")?;
291
292        let to_be_modified: &mut serde_json::Value =
293            json_config.pointer_mut(pointer).with_context(|| {
294                format!(
295                    "wanted to modify {pointer} of the configuration file, but that points nowhere"
296                )
297            })?;
298
299        to_be_modified.clone_from(&new_value);
300
301        let new_config: Config = serde_json::from_value(json_config).with_context(|| {
302            format!(
303                "wanted to change {pointer} of the configuration file to {new_value}, but the new configuration did not deserialize",
304            )
305        })?;
306
307        Ok(new_config)
308    }
309}
310
311#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
312pub struct ObjectStoreConfig {
313    /// E.g. `memory:///` or `s3://bucket`.
314    ///
315    /// Only `memory://` and `s3://` work: we build `object_store` without its default features (see
316    /// Cargo.toml), so the other schemes it lists — including `file://` — are not compiled in:
317    ///
318    ///   <https://docs.rs/object_store/latest/object_store/enum.ObjectStoreScheme.html>
319    pub url: UrlPwa,
320
321    /// Additional options passed to the builder of the object store.
322    #[serde(default)]
323    pub options: std::collections::HashMap<String, String>,
324}
325
326impl Default for ObjectStoreConfig {
327    fn default() -> Self {
328        Self {
329            url: From::<Url>::from("memory:///".try_into().unwrap()),
330            options: Default::default(),
331        }
332    }
333}
334
335pub mod phc {
336    use super::*;
337
338    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
339    #[serde(deny_unknown_fields)]
340    pub struct ExtraConfig {
341        /// Where can we reach the transcryptor?
342        pub transcryptor_url: UrlPwa,
343
344        /// Where can we reach the authentication server?
345        pub auths_url: UrlPwa,
346
347        /// The URL to pubhubs used by end-clients.
348        ///
349        /// Currently `https://app.pubhubs.net` for production, `https://main.pubhubs.ihub.ru.nl` for
350        /// acceptance, and `http://localhost:8080` for local development.  
351        pub global_client_url: UrlPwa,
352
353        /// The hubs that are known to us
354        pub hubs: Vec<hub::BasicInfo<UrlPwa>>,
355
356        /// `x_PHC` from the whitepaper; randomly generated if not set
357        ///
358        /// Generate using `cargo run tools generate scalar`.
359        pub master_enc_key_part: Option<elgamal::PrivateKey>,
360
361        /// Secret used to derive [`Attr::id`]s.
362        ///
363        /// Randomly generated if not set, which is not suitable for production.
364        ///
365        /// [`Attr::id`]: crate::attr::Attr::id
366        pub attr_id_secret: Option<B64UU>,
367
368        /// Authentication tokens issued to the global client are valid for this duration.
369        ///
370        /// Auth tokens are validated based on their own contents - there's no list of valid
371        /// authentication tokens in a database somewhere.  This means that when a user is banned,
372        /// the authentication tokens remain valid until they expire.  The validity duration of
373        /// auth tokens should thus not be too long.
374        #[serde(with = "time_ext::human_duration")]
375        #[serde(default = "default_auth_token_validity")]
376        pub auth_token_validity: core::time::Duration,
377
378        /// An expired auth token can be refreshed (without the user logging in again) for at most
379        /// this long after it expired.  Logs out users that have been inactive for this long.
380        #[serde(with = "time_ext::human_duration")]
381        #[serde(default = "default_auth_token_max_idle")]
382        pub auth_token_max_idle: core::time::Duration,
383
384        /// Auth tokens can be refreshed for at most this long after the user logged in, after
385        /// which the user must log in again, active or not.
386        ///
387        /// The max duration stops a leaked authtoken from being valid indefinitely.
388        #[serde(with = "time_ext::human_duration")]
389        #[serde(default = "default_auth_session_max_duration")]
390        pub auth_session_max_duration: core::time::Duration,
391
392        /// [`api::phc::user::PpNonce`]s issued to the global client are valid for this duration.
393        #[serde(with = "time_ext::human_duration")]
394        #[serde(default = "default_pp_nonce_validity")]
395        pub pp_nonce_validity: core::time::Duration,
396
397        /// Registration pseudonyms issued to the global client via [`api::phc::user::CardPseudEP`]
398        /// are valid for this duration.
399        #[serde(with = "time_ext::human_duration")]
400        #[serde(default = "default_card_pseud_validity")]
401        pub card_pseud_validity: core::time::Duration,
402
403        /// Secret used to derive `hmac`s for the retrieval of user objects.
404        ///
405        /// Randomly generated if not set.
406        pub user_object_hmac_secret: Option<B64UU>,
407
408        /// Quotas for a user
409        #[serde(default)]
410        pub user_quota: api::phc::user::Quota,
411
412        /// Deprecated; consumed and ignored on read, omitted on write.
413        #[serde(default, skip_serializing)]
414        pub card: serde::de::IgnoredAny,
415
416        #[serde(default)]
417        pub hub_cache: crate::servers::phc::HubCacheConfig,
418    }
419
420    fn default_auth_token_validity() -> core::time::Duration {
421        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
422        // to their Yivi app
423    }
424
425    fn default_auth_token_max_idle() -> core::time::Duration {
426        core::time::Duration::from_secs(90 * 24 * 60 * 60) // 90 days
427    }
428
429    fn default_auth_session_max_duration() -> core::time::Duration {
430        core::time::Duration::from_secs(365 * 24 * 60 * 60) // 1 year
431    }
432
433    fn default_pp_nonce_validity() -> core::time::Duration {
434        core::time::Duration::from_secs(30)
435        // no user interaction required
436    }
437
438    fn default_card_pseud_validity() -> core::time::Duration {
439        core::time::Duration::from_secs(30)
440        // no user interaction required
441    }
442}
443
444pub mod transcryptor {
445    use super::*;
446
447    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
448    #[serde(deny_unknown_fields)]
449    pub struct ExtraConfig {
450        /// `x_T` from the whitepaper; randomly generated if not set
451        ///
452        /// Generate using `cargo run tools generate scalar`.
453        pub master_enc_key_part: Option<elgamal::PrivateKey>,
454
455        /// Used to generate the *pseudonymisation factor secret* `g_H` given hub `H`'s identifier.
456        ///
457        /// Should **never be changed** in a production environment.
458        ///
459        /// Randomly generated when not set.t
460        pub pseud_factor_secret: Option<B64UU>,
461
462        /// Hybrid post-quantum [`kem`] decapsulation key, used to establish a shared secret
463        /// with pubhubs central.  Randomly generated when not set.
464        ///
465        /// Generate using `cargo run tools generate decap-key`
466        pub decap_key: Option<kem::DecapKeyBytes>,
467    }
468}
469
470pub mod auths {
471    use super::*;
472
473    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
474    #[serde(deny_unknown_fields)]
475    pub struct ExtraConfig {
476        #[serde(default)]
477        pub attribute_types: Vec<attr::Type>,
478
479        /// Yivi configuration.  If `None`, yivi is not supported.
480        pub yivi: Option<YiviConfig>,
481
482        /// Authentication must be completed within this timeframe
483        /// formatted as string understood by [`humantime::parse_duration`] such as `1 week`.
484        #[serde(with = "time_ext::human_duration")]
485        #[serde(default = "default_auth_window")]
486        pub auth_window: core::time::Duration,
487
488        /// Maximum number of attribute types a single [`api::auths::AuthStartReq`] may request,
489        /// and the maximum number of alternatives allowed per attribute type
490        /// (see [`api::auths::AuthStartReq::attr_type_choices`]).
491        ///
492        /// Bounds the work an anonymous authentication-start request can trigger.
493        #[serde(default = "default_max_attr_types_per_req")]
494        pub max_attr_types_per_req: usize,
495
496        /// Used to derive attribute keys (see [`api::auths::AttrKeysEP`])
497        ///
498        /// Randomly generated when not set.  When changed, users loose access to all data
499        /// stored at PHC.
500        pub attr_key_secret: Option<B64UU>,
501
502        /// Hybrid post-quantum [`kem`] decapsulation key, used to establish a shared secret
503        /// with pubhubs central.  Randomly generated when not set.
504        ///
505        /// Generate using `cargo run tools generate decap-key`
506        pub decap_key: Option<kem::DecapKeyBytes>,
507    }
508
509    fn default_auth_window() -> core::time::Duration {
510        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
511        // to their Yivi app
512    }
513
514    fn default_max_attr_types_per_req() -> usize {
515        4
516    }
517
518    impl ExtraConfig {
519        /// Removes the [`attr::SourceDetails`]s of unsupported sources from
520        /// [`attribute_types`].
521        ///
522        /// [`attribute_types`]: Self::attribute_types
523        pub(super) fn filter_attribute_types(&mut self) {
524            let mut supported_sources: std::collections::HashSet<attr::Source> = Default::default();
525
526            if self.yivi.is_some() {
527                assert!(supported_sources.insert(attr::Source::Yivi));
528            }
529
530            for attr_type in self.attribute_types.iter_mut() {
531                attr_type.filter_sources(|s| supported_sources.contains(&s))
532            }
533        }
534    }
535
536    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
537    #[serde(deny_unknown_fields)]
538    pub struct YiviConfig {
539        /// Where can the Yivi server trusted by the authentication server be reached
540        /// by the hub client for starting disclosure requests?
541        pub requestor_url: UrlPwa,
542
543        pub requestor_creds: yivi::Credentials<yivi::SigningKey>,
544
545        /// What server name to expect in signed session results
546        pub server_name: String,
547
548        /// Verify signed session results using this key.  If not set the key is retrieved
549        /// from the yivi server.
550        pub server_key: Option<yivi::VerifyingKey>,
551
552        /// Fine-tune handling of chained sessions, see
553        /// [`api::auths::AuthStartReq::yivi_chained_session`].
554        #[serde(default)]
555        pub chained_sessions: crate::servers::auths::yivi::ChainedSessionsConfig,
556
557        /// Configuration of the pubhubs card issued by the authentication server
558        #[serde(default)]
559        pub card: crate::servers::auths::card::CardConfig,
560    }
561
562    impl YiviConfig {
563        pub fn server_creds(&self) -> yivi::Credentials<yivi::VerifyingKey> {
564            yivi::Credentials {
565                name: self.server_name.clone(),
566                key: self
567                    .server_key
568                    .clone()
569                    .expect("bug: YiviConfig was not properly prepared"),
570            }
571        }
572    }
573}
574
575/// Trait to prepare [`Config`] for use by initializing random values,
576/// and replacing aliases in [`UrlPwa`]s.
577trait PrepareConfig<C> {
578    async fn prepare(&mut self, context: C) -> anyhow::Result<()>;
579}
580
581type Pcc = std::rc::Rc<actix_web::dev::Extensions>;
582
583impl PrepareConfig<Pcc> for Config {
584    async fn prepare(&mut self, mut c: Pcc) -> anyhow::Result<()> {
585        anyhow::ensure!(
586            self.preparation_state == PreparationState::Preliminary,
587            "configuration not properly prepared"
588        );
589
590        // temporarily move `host_aliases` into Pcc
591        Pcc::get_mut(&mut c)
592            .unwrap()
593            .insert(std::mem::take(&mut self.host_aliases));
594
595        macro_rules! prep {
596            ($server:ident) => {
597                if let Some(ref mut server) = self.$server {
598                    server.prepare(c.clone()).await?;
599                }
600            };
601        }
602
603        for_all_servers!(prep);
604
605        // move `host_aliases` back to self, drop the substitute
606        drop(std::mem::replace(
607            &mut self.host_aliases,
608            Pcc::get_mut(&mut c)
609                .unwrap()
610                .remove::<HostAliases>()
611                .unwrap(),
612        ));
613
614        self.preparation_state = PreparationState::Complete;
615
616        Ok(())
617    }
618}
619
620impl<Extra: PrepareConfig<Pcc> + GetServerType> PrepareConfig<Pcc> for ServerConfig<Extra> {
621    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
622        if self.port == 0 {
623            self.port = Extra::ServerT::default_port();
624        }
625
626        self.self_check_code
627            .get_or_insert_with(crate::misc::crypto::random_alphanumeric);
628
629        if self.signing_key.is_none() {
630            self.signing_key = Some(
631                api::SigningKey::generate()
632                    .map_err(|_| anyhow::anyhow!("failed to generate signing key"))?
633                    .encode(),
634            );
635        }
636        self.enc_key.get_or_insert_with(|| {
637            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
638        });
639
640        if self.admin_key.is_none() {
641            let admin_key =
642                serde_ext::bytes_wrapper::B16::from_bytes(crate::misc::crypto::random_32_bytes());
643
644            log::info!("{} admin key: {admin_key}", Extra::ServerT::NAME);
645
646            self.admin_key = Some(admin_key);
647        }
648
649        if let &mut Some(&mut ref mut osc) = &mut self.object_store.as_mut() {
650            c.get::<HostAliases>()
651                .expect("host aliases were not passed along")
652                .dealias(&mut osc.url);
653        }
654
655        self.extra.prepare(c).await?;
656
657        Ok(())
658    }
659}
660
661/// Populates `slot` with a freshly generated [`kem::DecapKeyBytes`] if it is `None`.
662fn ensure_decap_key(slot: &mut Option<kem::DecapKeyBytes>) -> anyhow::Result<()> {
663    if slot.is_none() {
664        *slot = Some(
665            kem::DecapKey::generate()
666                .and_then(|dk| dk.encode())
667                .map_err(|_| anyhow::anyhow!("generating kem decapsulation key"))?,
668        );
669    }
670    Ok(())
671}
672
673impl PrepareConfig<Pcc> for transcryptor::ExtraConfig {
674    async fn prepare(&mut self, _c: Pcc) -> anyhow::Result<()> {
675        self.master_enc_key_part
676            .get_or_insert_with(elgamal::PrivateKey::random);
677
678        self.pseud_factor_secret.get_or_insert_with(|| {
679            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
680        });
681
682        ensure_decap_key(&mut self.decap_key)?;
683
684        Ok(())
685    }
686}
687
688impl PrepareConfig<Pcc> for phc::ExtraConfig {
689    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
690        self.master_enc_key_part
691            .get_or_insert_with(elgamal::PrivateKey::random);
692
693        self.attr_id_secret.get_or_insert_with(|| {
694            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
695        });
696
697        self.user_object_hmac_secret.get_or_insert_with(|| {
698            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
699        });
700
701        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
702
703        ha.dealias(&mut self.transcryptor_url);
704        ha.dealias(&mut self.auths_url);
705        ha.dealias(&mut self.global_client_url);
706
707        for hub in self.hubs.iter_mut() {
708            hub.prepare(c.clone()).await?;
709        }
710
711        Ok(())
712    }
713}
714
715impl PrepareConfig<Pcc> for hub::BasicInfo<UrlPwa> {
716    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
717        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
718
719        ha.dealias(&mut self.url);
720
721        Ok(())
722    }
723}
724
725impl PrepareConfig<Pcc> for auths::ExtraConfig {
726    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
727        if let Some(ref mut yivi_cfg) = self.yivi {
728            yivi_cfg.prepare(c).await?;
729        }
730
731        self.filter_attribute_types();
732
733        self.attr_key_secret.get_or_insert_with(|| {
734            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
735        });
736
737        ensure_decap_key(&mut self.decap_key)?;
738
739        Ok(())
740    }
741}
742
743impl PrepareConfig<Pcc> for auths::YiviConfig {
744    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
745        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
746
747        ha.dealias(&mut self.requestor_url);
748
749        if self.server_key.is_none() {
750            let pk_url = self.requestor_url.as_ref().join("publickey")?.to_string();
751            log::debug!("yivi server key not set; retrieving from {pk_url}");
752
753            let client = awc::Client::default();
754            let payload: bytes::Bytes = crate::misc::task::retry(|| async {
755                match client.get(&pk_url).send().await {
756                    Ok(mut res) => match res.body().await {
757                        Ok(body) => Ok::<_, std::convert::Infallible>(Some(body)),
758                        Err(err) => {
759                            log::warn!(
760                                "error reading yivi server response at {}, retrying: {err}",
761                                self.requestor_url
762                            );
763                            Ok(None)
764                        }
765                    },
766                    Err(err) => {
767                        log::warn!(
768                            "could not reach yivi server at {}, retrying: {err}",
769                            self.requestor_url
770                        );
771                        Ok(None)
772                    }
773                }
774            })
775            .await
776            .expect("retry does not fail")
777            .ok_or_else(|| {
778                log::error!("could not reach yivi server at {}", self.requestor_url);
779                anyhow::anyhow!(
780                    "getting Yivi server's public key from {pk_url} failed after retries"
781                )
782            })?;
783
784            self.server_key = Some(yivi::VerifyingKey::RS256(
785                jwt::RS256Vk::from_public_key_pem(std::str::from_utf8(&payload)?)
786                    .context("decoding public key at {pk_url}")?,
787            ));
788        }
789
790        Ok(())
791    }
792}
793
794/// Used to implement the `server_config` method on `crate::servers::<SERVER>::Details`.
795pub trait GetServerConfig {
796    type Extra;
797
798    fn server_config(config: &Config) -> &ServerConfig<Self::Extra>;
799}
800
801macro_rules! implement_get_server_config {
802    ($server:ident) => {
803        impl GetServerConfig for crate::servers::$server::Details {
804            type Extra = crate::servers::config::$server::ExtraConfig;
805
806            fn server_config(config: &Config) -> &ServerConfig<Self::Extra> {
807                &config.$server.as_ref().unwrap()
808            }
809        }
810    };
811}
812
813for_all_servers!(implement_get_server_config);
814
815/// Used to implement the `ServerT` associated type of `<SERVER>::ExtraConfig`.
816trait GetServerType {
817    type ServerT: crate::servers::Server;
818}
819
820macro_rules! implement_server_type {
821    ($server:ident) => {
822        impl GetServerType for $server::ExtraConfig {
823            type ServerT = crate::servers::$server::Server;
824        }
825    };
826}
827
828for_all_servers!(implement_server_type);