script/dom/subtlecrypto/
chacha20_poly1305_operation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use chacha20poly1305::aead::{AeadMutInPlace, KeyInit, OsRng};
6use chacha20poly1305::{ChaCha20Poly1305, Key};
7
8use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
9    CryptoKeyMethods, KeyType, KeyUsage,
10};
11use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
12use crate::dom::bindings::error::Error;
13use crate::dom::bindings::root::DomRoot;
14use crate::dom::bindings::str::DOMString;
15use crate::dom::cryptokey::{CryptoKey, Handle};
16use crate::dom::globalscope::GlobalScope;
17use crate::dom::subtlecrypto::{
18    ALG_CHACHA20_POLY1305, ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives,
19    SubtleAeadParams, SubtleKeyAlgorithm,
20};
21use crate::script_runtime::CanGc;
22
23/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-encrypt>
24pub(crate) fn encrypt(
25    normalized_algorithm: &SubtleAeadParams,
26    key: &CryptoKey,
27    plaintext: &[u8],
28) -> Result<Vec<u8>, Error> {
29    // Step 1. If the iv member of normalizedAlgorithm does not have a length of 12 bytes, then
30    // throw an OperationError.
31    if normalized_algorithm.iv.len() != 12 {
32        return Err(Error::Operation(Some(
33            "The iv member of normalizedAlgorithm does not have a length of 12 bytes".to_string(),
34        )));
35    }
36
37    // Step 2. If the tagLength member of normalizedAlgorithm is present and is not 128, then throw
38    // an OperationError.
39    if normalized_algorithm
40        .tag_length
41        .is_some_and(|tag_length| tag_length != 128)
42    {
43        return Err(Error::Operation(Some(
44            "The tagLength member of normalizedAlgorithm is present and is not 128".to_string(),
45        )));
46    }
47
48    // Step 3. Let additionalData be the additionalData member of normalizedAlgorithm if present or
49    // the empty octet string otherwise.
50    let additional_data = normalized_algorithm
51        .additional_data
52        .as_deref()
53        .unwrap_or_default();
54
55    // Step 4. Let ciphertext be the output that results from performing the AEAD_CHACHA20_POLY1305
56    // encryption algorithm described in Section 2.8 of [RFC8439], using the key represented by
57    // [[handle]] internal slot of key as the key input parameter, the iv member of
58    // normalizedAlgorithm as the nonce input parameter, plaintext as the plaintext input
59    // parameter, and additionalData as the additional authenticated data (AAD) input parameter.
60    let Handle::ChaCha20Poly1305Key(handle) = key.handle() else {
61        return Err(Error::Operation(Some(
62            "Unable to access key represented by [[handle]] internal slot".to_string(),
63        )));
64    };
65    let mut cipher = ChaCha20Poly1305::new(handle);
66    let nonce = normalized_algorithm.iv.as_slice();
67    let mut ciphertext = plaintext.to_vec();
68    cipher
69        .encrypt_in_place(nonce.into(), additional_data, &mut ciphertext)
70        .map_err(|_| {
71            Error::Operation(Some(
72                "ChaCha20-Poly1305 fails to encrypt plaintext".to_string(),
73            ))
74        })?;
75
76    // Step 5. Return ciphertext.
77    Ok(ciphertext)
78}
79
80/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-decrypt>
81pub(crate) fn decrypt(
82    normalized_algorithm: &SubtleAeadParams,
83    key: &CryptoKey,
84    ciphertext: &[u8],
85) -> Result<Vec<u8>, Error> {
86    // Step 1. If the iv member of normalizedAlgorithm does not have a length of 12 bytes, then
87    // throw an OperationError.
88    if normalized_algorithm.iv.len() != 12 {
89        return Err(Error::Operation(Some(
90            "The iv member of normalizedAlgorithm does not have a length of 12 bytes".to_string(),
91        )));
92    }
93
94    // Step 2. If the tagLength member of normalizedAlgorithm is present and is not 128, then throw
95    // an OperationError.
96    if normalized_algorithm
97        .tag_length
98        .is_some_and(|tag_length| tag_length != 128)
99    {
100        return Err(Error::Operation(Some(
101            "The tagLength member of normalizedAlgorithm is present and is not 128".to_string(),
102        )));
103    }
104
105    // Step 3. If ciphertext has a length less than 128 bits, then throw an OperationError.
106    if ciphertext.len() < 16 {
107        return Err(Error::Operation(Some(
108            "Ciphertext has a length less than 128 bits".to_string(),
109        )));
110    }
111
112    // Step 4. Let additionalData be the additionalData member of normalizedAlgorithm if present or
113    // the empty octet string otherwise.
114    let additional_data = normalized_algorithm
115        .additional_data
116        .as_deref()
117        .unwrap_or_default();
118
119    // Step 5. Perform the AEAD_CHACHA20_POLY1305 decryption algorithm described in Section 2.8 of
120    // [RFC8439], using the key represented by [[handle]] internal slot of key as the key input
121    // parameter, the iv member of normalizedAlgorithm as the nonce input parameter, ciphertext as
122    // the ciphertext input parameter, and additionalData as the additional authenticated data
123    // (AAD) input parameter.
124    //
125    // If the result of the algorithm is the indication of authentication failure:
126    //     throw an OperationError
127    // Otherwise:
128    //     Let plaintext be the resulting plaintext.
129    let Handle::ChaCha20Poly1305Key(handle) = key.handle() else {
130        return Err(Error::Operation(Some(
131            "Unable to access key represented by [[handle]] internal slot".to_string(),
132        )));
133    };
134    let mut cipher = ChaCha20Poly1305::new(handle);
135    let nonce = normalized_algorithm.iv.as_slice();
136    let mut plaintext = ciphertext.to_vec();
137    cipher
138        .decrypt_in_place(nonce.into(), additional_data, &mut plaintext)
139        .map_err(|_| {
140            Error::Operation(Some(
141                "ChaCha20-Poly1305 fails to decrypt ciphertext".to_string(),
142            ))
143        })?;
144
145    // Step 6. Return plaintext.
146    Ok(plaintext)
147}
148
149/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-generate-key>
150pub(crate) fn generate_key(
151    global: &GlobalScope,
152    extractable: bool,
153    usages: Vec<KeyUsage>,
154    can_gc: CanGc,
155) -> Result<DomRoot<CryptoKey>, Error> {
156    // Step 1. If usages contains any entry which is not one of "encrypt", "decrypt", "wrapKey" or
157    // "unwrapKey", then throw a SyntaxError.
158    if usages.iter().any(|usage| {
159        !matches!(
160            usage,
161            KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
162        )
163    }) {
164        return Err(Error::Syntax(Some(
165            "Usages contains an entry which is not one of \"encrypt\", \"decrypt\", \"wrapKey\" \
166            or \"unwrapKey\""
167                .to_string(),
168        )));
169    }
170
171    // Step 2. Generate a 256-bit key.
172    // Step 3. If the key generation step fails, then throw an OperationError.
173    let generated_key = ChaCha20Poly1305::generate_key(&mut OsRng);
174
175    // Step 4. Let key be a new CryptoKey object representing the generated key.
176    // Step 5. Set the [[type]] internal slot of key to "secret".
177    // Step 6. Let algorithm be a new KeyAlgorithm.
178    // Step 7. Set the name attribute of algorithm to "ChaCha20-Poly1305".
179    // Step 8. Set the [[algorithm]] internal slot of key to algorithm.
180    // Step 9. Set the [[extractable]] internal slot of key to be extractable.
181    // Step 10. Set the [[usages]] internal slot of key to be usages.
182    let algorithm = SubtleKeyAlgorithm {
183        name: ALG_CHACHA20_POLY1305.to_string(),
184    };
185    let key = CryptoKey::new(
186        global,
187        KeyType::Secret,
188        extractable,
189        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
190        usages,
191        Handle::ChaCha20Poly1305Key(generated_key),
192        can_gc,
193    );
194
195    // Step 11. Return key.
196    Ok(key)
197}
198
199/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-import-key>
200pub(crate) fn import_key(
201    global: &GlobalScope,
202    format: KeyFormat,
203    key_data: &[u8],
204    extractable: bool,
205    usages: Vec<KeyUsage>,
206    can_gc: CanGc,
207) -> Result<DomRoot<CryptoKey>, Error> {
208    // Step 1. Let keyData be the key data to be imported.
209
210    // Step 2. If usages contains an entry which is not one of "encrypt", "decrypt", "wrapKey" or
211    // "unwrapKey", then throw a SyntaxError.
212    if usages.iter().any(|usage| {
213        !matches!(
214            usage,
215            KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
216        )
217    }) {
218        return Err(Error::Syntax(Some(
219            "Usages contains an entry which is not one of \"encrypt\", \"decrypt\", \"wrapKey\" \
220            or \"unwrapKey\""
221                .to_string(),
222        )));
223    }
224
225    // Step 3.
226    let data;
227    match format {
228        // If format is "raw-secret":
229        KeyFormat::Raw_secret => {
230            // Step 3.1. Let data be keyData.
231            data = key_data.to_vec();
232
233            // Step 3.2. If the length in bits of data is not 256 then throw a DataError.
234            if data.len() != 32 {
235                return Err(Error::Data(Some(
236                    "The length in bits of data is not 256".to_string(),
237                )));
238            }
239        },
240        // If format is "jwk":
241        KeyFormat::Jwk => {
242            // Step 3.1.
243            // If keyData is a JsonWebKey dictionary:
244            //     Let jwk equal keyData.
245            // Otherwise:
246            //     Throw a DataError.
247            let jwk = JsonWebKey::parse(GlobalScope::get_cx(), key_data)?;
248
249            // Step 3.2. If the kty field of jwk is not "oct", then throw a DataError.
250            if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") {
251                return Err(Error::Data(Some(
252                    "The kty field of jwk is not \"oct\"".to_string(),
253                )));
254            }
255
256            // Step 3.3. If jwk does not meet the requirements of Section 6.4 of JSON Web
257            // Algorithms [JWA], then throw a DataError.
258            // Step 3.4. Let data be the byte sequence obtained by decoding the k field of jwk.
259            data = jwk.decode_required_string_field(JwkStringField::K)?;
260
261            // Step 3.5. If the alg field of jwk is present, and is not "C20P", then throw a
262            // DataError.
263            if jwk.alg.as_ref().is_some_and(|alg| alg != "C20P") {
264                return Err(Error::Data(Some(
265                    "The alg field of jwk is present, and is not \"C20P\"".to_string(),
266                )));
267            }
268
269            // Step 3.6. If usages is non-empty and the use field of jwk is present and is not
270            // "enc", then throw a DataError.
271            if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
272                return Err(Error::Data(Some(
273                    "Usages is non-empty and the use field of jwk is present and is not \"enc\""
274                        .to_string(),
275                )));
276            }
277
278            // Step 3.7. If the key_ops field of jwk is present, and is invalid according to the
279            // requirements of JSON Web Key [JWK] or does not contain all of the specified usages
280            // values, then throw a DataError.
281            jwk.check_key_ops(&usages)?;
282
283            // Step 3.8. If the ext field of jwk is present and has the value false and extractable
284            // is true, then throw a DataError.
285            if jwk.ext.is_some_and(|ext| !ext) && extractable {
286                return Err(Error::Data(Some(
287                    "The ext field of jwk is present and has the value false and \
288                    extractable is true"
289                        .to_string(),
290                )));
291            }
292        },
293        // Otherwise:
294        _ => {
295            // throw a NotSupportedError.
296            return Err(Error::NotSupported(Some(
297                "ChaCha20-Poly1305 does not support this import key format".to_string(),
298            )));
299        },
300    }
301
302    // Step 4. Let key be a new CryptoKey object representing a key with value data.
303    // Step 5. Set the [[type]] internal slot of key to "secret".
304    // Step 6. Let algorithm be a new KeyAlgorithm.
305    // Step 7. Set the name attribute of algorithm to "ChaCha20-Poly1305".
306    // Step 8. Set the [[algorithm]] internal slot of key to algorithm.
307    let handle = Handle::ChaCha20Poly1305Key(Key::from_exact_iter(data).ok_or(Error::Data(
308        Some("ChaCha20-Poly1305 fails to create key from data".to_string()),
309    ))?);
310    let algorithm = SubtleKeyAlgorithm {
311        name: ALG_CHACHA20_POLY1305.to_string(),
312    };
313    let key = CryptoKey::new(
314        global,
315        KeyType::Secret,
316        extractable,
317        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
318        usages,
319        handle,
320        can_gc,
321    );
322
323    // Step 9. Return key.
324    Ok(key)
325}
326
327/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-export-key>
328pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
329    // Step 1. If the underlying cryptographic key material represented by the [[handle]] internal
330    // slot of key cannot be accessed, then throw an OperationError.
331    let Handle::ChaCha20Poly1305Key(key_handle) = key.handle() else {
332        return Err(Error::Operation(Some(
333            "The underlying cryptographic key material represented by the [[handle]] internal \
334            slot of key cannot be accessed"
335                .to_string(),
336        )));
337    };
338
339    // Step 2.
340    let result = match format {
341        // If format is "raw-secret":
342        KeyFormat::Raw_secret => {
343            // Step 2.1. Let data be a byte sequence containing the raw octets of the key
344            // represented by [[handle]] internal slot of key.
345            let data = key_handle.to_vec();
346
347            // Step 2.2 Let result be data.
348            ExportedKey::Bytes(data)
349        },
350        // If format is "jwk":
351        KeyFormat::Jwk => {
352            // Step 2.1. Let jwk be a new JsonWebKey dictionary.
353            // Step 2.2. Set the kty attribute of jwk to the string "oct".
354            let mut jwk = JsonWebKey {
355                kty: Some(DOMString::from("oct")),
356                ..Default::default()
357            };
358
359            // Step 2.3. Set the k attribute of jwk to be a string containing the raw octets of the
360            // key represented by [[handle]] internal slot of key, encoded according to Section 6.4
361            // of JSON Web Algorithms [JWA].
362            jwk.encode_string_field(JwkStringField::K, key_handle.as_slice());
363
364            // Step 2.4. Set the alg attribute of jwk to the string "C20P".
365            jwk.alg = Some(DOMString::from("C20P"));
366
367            // Step 2.5. Set the key_ops attribute of jwk to equal the usages attribute of key.
368            jwk.set_key_ops(key.usages());
369
370            // Step 2.6. Set the ext attribute of jwk to equal the [[extractable]] internal slot of
371            // key.
372            jwk.ext = Some(key.Extractable());
373
374            // Step 2.7. Let result be jwk.
375            ExportedKey::Jwk(Box::new(jwk))
376        },
377        // Otherwise:
378        _ => {
379            // throw a NotSupportedError.
380            return Err(Error::NotSupported(Some(
381                "ChaCha20-Poly1305 does not support this import key format".to_string(),
382            )));
383        },
384    };
385
386    // Step 3. Return result.
387    Ok(result)
388}
389
390/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-get-key-length>
391pub(crate) fn get_key_length() -> Result<Option<u32>, Error> {
392    // Step 1. Return 256.
393    Ok(Some(256))
394}