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 rand::TryRngCore;
7use rand::rngs::OsRng;
8use script_bindings::codegen::GenericBindings::CryptoKeyBinding::CryptoKeyMethods;
9use script_bindings::domstring::DOMString;
10
11use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{KeyType, KeyUsage};
12use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
13use crate::dom::bindings::error::Error;
14use crate::dom::bindings::root::DomRoot;
15use crate::dom::cryptokey::{CryptoKey, Handle};
16use crate::dom::globalscope::GlobalScope;
17use crate::dom::subtlecrypto::{
18    ALG_HMAC, ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512, ExportedKey, JsonWebKeyExt,
19    JwkStringField, KeyAlgorithmAndDerivatives, SubtleHmacImportParams, SubtleHmacKeyAlgorithm,
20    SubtleHmacKeyGenParams, SubtleKeyAlgorithm,
21};
22use crate::script_runtime::CanGc;
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.as_str() {
32            ALG_SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
33            ALG_SHA256 => hmac::HMAC_SHA256,
34            ALG_SHA384 => hmac::HMAC_SHA384,
35            ALG_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.as_str() {
55            ALG_SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
56            ALG_SHA256 => hmac::HMAC_SHA256,
57            ALG_SHA384 => hmac::HMAC_SHA384,
58            ALG_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    global: &GlobalScope,
73    normalized_algorithm: &SubtleHmacKeyGenParams,
74    extractable: bool,
75    usages: Vec<KeyUsage>,
76    can_gc: CanGc,
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().to_string(),
122    };
123    let algorithm = SubtleHmacKeyAlgorithm {
124        name: ALG_HMAC.to_string(),
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        global,
136        KeyType::Secret,
137        extractable,
138        KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
139        usages,
140        Handle::Hmac(key_data),
141        can_gc,
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    global: &GlobalScope,
151    normalized_algorithm: &SubtleHmacImportParams,
152    format: KeyFormat,
153    key_data: &[u8],
154    extractable: bool,
155    usages: Vec<KeyUsage>,
156    can_gc: CanGc,
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.as_ref();
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(GlobalScope::get_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.as_ref();
205
206            // Step 2.6.
207            match hash.name() {
208                // If the name attribute of hash is "SHA-1":
209                ALG_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                ALG_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                ALG_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                ALG_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: ALG_HMAC.to_string(),
298        hash: SubtleKeyAlgorithm {
299            name: hash.name().to_string(),
300        },
301        length,
302    };
303
304    // Step 8. Let key be a new CryptoKey object representing an HMAC key with the first length
305    // bits of data.
306    // Step 9. Set the [[type]] internal slot of key to "secret".
307    // Step 14. Set the [[algorithm]] internal slot of key to algorithm.
308    let truncated_data = data[..length as usize / 8].to_vec();
309    let key = CryptoKey::new(
310        global,
311        KeyType::Secret,
312        extractable,
313        KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
314        usages,
315        Handle::Hmac(truncated_data),
316        can_gc,
317    );
318
319    // Step 15. Return key.
320    Ok(key)
321}
322
323/// <https://w3c.github.io/webcrypto/#hmac-operations-export-key>
324pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
325    match format {
326        KeyFormat::Raw | KeyFormat::Raw_secret => match key.handle() {
327            Handle::Hmac(key_data) => Ok(ExportedKey::Bytes(key_data.as_slice().to_vec())),
328            _ => Err(Error::Operation(None)),
329        },
330        KeyFormat::Jwk => {
331            // Step 4.1. Let jwk be a new JsonWebKey dictionary.
332            // Step 4.2. Set the kty attribute of jwk to the string "oct".
333            let mut jwk = JsonWebKey {
334                kty: Some(DOMString::from("oct")),
335                ..Default::default()
336            };
337
338            // Step 4.3. Set the k attribute of jwk to be a string containing data, encoded according
339            // to Section 6.4 of JSON Web Algorithms [JWA].
340            let key_data = key.handle().as_bytes();
341            jwk.encode_string_field(JwkStringField::K, key_data);
342
343            // Step 4.4. Let algorithm be the [[algorithm]] internal slot of key.
344            // Step 4.5. Let hash be the hash attribute of algorithm.
345            // Step 4.6.
346            // If the name attribute of hash is "SHA-1":
347            //     Set the alg attribute of jwk to the string "HS1".
348            // If the name attribute of hash is "SHA-256":
349            //     Set the alg attribute of jwk to the string "HS256".
350            // If the name attribute of hash is "SHA-384":
351            //     Set the alg attribute of jwk to the string "HS384".
352            // If the name attribute of hash is "SHA-512":
353            //     Set the alg attribute of jwk to the string "HS512".
354            // Otherwise, the name attribute of hash is defined in another applicable
355            // specification:
356            //     Perform any key export steps defined by other applicable specifications, passing
357            //     format and key and obtaining alg.
358            //     Set the alg attribute of jwk to alg.
359            let hash_algorithm = match key.algorithm() {
360                KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(alg) => match &*alg.hash.name {
361                    ALG_SHA1 => "HS1",
362                    ALG_SHA256 => "HS256",
363                    ALG_SHA384 => "HS384",
364                    ALG_SHA512 => "HS512",
365                    _ => return Err(Error::NotSupported(None)),
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("[[length]] must not be zero".to_string()));
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: &str) -> Result<u32, Error> {
419    match hash {
420        ALG_SHA1 => Ok(512),
421        ALG_SHA256 => Ok(512),
422        ALG_SHA384 => Ok(1024),
423        ALG_SHA512 => Ok(1024),
424        _ => Err(Error::NotSupported(Some(
425            "Unidentified hash member".to_string(),
426        ))),
427    }
428}