pubhubs/api/sso.rs
1//! Data structures related to the authentication of users towards hubs.
2//!
3//! # Overview
4//!
5//! ## Pseudonyms
6//!
7//! The pseudonym a user $U$ gets in a hub $H$ is
8//! $$\mathrm{Sha512}(g_H \cdot \mathrm{Id}_U )$$
9//! mapped to a [`CurvePoint`], where:
10//!
11//! - $\mathrm{Id}_U$ is a permanent unchanging **secret user identifier**
12//! (a random [`CurvePoint`]). The secret user identifier is known to no one,
13//! not even the user itself. PHC and the transcryptor only get to see the ElGamal encrypted
14//! form known as the _polymorphic pseudonym_ (see below).
15//!
16//! > **Note:** The secret user identifier should not be confused with the `user_id`
17//! > used by PHC internally to identify a user.)
18//!
19//!
20//! - $g_H$ is the **pseudonymisation factor**, a [`scalar`](Scalar) unique
21//! to the hub $H$, known only by the transcryptor:
22//! $$g_H := \mathrm{Sha512}(H \Vert \ell_d \Vert d \Vert \ell_g \Vert g)$$
23//! where $H$ is the [**hub id**](crate::hub::BasicInfo::id) (32 bytes),
24//! $d := \text{"pubhubs-pseud-factor"}$, $g$ is the transcryptor's
25//! pseudonymisation-factor secret, and $\ell_d, \ell_g$ are the byte
26//! lengths of $d, g$ encoded as 8-byte big-endian unsigned integers.
27//!
28//! <details class="toggle">
29//! <summary class="hideme"><span>Expand example </span></summary>
30//!
31//! **Example**
32//! ```
33//! use sha2::Digest; // brings `chain_update` and `new` into scope
34//! let h = pubhubs::id::Id::from([7u8; 32]);
35//! let g: &[u8] = b"abc"; // 3 bytes
36//! let d = "pubhubs-pseud-factor"; // 20 bytes
37//! assert_eq!(
38//! pubhubs::phcrypto::pseud_factor_for_hub(g, h),
39//! curve25519_dalek::Scalar::from_hash(
40//! sha2::Sha512::new()
41//! .chain_update(h.as_slice())
42//! .chain_update([0, 0, 0, 0, 0, 0, 0, 20u8])
43//! .chain_update(d.as_bytes())
44//! .chain_update([0, 0, 0, 0, 0, 0, 0, 3u8])
45//! .chain_update(g),
46//! ),
47//! );
48//! ```
49//!
50//! </details>
51//!
52//! > **Note:** The white paper uses $g_H\cdot \mathrm{Id}_U$ instead
53//! > of $\mathrm{Sha512}(g_H\cdot \mathrm{Id}_U)$. The hash has been added to
54//! > protect the pseudonym against harvest-now-decrypt-later-by-a-quantum-computer attacks.
55//!
56//! The SSO flow described below gets the pseudonym
57//! $\mathrm{Sha512}(g_H \cdot \mathrm{Id}_U )$ of a user $U$ to the hub $H$ in such a way that:
58//! - The hub learns only this pseudonym.
59//! - PHC learns $U$, but not what hub $H$ they are visiting.
60//! - The transcryptor learns $H$ (and knows $g_H$), but can not* deduce $U$.
61//!
62//! ## Polymorphic pseudonyms
63//!
64//! A **polymorphic pseudonym** $\mathrm{PP}_U$
65//! for the user $U$ is an [ElGamal encryption](elgamal::Triple) of
66//! $\mathrm{Id}_U$ of the form
67//! $$ \mathrm{PP}_U \ :=\ (rB,\ \mathrm{Id}_U + rxB,\ xB).$$
68//! Here:
69//! - $B$ denotes the base point used by [`CurvePoint`].
70//! - $x := x_{\mathrm{T}} x_{\mathrm{PHC}}$ is the **master encryption key**,
71//! that splits into two **master encryption key parts**, $x_{\mathrm{T}}$ and $x_{\mathrm{PHC}}$,
72//! picked by the transcryptor and PHC, respectively.
73//! - $r$ is a random [`Scalar`].
74//!
75//! While each user has just one $\mathrm{Id}_U$,
76//! it has many different polymorphic pseudonyms on account of the random factor $r$.
77//! The polymorphic pseudonym serves two purposes:
78//!
79//! 1. It 'identifies' the user $U$ (towards the transcryptor) without always
80//! having the same shape, so it can not be used to track logins of the same user.
81//!
82//! 2. It allows operations to be performed on $\mathrm{Id}_U$ without revealing $\mathrm{Id}_U$
83//! itself (via the _homomorphic_ properties of ElGamal encryption, see [`elgamal::Triple::rsk`].)
84//!
85//!
86//!
87//!
88//! ## Flow
89//!
90//! 1. The user first obtains a [`PolymorphicPseudonymPackage`] (**PPP**) from PHC,
91//! which contains a polymorphic pseudonym nonce (**ppnonce**) and a polymorphic pseudonym.
92//! The polymorphic pseudonym is the ElGamal encryption of
93
94use crate::api::*;
95
96use serde::{Deserialize, Serialize};
97
98use crate::common::elgamal;
99use crate::misc::jwt;
100
101/// Returned (in sealed form) by [`phc::user::PppEP`], needed for [`tr::EhppEP`].
102#[derive(Serialize, Deserialize, Debug, Clone)]
103#[serde(deny_unknown_fields)]
104pub struct PolymorphicPseudonymPackage {
105 /// The actual polymorphic pseudonym for the user
106 pub polymorphic_pseudonym: elgamal::Triple,
107
108 pub nonce: phc::user::PpNonce,
109}
110
111having_message_code!(PolymorphicPseudonymPackage, Ppp);
112
113/// Returned (in sealed form) by [`tr::EhppEP`], needed for [`phc::user::HhppEP`].
114#[derive(Serialize, Deserialize, Debug, Clone)]
115#[serde(deny_unknown_fields)]
116pub struct EncryptedHubPseudonymPackage {
117 /// Hub pseudonym `g_H Id_U`, elgamal encrypted for `x_PHC`.
118 pub encrypted_hub_pseudonym: elgamal::Triple,
119
120 /// Nonce, from [`hub::EnterStartEP`]
121 pub hub_nonce: hub::EnterNonce,
122
123 /// Nonce, from [`PolymorphicPseudonymPackage::nonce`]
124 pub phc_nonce: phc::user::PpNonce,
125}
126
127having_message_code!(EncryptedHubPseudonymPackage, Ehpp);
128
129/// Returned (in sealed form) by [`phc::user::HhppEP`], needed for [`hub::EnterCompleteEP`].
130#[derive(Serialize, Deserialize, Debug, Clone)]
131#[serde(deny_unknown_fields)]
132pub struct HashedHubPseudonymPackage {
133 /// The hashed hub pseudonym, hashed to a point on curve25519 so we can decide to add an
134 /// additional layer of ElGamal encryption later on.
135 pub hashed_hub_pseudonym: CurvePoint,
136
137 /// When the original pseudonym was issued
138 pub pp_issued_at: jwt::NumericDate,
139
140 /// Nonce, from [`hub::EnterStartEP`]
141 pub hub_nonce: hub::EnterNonce,
142}
143
144impl Signable for HashedHubPseudonymPackage {
145 const CODE: MessageCode = MessageCode::Hhpp;
146 const CONSTELLATION_BOUND: bool = true;
147}