Skip to main content

pubhubs/servers/phc/
user_sso.rs

1//! User endpoints for entering hubs
2use std::rc::Rc;
3
4use curve25519_dalek::RistrettoPoint;
5
6use crate::api;
7use crate::id;
8
9use serde::{Deserialize, Serialize};
10
11use super::server::*;
12use crate::api::phc::user::*;
13use crate::api::sso::*;
14
15impl App {
16    /// Implements [`PppEP`].
17    pub(crate) async fn handle_user_ppp(
18        app: Rc<Self>,
19        auth_token: actix_web::web::Header<AuthToken>,
20    ) -> api::Result<PppResp> {
21        let running_state = app.running_state_or_please_retry()?;
22
23        let Ok((user_state, _)) = app
24            .open_auth_token_and_get_user_state(auth_token.into_inner())
25            .await?
26        else {
27            return Ok(PppResp::RetryWithNewAuthToken);
28        };
29
30        let now = api::NumericDate::now();
31
32        let nonce_inner = PpNonceInner {
33            user_id: user_state.id,
34            not_valid_after: now.add_clamp(app.pp_nonce_validity.as_secs()),
35            issued_at: now,
36        };
37
38        Ok(PppResp::Success(api::Sealed::new(
39            &PolymorphicPseudonymPackage {
40                // we make sure to rerandomize the polymorphic pseudonym so the transcryptor cannot
41                // track the user based on it
42                polymorphic_pseudonym: user_state.polymorphic_pseudonym.rerandomize(),
43                nonce: api::Sealed::new(&nonce_inner, &app.pp_nonce_secret)?.into(),
44            },
45            &running_state.t_sealing_secret,
46        )?))
47    }
48
49    /// Implements [`HhppEP`].
50    pub(crate) async fn handle_user_hhpp(
51        app: Rc<Self>,
52        req: actix_web::web::Json<HhppReq>,
53        auth_token: actix_web::web::Header<AuthToken>,
54    ) -> api::Result<HhppResp> {
55        let running_state = app.running_state_or_please_retry()?;
56
57        let Ok(auth_token_user_id) = app.open_auth_token(auth_token.into_inner()) else {
58            return Ok(HhppResp::RetryWithNewAuthToken);
59        };
60
61        let req = req.into_inner();
62        let hhpp_signature_scheme = req.hhpp_signature_scheme;
63
64        let Ok(EncryptedHubPseudonymPackage {
65            encrypted_hub_pseudonym,
66            hub_nonce,
67            phc_nonce,
68            hub_id_mac,
69        }) = req.ehpp.open(&running_state.t_sealing_secret)
70        else {
71            log::debug!("invalid Ehpp submitted to Hhpp endpoint");
72            return Ok(HhppResp::RetryWithNewPpp);
73        };
74
75        let Ok(PpNonceInner {
76            user_id: phc_nonce_user_id,
77            issued_at: pp_issued_at,
78            not_valid_after,
79        }) = api::Sealed::<PpNonceInner>::from(phc_nonce).open(&app.pp_nonce_secret)
80        else {
81            log::info!("Ehpp containing invalid PHC nonce submitted to Hhpp endpoint");
82            return Ok(HhppResp::RetryWithNewPpp);
83            // this is not so dramatic, since the key used to seal PHC nonces may change regularly
84        };
85
86        if phc_nonce_user_id != auth_token_user_id {
87            log::warn!("user {auth_token_user_id} used {phc_nonce_user_id}'s PP nonce");
88            return Err(api::ErrorCode::BadRequest);
89        }
90
91        if not_valid_after < api::NumericDate::now() {
92            log::debug!("Ehpp containing expired PHC nonce submitted to Hhpp endpoint");
93            return Ok(HhppResp::RetryWithNewPpp);
94        }
95
96        let Some(hub_pseudonym) =
97            encrypted_hub_pseudonym.decrypt_and_check_pk(&app.master_enc_key_part)
98        else {
99            log::warn!("hub pseudonym was encrypted for the wrong public key");
100            return Err(api::ErrorCode::InternalError);
101            // Internal error, because the encrypted_hub_pseudonym is guaranteed to be
102            // generated by the transcryptor, and the master encryption key should not have changed
103            // if the sealing secret is still valid.
104        };
105
106        let hashed_hub_pseudonym: api::CurvePoint =
107            RistrettoPoint::hash_from_bytes::<sha2::Sha512>(hub_pseudonym.compress().as_bytes())
108                .compress()
109                .into();
110
111        let hhpp = HashedHubPseudonymPackage {
112            hashed_hub_pseudonym,
113            pp_issued_at,
114            hub_nonce,
115            hub_id_mac,
116        };
117
118        // Sign with the key the hub can verify (see `HhppSignatureScheme`): the ed25519 component
119        // for pre-hybrid hubs, the hybrid composite for updated ones.  Validity reuses
120        // `pp_nonce_validity` (TODO: a dedicated config field for the HHPP's validity?).
121        let signed = match hhpp_signature_scheme {
122            HhppSignatureScheme::Ed25519 => api::Signed::new_opts(
123                app.shared.signing_key.ed25519_signing_key(),
124                &hhpp,
125                app.pp_nonce_validity,
126                Some(&running_state.constellation),
127            )?,
128            HhppSignatureScheme::HybridInterim => api::Signed::new_opts(
129                &app.shared.signing_key,
130                &hhpp,
131                app.pp_nonce_validity,
132                Some(&running_state.constellation),
133            )?,
134            HhppSignatureScheme::HybridStandard => {
135                log::warn!(
136                    "hub requested the not-yet-implemented conformant HHPP signature scheme"
137                );
138                return Err(api::ErrorCode::BadRequest);
139            }
140        };
141
142        Ok(HhppResp::Success(signed))
143    }
144}
145
146/// The contents of a [`PpNonce`].
147#[derive(Serialize, Deserialize, Debug)]
148struct PpNonceInner {
149    /// When this nonce expires.
150    not_valid_after: api::NumericDate,
151
152    /// When this nonce was issued
153    issued_at: api::NumericDate,
154
155    /// The [`id::Id`] of the user requesting this [`PolymorphicPseudonymPackage`].
156    user_id: id::Id,
157}
158
159api::having_message_code!(PpNonceInner, PpNonce);
160
161impl From<api::Sealed<PpNonceInner>> for PpNonce {
162    fn from(sealed: api::Sealed<PpNonceInner>) -> Self {
163        Self {
164            inner: sealed.inner,
165        }
166    }
167}
168
169impl From<PpNonce> for api::Sealed<PpNonceInner> {
170    fn from(nonce: PpNonce) -> Self {
171        nonce.inner.into()
172    }
173}