script/dom/webcrypto/subtlecrypto/hmac_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 aws_lc_rs::constant_time::verify_slices_are_equal;
6use aws_lc_rs::hmac;
7use js::context::JSContext;
8use rand::TryRng;
9use rand::rngs::SysRng;
10use script_bindings::codegen::GenericBindings::CryptoKeyBinding::CryptoKeyMethods;
11use script_bindings::domstring::DOMString;
12use zeroize::Zeroizing;
13
14use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{KeyType, KeyUsage};
15use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
16use crate::dom::bindings::error::Error;
17use crate::dom::bindings::root::DomRoot;
18use crate::dom::cryptokey::{CryptoKey, Handle, KeyUsageVecHelper};
19use crate::dom::globalscope::GlobalScope;
20use crate::dom::subtlecrypto::{
21 CryptoAlgorithm, ExportedKey, HmacImportParams, HmacKeyAlgorithm, HmacKeyGenParams,
22 JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives, NormalizedAlgorithm,
23};
24
25/// <https://w3c.github.io/webcrypto/#hmac-operations-sign>
26pub(crate) fn sign(key: &CryptoKey, message: &[u8]) -> Result<Vec<u8>, Error> {
27 // Step 1. Let mac be the result of performing the MAC Generation operation described in
28 // Section 4 of [FIPS-198-1] using the key represented by the [[handle]] internal slot of key,
29 // the hash function identified by the hash attribute of the [[algorithm]] internal slot of key
30 // and message as the input data text.
31 let hash_function = match key.algorithm() {
32 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algo) => match algo.hash.name() {
33 CryptoAlgorithm::Sha1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
34 CryptoAlgorithm::Sha256 => hmac::HMAC_SHA256,
35 CryptoAlgorithm::Sha384 => hmac::HMAC_SHA384,
36 CryptoAlgorithm::Sha512 => hmac::HMAC_SHA512,
37 _ => {
38 return Err(Error::NotSupported(Some(
39 "Unsupported hash algorithm for HMAC".into(),
40 )));
41 },
42 },
43 _ => {
44 return Err(Error::NotSupported(Some(
45 "The key algorithm is not HMAC".into(),
46 )));
47 },
48 };
49 let sign_key = hmac::Key::new(hash_function, key.handle().as_bytes());
50 let mac = hmac::sign(&sign_key, message);
51
52 // Step 2. Return mac.
53 Ok(mac.as_ref().to_vec())
54}
55
56/// <https://w3c.github.io/webcrypto/#hmac-operations-verify>
57pub(crate) fn verify(key: &CryptoKey, message: &[u8], signature: &[u8]) -> Result<bool, Error> {
58 // Step 1. Let mac be the result of performing the MAC Generation operation described in
59 // Section 4 of [FIPS-198-1] using the key represented by the [[handle]] internal slot of key,
60 // the hash function identified by the hash attribute of the [[algorithm]] internal slot of key
61 // and message as the input data text.
62 let hash_function = match key.algorithm() {
63 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algo) => match algo.hash.name() {
64 CryptoAlgorithm::Sha1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
65 CryptoAlgorithm::Sha256 => hmac::HMAC_SHA256,
66 CryptoAlgorithm::Sha384 => hmac::HMAC_SHA384,
67 CryptoAlgorithm::Sha512 => hmac::HMAC_SHA512,
68 _ => {
69 return Err(Error::NotSupported(Some(
70 "Unsupported hash algorithm for HMAC".into(),
71 )));
72 },
73 },
74 _ => {
75 return Err(Error::NotSupported(Some(
76 "The key algorithm is not HMAC".into(),
77 )));
78 },
79 };
80 let sign_key = hmac::Key::new(hash_function, key.handle().as_bytes());
81 let mac = hmac::sign(&sign_key, message);
82
83 // Step 2. Return true if mac is equal to signature and false otherwise. This comparison must
84 // be performed in constant-time.
85 Ok(verify_slices_are_equal(mac.as_ref(), signature).is_ok())
86}
87
88/// <https://w3c.github.io/webcrypto/#hmac-operations-generate-key>
89pub(crate) fn generate_key(
90 cx: &mut JSContext,
91 global: &GlobalScope,
92 normalized_algorithm: &HmacKeyGenParams,
93 extractable: bool,
94 usages: Vec<KeyUsage>,
95) -> Result<DomRoot<CryptoKey>, Error> {
96 // Step 1. If usages contains any entry which is not "sign" or "verify", then throw a SyntaxError.
97 if usages
98 .iter()
99 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
100 {
101 return Err(Error::Syntax(Some(
102 "Usages contains an entry which is not \"sign\" or \"verify\"".into(),
103 )));
104 }
105
106 // Step 2.
107 let length = match normalized_algorithm.length {
108 // If the length member of normalizedAlgorithm is not present:
109 None => {
110 // Let length be the block size in bits of the hash function identified by the
111 // hash member of normalizedAlgorithm.
112 hash_function_block_size_in_bits(normalized_algorithm.hash.name())?
113 },
114 // Otherwise, if the length member of normalizedAlgorithm is non-zero:
115 Some(length) if length != 0 => {
116 // Let length be equal to the length member of normalizedAlgorithm.
117 length
118 },
119 // Otherwise:
120 _ => {
121 // throw an OperationError.
122 return Err(Error::Operation(Some(
123 "The length member of normalizedAlgorithm is zero".into(),
124 )));
125 },
126 };
127
128 // Step 3. Generate a key of length length bits.
129 // Step 4. If the key generation step fails, then throw an OperationError.
130 let mut key_data = vec![0; length as usize];
131 if SysRng.try_fill_bytes(&mut key_data).is_err() {
132 return Err(Error::JSFailed);
133 }
134
135 // Step 6. Let algorithm be a new HmacKeyAlgorithm.
136 // Step 7. Set the name attribute of algorithm to "HMAC".
137 // Step 8. Set the length attribute of algorithm to length.
138 // Step 9. Let hash be a new KeyAlgorithm.
139 // Step 10. Set the name attribute of hash to equal the name member of the hash member of
140 // normalizedAlgorithm.
141 // Step 11. Set the hash attribute of algorithm to hash.
142 let algorithm = HmacKeyAlgorithm {
143 name: CryptoAlgorithm::Hmac,
144 hash: normalized_algorithm.hash.clone(),
145 length,
146 };
147
148 // Step 5. Let key be a new CryptoKey object representing the generated key.
149 // Step 12. Set the [[type]] internal slot of key to "secret".
150 // Step 13. Set the [[algorithm]] internal slot of key to algorithm.
151 // Step 14. Set the [[extractable]] internal slot of key to be extractable.
152 // Step 15. Set the [[usages]] internal slot of key to be the normalized value of usages.
153 let key = CryptoKey::new(
154 cx,
155 global,
156 KeyType::Secret,
157 extractable,
158 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
159 usages.normalized_value(),
160 Handle::Hmac(key_data.into()),
161 );
162
163 // Step 16. Return key.
164 Ok(key)
165}
166
167/// <https://w3c.github.io/webcrypto/#hmac-operations-import-key>
168pub(crate) fn import_key(
169 cx: &mut JSContext,
170 global: &GlobalScope,
171 normalized_algorithm: &HmacImportParams,
172 format: KeyFormat,
173 key_data: &[u8],
174 extractable: bool,
175 usages: Vec<KeyUsage>,
176) -> Result<DomRoot<CryptoKey>, Error> {
177 // Step 1. If the length member of normalizedAlgorithm is present and is zero, then throw a
178 // DataError.
179 if normalized_algorithm
180 .length
181 .is_some_and(|length| length == 0)
182 {
183 return Err(Error::Data(Some(
184 "The length member of normalizedAlgorithm is present and is zero".into(),
185 )));
186 }
187
188 // Step 2. Let keyData be the key data to be imported.
189
190 // Step 3. If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
191 // Note: This is not explicitly spec'ed, but also throw a SyntaxError if usages is empty
192 if usages
193 .iter()
194 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify)) ||
195 usages.is_empty()
196 {
197 return Err(Error::Syntax(Some(
198 "Usages contains an entry which is not \"sign\" or \"verify\", or is empty".into(),
199 )));
200 }
201
202 // Step 4. Let hash be a new KeyAlgorithm.
203 let hash;
204
205 // Step 5.
206 let data: Zeroizing<Vec<u8>>;
207 match format {
208 // If format is "raw":
209 KeyFormat::Raw | KeyFormat::Raw_secret => {
210 // Step 5.1. Let data be keyData.
211 data = key_data.to_vec().into();
212
213 // Step 5.2. Set hash to equal the hash member of normalizedAlgorithm.
214 hash = &normalized_algorithm.hash;
215 },
216 // If format is "jwk":
217 KeyFormat::Jwk => {
218 // Step 5.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData.
219 // Otherwise: Throw a DataError.
220 // NOTE: Deserialize keyData to JsonWebKey dictionary by running JsonWebKey::parse
221 let jwk = JsonWebKey::parse(cx, key_data)?;
222
223 // Step 5.2. If the kty field of jwk is not "oct", then throw a DataError.
224 if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") {
225 return Err(Error::Data(Some(
226 "The kty field of jwk is not \"oct\"".into(),
227 )));
228 }
229
230 // Step 5.3. If jwk does not meet the requirements of Section 6.4 of JSON Web
231 // Algorithms [JWA], then throw a DataError.
232 // NOTE: Done by Step 5.4 and 5.6.
233
234 // Step 5.4. Let data be the byte sequence obtained by decoding the k field of jwk.
235 data = jwk.decode_required_string_field(JwkStringField::K)?;
236
237 // Step 5.5. Set the hash to equal the hash member of normalizedAlgorithm.
238 hash = &normalized_algorithm.hash;
239
240 // Step 5.6.
241 match hash.name() {
242 // If the name attribute of hash is "SHA-1":
243 CryptoAlgorithm::Sha1 => {
244 // If the alg field of jwk is present and is not "HS1", then throw a DataError.
245 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS1") {
246 return Err(Error::Data(Some(
247 "The alg field of jwk is present, and is not \"HS1\"".into(),
248 )));
249 }
250 },
251 // If the name attribute of hash is "SHA-256":
252 CryptoAlgorithm::Sha256 => {
253 // If the alg field of jwk is present and is not "HS256", then throw a DataError.
254 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS256") {
255 return Err(Error::Data(Some(
256 "The alg field of jwk is present, and is not \"HS256\"".into(),
257 )));
258 }
259 },
260 // If the name attribute of hash is "SHA-384":
261 CryptoAlgorithm::Sha384 => {
262 // If the alg field of jwk is present and is not "HS384", then throw a DataError.
263 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS384") {
264 return Err(Error::Data(Some(
265 "The alg field of jwk is present, and is not \"HS384\"".into(),
266 )));
267 }
268 },
269 // If the name attribute of hash is "SHA-512":
270 CryptoAlgorithm::Sha512 => {
271 // If the alg field of jwk is present and is not "HS512", then throw a DataError.
272 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS512") {
273 return Err(Error::Data(Some(
274 "The alg field of jwk is present, and is not \"HS512\"".into(),
275 )));
276 }
277 },
278 // Otherwise,
279 _name => {
280 // if the name attribute of hash is defined in another applicable specification:
281 // Perform any key import steps defined by other applicable specifications,
282 // passing format, jwk and hash and obtaining hash
283 // NOTE: Currently not support applicable specification.
284 return Err(Error::NotSupported(Some(
285 "Unsupported hash algorithm".into(),
286 )));
287 },
288 }
289
290 // Step 5.7. If usages is non-empty and the use field of jwk is present and is not
291 // "sig", then throw a DataError.
292 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
293 return Err(Error::Data(Some(
294 "Usages is non-empty and the use field of jwk is present and is not \"sig\""
295 .into(),
296 )));
297 }
298
299 // Step 5.8. If the key_ops field of jwk is present, and is invalid according to
300 // the requirements of JSON Web Key [JWK] or does not contain all of the specified
301 // usages values, then throw a DataError.
302 jwk.check_key_ops(&usages)?;
303
304 // Step 5.9. If the ext field of jwk is present and has the value false and
305 // extractable is true, then throw a DataError.
306 if jwk.ext.is_some_and(|ext| !ext) && extractable {
307 return Err(Error::Data(Some(
308 "The ext field of jwk is present and has the value false and extractable is true"
309 .into(),
310 )));
311 }
312 },
313 // Otherwise:
314 _ => {
315 // throw a NotSupportedError.
316 return Err(Error::NotSupported(Some(
317 "Unsupported import key format for HMAC key".into(),
318 )));
319 },
320 }
321
322 // Step 6. Let length be the length in bits of data.
323 let mut length = data.len() as u32 * 8;
324
325 // Step 7. If length is zero then throw a DataError.
326 if length == 0 {
327 return Err(Error::Data(Some(
328 "The length in bits of data is zero".into(),
329 )));
330 }
331
332 // Step 8. If the length member of normalizedAlgorithm is present:
333 if let Some(given_length) = normalized_algorithm.length {
334 // If the length member of normalizedAlgorithm is greater than length:
335 if given_length > length {
336 // throw a DataError.
337 return Err(Error::Data(Some(
338 "The length member of normalizedAlgorithm is greater than the length in bits of data"
339 .into(),
340 )));
341 }
342 // Otherwise:
343 else {
344 // Set length equal to the length member of normalizedAlgorithm.
345 length = given_length;
346 }
347 }
348
349 // Step 9. Let key be a new CryptoKey object representing an HMAC key with the first length
350 // bits of data.
351 // Step 10. Set the [[type]] internal slot of key to "secret".
352 // Step 11. Let algorithm be a new HmacKeyAlgorithm.
353 // Step 12. Set the name attribute of algorithm to "HMAC".
354 // Step 13. Set the length attribute of algorithm to length.
355 // Step 14. Set the hash attribute of algorithm to hash.
356 // Step 15. Set the [[algorithm]] internal slot of key to algorithm.
357 let algorithm = HmacKeyAlgorithm {
358 name: CryptoAlgorithm::Hmac,
359 hash: hash.clone(),
360 length,
361 };
362 let truncated_data = data[..length as usize / 8].to_vec();
363 let key = CryptoKey::new(
364 cx,
365 global,
366 KeyType::Secret,
367 extractable,
368 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
369 usages.normalized_value(),
370 Handle::Hmac(truncated_data.into()),
371 );
372
373 // Step 16. Return key.
374 Ok(key)
375}
376
377/// <https://w3c.github.io/webcrypto/#hmac-operations-export-key>
378pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
379 match format {
380 KeyFormat::Raw | KeyFormat::Raw_secret => match key.handle() {
381 Handle::Hmac(key_data) => Ok(ExportedKey::new_bytes(key_data.as_slice().to_vec())),
382 _ => Err(Error::Operation(Some(
383 "The key handle is not representing an HMAC key".into(),
384 ))),
385 },
386 KeyFormat::Jwk => {
387 // Step 4.1. Let jwk be a new JsonWebKey dictionary.
388 let mut jwk = JsonWebKey::default();
389
390 // Step 4.2. Set the kty attribute of jwk to the string "oct".
391 jwk.kty = Some(DOMString::from_static("oct"));
392
393 // Step 4.3. Set the k attribute of jwk to be a string containing data, encoded according
394 // to Section 6.4 of JSON Web Algorithms [JWA].
395 let key_data = key.handle().as_bytes();
396 jwk.encode_string_field(JwkStringField::K, key_data);
397
398 // Step 4.4. Let algorithm be the [[algorithm]] internal slot of key.
399 // Step 4.5. Let hash be the hash attribute of algorithm.
400 // Step 4.6.
401 // If the name attribute of hash is "SHA-1":
402 // Set the alg attribute of jwk to the string "HS1".
403 // If the name attribute of hash is "SHA-256":
404 // Set the alg attribute of jwk to the string "HS256".
405 // If the name attribute of hash is "SHA-384":
406 // Set the alg attribute of jwk to the string "HS384".
407 // If the name attribute of hash is "SHA-512":
408 // Set the alg attribute of jwk to the string "HS512".
409 // Otherwise, the name attribute of hash is defined in another applicable
410 // specification:
411 // Perform any key export steps defined by other applicable specifications, passing
412 // format and key and obtaining alg.
413 // Set the alg attribute of jwk to alg.
414 let hash_algorithm = match key.algorithm() {
415 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm) => {
416 match algorithm.hash.name() {
417 CryptoAlgorithm::Sha1 => "HS1",
418 CryptoAlgorithm::Sha256 => "HS256",
419 CryptoAlgorithm::Sha384 => "HS384",
420 CryptoAlgorithm::Sha512 => "HS512",
421 _ => {
422 return Err(Error::NotSupported(Some(
423 "Unsupported hash algorithm for HMAC".into(),
424 )));
425 },
426 }
427 },
428 _ => {
429 return Err(Error::NotSupported(Some(
430 "The key algorithm is not HMAC".into(),
431 )));
432 },
433 };
434 jwk.alg = Some(DOMString::from(hash_algorithm));
435
436 // Step 4.7. Set the key_ops attribute of jwk to the usages attribute of key.
437 jwk.set_key_ops(key.usages());
438
439 // Step 4.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
440 jwk.ext = Some(key.Extractable());
441
442 // Step 4.9. Let result be jwk.
443 Ok(ExportedKey::new_jwk(jwk))
444 },
445 // Otherwise:
446 _ => {
447 // throw a NotSupportedError.
448 Err(Error::NotSupported(Some(
449 "Unsupported export key format for HMAC key".into(),
450 )))
451 },
452 }
453}
454
455/// <https://w3c.github.io/webcrypto/#hmac-operations-get-key-length>
456pub(crate) fn get_key_length(
457 normalized_derived_key_algorithm: &HmacImportParams,
458) -> Result<Option<u32>, Error> {
459 // Step 1.
460 let length = match normalized_derived_key_algorithm.length {
461 // If the length member of normalizedDerivedKeyAlgorithm is not present:
462 None => {
463 // Let length be the block size in bits of the hash function identified by the hash
464 // member of normalizedDerivedKeyAlgorithm.
465 hash_function_block_size_in_bits(normalized_derived_key_algorithm.hash.name())?
466 },
467 // Otherwise, if the length member of normalizedDerivedKeyAlgorithm is non-zero:
468 Some(length) if length != 0 => {
469 // Let length be equal to the length member of normalizedDerivedKeyAlgorithm.
470 length
471 },
472 // Otherwise:
473 _ => {
474 // throw a TypeError.
475 return Err(Error::Type(c"[[length]] must not be zero".to_owned()));
476 },
477 };
478
479 // Step 2. Return length.
480 Ok(Some(length))
481}
482
483/// Return the block size in bits of a hash function, according to Figure 1 of
484/// <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf>.
485fn hash_function_block_size_in_bits(hash: CryptoAlgorithm) -> Result<u32, Error> {
486 match hash {
487 CryptoAlgorithm::Sha1 => Ok(512),
488 CryptoAlgorithm::Sha256 => Ok(512),
489 CryptoAlgorithm::Sha384 => Ok(1024),
490 CryptoAlgorithm::Sha512 => Ok(1024),
491 _ => Err(Error::NotSupported(Some(
492 "Unidentified hash member".to_string(),
493 ))),
494 }
495}