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 + app.pp_nonce_validity,
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
63        let Ok(EncryptedHubPseudonymPackage {
64            encrypted_hub_pseudonym,
65            hub_nonce,
66            phc_nonce,
67        }) = req.ehpp.open(&running_state.t_sealing_secret)
68        else {
69            log::debug!("invalid Ehpp submitted to Hhpp endpoint");
70            return Ok(HhppResp::RetryWithNewPpp);
71        };
72
73        let Ok(PpNonceInner {
74            user_id: phc_nonce_user_id,
75            issued_at: pp_issued_at,
76            not_valid_after,
77        }) = api::Sealed::<PpNonceInner>::from(phc_nonce).open(&app.pp_nonce_secret)
78        else {
79            log::info!("Ehpp containing invalid PHC nonce submitted to Hhpp endpoint");
80            return Ok(HhppResp::RetryWithNewPpp);
81            // this is not so dramatic, since the key used to seal PHC nonces may change regularly
82        };
83
84        if phc_nonce_user_id != auth_token_user_id {
85            log::warn!("user {auth_token_user_id} used {phc_nonce_user_id}'s PP nonce");
86            return Err(api::ErrorCode::BadRequest);
87        }
88
89        if not_valid_after < api::NumericDate::now() {
90            log::debug!("Ehpp containing expired PHC nonce submitted to Hhpp endpoint");
91            return Ok(HhppResp::RetryWithNewPpp);
92        }
93
94        let Some(hub_pseudonym) =
95            encrypted_hub_pseudonym.decrypt_and_check_pk(&app.master_enc_key_part)
96        else {
97            log::warn!("hub pseudonym was encrypted for the wrong public key");
98            return Err(api::ErrorCode::InternalError);
99            // Internal error, because the encrypted_hub_pseudonym is guaranteed to be
100            // generated by the transcryptor, and the master encryption key should not have changed
101            // if the sealing secret is still valid.
102        };
103
104        let hashed_hub_pseudonym: api::CurvePoint =
105            RistrettoPoint::hash_from_bytes::<sha2::Sha512>(hub_pseudonym.compress().as_bytes())
106                .compress()
107                .into();
108
109        Ok(HhppResp::Success(api::Signed::new_opts(
110            &*app.jwt_key,
111            &HashedHubPseudonymPackage {
112                hashed_hub_pseudonym,
113                pp_issued_at,
114                hub_nonce,
115            },
116            app.pp_nonce_validity,
117            // not sure if we should get a seprate configuration field for
118            // Hhpp's validity duration
119            Some(&running_state.constellation),
120        )?))
121    }
122}
123
124/// The contents of a [`PpNonce`].
125#[derive(Serialize, Deserialize, Debug)]
126struct PpNonceInner {
127    /// When this nonce expires.
128    not_valid_after: api::NumericDate,
129
130    /// When this nonce was issued
131    issued_at: api::NumericDate,
132
133    /// The [`id::Id`] of the user requesting this [`PolymorphicPseudonymPackage`].
134    user_id: id::Id,
135}
136
137api::having_message_code!(PpNonceInner, PpNonce);
138
139impl From<api::Sealed<PpNonceInner>> for PpNonce {
140    fn from(sealed: api::Sealed<PpNonceInner>) -> Self {
141        Self {
142            inner: sealed.inner,
143        }
144    }
145}
146
147impl From<PpNonce> for api::Sealed<PpNonceInner> {
148    fn from(nonce: PpNonce) -> Self {
149        nonce.inner.into()
150    }
151}