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