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