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