Skip to main content

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}