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};
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 algorithm = SubtleHmacKeyAlgorithm {
143        name: CryptoAlgorithm::Hmac,
144        hash: normalized_algorithm.hash.clone(),
145        length,
146    };
147
148    // Step 5. Let key be a new CryptoKey object representing the generated key.
149    // Step 12. Set the [[type]] internal slot of key to "secret".
150    // Step 13. Set the [[algorithm]] internal slot of key to algorithm.
151    // Step 14. Set the [[extractable]] internal slot of key to be extractable.
152    // Step 15. Set the [[usages]] internal slot of key to be usages.
153    let key = CryptoKey::new(
154        cx,
155        global,
156        KeyType::Secret,
157        extractable,
158        KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
159        usages,
160        Handle::Hmac(key_data.into()),
161    );
162
163    // Step 16. Return key.
164    Ok(key)
165}
166
167/// <https://w3c.github.io/webcrypto/#hmac-operations-import-key>
168pub(crate) fn import_key(
169    cx: &mut JSContext,
170    global: &GlobalScope,
171    normalized_algorithm: &SubtleHmacImportParams,
172    format: KeyFormat,
173    key_data: &[u8],
174    extractable: bool,
175    usages: Vec<KeyUsage>,
176) -> Result<DomRoot<CryptoKey>, Error> {
177    // Step 1. Let keyData be the key data to be imported.
178
179    // Step 2. If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
180    // Note: This is not explicitly spec'ed, but also throw a SyntaxError if usages is empty
181    if usages
182        .iter()
183        .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify)) ||
184        usages.is_empty()
185    {
186        return Err(Error::Syntax(Some(
187            "Usages contains an entry which is not \"sign\" or \"verify\", or is empty".into(),
188        )));
189    }
190
191    // Step 3. Let hash be a new KeyAlgorithm.
192    let hash;
193
194    // Step 4.
195    let data: Zeroizing<Vec<u8>>;
196    match format {
197        // If format is "raw":
198        KeyFormat::Raw | KeyFormat::Raw_secret => {
199            // Step 4.1. Let data be keyData.
200            data = key_data.to_vec().into();
201
202            // Step 4.2. Set hash to equal the hash member of normalizedAlgorithm.
203            hash = &normalized_algorithm.hash;
204        },
205        // If format is "jwk":
206        KeyFormat::Jwk => {
207            // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData.
208            // Otherwise: Throw a DataError.
209            // NOTE: Deserialize keyData to JsonWebKey dictionary by running JsonWebKey::parse
210            let jwk = JsonWebKey::parse(cx, key_data)?;
211
212            // Step 2.2. If the kty field of jwk is not "oct", then throw a DataError.
213            if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") {
214                return Err(Error::Data(Some(
215                    "The kty field of jwk is not \"oct\"".into(),
216                )));
217            }
218
219            // Step 2.3. If jwk does not meet the requirements of Section 6.4 of JSON Web
220            // Algorithms [JWA], then throw a DataError.
221            // NOTE: Done by Step 2.4 and 2.6.
222
223            // Step 2.4. Let data be the byte sequence obtained by decoding the k field of jwk.
224            data = jwk.decode_required_string_field(JwkStringField::K)?;
225
226            // Step 2.5. Set the hash to equal the hash member of normalizedAlgorithm.
227            hash = &normalized_algorithm.hash;
228
229            // Step 2.6.
230            match hash.name() {
231                // If the name attribute of hash is "SHA-1":
232                CryptoAlgorithm::Sha1 => {
233                    // If the alg field of jwk is present and is not "HS1", then throw a DataError.
234                    if jwk.alg.as_ref().is_some_and(|alg| alg != "HS1") {
235                        return Err(Error::Data(Some(
236                            "The alg field of jwk is present, and is not \"HS1\"".into(),
237                        )));
238                    }
239                },
240                // If the name attribute of hash is "SHA-256":
241                CryptoAlgorithm::Sha256 => {
242                    // If the alg field of jwk is present and is not "HS256", then throw a DataError.
243                    if jwk.alg.as_ref().is_some_and(|alg| alg != "HS256") {
244                        return Err(Error::Data(Some(
245                            "The alg field of jwk is present, and is not \"HS256\"".into(),
246                        )));
247                    }
248                },
249                // If the name attribute of hash is "SHA-384":
250                CryptoAlgorithm::Sha384 => {
251                    // If the alg field of jwk is present and is not "HS384", then throw a DataError.
252                    if jwk.alg.as_ref().is_some_and(|alg| alg != "HS384") {
253                        return Err(Error::Data(Some(
254                            "The alg field of jwk is present, and is not \"HS384\"".into(),
255                        )));
256                    }
257                },
258                // If the name attribute of hash is "SHA-512":
259                CryptoAlgorithm::Sha512 => {
260                    // If the alg field of jwk is present and is not "HS512", then throw a DataError.
261                    if jwk.alg.as_ref().is_some_and(|alg| alg != "HS512") {
262                        return Err(Error::Data(Some(
263                            "The alg field of jwk is present, and is not \"HS512\"".into(),
264                        )));
265                    }
266                },
267                // Otherwise,
268                _name => {
269                    // if the name attribute of hash is defined in another applicable specification:
270                    // Perform any key import steps defined by other applicable specifications,
271                    // passing format, jwk and hash and obtaining hash
272                    // NOTE: Currently not support applicable specification.
273                    return Err(Error::NotSupported(Some(
274                        "Unsupported hash algorithm".into(),
275                    )));
276                },
277            }
278
279            // Step 2.7. If usages is non-empty and the use field of jwk is present and is not
280            // "sig", then throw a DataError.
281            if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
282                return Err(Error::Data(Some(
283                    "Usages is non-empty and the use field of jwk is present and is not \"sig\""
284                        .into(),
285                )));
286            }
287
288            // Step 2.8. If the key_ops field of jwk is present, and is invalid according to
289            // the requirements of JSON Web Key [JWK] or does not contain all of the specified
290            // usages values, then throw a DataError.
291            jwk.check_key_ops(&usages)?;
292
293            // Step 2.9. If the ext field of jwk is present and has the value false and
294            // extractable is true, then throw a DataError.
295            if jwk.ext.is_some_and(|ext| !ext) && extractable {
296                return Err(Error::Data(Some(
297                    "The ext field of jwk is present and has the value false and extractable is true"
298                        .into(),
299                )));
300            }
301        },
302        // Otherwise:
303        _ => {
304            // throw a NotSupportedError.
305            return Err(Error::NotSupported(Some(
306                "Unsupported import key format for HMAC key".into(),
307            )));
308        },
309    }
310
311    // Step 5. Let length be the length in bits of data.
312    let mut length = data.len() as u32 * 8;
313
314    // Step 6. If length is zero then throw a DataError.
315    if length == 0 {
316        return Err(Error::Data(Some(
317            "The length in bits of data is zero".into(),
318        )));
319    }
320
321    // Step 7. If the length member of normalizedAlgorithm is present:
322    if let Some(given_length) = normalized_algorithm.length {
323        //  If the length member of normalizedAlgorithm is greater than length:
324        if given_length > length {
325            // throw a DataError.
326            return Err(Error::Data(Some(
327                "The length member of normalizedAlgorithm is greater than the length in bits of data"
328                    .into(),
329            )));
330        }
331        // Otherwise:
332        else {
333            // Set length equal to the length member of normalizedAlgorithm.
334            length = given_length;
335        }
336    }
337
338    // Step 10. Let algorithm be a new HmacKeyAlgorithm.
339    // Step 11. Set the name attribute of algorithm to "HMAC".
340    // Step 12. Set the length attribute of algorithm to length.
341    // Step 13. Set the hash attribute of algorithm to hash.
342    let algorithm = SubtleHmacKeyAlgorithm {
343        name: CryptoAlgorithm::Hmac,
344        hash: hash.clone(),
345        length,
346    };
347
348    // Step 8. Let key be a new CryptoKey object representing an HMAC key with the first length
349    // bits of data.
350    // Step 9. Set the [[type]] internal slot of key to "secret".
351    // Step 14. Set the [[algorithm]] internal slot of key to algorithm.
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,
360        Handle::Hmac(truncated_data.into()),
361    );
362
363    // Step 15. Return key.
364    Ok(key)
365}
366
367/// <https://w3c.github.io/webcrypto/#hmac-operations-export-key>
368pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
369    match format {
370        KeyFormat::Raw | KeyFormat::Raw_secret => match key.handle() {
371            Handle::Hmac(key_data) => Ok(ExportedKey::new_bytes(key_data.as_slice().to_vec())),
372            _ => Err(Error::Operation(Some(
373                "The key handle is not representing an HMAC key".into(),
374            ))),
375        },
376        KeyFormat::Jwk => {
377            // Step 4.1. Let jwk be a new JsonWebKey dictionary.
378            let mut jwk = JsonWebKey::default();
379
380            // Step 4.2. Set the kty attribute of jwk to the string "oct".
381            jwk.kty = Some(DOMString::from("oct"));
382
383            // Step 4.3. Set the k attribute of jwk to be a string containing data, encoded according
384            // to Section 6.4 of JSON Web Algorithms [JWA].
385            let key_data = key.handle().as_bytes();
386            jwk.encode_string_field(JwkStringField::K, key_data);
387
388            // Step 4.4. Let algorithm be the [[algorithm]] internal slot of key.
389            // Step 4.5. Let hash be the hash attribute of algorithm.
390            // Step 4.6.
391            // If the name attribute of hash is "SHA-1":
392            //     Set the alg attribute of jwk to the string "HS1".
393            // If the name attribute of hash is "SHA-256":
394            //     Set the alg attribute of jwk to the string "HS256".
395            // If the name attribute of hash is "SHA-384":
396            //     Set the alg attribute of jwk to the string "HS384".
397            // If the name attribute of hash is "SHA-512":
398            //     Set the alg attribute of jwk to the string "HS512".
399            // Otherwise, the name attribute of hash is defined in another applicable
400            // specification:
401            //     Perform any key export steps defined by other applicable specifications, passing
402            //     format and key and obtaining alg.
403            //     Set the alg attribute of jwk to alg.
404            let hash_algorithm = match key.algorithm() {
405                KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm) => {
406                    match algorithm.hash.name() {
407                        CryptoAlgorithm::Sha1 => "HS1",
408                        CryptoAlgorithm::Sha256 => "HS256",
409                        CryptoAlgorithm::Sha384 => "HS384",
410                        CryptoAlgorithm::Sha512 => "HS512",
411                        _ => {
412                            return Err(Error::NotSupported(Some(
413                                "Unsupported hash algorithm for HMAC".into(),
414                            )));
415                        },
416                    }
417                },
418                _ => {
419                    return Err(Error::NotSupported(Some(
420                        "The key algorithm is not HMAC".into(),
421                    )));
422                },
423            };
424            jwk.alg = Some(DOMString::from(hash_algorithm));
425
426            // Step 4.7. Set the key_ops attribute of jwk to the usages attribute of key.
427            jwk.set_key_ops(key.usages());
428
429            // Step 4.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
430            jwk.ext = Some(key.Extractable());
431
432            // Step 4.9. Let result be jwk.
433            Ok(ExportedKey::new_jwk(jwk))
434        },
435        // Otherwise:
436        _ => {
437            // throw a NotSupportedError.
438            Err(Error::NotSupported(Some(
439                "Unsupported export key format for HMAC key".into(),
440            )))
441        },
442    }
443}
444
445/// <https://w3c.github.io/webcrypto/#hmac-operations-get-key-length>
446pub(crate) fn get_key_length(
447    normalized_derived_key_algorithm: &SubtleHmacImportParams,
448) -> Result<Option<u32>, Error> {
449    // Step 1.
450    let length = match normalized_derived_key_algorithm.length {
451        // If the length member of normalizedDerivedKeyAlgorithm is not present:
452        None => {
453            // Let length be the block size in bits of the hash function identified by the hash
454            // member of normalizedDerivedKeyAlgorithm.
455            hash_function_block_size_in_bits(normalized_derived_key_algorithm.hash.name())?
456        },
457        // Otherwise, if the length member of normalizedDerivedKeyAlgorithm is non-zero:
458        Some(length) if length != 0 => {
459            // Let length be equal to the length member of normalizedDerivedKeyAlgorithm.
460            length
461        },
462        // Otherwise:
463        _ => {
464            // throw a TypeError.
465            return Err(Error::Type(c"[[length]] must not be zero".to_owned()));
466        },
467    };
468
469    // Step 2. Return length.
470    Ok(Some(length))
471}
472
473/// Return the block size in bits of a hash function, according to Figure 1 of
474/// <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf>.
475fn hash_function_block_size_in_bits(hash: CryptoAlgorithm) -> Result<u32, Error> {
476    match hash {
477        CryptoAlgorithm::Sha1 => Ok(512),
478        CryptoAlgorithm::Sha256 => Ok(512),
479        CryptoAlgorithm::Sha384 => Ok(1024),
480        CryptoAlgorithm::Sha512 => Ok(1024),
481        _ => Err(Error::NotSupported(Some(
482            "Unidentified hash member".to_string(),
483        ))),
484    }
485}