pubhubs/servers/transcryptor/
server.rs1use std::ops::{Deref, DerefMut};
2use std::rc::Rc;
3
4use actix_web::web;
5
6use crate::common::{elgamal, kem};
7use crate::misc::crypto;
8use crate::misc::serde_ext::bytes_wrapper::B64UU;
9use crate::phcrypto;
10use crate::{
11 api::{self, EndpointDetails as _},
12 servers::{
13 self, AppBase, AppCreatorBase, Constellation, DiscoverVerdict, Handle, Server as _,
14 constellation,
15 },
16};
17
18use api::tr::*;
19
20pub type Server = servers::ServerImpl<Details>;
22
23pub struct Details;
24impl servers::Details for Details {
25 const NAME: servers::Name = servers::Name::Transcryptor;
26 type AppT = App;
27 type AppCreatorT = AppCreator;
28 type ExtraRunningState = ExtraRunningState;
29 type RunningStateSeed = ();
30 type ExtraSharedState = ExtraSharedState;
31 type ExtraServerState = ExtraServerState;
32 type ObjectStoreT = servers::object_store::UseNone;
33
34 fn create_running_state(
35 server: &Server,
36 constellation: &Constellation,
37 _seed: &(),
38 ) -> anyhow::Result<Self::ExtraRunningState> {
39 let phc_ss = server
40 .extra()
41 .decap_key
42 .decap(&constellation.transcryptor_ss_encap)
43 .map_err(|_| anyhow::anyhow!("decapsulating shared secret from PHC failed"))?;
44
45 Ok(ExtraRunningState {
46 phc_sealing_secret: phcrypto::sealing_secret(&phc_ss),
47 phc_ss,
48 })
49 }
50
51 fn create_extra_shared_state(_config: &servers::Config) -> anyhow::Result<ExtraSharedState> {
52 Ok(ExtraSharedState {})
53 }
54
55 fn create_extra_server_state(config: &servers::Config) -> anyhow::Result<ExtraServerState> {
56 let xconf = config.transcryptor.as_ref().unwrap();
57 let decap_key = xconf
58 .decap_key
59 .as_ref()
60 .expect("decap_key was not set nor generated")
61 .decode()
62 .map_err(|_| anyhow::anyhow!("decoding kem decapsulation key"))?;
63 Ok(ExtraServerState { decap_key })
64 }
65}
66
67pub struct ExtraSharedState {}
68
69pub struct ExtraServerState {
70 pub(super) decap_key: kem::DecapKey,
71}
72
73#[derive(Clone, Debug)]
74pub struct ExtraRunningState {
75 #[expect(dead_code)]
77 phc_ss: kem::SharedSecret,
78
79 pub(super) phc_sealing_secret: crypto::SealingKey,
81}
82
83pub struct App {
84 base: AppBase<Server>,
85 master_enc_key_part: elgamal::PrivateKey,
86 master_enc_key_part_inv: curve25519_dalek::Scalar,
87 master_enc_key_part_hash: crate::id::Id,
88 pseud_factor_secret: B64UU,
89 encap_key: kem::EncapKeyBytes,
90}
91
92impl Deref for App {
93 type Target = AppBase<Server>;
94
95 fn deref(&self) -> &Self::Target {
96 &self.base
97 }
98}
99
100impl crate::servers::App<Server> for App {
101 fn configure_actix_app(self: &Rc<Self>, sc: &mut web::ServiceConfig) {
102 EhppEP::add_to(self, sc, App::handle_ehpp);
103 api::server::HubPingEP::add_to(self, sc, App::handle_hub_ping);
104 }
105
106 fn check_constellation(&self, constellation: &Constellation) -> bool {
107 let Constellation {
110 inner:
111 constellation::Inner {
112 transcryptor_verifying_key,
114 transcryptor_master_enc_key_part_hash,
115 transcryptor_encap_key_id,
116
117 transcryptor_url: _,
119 transcryptor_ss_encap: _,
120 auths_verifying_key: _,
121 auths_url: _,
122 auths_encap_key_id: _,
123 auths_ss_encap: _,
124 phc_jwt_key: _,
125 phc_verifying_key: _,
126 phc_master_enc_key_part_hash: _,
127 phc_url: _,
128 global_client_url: _,
129 ph_version: _, },
131 id: _,
132 created_at: _,
133 } = constellation;
134
135 if *transcryptor_encap_key_id != self.encap_key.id() {
138 return false;
139 }
140
141 transcryptor_verifying_key == &self.shared.verifying_key_bytes
142 && *transcryptor_master_enc_key_part_hash == self.master_enc_key_part_hash
143 }
144
145 fn master_enc_key_part(&self) -> Option<&elgamal::PrivateKey> {
146 Some(&self.master_enc_key_part)
147 }
148
149 fn encap_key(&self) -> Option<&kem::EncapKeyBytes> {
150 Some(&self.encap_key)
151 }
152
153 fn master_enc_key_part_sealing_key(&self) -> Option<&api::SealingKey> {
154 self.running_state.as_ref().map(|rs| &rs.phc_sealing_secret)
155 }
156
157 async fn discover(
158 self: &Rc<Self>,
159 phc_inf: api::DiscoveryInfoResp,
160 ) -> api::Result<DiscoverVerdict<()>> {
161 self.discover_as_non_phc(phc_inf).await
162 }
163}
164
165impl App {
166 async fn handle_hub_ping(
168 app: Rc<Self>,
169 signed_req: web::Json<api::phc::hub::TicketSigned<api::server::PingReq>>,
170 ) -> api::Result<api::server::PingResp> {
171 crate::servers::AppBase::<Server>::handle_hub_ping(app, signed_req).await
172 }
173
174 async fn handle_ehpp(app: Rc<Self>, req: web::Json<EhppReq>) -> api::Result<EhppResp> {
176 let running_state = app.running_state_or_please_retry()?;
177
178 let EhppReq {
179 hub_nonce,
180 hub,
181 ppp,
182 hub_mac_key,
183 } = req.into_inner();
184
185 let Ok(api::sso::PolymorphicPseudonymPackage {
186 polymorphic_pseudonym,
187 nonce: phc_nonce,
188 }) = ppp.open(&running_state.phc_sealing_secret)
189 else {
190 return Ok(EhppResp::RetryWithNewPpp);
191 };
192
193 let encrypted_hub_pseudonym: elgamal::Triple = phcrypto::t_encrypted_hub_pseudonym(
194 polymorphic_pseudonym,
195 &***app.pseud_factor_secret,
196 &app.master_enc_key_part_inv,
197 hub,
198 );
199
200 let hub_id_mac = hub_mac_key.map(|key| key.mac(&hub));
201
202 Ok(EhppResp::Success(api::Sealed::new(
203 &api::sso::EncryptedHubPseudonymPackage {
204 encrypted_hub_pseudonym,
205 hub_nonce,
206 phc_nonce,
207 hub_id_mac,
208 },
209 &running_state.phc_sealing_secret,
210 )?))
211 }
212}
213
214#[derive(Clone)]
215pub struct AppCreator {
216 base: AppCreatorBase<Server>,
217 master_enc_key_part: elgamal::PrivateKey,
218 master_enc_key_part_inv: curve25519_dalek::Scalar,
219 pseud_factor_secret: B64UU,
220 encap_key: kem::EncapKeyBytes,
221}
222
223impl Deref for AppCreator {
224 type Target = AppCreatorBase<Server>;
225
226 fn deref(&self) -> &Self::Target {
227 &self.base
228 }
229}
230
231impl DerefMut for AppCreator {
232 fn deref_mut(&mut self) -> &mut Self::Target {
233 &mut self.base
234 }
235}
236
237impl crate::servers::AppCreator<Server> for AppCreator {
238 type ContextT = ();
239
240 fn new(config: &servers::Config) -> anyhow::Result<Self> {
241 let xconf = &config.transcryptor.as_ref().unwrap();
242
243 let master_enc_key_part: elgamal::PrivateKey = xconf
244 .master_enc_key_part
245 .clone()
246 .expect("master_enc_key_part was not generated");
247
248 let pseud_factor_secret = xconf
249 .pseud_factor_secret
250 .clone()
251 .expect("pseud_factor_secret was not generated");
252
253 let encap_key = xconf
254 .decap_key
255 .as_ref()
256 .expect("decap_key was not set nor generated")
257 .decode()
258 .and_then(|dk| dk.encap_key().encode())
259 .map_err(|_| anyhow::anyhow!("deriving kem encapsulation key"))?;
260
261 Ok(Self {
262 base: AppCreatorBase::<Server>::new(config)?,
263 master_enc_key_part_inv: master_enc_key_part.as_scalar().invert(),
264 master_enc_key_part,
265 pseud_factor_secret,
266 encap_key,
267 })
268 }
269
270 fn into_app(
271 self,
272 handle: &Handle<Server>,
273 _context: &Self::ContextT,
274 generation: usize,
275 ) -> App {
276 App {
277 base: AppBase::new(self.base, handle, generation),
278 master_enc_key_part_hash: phcrypto::master_enc_key_part_hash(
279 self.master_enc_key_part.public_key(),
280 ),
281 master_enc_key_part: self.master_enc_key_part,
282 master_enc_key_part_inv: self.master_enc_key_part_inv,
283 pseud_factor_secret: self.pseud_factor_secret,
284 encap_key: self.encap_key,
285 }
286 }
287}