Skip to main content

pubhubs/
phcrypto.rs

1//! Pubhubs specific crypto
2
3use crate::{
4    api, attr,
5    common::{
6        elgamal, kem,
7        secret::{self, DigestibleSecret},
8    },
9    id,
10    misc::{crypto, jwt},
11    servers::constellation,
12};
13
14use curve25519_dalek::Scalar;
15use sha2::digest::Digest;
16
17/// Computes the `x B` from `x_T * B` and `x_PHC`, used by PHC to create the constellation
18pub fn combine_master_enc_key_parts(
19    public_part: &elgamal::PublicKey,
20    private_part: &elgamal::PrivateKey,
21) -> elgamal::PublicKey {
22    private_part.scale(public_part)
23}
24
25/// Hash of a master encryption key part (`x_T B` or `x_PHC B`), published in the constellation and
26/// discovery info in place of the part itself, so the part is not exposed in the clear.
27pub fn master_enc_key_part_hash(part: &elgamal::PublicKey) -> id::Id {
28    b"".as_slice().derive_id(
29        sha2::Sha256::new().chain_update(part),
30        "pubhubs-master-enc-key-part-hash",
31    )
32}
33
34/// Computes the **pseudonymisation factor** $g_H$ for the hub identified by `hub_id`,
35/// from the transcryptor's `pseud_factor_secret`.  See [`crate::api::sso`] for the
36/// exact formula.
37pub fn pseud_factor_for_hub(pseud_factor_secret: impl DigestibleSecret, hub_id: id::Id) -> Scalar {
38    pseud_factor_secret.derive_scalar(
39        sha2::Sha512::new().chain_update(hub_id.as_slice()),
40        "pubhubs-pseud-factor",
41    )
42}
43
44/// Turns the given polymorphic pseudonym `pp` (which should be `Id_U` elgamal encrypted for `x`)
45/// into an encrypted hub pseudonym (which should be `g_H Id_U` elgamal encrypted for `x_PHC`).
46pub fn t_encrypted_hub_pseudonym(
47    pp: elgamal::Triple,
48    pseud_factor_secret: impl DigestibleSecret,
49    master_enc_key_part_inv: &Scalar,
50    hub_id: id::Id,
51) -> elgamal::Triple {
52    let g_h = pseud_factor_for_hub(pseud_factor_secret, hub_id);
53    pp.rsk_with_s(&g_h).and_k(master_enc_key_part_inv)
54}
55
56/// Combines a post-quantum ML-KEM and classical Ristretto-DH shared secret.
57pub fn kem_shared_secret(
58    ss_ml: &aws_lc_rs::kem::SharedSecret,
59    ss_ec: &elgamal::SharedSecret,
60) -> kem::SharedSecret {
61    let inner: [u8; 32] = ss_ml
62        .as_ref()
63        .update_digest(
64            sha2::Sha256::new()
65                .chain_update(secret::encode_usize(ss_ec.as_bytes().len()))
66                .chain_update(ss_ec.as_bytes()),
67            "pubhubs-kem-combinator",
68        )
69        .finalize()
70        .into();
71    inner.into()
72}
73
74/// Computes the [`jwt::HS256`] key used to sign [`Attr`] from the secret shared between the
75/// authentication server and pubhubs central.
76///
77/// [`Attr`]: crate::attr::Attr
78pub fn attr_signing_key(shared_secret: &kem::SharedSecret) -> jwt::HS256 {
79    shared_secret.derive_hs256(sha2::Sha256::new(), "pubhubs-attr-signing")
80}
81
82/// Computes the [`crypto::SealingKey`] used to seal messages between servers shared a secret.
83pub fn sealing_secret(shared_secret: &kem::SharedSecret) -> crypto::SealingKey {
84    shared_secret.derive_sealing_key(sha2::Sha256::new(), "pubhubs-sealing-secret")
85}
86
87/// Derives an [`Id`] for an [`Attr`].
88///
89/// [`Attr`]: attr::Attr
90/// [`Id`]: id::Id
91pub fn attr_id(attr: &attr::Attr, secret: impl secret::DigestibleSecret) -> crate::id::Id {
92    secret.derive_id(
93        sha2::Sha256::new()
94            .chain_update(attr.attr_type.as_slice())
95            .chain_update(secret::encode_usize(attr.value.len()))
96            .chain_update(attr.value.as_bytes()),
97        "pubhubs-attr-id",
98    )
99}
100
101/// Derives an [`id::Id`] for a [`constellation::Inner`].
102pub fn constellation_id(c: &constellation::Inner) -> id::Id {
103    b"".as_slice()
104        .derive_id(c.sha256(), "pubhubs-constellation-id")
105}
106
107/// Derives an [`id::Id`] for a [`kem::EncapKeyBytes`].
108pub fn encap_key_id(ek: &kem::EncapKeyBytes) -> id::Id {
109    // `ml` (ML-KEM-768) and `ec` (Ristretto) are fixed-length, so concatenating them is unambiguous.
110    b"".as_slice().derive_id(
111        sha2::Sha256::new()
112            .chain_update(&ek.ml[..])
113            .chain_update(&ek.ec[..]),
114        "pubhubs-encap-key-id",
115    )
116}
117
118/// Derives an [`id::Id`] for a [`jwt::JWT`].
119pub fn jwt_id(jwt: &jwt::JWT) -> id::Id {
120    b"".as_slice().derive_id(jwt.sha256(), "pubhubs-jwt-id")
121}
122
123/// Derives an `hmac` for a user object stored at pubhubs central.
124///
125/// See [`crate::api::phc::user::GetObjectEP`].
126pub fn phc_user_object_hmac(
127    object_id: crate::id::Id,
128    secret: impl secret::DigestibleSecret,
129) -> crate::id::Id {
130    secret.derive_id(
131        sha2::Sha256::new().chain_update(object_id.as_slice()),
132        "pubhubs-user-object-hmac",
133    )
134}
135
136/// Derives attribute keys for a given [`attr::Attr`]ibute and a list of timestamps.
137pub fn auths_attr_keys(
138    attr: attr::Attr,
139    secret: impl secret::DigestibleSecret,
140    timestamps: impl IntoIterator<Item = api::NumericDate>,
141) -> Vec<Vec<u8>> {
142    let attr_secret = attr_id(&attr, secret);
143
144    timestamps
145        .into_iter()
146        .map(|ts| {
147            attr_secret.derive_bytes(
148                sha2::Sha256::new().chain_update(ts.timestamp().to_be_bytes()),
149                "pubhubs-attr-key",
150            )
151        })
152        .collect()
153}