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;
12
13use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{KeyType, KeyUsage};
14use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
15use crate::dom::bindings::error::Error;
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::cryptokey::{CryptoKey, Handle};
18use crate::dom::globalscope::GlobalScope;
19use crate::dom::subtlecrypto::{
20 CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives,
21 NormalizedAlgorithm, SubtleHmacImportParams, SubtleHmacKeyAlgorithm, SubtleHmacKeyGenParams,
22 SubtleKeyAlgorithm,
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: &SubtleHmacKeyGenParams,
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 OsRng.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 hash = SubtleKeyAlgorithm {
143 name: normalized_algorithm.hash.name(),
144 };
145 let algorithm = SubtleHmacKeyAlgorithm {
146 name: CryptoAlgorithm::Hmac,
147 hash,
148 length,
149 };
150
151 // Step 5. Let key be a new CryptoKey object representing the generated key.
152 // Step 12. Set the [[type]] internal slot of key to "secret".
153 // Step 13. Set the [[algorithm]] internal slot of key to algorithm.
154 // Step 14. Set the [[extractable]] internal slot of key to be extractable.
155 // Step 15. Set the [[usages]] internal slot of key to be usages.
156 let key = CryptoKey::new(
157 cx,
158 global,
159 KeyType::Secret,
160 extractable,
161 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
162 usages,
163 Handle::Hmac(key_data),
164 );
165
166 // Step 16. Return key.
167 Ok(key)
168}
169
170/// <https://w3c.github.io/webcrypto/#hmac-operations-import-key>
171pub(crate) fn import_key(
172 cx: &mut JSContext,
173 global: &GlobalScope,
174 normalized_algorithm: &SubtleHmacImportParams,
175 format: KeyFormat,
176 key_data: &[u8],
177 extractable: bool,
178 usages: Vec<KeyUsage>,
179) -> Result<DomRoot<CryptoKey>, Error> {
180 // Step 1. Let keyData be the key data to be imported.
181
182 // Step 2. If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
183 // Note: This is not explicitly spec'ed, but also throw a SyntaxError if usages is empty
184 if usages
185 .iter()
186 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify)) ||
187 usages.is_empty()
188 {
189 return Err(Error::Syntax(Some(
190 "Usages contains an entry which is not \"sign\" or \"verify\", or is empty".into(),
191 )));
192 }
193
194 // Step 3. Let hash be a new KeyAlgorithm.
195 let hash;
196
197 // Step 4.
198 let data;
199 match format {
200 // If format is "raw":
201 KeyFormat::Raw | KeyFormat::Raw_secret => {
202 // Step 4.1. Let data be keyData.
203 data = key_data.to_vec();
204
205 // Step 4.2. Set hash to equal the hash member of normalizedAlgorithm.
206 hash = &normalized_algorithm.hash;
207 },
208 // If format is "jwk":
209 KeyFormat::Jwk => {
210 // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData.
211 // Otherwise: Throw a DataError.
212 // NOTE: Deserialize keyData to JsonWebKey dictionary by running JsonWebKey::parse
213 let jwk = JsonWebKey::parse(cx, key_data)?;
214
215 // Step 2.2. If the kty field of jwk is not "oct", then throw a DataError.
216 if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") {
217 return Err(Error::Data(Some(
218 "The kty field of jwk is not \"oct\"".into(),
219 )));
220 }
221
222 // Step 2.3. If jwk does not meet the requirements of Section 6.4 of JSON Web
223 // Algorithms [JWA], then throw a DataError.
224 // NOTE: Done by Step 2.4 and 2.6.
225
226 // Step 2.4. Let data be the byte sequence obtained by decoding the k field of jwk.
227 data = jwk.decode_required_string_field(JwkStringField::K)?;
228
229 // Step 2.5. Set the hash to equal the hash member of normalizedAlgorithm.
230 hash = &normalized_algorithm.hash;
231
232 // Step 2.6.
233 match hash.name() {
234 // If the name attribute of hash is "SHA-1":
235 CryptoAlgorithm::Sha1 => {
236 // If the alg field of jwk is present and is not "HS1", then throw a DataError.
237 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS1") {
238 return Err(Error::Data(Some(
239 "The alg field of jwk is present, and is not \"HS1\"".into(),
240 )));
241 }
242 },
243 // If the name attribute of hash is "SHA-256":
244 CryptoAlgorithm::Sha256 => {
245 // If the alg field of jwk is present and is not "HS256", then throw a DataError.
246 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS256") {
247 return Err(Error::Data(Some(
248 "The alg field of jwk is present, and is not \"HS256\"".into(),
249 )));
250 }
251 },
252 // If the name attribute of hash is "SHA-384":
253 CryptoAlgorithm::Sha384 => {
254 // If the alg field of jwk is present and is not "HS384", then throw a DataError.
255 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS384") {
256 return Err(Error::Data(Some(
257 "The alg field of jwk is present, and is not \"HS384\"".into(),
258 )));
259 }
260 },
261 // If the name attribute of hash is "SHA-512":
262 CryptoAlgorithm::Sha512 => {
263 // If the alg field of jwk is present and is not "HS512", then throw a DataError.
264 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS512") {
265 return Err(Error::Data(Some(
266 "The alg field of jwk is present, and is not \"HS512\"".into(),
267 )));
268 }
269 },
270 // Otherwise,
271 _name => {
272 // if the name attribute of hash is defined in another applicable specification:
273 // Perform any key import steps defined by other applicable specifications,
274 // passing format, jwk and hash and obtaining hash
275 // NOTE: Currently not support applicable specification.
276 return Err(Error::NotSupported(Some(
277 "Unsupported hash algorithm".into(),
278 )));
279 },
280 }
281
282 // Step 2.7. If usages is non-empty and the use field of jwk is present and is not
283 // "sig", then throw a DataError.
284 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
285 return Err(Error::Data(Some(
286 "Usages is non-empty and the use field of jwk is present and is not \"sig\""
287 .into(),
288 )));
289 }
290
291 // Step 2.8. If the key_ops field of jwk is present, and is invalid according to
292 // the requirements of JSON Web Key [JWK] or does not contain all of the specified
293 // usages values, then throw a DataError.
294 jwk.check_key_ops(&usages)?;
295
296 // Step 2.9. If the ext field of jwk is present and has the value false and
297 // extractable is true, then throw a DataError.
298 if jwk.ext.is_some_and(|ext| !ext) && extractable {
299 return Err(Error::Data(Some(
300 "The ext field of jwk is present and has the value false and extractable is true"
301 .into(),
302 )));
303 }
304 },
305 // Otherwise:
306 _ => {
307 // throw a NotSupportedError.
308 return Err(Error::NotSupported(Some(
309 "Unsupported import key format for HMAC key".into(),
310 )));
311 },
312 }
313
314 // Step 5. Let length be the length in bits of data.
315 let mut length = data.len() as u32 * 8;
316
317 // Step 6. If length is zero then throw a DataError.
318 if length == 0 {
319 return Err(Error::Data(Some(
320 "The length in bits of data is zero".into(),
321 )));
322 }
323
324 // Step 7. If the length member of normalizedAlgorithm is present:
325 if let Some(given_length) = normalized_algorithm.length {
326 // If the length member of normalizedAlgorithm is greater than length:
327 if given_length > length {
328 // throw a DataError.
329 return Err(Error::Data(Some(
330 "The length member of normalizedAlgorithm is greater than the length in bits of data"
331 .into(),
332 )));
333 }
334 // Otherwise:
335 else {
336 // Set length equal to the length member of normalizedAlgorithm.
337 length = given_length;
338 }
339 }
340
341 // Step 10. Let algorithm be a new HmacKeyAlgorithm.
342 // Step 11. Set the name attribute of algorithm to "HMAC".
343 // Step 12. Set the length attribute of algorithm to length.
344 // Step 13. Set the hash attribute of algorithm to hash.
345 let algorithm = SubtleHmacKeyAlgorithm {
346 name: CryptoAlgorithm::Hmac,
347 hash: SubtleKeyAlgorithm { name: hash.name() },
348 length,
349 };
350
351 // Step 8. Let key be a new CryptoKey object representing an HMAC key with the first length
352 // bits of data.
353 // Step 9. Set the [[type]] internal slot of key to "secret".
354 // Step 14. Set the [[algorithm]] internal slot of key to algorithm.
355 let truncated_data = data[..length as usize / 8].to_vec();
356 let key = CryptoKey::new(
357 cx,
358 global,
359 KeyType::Secret,
360 extractable,
361 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
362 usages,
363 Handle::Hmac(truncated_data),
364 );
365
366 // Step 15. Return key.
367 Ok(key)
368}
369
370/// <https://w3c.github.io/webcrypto/#hmac-operations-export-key>
371pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
372 match format {
373 KeyFormat::Raw | KeyFormat::Raw_secret => match key.handle() {
374 Handle::Hmac(key_data) => Ok(ExportedKey::Bytes(key_data.as_slice().to_vec())),
375 _ => Err(Error::Operation(Some(
376 "The key handle is not representing an HMAC key".into(),
377 ))),
378 },
379 KeyFormat::Jwk => {
380 // Step 4.1. Let jwk be a new JsonWebKey dictionary.
381 // Step 4.2. Set the kty attribute of jwk to the string "oct".
382 let mut jwk = JsonWebKey {
383 kty: Some(DOMString::from("oct")),
384 ..Default::default()
385 };
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::Jwk(Box::new(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}