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