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}