Skip to main content

pubhubs/servers/transcryptor/
server.rs

1use std::ops::{Deref, DerefMut};
2use std::rc::Rc;
3
4use actix_web::web;
5
6use crate::common::elgamal;
7use crate::misc::crypto;
8use crate::misc::serde_ext::bytes_wrapper::B64UU;
9use crate::phcrypto;
10use crate::{
11    api::{self, EndpointDetails as _},
12    servers::{self, AppBase, AppCreatorBase, Constellation, Handle, constellation},
13};
14
15use api::tr::*;
16
17/// Transcryptor
18pub type Server = servers::ServerImpl<Details>;
19
20pub struct Details;
21impl servers::Details for Details {
22    const NAME: servers::Name = servers::Name::Transcryptor;
23    type AppT = App;
24    type AppCreatorT = AppCreator;
25    type ExtraRunningState = ExtraRunningState;
26    type ExtraSharedState = ExtraSharedState;
27    type ObjectStoreT = servers::object_store::UseNone;
28
29    fn create_running_state(
30        server: &Server,
31        constellation: &Constellation,
32    ) -> anyhow::Result<Self::ExtraRunningState> {
33        let phc_ss = server.enc_key.shared_secret(&constellation.phc_enc_key);
34
35        Ok(ExtraRunningState {
36            phc_sealing_secret: phcrypto::sealing_secret(&phc_ss),
37            phc_ss,
38        })
39    }
40
41    fn create_extra_shared_state(_config: &servers::Config) -> anyhow::Result<ExtraSharedState> {
42        Ok(ExtraSharedState {})
43    }
44}
45
46pub struct ExtraSharedState {}
47
48#[derive(Clone, Debug)]
49pub struct ExtraRunningState {
50    /// Secret shared with pubhubs central
51    #[expect(dead_code)]
52    phc_ss: elgamal::SharedSecret,
53
54    /// Key used to (un)seal messages to and from PHC
55    pub(super) phc_sealing_secret: crypto::SealingKey,
56}
57
58pub struct App {
59    base: AppBase<Server>,
60    master_enc_key_part: elgamal::PrivateKey,
61    master_enc_key_part_inv: curve25519_dalek::Scalar,
62    pseud_factor_secret: B64UU,
63}
64
65impl Deref for App {
66    type Target = AppBase<Server>;
67
68    fn deref(&self) -> &Self::Target {
69        &self.base
70    }
71}
72
73impl crate::servers::App<Server> for App {
74    fn configure_actix_app(self: &Rc<Self>, sc: &mut web::ServiceConfig) {
75        EhppEP::add_to(self, sc, App::handle_ehpp);
76        api::server::HubPingEP::add_to(self, sc, App::handle_hub_ping);
77    }
78
79    fn check_constellation(&self, constellation: &Constellation) -> bool {
80        // Dear maintainer: this destructuring is intentional, making sure that this `check_constellation` function
81        // is updated when new fields are added to the constellation
82        let Constellation {
83            inner:
84                constellation::Inner {
85                    // These fields we must check:
86                    transcryptor_jwt_key: jwt_key,
87                    transcryptor_enc_key: enc_key,
88                    transcryptor_master_enc_key_part: master_enc_key_part,
89
90                    // These fields we don't care about:
91                    transcryptor_url: _,
92                    auths_enc_key: _,
93                    auths_jwt_key: _,
94                    auths_url: _,
95                    phc_jwt_key: _,
96                    phc_enc_key: _,
97                    phc_url: _,
98                    master_enc_key: _,
99                    global_client_url: _,
100                    ph_version: _, // (already checked)
101                },
102            id: _,
103            created_at: _,
104        } = constellation;
105
106        enc_key == self.enc_key.public_key()
107            && **jwt_key == self.jwt_key.verifying_key()
108            && master_enc_key_part == self.master_enc_key_part.public_key()
109    }
110
111    fn master_enc_key_part(&self) -> Option<&elgamal::PrivateKey> {
112        Some(&self.master_enc_key_part)
113    }
114}
115
116impl App {
117    /// Implements [`api::server::HubPingEP`].
118    async fn handle_hub_ping(
119        app: Rc<Self>,
120        signed_req: web::Json<api::phc::hub::TicketSigned<api::server::PingReq>>,
121    ) -> api::Result<api::server::PingResp> {
122        crate::servers::AppBase::<Server>::handle_hub_ping(app, signed_req).await
123    }
124
125    /// Implements [`EhppEP`]
126    async fn handle_ehpp(app: Rc<Self>, req: web::Json<EhppReq>) -> api::Result<EhppResp> {
127        let running_state = app.running_state_or_please_retry()?;
128
129        let EhppReq {
130            hub_nonce,
131            hub,
132            ppp,
133        } = req.into_inner();
134
135        let Ok(api::sso::PolymorphicPseudonymPackage {
136            polymorphic_pseudonym,
137            nonce: phc_nonce,
138        }) = ppp.open(&running_state.phc_sealing_secret)
139        else {
140            return Ok(EhppResp::RetryWithNewPpp);
141        };
142
143        let encrypted_hub_pseudonym: elgamal::Triple = phcrypto::t_encrypted_hub_pseudonym(
144            polymorphic_pseudonym,
145            &***app.pseud_factor_secret,
146            &app.master_enc_key_part_inv,
147            hub,
148        );
149
150        Ok(EhppResp::Success(api::Sealed::new(
151            &api::sso::EncryptedHubPseudonymPackage {
152                encrypted_hub_pseudonym,
153                hub_nonce,
154                phc_nonce,
155            },
156            &running_state.phc_sealing_secret,
157        )?))
158    }
159}
160
161#[derive(Clone)]
162pub struct AppCreator {
163    base: AppCreatorBase<Server>,
164    master_enc_key_part: elgamal::PrivateKey,
165    master_enc_key_part_inv: curve25519_dalek::Scalar,
166    pseud_factor_secret: B64UU,
167}
168
169impl Deref for AppCreator {
170    type Target = AppCreatorBase<Server>;
171
172    fn deref(&self) -> &Self::Target {
173        &self.base
174    }
175}
176
177impl DerefMut for AppCreator {
178    fn deref_mut(&mut self) -> &mut Self::Target {
179        &mut self.base
180    }
181}
182
183impl crate::servers::AppCreator<Server> for AppCreator {
184    type ContextT = ();
185
186    fn new(config: &servers::Config) -> anyhow::Result<Self> {
187        let xconf = &config.transcryptor.as_ref().unwrap();
188
189        let master_enc_key_part: elgamal::PrivateKey = xconf
190            .master_enc_key_part
191            .clone()
192            .expect("master_enc_key_part was not generated");
193
194        let pseud_factor_secret = xconf
195            .pseud_factor_secret
196            .clone()
197            .expect("pseud_factor_secret was not generated");
198
199        Ok(Self {
200            base: AppCreatorBase::<Server>::new(config)?,
201            master_enc_key_part_inv: master_enc_key_part.as_scalar().invert(),
202            master_enc_key_part,
203            pseud_factor_secret,
204        })
205    }
206
207    fn into_app(
208        self,
209        handle: &Handle<Server>,
210        _context: &Self::ContextT,
211        generation: usize,
212    ) -> App {
213        App {
214            base: AppBase::new(self.base, handle, generation),
215            master_enc_key_part: self.master_enc_key_part,
216            master_enc_key_part_inv: self.master_enc_key_part_inv,
217            pseud_factor_secret: self.pseud_factor_secret,
218        }
219    }
220}