Skip to main content

pg_core/client/
header.rs

1//! PostGuard header definitions.
2
3use crate::artifacts::{deserialize_bin_or_b64, serialize_bin_or_b64};
4use crate::artifacts::{MultiRecipientCiphertext, PublicKey, UserSecretKey};
5use crate::consts::*;
6use crate::error::Error;
7use crate::identity::{EncryptionPolicy, HiddenPolicy, Policy};
8
9use ibe::kem::cgw_kv::CGWKV;
10use ibe::kem::mkem::MultiRecipient;
11use ibe::kem::{SharedSecret, IBKEM};
12
13use ibs::gg::Signature;
14
15use alloc::collections::BTreeMap;
16use alloc::fmt::Debug;
17use alloc::string::String;
18use alloc::vec::Vec;
19
20use rand::{CryptoRng, RngCore};
21use serde::{Deserialize, Serialize};
22
23/// Possible encryption modes.
24#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)]
25pub enum Mode {
26    /// The payload is a stream, processed in segments.
27    Streaming {
28        /// The size of segments.
29        segment_size: u32,
30
31        /// Possible size hint about the payload in the form (min, max), defaults to (0, None).
32        ///
33        /// Can be used to allocate memory beforehand, saving re-allocations.
34        size_hint: (u64, Option<u64>),
35    },
36
37    /// The payload is processed fully in memory, its size is known beforehand.
38    InMemory {
39        /// The size of the payload.
40        size: u32,
41    },
42}
43
44impl Default for Mode {
45    fn default() -> Self {
46        Mode::Streaming {
47            segment_size: SYMMETRIC_CRYPTO_DEFAULT_CHUNK,
48            size_hint: (0, None),
49        }
50    }
51}
52
53/// An initialization vector (IV).
54#[derive(Debug, Eq, PartialEq, Clone, Copy)]
55pub struct Iv<const N: usize>(pub [u8; N]);
56
57impl<const N: usize> Iv<N> {
58    fn random<R: RngCore + CryptoRng>(r: &mut R) -> Self {
59        let mut buf = [0u8; N];
60        r.fill_bytes(&mut buf);
61        Self(buf)
62    }
63}
64
65// The IV is not secret but we do want to have the possibility to encode it as human-readable.
66impl<const N: usize> Serialize for Iv<N> {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: serde::Serializer,
70    {
71        serialize_bin_or_b64(&self.0, serializer)
72    }
73}
74
75impl<'de, const N: usize> Deserialize<'de> for Iv<N> {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: serde::Deserializer<'de>,
79    {
80        let mut buf = [0u8; N];
81        deserialize_bin_or_b64(&mut buf, deserializer)?;
82
83        Ok(Self(buf))
84    }
85}
86
87/// Supported symmetric-key encryption algorithms.
88// We only target 128-bit security because it more closely matches the security target BLS12-381.
89#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)]
90pub enum Algorithm {
91    /// AES-128-GCM.
92    // Good performance with hardware acceleration.
93    Aes128Gcm(Iv<12>),
94}
95
96impl Algorithm {
97    fn new_aes128_gcm<R: RngCore + CryptoRng>(r: &mut R) -> Self {
98        Self::Aes128Gcm(Iv::random(r))
99    }
100}
101
102/// A header contains header data for _all_ recipients.
103#[derive(Debug, Serialize, Deserialize, Clone)]
104pub struct Header {
105    /// Map of recipient identifiers to [`RecipientHeader`]s.
106    pub recipients: BTreeMap<String, RecipientHeader>,
107
108    /// The symmetric-key encryption algorithm used.
109    pub algo: Algorithm,
110
111    /// The encryption mode.
112    #[serde(default)]
113    pub mode: Mode,
114}
115
116/// Contains header data specific to _one_ recipient.
117#[derive(Serialize, Deserialize, Clone, Debug)]
118pub struct RecipientHeader {
119    /// The [`HiddenPolicy`] associated with this identifier.
120    pub policy: HiddenPolicy,
121
122    /// Ciphertext for this specific recipient.
123    pub ct: MultiRecipientCiphertext<CGWKV>,
124}
125
126impl RecipientHeader {
127    /// Decapsulates a [`ibe::kem::SharedSecret`] from a [`RecipientHeader`].
128    ///
129    /// These bytes can either directly be used for an AEAD, or a key derivation function.
130    pub fn decaps(&self, usk: &UserSecretKey<CGWKV>) -> Result<SharedSecret, Error> {
131        CGWKV::multi_decaps(None, &usk.0, &self.ct.0).map_err(|_e| Error::KEM)
132    }
133}
134
135impl Header {
136    /// Creates a new [`Header`] using the Master Public Key and the policies.
137    pub fn new<R: RngCore + CryptoRng>(
138        pk: &PublicKey<CGWKV>,
139        policies: &EncryptionPolicy,
140        rng: &mut R,
141    ) -> Result<(Self, SharedSecret), Error> {
142        // Canonicalize before deriving *and* before storing, so the hidden
143        // policies that go on the wire carry the same values the identities
144        // were derived from.
145        //
146        // The KEM identities are unaffected either way: `Policy::derive`
147        // canonicalizes internally, so `derive_kem` below reaches the same
148        // identity from a raw policy. What this call changes is what the
149        // *stored* `HiddenPolicy` says, and `to_hidden` blanks the value of
150        // every attribute type outside `HINT_TYPES` — so its only observable
151        // effect on the wire is the hint a recipient is shown for a hinted
152        // type, not what anyone derives. That also means no wire-compat fixture
153        // can reach it: a non-canonical recipient value is invisible to every
154        // reader. The sender side is where a fixture bites, because
155        // `SignatureExt.pol` is a full `Policy` (see `canonical_signing_key`).
156        let policies: EncryptionPolicy = policies
157            .iter()
158            .map(|(rid, policy)| (rid.clone(), policy.canonical()))
159            .collect();
160
161        // Map each RecipientPolicy to an IBE identity.
162        let ids = policies
163            .values()
164            .map(Policy::derive_kem::<CGWKV>)
165            .collect::<Result<Vec<<CGWKV as IBKEM>::Id>, _>>()?;
166
167        // Generate the shared secret and ciphertexts.
168        let (cts, ss) = CGWKV::multi_encaps(&pk.0, &ids[..], rng);
169
170        // Generate all RecipientHeaders.
171        let recipient_info: BTreeMap<String, RecipientHeader> = policies
172            .iter()
173            .zip(cts)
174            .map(|((rid, policy), ct)| {
175                (
176                    rid.clone(),
177                    RecipientHeader {
178                        policy: policy.to_hidden(),
179                        ct: MultiRecipientCiphertext(ct),
180                    },
181                )
182            })
183            .collect();
184
185        Ok((
186            Header {
187                recipients: recipient_info,
188                algo: Algorithm::new_aes128_gcm(rng),
189                mode: Mode::default(),
190            },
191            ss,
192        ))
193    }
194
195    /// Set the encryption mode.
196    pub fn with_mode(mut self, mode: Mode) -> Self {
197        self.mode = mode;
198        self
199    }
200
201    /// Set the encryption algorithm.
202    pub fn with_algo(mut self, algo: Algorithm) -> Self {
203        self.algo = algo;
204        self
205    }
206}
207
208/// An IBS signature, extended with the identity claims.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct SignatureExt {
211    /// The identity-based signature.
212    pub sig: Signature,
213
214    /// The claimed identity as a [`Policy`] associated with this signature.
215    pub pol: Policy,
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::test::TestSetup;
222
223    #[test]
224    fn test_enc_dec_json() {
225        let mut rng = rand::thread_rng();
226        let setup = TestSetup::new(&mut rng);
227
228        let (header, _ss) = Header::new(&setup.ibe_pk, &setup.policy, &mut rng).unwrap();
229        let header2 = header.clone();
230
231        let s = serde_json::to_string(&header).unwrap();
232        let decoded: Header = serde_json::from_str(&s).unwrap();
233
234        assert_eq!(decoded.recipients.len(), 2);
235
236        assert_eq!(
237            &decoded.recipients.get("Bob").unwrap().policy,
238            &setup.policy.get("Bob").unwrap().to_hidden()
239        );
240
241        assert_eq!(&decoded.algo, &header2.algo);
242        assert_eq!(&decoded.mode, &header2.mode);
243    }
244
245    #[test]
246    fn test_enc_dec_binary() {
247        let mut rng = rand::thread_rng();
248        let setup = TestSetup::new(&mut rng);
249
250        let (header, _ss) = Header::new(&setup.ibe_pk, &setup.policy, &mut rng).unwrap();
251        let header2 = header.clone();
252
253        let v = crate::bincode_compat::serialize(&header).unwrap();
254        let decoded: Header = crate::bincode_compat::deserialize(&v).unwrap();
255
256        assert_eq!(decoded.recipients.len(), 2);
257        assert_eq!(
258            &decoded.recipients.get("Charlie").unwrap().policy,
259            &setup.policy.get("Charlie").unwrap().to_hidden()
260        );
261        assert_eq!(&decoded.algo, &header2.algo);
262        assert_eq!(&decoded.mode, &header2.mode);
263    }
264
265    #[test]
266    fn test_round() {
267        // This test tests that both encoding methods derive the same keys as the sender.
268
269        let mut rng = rand::thread_rng();
270        let setup = TestSetup::new(&mut rng);
271
272        // Take Bob's usk for email + name.
273        let test_usk = &setup.usks[2];
274
275        let (header, ss1) = Header::new(&setup.ibe_pk, &setup.policy, &mut rng).unwrap();
276        let header2 = header.clone();
277        let header3 = header.clone();
278
279        // encode as binary
280        let bytes = crate::bincode_compat::serialize(&header).unwrap();
281
282        // encode as JSON
283        let json = serde_json::to_string(&header2).unwrap();
284
285        let decoded1: Header = crate::bincode_compat::deserialize(&bytes).unwrap();
286        let ss2 = decoded1
287            .recipients
288            .get("Bob")
289            .unwrap()
290            .decaps(test_usk)
291            .unwrap();
292
293        let decoded2: Header = serde_json::from_str(&json).unwrap();
294        let ss3 = decoded2
295            .recipients
296            .get("Bob")
297            .unwrap()
298            .decaps(test_usk)
299            .unwrap();
300
301        assert_eq!(&decoded1.recipients.len(), &header3.recipients.len());
302        assert_eq!(&decoded1.algo, &header3.algo);
303        assert_eq!(&decoded1.mode, &header3.mode);
304
305        // Make sure we derive the same keys.
306        assert_eq!(&ss1, &ss2);
307        assert_eq!(&ss1, &ss3);
308    }
309}