Skip to main content

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