1use std::cell::{Cell, RefCell};
3use std::collections::HashMap;
4use std::convert::Infallible;
5use std::ops::{Deref, DerefMut};
6use std::rc::Rc;
7
8use actix_web::web;
9use sha2::digest::Digest as _;
10
11use crate::api::{self, ApiResultExt as _, EndpointDetails as _, NoPayload};
12use crate::client;
13use crate::common::secret::DigestibleSecret as _;
14use crate::handle;
15use crate::id;
16use crate::misc::crypto;
17use crate::misc::jwt;
18use crate::misc::serde_ext;
19use crate::misc::time_ext;
20use crate::phcrypto;
21use crate::servers::{
22 self, AppBase, AppCreatorBase, Constellation, DiscoverVerdict, Handle, Server as _,
23 constellation,
24};
25
26use crate::{
27 common::{elgamal, kem},
28 hub,
29};
30
31pub type Server = servers::ServerImpl<Details>;
33
34pub struct Details;
35impl servers::Details for Details {
36 const NAME: servers::Name = servers::Name::PubhubsCentral;
37 type AppT = App;
38 type AppCreatorT = AppCreator;
39 type ExtraRunningState = ExtraRunningState;
40 type RunningStateSeed = RunningStateSeed;
41 type ExtraSharedState = ExtraSharedState;
42 type ExtraServerState = ();
43 type ObjectStoreT = servers::object_store::DefaultObjectStore;
44
45 fn create_running_state(
46 _server: &Server,
47 _constellation: &Constellation,
48 seed: &RunningStateSeed,
49 ) -> anyhow::Result<Self::ExtraRunningState> {
50 let t_ss = seed.t_ss.clone();
52 let auths_ss = seed.auths_ss.clone();
53 Ok(ExtraRunningState {
54 attr_signing_key: phcrypto::attr_signing_key(&auths_ss),
55 t_sealing_secret: phcrypto::sealing_secret(&t_ss),
56 auths_sealing_secret: phcrypto::sealing_secret(&auths_ss),
57 master_enc_key: seed.master_enc_key.clone(),
58 t_ss,
59 auths_ss,
60 })
61 }
62
63 fn create_extra_shared_state(config: &servers::Config) -> anyhow::Result<ExtraSharedState> {
64 let mut hubs: crate::map::Map<hub::BasicInfo> = Default::default();
65
66 for basic_hub_info in config.phc.as_ref().unwrap().hubs.iter() {
67 if let Some(hub_or_id) = hubs.insert_new(basic_hub_info.clone().into()) {
68 anyhow::bail!("two hubs are known as {hub_or_id}");
69 }
70 }
71
72 Ok(ExtraSharedState { hubs })
73 }
74
75 fn create_extra_server_state(_config: &servers::Config) -> anyhow::Result<()> {
76 Ok(())
77 }
78}
79
80pub struct ExtraSharedState {
81 pub hubs: crate::map::Map<hub::BasicInfo>,
83}
84
85pub struct App {
86 pub base: AppBase<Server>,
87 pub transcryptor_url: url::Url,
88 pub auths_url: url::Url,
89 pub global_client_url: url::Url,
90 pub master_enc_key_part: elgamal::PrivateKey,
91 pub attr_id_secret: Box<[u8]>,
92 pub auth_token_secret: crypto::SealingKey,
93 pub auth_token_validity: core::time::Duration,
94 pub auth_token_max_idle: core::time::Duration,
95 pub auth_session_max_duration: core::time::Duration,
96 pub pp_nonce_secret: crypto::SealingKey,
97 pub pp_nonce_validity: core::time::Duration,
98 pub user_object_hmac_secret: Box<[u8]>,
99 pub quota: api::phc::user::Quota,
100 pub card_pseud_validity: core::time::Duration,
101
102 pub broadcast: tokio::sync::broadcast::Sender<InterAppMsg>,
104
105 pub cached_hub_info: std::cell::RefCell<api::CachedResponse<api::phc::user::CachedHubInfoEP>>,
106 pub hub_cache_config: HubCacheConfig,
107}
108
109impl Deref for App {
110 type Target = AppBase<Server>;
111
112 fn deref(&self) -> &Self::Target {
113 &self.base
114 }
115}
116
117#[derive(Clone, Debug)]
118pub struct ExtraRunningState {
119 pub(super) t_ss: kem::SharedSecret,
121
122 pub(super) auths_ss: kem::SharedSecret,
124
125 pub(super) attr_signing_key: jwt::HS256,
129
130 pub(super) t_sealing_secret: crypto::SealingKey,
132
133 #[expect(dead_code)]
135 pub(super) auths_sealing_secret: crypto::SealingKey,
136
137 pub(super) master_enc_key: Option<elgamal::PublicKey>,
142}
143
144pub struct RunningStateSeed {
149 pub(super) t_ss: kem::SharedSecret,
150 pub(super) auths_ss: kem::SharedSecret,
151 pub(super) master_enc_key: Option<elgamal::PublicKey>,
152}
153
154impl crate::servers::App<Server> for App {
155 fn configure_actix_app(self: &Rc<Self>, sc: &mut web::ServiceConfig) {
156 api::phc::hub::TicketEP::add_to(self, sc, App::handle_hub_ticket);
157 api::server::HubPingEP::add_to(self, sc, App::handle_hub_ping);
158
159 api::phc::user::WelcomeEP::caching_add_to(self, sc, App::cached_handle_user_welcome);
160 api::phc::user::EnterEP::add_to(self, sc, App::handle_user_enter);
161 api::phc::user::RefreshEP::add_to(self, sc, App::handle_user_refresh);
162 api::phc::user::StateEP::add_to(self, sc, App::handle_user_state);
163
164 api::phc::user::NewObjectEP::add_to(self, sc, App::handle_user_new_object);
165 api::phc::user::OverwriteObjectEP::add_to(self, sc, App::handle_user_overwrite_object);
166 api::phc::user::GetObjectEP::add_to(self, sc, App::handle_user_get_object);
167
168 api::phc::user::PppEP::add_to(self, sc, App::handle_user_ppp);
169 api::phc::user::HhppEP::add_to(self, sc, App::handle_user_hhpp);
170
171 api::phc::user::CardPseudEP::add_to(self, sc, App::handle_user_card_pseud);
172
173 sc.app_data(web::Data::new(self.clone())).route(
175 api::phc::user::CachedHubInfoEP::PATH,
176 web::method(api::phc::user::CachedHubInfoEP::METHOD).to(App::handle_cached_hub_info),
177 );
178 }
179
180 fn check_constellation(&self, _constellation: &Constellation) -> bool {
181 panic!("PHC creates the constellation; it has no need to check it")
182 }
183
184 async fn discover(
185 self: &Rc<Self>,
186 _phc_di: api::DiscoveryInfoResp,
187 ) -> api::Result<DiscoverVerdict<RunningStateSeed>> {
188 let (tdi_res, asdi_res) = tokio::join!(
189 self.discovery_info_of(servers::Name::Transcryptor, &self.transcryptor_url),
190 self.discovery_info_of(servers::Name::AuthenticationServer, &self.auths_url)
191 );
192
193 let tdi = tdi_res?;
194 let asdi = asdi_res?;
195
196 for (odi, other_server_name) in [
197 (&tdi, servers::Name::Transcryptor),
198 (&asdi, servers::Name::AuthenticationServer),
199 ] {
200 if let Some(ref other_version) = odi.version
201 && let Some(my_version) = &self.version
202 {
203 let other_version = crate::servers::version::to_semver(other_version).map_err(|err| {
204 log::error!(
205 "{my_server_name}: could not parse semantic version returned by {other_server_name}: {other_version}: {err}",
206 my_server_name = Server::NAME
207 );
208 api::ErrorCode::InternalError
209 })?;
210
211 let my_version = crate::servers::version::to_semver(my_version).map_err(|err| {
212 log::error!(
213 "{my_server_name}: could not parse my semantic version {my_version}: {err}",
214 my_server_name = Server::NAME
215 );
216 api::ErrorCode::InternalError
217 })?;
218
219 if my_version < other_version {
220 log::warn!(
221 "{my_server_name}: {other_server_name}'s version ({other_version}) > my version ({my_version})",
222 my_server_name = Server::NAME,
223 );
224 return Ok(DiscoverVerdict::BinaryOutdated);
225 }
226 } else {
227 log::warn!(
228 "{my_server_name}: not checking my version ({my_version}) against {other_server_name}'s version ({other_version})",
229 my_server_name = Server::NAME,
230 my_version = crate::servers::version::VERSION,
231 other_version = odi.version.as_deref().unwrap_or("n/a")
232 );
233 }
234 }
235
236 let current_rs = self.running_state.as_ref();
237
238 let (transcryptor_encap_key_id, transcryptor_ss_encap, t_ss) = Self::encap_or_reuse(
239 servers::Name::Transcryptor,
240 tdi.encap_key.as_ref(),
241 current_rs.map(|rs| {
242 (
243 &rs.constellation.transcryptor_encap_key_id,
244 &rs.constellation.transcryptor_ss_encap,
245 &rs.t_ss,
246 )
247 }),
248 )?;
249
250 let (auths_encap_key_id, auths_ss_encap, auths_ss) = Self::encap_or_reuse(
251 servers::Name::AuthenticationServer,
252 asdi.encap_key.as_ref(),
253 current_rs.map(|rs| {
254 (
255 &rs.constellation.auths_encap_key_id,
256 &rs.constellation.auths_ss_encap,
257 &rs.auths_ss,
258 )
259 }),
260 )?;
261
262 let new_constellation_inner = constellation::Inner {
263 transcryptor_master_enc_key_part_hash: tdi.master_enc_key_part_hash.ok_or_else(
265 || {
266 log::error!("transcryptor's discovery info has no master_enc_key_part_hash");
267 api::ErrorCode::InternalError
268 },
269 )?,
270 phc_master_enc_key_part_hash: phcrypto::master_enc_key_part_hash(
271 self.master_enc_key_part.public_key(),
272 ),
273 global_client_url: self.global_client_url.clone(),
274 phc_url: self.phc_url.clone(),
275 phc_jwt_key: crate::misc::serde_ext::ByteArray::from(
277 self.shared.signing_key.verifying_key().ed25519_bytes(),
278 )
279 .into(),
280 phc_verifying_key: self.shared.verifying_key_bytes.clone(),
281 transcryptor_url: self.transcryptor_url.clone(),
282 transcryptor_verifying_key: tdi.verifying_key.clone(),
284 transcryptor_encap_key_id,
285 transcryptor_ss_encap,
286 auths_url: self.auths_url.clone(),
287 auths_verifying_key: asdi.verifying_key.clone(),
288 auths_encap_key_id,
289 auths_ss_encap,
290 ph_version: self.version.clone(),
291 };
292
293 let new_constellation_id = constellation::Inner::derive_id(&new_constellation_inner);
294
295 let prior_master_enc_key = 'prior: {
300 let Some(rs) = self.running_state.as_ref() else {
301 break 'prior None;
302 };
303 if rs.constellation.transcryptor_master_enc_key_part_hash
304 != new_constellation_inner.transcryptor_master_enc_key_part_hash
305 || rs.constellation.phc_master_enc_key_part_hash
306 != new_constellation_inner.phc_master_enc_key_part_hash
307 {
308 log::warn!(
309 "a master encryption key part changed; this invalidates all existing \
310 polymorphic pseudonyms and should only happen in an ephemeral test setup"
311 );
312 break 'prior None;
313 }
314 rs.master_enc_key.clone()
315 };
316
317 let master_enc_key = match prior_master_enc_key {
321 existing @ Some(_) => existing,
322 None => self.master_enc_key_from_sealed_part(&tdi, &t_ss, new_constellation_id)?,
323 };
324
325 if self.running_state.is_none()
326 || self.running_state.as_ref().unwrap().constellation.inner != new_constellation_inner
327 {
328 if let Some(ref running_state) = self.running_state {
329 log::info!(
330 "Detected change in constellation {} -> {}",
331 running_state.constellation.id,
332 new_constellation_id
333 );
334 } else {
335 log::info!("Computed constellation {new_constellation_id}");
336 }
337
338 return Ok(DiscoverVerdict::ConstellationOutdated {
339 new_constellation: Box::new(Constellation {
340 id: new_constellation_id,
341 created_at: api::NumericDate::now(),
342 inner: new_constellation_inner,
343 }),
344 seed: RunningStateSeed {
345 t_ss,
346 auths_ss,
347 master_enc_key,
348 },
349 });
350 }
351
352 let running_state = self.running_state.as_ref().expect(
353 "running_state should be set here, but isn't (the block above returns when it is None)",
354 );
355
356 if running_state.master_enc_key != master_enc_key {
360 log::info!("master encryption key (re)derived; updating running state only");
361 return Ok(DiscoverVerdict::RunningStateOutdated {
362 seed: RunningStateSeed {
363 t_ss,
364 auths_ss,
365 master_enc_key,
366 },
367 });
368 }
369
370 let constellation = &running_state.constellation;
371
372 log::info!("My own constellation is up-to-date");
373
374 let mut js = tokio::task::JoinSet::new();
377
378 if tdi
379 .constellation_or_id
380 .as_ref()
381 .is_some_and(|c| *c.id() != constellation.id)
382 {
383 log::info!(
385 "{phc}: {t}'s constellation is out of date - invoking its discovery..",
386 phc = servers::Name::PubhubsCentral,
387 t = servers::Name::Transcryptor
388 );
389 let url = self.transcryptor_url.clone();
390 js.spawn_local(
391 self.client
392 .query::<api::DiscoveryRun>(&url, NoPayload)
393 .into_future(),
394 );
395 }
396
397 if asdi
398 .constellation_or_id
399 .as_ref()
400 .is_some_and(|c| *c.id() != constellation.id)
401 {
402 log::info!(
404 "{phc}: {auths}'s constellation is out of date - invoking its discovery..",
405 phc = servers::Name::PubhubsCentral,
406 auths = servers::Name::AuthenticationServer
407 );
408 let url = self.auths_url.clone();
409 js.spawn_local(
410 self.client
411 .query::<api::DiscoveryRun>(&url, NoPayload)
412 .into_future(),
413 );
414 }
415
416 let result_maybe = js.join_next().await;
417
418 js.detach_all();
421
422 match result_maybe {
423 None => {
425 if tdi.constellation_or_id.is_some() && asdi.constellation_or_id.is_some() {
426 assert!(
430 running_state.master_enc_key.is_some(),
431 "all servers are on our constellation, but the master encryption key was not derived"
432 );
433 log::info!("Constellation of all servers up to date!");
434 Ok(DiscoverVerdict::Alright)
435 } else {
436 log::info!("Waiting for the other servers to update their constellation.");
437 Err(api::ErrorCode::PleaseRetry)
438 }
439 }
440 Some(Err(join_err)) => {
442 log::error!("discovery run task joined unexpectedly: {join_err}");
443 Err(api::ErrorCode::InternalError)
444 }
445 Some(Ok(res)) => {
447 match res.retryable() {
448 Ok(_) => {
449 Err(api::ErrorCode::PleaseRetry)
453 }
454 Err(err) => {
455 log::error!("Failed to run discovery of other server: {err}",);
456 Err(api::ErrorCode::InternalError)
457 }
458 }
459 }
460 }
461 }
462
463 fn master_enc_key_part(&self) -> Option<&elgamal::PrivateKey> {
464 Some(&self.master_enc_key_part)
465 }
466
467 async fn local_task(weak: std::rc::Weak<Self>) {
468 use tokio::sync::broadcast::error::RecvError;
469
470 let mut receiver: tokio::sync::broadcast::Receiver<InterAppMsg>;
471
472 {
473 let Some(app) = weak.upgrade() else {
474 log::debug!("App is gone before local task started");
475 return;
476 };
477
478 receiver = app.broadcast.subscribe();
479 }
480
481 loop {
482 let recv_result = receiver.recv().await;
483 let Ok(msg) = recv_result else {
484 match recv_result.unwrap_err() {
485 RecvError::Closed => {
486 return;
487 }
488 RecvError::Lagged(skipped) => {
489 log::error!(
490 "PHC local task on {:?} is lagging behind, \
491 and has skipped processing {skipped} messages!",
492 std::thread::current().id()
493 );
494 continue;
495 }
496 }
497 };
498
499 let Some(app) = weak.upgrade() else {
500 log::warn!("Inter app message dropped because app is gone");
501 return;
502 };
503
504 match msg {
505 InterAppMsg::UpdatedHubInfo(cached_hub_info) => {
506 app.cached_hub_info.replace(cached_hub_info);
507 }
508 }
509 }
510 }
511
512 async fn global_task(app: Rc<Self>) -> anyhow::Result<Infallible> {
513 let localset = tokio::task::LocalSet::new();
514 let _hcu = HubCacheUpdater::new(app, &localset);
515
516 localset.await;
517
518 log::error!("bug: PHC global task exits prematurely");
519 anyhow::bail!("bug: PHC global task exits prematurely")
520 }
521}
522
523#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
525pub struct HubCacheConfig {
526 #[serde(with = "time_ext::human_duration")]
528 #[serde(default = "default_hub_cache_request_interval")]
529 request_interval: core::time::Duration,
530
531 #[serde(with = "time_ext::human_duration")]
533 #[serde(default = "default_hub_cache_request_timeout")]
534 request_timeout: core::time::Duration,
535
536 #[serde(with = "time_ext::human_duration")]
538 #[serde(default = "default_hub_cache_push_interval")]
539 push_interval: core::time::Duration,
540}
541
542fn default_hub_cache_request_interval() -> core::time::Duration {
543 core::time::Duration::from_secs(60)
544}
545
546fn default_hub_cache_request_timeout() -> core::time::Duration {
547 core::time::Duration::from_secs(10)
548}
549
550fn default_hub_cache_push_interval() -> core::time::Duration {
551 core::time::Duration::from_secs(5)
552}
553
554impl Default for HubCacheConfig {
555 fn default() -> Self {
556 serde_ext::default_object()
557 }
558}
559
560struct HubCacheUpdater {
561 app: Rc<App>,
562 hub_info: RefCell<HashMap<handle::Handle, Option<api::hub::InfoResp>>>,
563 unpublished_updates: Cell<bool>,
564}
565
566impl HubCacheUpdater {
567 fn new(app: Rc<App>, localset: &tokio::task::LocalSet) -> Rc<Self> {
568 let hcu = Rc::new(Self {
569 app: app.clone(),
570 hub_info: RefCell::new(Default::default()),
571 unpublished_updates: Cell::new(false),
572 });
573
574 for basic_hub_info in app.shared.hubs.values() {
575 localset.spawn_local(hcu.clone().handle_hub(basic_hub_info.clone()));
576 }
577
578 localset.spawn_local(hcu.clone().push_updates());
579
580 hcu
581 }
582
583 async fn handle_hub(self: Rc<Self>, basic_hub_info: hub::BasicInfo) {
584 let hub_handle = basic_hub_info.handles.preferred();
585
586 self.hub_info.borrow_mut().insert(hub_handle.clone(), None);
587
588 let mut interval = tokio::time::interval(self.app.hub_cache_config.request_interval);
589 let mut failure_since: Option<std::time::SystemTime> = None;
590
591 loop {
592 interval.tick().await;
593
594 let hir = self
595 .app
596 .client
597 .query::<api::hub::InfoEP>(&basic_hub_info.url, api::NoPayload)
598 .quiet()
599 .timeout(self.app.hub_cache_config.request_timeout)
600 .await;
601
602 let Ok(hi) = hir else {
603 if failure_since.is_none() {
604 log::warn!("hub {hub_handle} not reachable");
605 failure_since = Some(std::time::SystemTime::now());
606 }
607
608 continue;
609 };
610
611 if let Some(time) = failure_since.take() {
612 log::info!(
613 "hub {hub_handle} is reachable again; it was unreachable since {}.)",
614 time_ext::format_time(time)
615 );
616 }
617
618 use std::collections::hash_map::Entry;
619
620 {
621 let mut hub_info = self.hub_info.borrow_mut();
622
623 let Entry::Occupied(mut oe) = hub_info.entry(hub_handle.clone()) else {
624 panic!("bug: hub info cache entry for hub {hub_handle} disappeared");
625 };
626
627 if oe.get().as_ref() == Some(&hi) {
628 continue;
629 }
630
631 oe.insert(Some(hi));
632 }
633
634 if !self.unpublished_updates.replace(true) {
635 log::trace!("new hub info on {hub_handle} will be pushed to app soon");
636 }
637 }
638 }
639
640 async fn push_updates(self: Rc<Self>) {
641 let mut interval = tokio::time::interval(self.app.hub_cache_config.push_interval);
642
643 loop {
644 interval.tick().await;
645
646 if !self.unpublished_updates.get() {
647 continue;
648 }
649
650 let chir = api::phc::user::CachedHubInfoResp {
651 hubs: self.hub_info.borrow().clone(),
652 };
653
654 let cr = api::Responder(Ok(chir)).into_cached();
655
656 log::trace!("pushing updated cached hub info to apps");
657 if self
658 .app
659 .broadcast
660 .send(InterAppMsg::UpdatedHubInfo(cr))
661 .is_err()
662 {
663 log::error!("failed to internally broadcast updated hub information");
664 continue;
665 };
666
667 self.unpublished_updates.set(false);
668 }
669 }
670}
671
672impl App {
673 async fn discovery_info_of(
675 &self,
676 name: servers::Name,
677 url: &url::Url,
678 ) -> api::Result<api::DiscoveryInfoResp> {
679 let tdi = self
680 .client
681 .query::<api::DiscoveryInfo>(url, NoPayload)
682 .await
683 .into_server_result()?;
684
685 client::discovery::DiscoveryInfoCheck {
686 phc_url: &self.phc_url,
687 name,
688 self_check_code: None,
689 constellation: None,
690 }
691 .check(tdi, url)
692 }
693
694 fn master_enc_key_from_sealed_part(
705 &self,
706 tdi: &api::DiscoveryInfoResp,
707 t_ss: &kem::SharedSecret,
708 constellation_id: id::Id,
709 ) -> api::Result<Option<elgamal::PublicKey>> {
710 if tdi.constellation_or_id.as_ref().map(|c| *c.id()) != Some(constellation_id) {
711 return Ok(None);
712 }
713
714 let Some(sealed) = tdi.master_enc_key_part_sealed.clone() else {
715 log::error!(
716 "transcryptor adopted our constellation but published no sealed master key part"
717 );
718 return Err(api::ErrorCode::InternalError);
719 };
720
721 let api::MasterEncKeyPart(transcryptor_part) =
722 sealed.open(&phcrypto::sealing_secret(t_ss)).map_err(|_| {
723 log::error!(
724 "could not open the transcryptor's sealed master encryption key part, even \
725 though it has adopted our constellation"
726 );
727 api::ErrorCode::InternalError
728 })?;
729
730 if tdi.master_enc_key_part_hash
731 != Some(phcrypto::master_enc_key_part_hash(&transcryptor_part))
732 {
733 log::error!(
734 "transcryptor's sealed master_enc_key_part does not match its published hash"
735 );
736 return Err(api::ErrorCode::InternalError);
737 }
738
739 Ok(Some(phcrypto::combine_master_enc_key_parts(
740 &transcryptor_part,
741 &self.master_enc_key_part,
742 )))
743 }
744
745 fn encap_or_reuse(
749 peer: servers::Name,
750 peer_encap_key: Option<&kem::EncapKeyBytes>,
751 prior: Option<(&id::Id, &kem::CiphertextBytes, &kem::SharedSecret)>,
752 ) -> api::Result<(id::Id, kem::CiphertextBytes, kem::SharedSecret)> {
753 let peer_encap_key = peer_encap_key.ok_or_else(|| {
754 log::error!("{peer}'s discovery info has no encapsulation key");
755 api::ErrorCode::InternalError
756 })?;
757
758 let new_id = peer_encap_key.id();
759
760 if let Some((prev_id, prev_ct, prev_ss)) = prior
761 && *prev_id == new_id
762 {
763 return Ok((*prev_id, prev_ct.clone(), prev_ss.clone()));
764 }
765
766 let ek = peer_encap_key.decode().map_err(|_| {
767 log::error!("failed to decode {peer}'s encap_key");
768 api::ErrorCode::InternalError
769 })?;
770 let (ct, ss) = ek.encap().map_err(|_| {
771 log::error!("failed to encapsulate for {peer}");
772 api::ErrorCode::InternalError
773 })?;
774 Ok((new_id, ct, ss))
775 }
776}
777
778#[derive(Clone)]
779pub struct AppCreator {
780 pub base: AppCreatorBase<Server>,
781 pub transcryptor_url: url::Url,
782 pub auths_url: url::Url,
783 pub global_client_url: url::Url,
784 pub master_enc_key_part: elgamal::PrivateKey,
785 pub attr_id_secret: Box<[u8]>,
786 pub auth_token_secret: crypto::SealingKey,
787 pub auth_token_validity: core::time::Duration,
788 pub auth_token_max_idle: core::time::Duration,
789 pub auth_session_max_duration: core::time::Duration,
790 pub pp_nonce_secret: crypto::SealingKey,
791 pub pp_nonce_validity: core::time::Duration,
792 pub user_object_hmac_secret: Box<[u8]>,
793 pub quota: api::phc::user::Quota,
794 pub card_pseud_validity: core::time::Duration,
795 pub hub_cache_config: HubCacheConfig,
796}
797
798impl Deref for AppCreator {
799 type Target = AppCreatorBase<Server>;
800
801 fn deref(&self) -> &Self::Target {
802 &self.base
803 }
804}
805
806impl DerefMut for AppCreator {
807 fn deref_mut(&mut self) -> &mut Self::Target {
808 &mut self.base
809 }
810}
811
812#[derive(Clone)]
813pub struct AppCreatorContext {
814 broadcast: tokio::sync::broadcast::Sender<InterAppMsg>,
815}
816
817impl Default for AppCreatorContext {
818 fn default() -> Self {
819 Self {
820 broadcast: tokio::sync::broadcast::Sender::<InterAppMsg>::new(10),
821 }
822 }
823}
824
825impl crate::servers::AppCreator<Server> for AppCreator {
826 type ContextT = AppCreatorContext;
827
828 fn into_app(self, handle: &Handle<Server>, context: &Self::ContextT, generation: usize) -> App {
829 App {
830 base: AppBase::new(self.base, handle, generation),
831 transcryptor_url: self.transcryptor_url,
832 auths_url: self.auths_url,
833 global_client_url: self.global_client_url,
834 master_enc_key_part: self.master_enc_key_part,
835 attr_id_secret: self.attr_id_secret,
836 auth_token_secret: self.auth_token_secret,
837 auth_token_validity: self.auth_token_validity,
838 auth_token_max_idle: self.auth_token_max_idle,
839 auth_session_max_duration: self.auth_session_max_duration,
840 pp_nonce_secret: self.pp_nonce_secret,
841 pp_nonce_validity: self.pp_nonce_validity,
842 user_object_hmac_secret: self.user_object_hmac_secret,
843 quota: self.quota,
844 card_pseud_validity: self.card_pseud_validity,
845 broadcast: context.broadcast.clone(),
846 cached_hub_info: std::cell::RefCell::new(
848 api::Responder(Err(api::ErrorCode::PleaseRetry)).into_cached(),
849 ),
850 hub_cache_config: self.hub_cache_config,
851 }
852 }
853
854 fn new(config: &servers::Config) -> anyhow::Result<Self> {
855 let xconf = &config.phc.as_ref().unwrap();
856
857 let master_enc_key_part: elgamal::PrivateKey = xconf
858 .master_enc_key_part
859 .clone()
860 .expect("master_enc_key_part not generated");
861
862 let base = AppCreatorBase::<Server>::new(config)?;
863
864 let enc_key: &[u8] = &base.enc_key;
865 let auth_token_secret: crypto::SealingKey =
866 enc_key.derive_sealing_key(sha2::Sha256::new(), "pubhubs-phc-auth-token-secret");
867
868 let pp_nonce_secret: crypto::SealingKey =
869 enc_key.derive_sealing_key(sha2::Sha256::new(), "pubhubs-pp-nonce-secret");
870
871 Ok(Self {
872 base,
873 transcryptor_url: xconf.transcryptor_url.as_ref().clone(),
874 auths_url: xconf.auths_url.as_ref().clone(),
875 global_client_url: xconf.global_client_url.as_ref().clone(),
876 master_enc_key_part,
877 attr_id_secret: <serde_bytes::ByteBuf as Clone>::clone(
878 xconf
879 .attr_id_secret
880 .as_ref()
881 .expect("attr_id_secret was not initialized"),
882 )
883 .into_vec()
884 .into_boxed_slice(),
885 auth_token_secret,
886 auth_token_validity: xconf.auth_token_validity,
887 auth_token_max_idle: xconf.auth_token_max_idle,
888 auth_session_max_duration: xconf.auth_session_max_duration,
889 pp_nonce_secret,
890 pp_nonce_validity: xconf.pp_nonce_validity,
891 user_object_hmac_secret: <serde_bytes::ByteBuf as Clone>::clone(
892 xconf
893 .user_object_hmac_secret
894 .as_ref()
895 .expect("user_object_hmac_secret was not initialized"),
896 )
897 .into_vec()
898 .into_boxed_slice(),
899 quota: xconf.user_quota.clone(),
900 card_pseud_validity: xconf.card_pseud_validity,
901 hub_cache_config: xconf.hub_cache.clone(),
902 })
903 }
904}
905
906#[derive(Clone, Debug)]
908pub(crate) enum InterAppMsg {
909 UpdatedHubInfo(api::CachedResponse<api::phc::user::CachedHubInfoEP>),
910}