Skip to main content

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}