Skip to main content

pg_core/client/web/
mod.rs

1//! Implementation for the web, backed by [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).
2//!
3//! This module utilizes the symmetric primitives provided by [Web
4//! Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). The streaming
5//! interface, enabled using the feature `"stream"` enables an interface to encrypt data from a
6//! [`Stream<Item = Result<Uint8Array, JsValue>>`][`futures::stream::Stream`] into a
7//! [`Sink<Uint8Array, Error = JsValue>`][`futures::sink::Sink`]. These can easily interact with
8//! [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) using the
9//! [wasm-streams](https://docs.rs/wasm-streams/latest/wasm_streams/index.html) crate.
10//!
11//! This module is only available on the `target = "wasm32-unknown-unknown"` and the output
12//! _should_ be used in browser environments. This also greatly reduces the bundle size.
13//!
14//! This module can largely be simplified when [the AEAD crate](https://docs.rs/aead/latest/aead/index.html) will support async, see
15//! [the relevant issue](https://github.com/RustCrypto/traits/issues/304).
16//!
17
18#[cfg(not(any(target_arch = "wasm32", docsrs)))]
19compile_error!("\"web\" feature should only be enabled on wasm32 targets");
20
21mod aesgcm;
22
23#[cfg(feature = "stream")]
24pub mod stream;
25
26use super::web::aesgcm::encrypt;
27use super::web::aesgcm::{decrypt, get_key};
28
29use crate::artifacts::{PublicKey, UserSecretKey};
30use crate::client::*;
31use crate::error::Error;
32use crate::identity::{EncryptionPolicy, Policy};
33
34use ibe::kem::cgw_kv::CGWKV;
35use ibs::gg::Signer;
36
37use js_sys::Error as JsError;
38use js_sys::Uint8Array;
39use rand::{CryptoRng, RngCore};
40use wasm_bindgen::JsValue;
41
42use alloc::string::ToString;
43use alloc::vec::Vec;
44
45/// In-memory configuration for a [`Sealer`].
46#[derive(Debug)]
47pub struct SealerMemoryConfig {
48    key: [u8; KEY_SIZE],
49    nonce: [u8; IV_SIZE],
50}
51
52/// In-memory configuration for an [`Unsealer`].
53#[derive(Debug)]
54pub struct UnsealerMemoryConfig {
55    message_len: usize,
56}
57
58impl SealerConfig for SealerMemoryConfig {}
59impl super::sealed::SealerConfig for SealerMemoryConfig {}
60
61impl UnsealerConfig for UnsealerMemoryConfig {}
62impl super::sealed::UnsealerConfig for UnsealerMemoryConfig {}
63
64/// The AEAD plaintext as this version writes it.
65///
66/// `pub_pol` is a copy of the sender's public signing policy, the same value
67/// that goes into `h_sig_ext` outside the AEAD. Only the copy in here is
68/// covered by the AEAD, so a reader can tell that a party on the wire replaced
69/// the header signature block with one made by another signing key.
70///
71/// The copy is authenticated against the DEM key, not against the sender's
72/// signing key, so it covers a party on the wire and nobody who holds the DEM
73/// key themselves. Read it as a check against the wire, not as sender
74/// authentication. Binding the policy under something only the sender controls
75/// is a design change, not this check.
76#[derive(Debug, Serialize, Deserialize)]
77struct MessageAndSignature {
78    message: Vec<u8>,
79    sig: SignatureExt,
80    pub_pol: Policy,
81}
82
83/// The part of that plaintext every version writes.
84///
85/// bincode encodes fields positionally and ignores trailing bytes, so this
86/// decodes both a container sealed by this version and one sealed before
87/// `pub_pol` existed. The reader decodes this and inspects what follows
88/// itself, rather than decoding a shape an older sealer never wrote.
89#[derive(Debug, Serialize, Deserialize)]
90struct MessageAndSignaturePrefix {
91    message: Vec<u8>,
92    sig: SignatureExt,
93}
94
95impl<'r, R: RngCore + CryptoRng> Sealer<'r, R, SealerMemoryConfig> {
96    /// Create a new [`Sealer`].
97    pub fn new(
98        mpk: &PublicKey<CGWKV>,
99        policies: &EncryptionPolicy,
100        pub_sign_key: &SigningKeyExt,
101        rng: &'r mut R,
102    ) -> Result<Self, Error> {
103        let (header, ss) = Header::new(mpk, policies, rng)?;
104        let Algorithm::Aes128Gcm(iv) = header.algo;
105
106        let mut key = [0u8; KEY_SIZE];
107        let mut nonce = [0u8; IV_SIZE];
108        key.copy_from_slice(&ss.0[..KEY_SIZE]);
109        nonce.copy_from_slice(&iv.0[..IV_SIZE]);
110
111        Ok(Self {
112            rng,
113            header,
114            pub_sign_key: crate::client::canonical_signing_key(pub_sign_key),
115            priv_sign_key: None,
116            config: SealerMemoryConfig { key, nonce },
117        })
118    }
119
120    /// Seals the entire payload.
121    pub async fn seal(mut self, message: &Uint8Array) -> Result<Uint8Array, Error> {
122        let mut out = Vec::with_capacity(message.byte_length() as usize + 1024);
123
124        out.extend_from_slice(&PRELUDE);
125        out.extend_from_slice(&VERSION_2.to_be_bytes());
126        self.header = self.header.with_mode(Mode::InMemory {
127            size: message.byte_length(),
128        });
129
130        let header_buf = crate::bincode_compat::serialize(&self.header)?;
131        out.extend_from_slice(&(header_buf.len() as u32).to_be_bytes());
132        out.extend_from_slice(&header_buf);
133
134        let signer = Signer::new().chain(header_buf);
135        let h_sig = signer.clone().sign(&self.pub_sign_key.key.0, self.rng);
136
137        let h_sig_ext = SignatureExt {
138            sig: h_sig,
139            pol: self.pub_sign_key.policy.clone(),
140        };
141
142        let h_sig_ext_bytes = crate::bincode_compat::serialize(&h_sig_ext)?;
143        out.extend_from_slice(&(h_sig_ext_bytes.len() as u32).to_be_bytes());
144        out.extend_from_slice(&h_sig_ext_bytes);
145
146        let m = message.to_vec();
147        let pub_pol = self.pub_sign_key.policy.clone();
148        let m_sig_key = self.priv_sign_key.unwrap_or(self.pub_sign_key);
149        let m_sig = signer.chain(&m).sign(&m_sig_key.key.0, self.rng);
150
151        let input = crate::bincode_compat::serialize(&MessageAndSignature {
152            message: m,
153            sig: SignatureExt {
154                sig: m_sig,
155                pol: m_sig_key.policy.clone(),
156            },
157            pub_pol,
158        })?;
159
160        let key = get_key(&self.config.key).await?;
161        let ciphertext = encrypt(
162            &key,
163            &self.config.nonce,
164            &Uint8Array::new_with_length(0),
165            &Uint8Array::from(input.as_slice()),
166        )
167        .await?;
168
169        out.extend_from_slice(&ciphertext.to_vec());
170
171        Ok(Uint8Array::from(out.as_slice()))
172    }
173}
174
175impl Unsealer<Uint8Array, UnsealerMemoryConfig> {
176    /// Create a new [`Unsealer`].
177    pub fn new(input: &Uint8Array, vk: &VerifyingKey) -> Result<Self, Error> {
178        let b = input.to_vec();
179        let (preamble_bytes, b) = try_split_at(&b, PREAMBLE_SIZE, "preamble")?;
180        let (version, header_len) = preamble_checked(preamble_bytes)?;
181
182        let (header_bytes, b) = try_split_at(b, header_len, "header")?;
183        let (h_sig_len_bytes, b) = try_split_at(b, SIG_SIZE_SIZE, "header signature length")?;
184        let h_sig_len = u32::from_be_bytes(h_sig_len_bytes.try_into()?);
185        let (h_sig_bytes, ct) = try_split_at(b, h_sig_len as usize, "header signature")?;
186
187        let h_sig_ext: SignatureExt = crate::bincode_compat::deserialize(h_sig_bytes)?;
188        let id = h_sig_ext.pol.derive_ibs()?;
189
190        let verifier = Verifier::default().chain(&header_bytes);
191
192        if !verifier.clone().verify(&vk.0, &h_sig_ext.sig, &id) {
193            return Err(Error::IncorrectSignature.into());
194        }
195
196        let header: Header = crate::bincode_compat::deserialize(header_bytes)?;
197        let message_len = match header.mode {
198            Mode::InMemory { size } => size as usize,
199            _ => return Err(Error::ModeNotSupported(header.mode).into()),
200        };
201
202        Ok(Self {
203            version,
204            header,
205            pub_id: h_sig_ext.pol,
206            r: Uint8Array::from(ct),
207            verifier,
208            vk: vk.clone(),
209            config: UnsealerMemoryConfig { message_len },
210        })
211    }
212
213    /// Unseals the payload.
214    pub async fn unseal(
215        self,
216        ident: &str,
217        usk: &UserSecretKey<CGWKV>,
218    ) -> Result<(Uint8Array, VerificationResult), Error> {
219        let rec_info = self
220            .header
221            .recipients
222            .get(ident)
223            .ok_or_else(|| Error::UnknownIdentifier(ident.to_string()))?;
224
225        let ss = rec_info.decaps(usk)?;
226        let key = get_key(&ss.0[..KEY_SIZE]).await?;
227
228        let Algorithm::Aes128Gcm(iv) = self.header.algo;
229
230        let plain = decrypt(&key, &iv.0, &Uint8Array::new_with_length(0), &self.r)
231            .await?
232            .to_vec();
233
234        let (msg, read): (MessageAndSignaturePrefix, usize) =
235            crate::bincode_compat::deserialize_with_len(&plain).map_err(Into::<Error>::into)?;
236
237        // A container sealed by this version carries the sender's public
238        // signing policy behind the message signature, under the AEAD. The
239        // header signature outside the AEAD claims a policy too; if they
240        // disagree, that block was swapped. Nothing following means the sealer
241        // predates the copy. Both readings are authenticated against the DEM
242        // key and reach no further, so the absence branch is not the safe half
243        // of the two — see the note on `MessageAndSignature`.
244        if let Some(trailing) = plain.get(read..).filter(|t| !t.is_empty()) {
245            let sealed_pub_pol: Policy =
246                crate::bincode_compat::deserialize(trailing).map_err(Into::<Error>::into)?;
247
248            if sealed_pub_pol != self.pub_id {
249                return Err(Error::IncorrectSignature);
250            }
251        }
252
253        let id = msg.sig.pol.derive_ibs()?;
254        let verified = self
255            .verifier
256            .chain(&msg.message)
257            .verify(&self.vk.0, &msg.sig.sig, &id);
258
259        if !verified {
260            return Err(Error::IncorrectSignature.into());
261        }
262
263        debug_assert_eq!(self.config.message_len, msg.message.len());
264
265        let res = Uint8Array::from(msg.message.as_slice());
266
267        let private = if self.pub_id == msg.sig.pol {
268            None
269        } else {
270            Some(msg.sig.pol)
271        };
272
273        Ok((
274            res,
275            VerificationResult {
276                public: self.pub_id,
277                private,
278            },
279        ))
280    }
281}
282
283impl From<Error> for JsValue {
284    fn from(err: Error) -> Self {
285        JsError::new(&err.to_string()).into()
286    }
287}
288
289impl From<JsValue> for Error {
290    fn from(e: JsValue) -> Self {
291        Error::JavaScript(e)
292    }
293}