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