script_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 script_bindings::codegen::GenericBindings::CryptoKeyBinding::{
9 CryptoKeyMethods, KeyType, KeyUsage,
10};
11use script_bindings::codegen::GenericBindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
12use script_bindings::error::Error;
13use script_bindings::root::DomRoot;
14use script_bindings::str::DOMString;
15use zeroize::Zeroizing;
16
17use crate::cryptokey::{CryptoKey, Handle, KeyUsageSliceHelper};
18use crate::subtlecrypto::{
19 AeadParams, CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithm,
20 KeyAlgorithmAndDerivatives,
21};
22use crate::traits::Equivalence;
23
24/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-encrypt>
25pub(crate) fn encrypt<D: Equivalence>(
26 normalized_algorithm: &AeadParams,
27 key: &CryptoKey<D>,
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<D: Equivalence>(
87 normalized_algorithm: &AeadParams,
88 key: &CryptoKey<D>,
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<D: Equivalence>(
160 cx: &mut JSContext,
161 global: &D::GlobalScope,
162 extractable: bool,
163 usages: Vec<KeyUsage>,
164) -> Result<DomRoot<CryptoKey<D>>, 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 usages.ensure_only_contain_entries_from(&[
168 KeyUsage::Encrypt,
169 KeyUsage::Decrypt,
170 KeyUsage::WrapKey,
171 KeyUsage::UnwrapKey,
172 ])?;
173
174 // Step 2. Generate a 256-bit key.
175 // Step 3. If the key generation step fails, then throw an OperationError.
176 let chacha20poly1305_key = Key::try_generate()
177 .map_err(|_| Error::Operation(Some("Failed to generate ChaCha20Poly1305 key".into())))?;
178
179 // Step 4. Let key be a new CryptoKey object representing the generated key.
180 // Step 5. Set the [[type]] internal slot of key to "secret".
181 // Step 6. Let algorithm be a new KeyAlgorithm.
182 // Step 7. Set the name attribute of algorithm to "ChaCha20-Poly1305".
183 // Step 8. Set the [[algorithm]] internal slot of key to algorithm.
184 // Step 9. Set the [[extractable]] internal slot of key to be extractable.
185 // Step 10. Set the [[usages]] internal slot of key to be the normalized value of usages.
186 let algorithm = KeyAlgorithm {
187 name: CryptoAlgorithm::ChaCha20Poly1305,
188 };
189 let key = CryptoKey::new(
190 cx,
191 global,
192 KeyType::Secret,
193 extractable,
194 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
195 usages.normalized_value(),
196 Handle::ChaCha20Poly1305Key(chacha20poly1305_key),
197 );
198
199 // Step 11. Return key.
200 Ok(key)
201}
202
203/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-import-key>
204pub(crate) fn import_key<D: Equivalence>(
205 cx: &mut JSContext,
206 global: &D::GlobalScope,
207 format: KeyFormat,
208 key_data: &[u8],
209 extractable: bool,
210 usages: Vec<KeyUsage>,
211) -> Result<DomRoot<CryptoKey<D>>, Error> {
212 // Step 1. Let keyData be the key data to be imported.
213
214 // Step 2. If usages contains an entry which is not one of "encrypt", "decrypt", "wrapKey" or
215 // "unwrapKey", then throw a SyntaxError.
216 usages.ensure_only_contain_entries_from(&[
217 KeyUsage::Encrypt,
218 KeyUsage::Decrypt,
219 KeyUsage::WrapKey,
220 KeyUsage::UnwrapKey,
221 ])?;
222
223 // Step 3.
224 let data: Zeroizing<Vec<u8>>;
225 match format {
226 // If format is "raw-secret":
227 KeyFormat::Raw_secret => {
228 // Step 3.1. Let data be keyData.
229 data = key_data.to_vec().into();
230
231 // Step 3.2. If the length in bits of data is not 256 then throw a DataError.
232 if data.len() != 32 {
233 return Err(Error::Data(Some(
234 "The length in bits of data is not 256".to_string(),
235 )));
236 }
237 },
238 // If format is "jwk":
239 KeyFormat::Jwk => {
240 // Step 3.1.
241 // If keyData is a JsonWebKey dictionary:
242 // Let jwk equal keyData.
243 // Otherwise:
244 // Throw a DataError.
245 let jwk = JsonWebKey::parse(cx, key_data)?;
246
247 // Step 3.2. If the kty field of jwk is not "oct", then throw a DataError.
248 if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") {
249 return Err(Error::Data(Some(
250 "The kty field of jwk is not \"oct\"".to_string(),
251 )));
252 }
253
254 // Step 3.3. If jwk does not meet the requirements of Section 6.4 of JSON Web
255 // Algorithms [JWA], then throw a DataError.
256 // Step 3.4. Let data be the byte sequence obtained by decoding the k field of jwk.
257 data = jwk.decode_required_string_field(JwkStringField::K)?;
258
259 // Step 3.5. If the alg field of jwk is present, and is not "C20P", then throw a
260 // DataError.
261 if jwk.alg.as_ref().is_some_and(|alg| alg != "C20P") {
262 return Err(Error::Data(Some(
263 "The alg field of jwk is present, and is not \"C20P\"".to_string(),
264 )));
265 }
266
267 // Step 3.6. If usages is non-empty and the use field of jwk is present and is not
268 // "enc", then throw a DataError.
269 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
270 return Err(Error::Data(Some(
271 "Usages is non-empty and the use field of jwk is present and is not \"enc\""
272 .to_string(),
273 )));
274 }
275
276 // Step 3.7. If the key_ops field of jwk is present, and is invalid according to the
277 // requirements of JSON Web Key [JWK] or does not contain all of the specified usages
278 // values, then throw a DataError.
279 jwk.check_key_ops(&usages)?;
280
281 // Step 3.8. If the ext field of jwk is present and has the value false and extractable
282 // is true, then throw a DataError.
283 if jwk.ext.is_some_and(|ext| !ext) && extractable {
284 return Err(Error::Data(Some(
285 "The ext field of jwk is present and has the value false and \
286 extractable is true"
287 .to_string(),
288 )));
289 }
290 },
291 // Otherwise:
292 _ => {
293 // throw a NotSupportedError.
294 return Err(Error::NotSupported(Some(
295 "ChaCha20-Poly1305 does not support this import key format".to_string(),
296 )));
297 },
298 }
299
300 // Step 4. Let key be a new CryptoKey object representing a key with value data.
301 // Step 5. Set the [[type]] internal slot of key to "secret".
302 // Step 6. Let algorithm be a new KeyAlgorithm.
303 // Step 7. Set the name attribute of algorithm to "ChaCha20-Poly1305".
304 // Step 8. Set the [[algorithm]] internal slot of key to algorithm.
305 let handle = Handle::ChaCha20Poly1305Key(Key::try_from(data.as_slice()).map_err(|_| {
306 Error::Data(Some(
307 "ChaCha20-Poly1305 fails to create key from data".into(),
308 ))
309 })?);
310 let algorithm = KeyAlgorithm {
311 name: CryptoAlgorithm::ChaCha20Poly1305,
312 };
313 let key = CryptoKey::new(
314 cx,
315 global,
316 KeyType::Secret,
317 extractable,
318 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
319 usages.normalized_value(),
320 handle,
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<D: Equivalence>(
329 format: KeyFormat,
330 key: &CryptoKey<D>,
331) -> Result<ExportedKey, Error> {
332 // Step 1. If the underlying cryptographic key material represented by the [[handle]] internal
333 // slot of key cannot be accessed, then throw an OperationError.
334 let Handle::ChaCha20Poly1305Key(chacha20poly1305_key) = key.handle() else {
335 return Err(Error::Operation(Some(
336 "The underlying cryptographic key material represented by the [[handle]] internal \
337 slot of key cannot be accessed"
338 .to_string(),
339 )));
340 };
341
342 // Step 2.
343 let result = match format {
344 // If format is "raw-secret":
345 KeyFormat::Raw_secret => {
346 // Step 2.1. Let data be a byte sequence containing the raw octets of the key
347 // represented by [[handle]] internal slot of key.
348 let data = chacha20poly1305_key.to_vec();
349
350 // Step 2.2 Let result be data.
351 ExportedKey::new_bytes(data)
352 },
353 // If format is "jwk":
354 KeyFormat::Jwk => {
355 // Step 2.1. Let jwk be a new JsonWebKey dictionary.
356 let mut jwk = JsonWebKey::default();
357
358 // Step 2.2. Set the kty attribute of jwk to the string "oct".
359 jwk.kty = Some(DOMString::from_static("oct"));
360
361 // Step 2.3. Set the k attribute of jwk to be a string containing the raw octets of the
362 // key represented by [[handle]] internal slot of key, encoded according to Section 6.4
363 // of JSON Web Algorithms [JWA].
364 jwk.encode_string_field(JwkStringField::K, chacha20poly1305_key.as_slice());
365
366 // Step 2.4. Set the alg attribute of jwk to the string "C20P".
367 jwk.alg = Some(DOMString::from_static("C20P"));
368
369 // Step 2.5. Set the key_ops attribute of jwk to equal the usages attribute of key.
370 jwk.set_key_ops(key.usages());
371
372 // Step 2.6. Set the ext attribute of jwk to equal the [[extractable]] internal slot of
373 // key.
374 jwk.ext = Some(key.Extractable());
375
376 // Step 2.7. Let result be jwk.
377 ExportedKey::new_jwk(jwk)
378 },
379 // Otherwise:
380 _ => {
381 // throw a NotSupportedError.
382 return Err(Error::NotSupported(Some(
383 "ChaCha20-Poly1305 does not support this import key format".to_string(),
384 )));
385 },
386 };
387
388 // Step 3. Return result.
389 Ok(result)
390}
391
392/// <https://wicg.github.io/webcrypto-modern-algos/#chacha20-poly1305-operations-get-key-length>
393pub(crate) fn get_key_length() -> Result<Option<u32>, Error> {
394 // Step 1. Return 256.
395 Ok(Some(256))
396}