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 base64ct::{Base64UrlUnpadded, Encoding};
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 ALG_HMAC, ALG_SHA1, ALG_SHA256, ALG_SHA384, ALG_SHA512, ExportedKey, JsonWebKeyExt,
20 KeyAlgorithmAndDerivatives, SubtleHmacImportParams, SubtleHmacKeyAlgorithm,
21 SubtleHmacKeyGenParams, SubtleKeyAlgorithm,
22};
23use crate::script_runtime::CanGc;
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.as_str() {
33 ALG_SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
34 ALG_SHA256 => hmac::HMAC_SHA256,
35 ALG_SHA384 => hmac::HMAC_SHA384,
36 ALG_SHA512 => hmac::HMAC_SHA512,
37 _ => return Err(Error::NotSupported),
38 },
39 _ => return Err(Error::NotSupported),
40 };
41 let sign_key = hmac::Key::new(hash_function, key.handle().as_bytes());
42 let mac = hmac::sign(&sign_key, message);
43
44 // Step 2. Return mac.
45 Ok(mac.as_ref().to_vec())
46}
47
48/// <https://w3c.github.io/webcrypto/#hmac-operations-verify>
49pub(crate) fn verify(key: &CryptoKey, message: &[u8], signature: &[u8]) -> Result<bool, Error> {
50 // Step 1. Let mac be the result of performing the MAC Generation operation described in
51 // Section 4 of [FIPS-198-1] using the key represented by the [[handle]] internal slot of key,
52 // the hash function identified by the hash attribute of the [[algorithm]] internal slot of key
53 // and message as the input data text.
54 let hash_function = match key.algorithm() {
55 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algo) => match algo.hash.name.as_str() {
56 ALG_SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
57 ALG_SHA256 => hmac::HMAC_SHA256,
58 ALG_SHA384 => hmac::HMAC_SHA384,
59 ALG_SHA512 => hmac::HMAC_SHA512,
60 _ => return Err(Error::NotSupported),
61 },
62 _ => return Err(Error::NotSupported),
63 };
64 let sign_key = hmac::Key::new(hash_function, key.handle().as_bytes());
65 let mac = hmac::sign(&sign_key, message);
66
67 // Step 2. Return true if mac is equal to signature and false otherwise.
68 Ok(mac.as_ref() == signature)
69}
70
71/// <https://w3c.github.io/webcrypto/#hmac-operations-generate-key>
72pub(crate) fn generate_key(
73 global: &GlobalScope,
74 normalized_algorithm: &SubtleHmacKeyGenParams,
75 extractable: bool,
76 usages: Vec<KeyUsage>,
77 can_gc: CanGc,
78) -> Result<DomRoot<CryptoKey>, Error> {
79 // Step 1. If usages contains any entry which is not "sign" or "verify", then throw a SyntaxError.
80 if usages
81 .iter()
82 .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
83 {
84 return Err(Error::Syntax(None));
85 }
86
87 // Step 2.
88 let length = match normalized_algorithm.length {
89 // If the length member of normalizedAlgorithm is not present:
90 None => {
91 // Let length be the block size in bits of the hash function identified by the
92 // hash member of normalizedAlgorithm.
93 normalized_algorithm.hash.block_size_in_bits()?
94 },
95 // Otherwise, if the length member of normalizedAlgorithm is non-zero:
96 Some(length) if length != 0 => {
97 // Let length be equal to the length member of normalizedAlgorithm.
98 length
99 },
100 // Otherwise:
101 _ => {
102 // throw an OperationError.
103 return Err(Error::Operation);
104 },
105 };
106
107 // Step 3. Generate a key of length length bits.
108 // Step 4. If the key generation step fails, then throw an OperationError.
109 let mut key_data = vec![0; length as usize];
110 if OsRng.try_fill_bytes(&mut key_data).is_err() {
111 return Err(Error::JSFailed);
112 }
113
114 // Step 6. Let algorithm be a new HmacKeyAlgorithm.
115 // Step 7. Set the name attribute of algorithm to "HMAC".
116 // Step 8. Set the length attribute of algorithm to length.
117 // Step 9. Let hash be a new KeyAlgorithm.
118 // Step 10. Set the name attribute of hash to equal the name member of the hash member of
119 // normalizedAlgorithm.
120 // Step 11. Set the hash attribute of algorithm to hash.
121 let hash = SubtleKeyAlgorithm {
122 name: normalized_algorithm.hash.name.clone(),
123 };
124 let algorithm = SubtleHmacKeyAlgorithm {
125 name: ALG_HMAC.to_string(),
126 hash,
127 length,
128 };
129
130 // Step 5. Let key be a new CryptoKey object representing the generated key.
131 // Step 12. Set the [[type]] internal slot of key to "secret".
132 // Step 13. Set the [[algorithm]] internal slot of key to algorithm.
133 // Step 14. Set the [[extractable]] internal slot of key to be extractable.
134 // Step 15. Set the [[usages]] internal slot of key to be usages.
135 let key = CryptoKey::new(
136 global,
137 KeyType::Secret,
138 extractable,
139 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
140 usages,
141 Handle::Hmac(key_data),
142 can_gc,
143 );
144
145 // Step 16. Return key.
146 Ok(key)
147}
148
149/// <https://w3c.github.io/webcrypto/#hmac-operations-import-key>
150pub(crate) fn import_key(
151 global: &GlobalScope,
152 normalized_algorithm: &SubtleHmacImportParams,
153 format: KeyFormat,
154 key_data: &[u8],
155 extractable: bool,
156 usages: Vec<KeyUsage>,
157 can_gc: CanGc,
158) -> Result<DomRoot<CryptoKey>, Error> {
159 // Step 1. Let keyData be the key data to be imported.
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 => {
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.clone();
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);
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 = Base64UrlUnpadded::decode_vec(&jwk.k.as_ref().ok_or(Error::Data)?.str())
202 .map_err(|_| Error::Data)?;
203
204 // Step 2.5. Set the hash to equal the hash member of normalizedAlgorithm.
205 hash = normalized_algorithm.hash.clone();
206
207 // Step 2.6.
208 match hash.name.as_str() {
209 // If the name attribute of hash is "SHA-1":
210 ALG_SHA1 => {
211 // If the alg field of jwk is present and is not "HS1", then throw a DataError.
212 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS1") {
213 return Err(Error::Data);
214 }
215 },
216 // If the name attribute of hash is "SHA-256":
217 ALG_SHA256 => {
218 // If the alg field of jwk is present and is not "HS256", then throw a DataError.
219 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS256") {
220 return Err(Error::Data);
221 }
222 },
223 // If the name attribute of hash is "SHA-384":
224 ALG_SHA384 => {
225 // If the alg field of jwk is present and is not "HS384", then throw a DataError.
226 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS384") {
227 return Err(Error::Data);
228 }
229 },
230 // If the name attribute of hash is "SHA-512":
231 ALG_SHA512 => {
232 // If the alg field of jwk is present and is not "HS512", then throw a DataError.
233 if jwk.alg.as_ref().is_some_and(|alg| alg != "HS512") {
234 return Err(Error::Data);
235 }
236 },
237 // Otherwise,
238 _name => {
239 // if the name attribute of hash is defined in another applicable specification:
240 // Perform any key import steps defined by other applicable specifications,
241 // passing format, jwk and hash and obtaining hash
242 // NOTE: Currently not support applicable specification.
243 return Err(Error::NotSupported);
244 },
245 }
246
247 // Step 2.7. If usages is non-empty and the use field of jwk is present and is not
248 // "sig", then throw a DataError.
249 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
250 return Err(Error::Data);
251 }
252
253 // Step 2.8. If the key_ops field of jwk is present, and is invalid according to
254 // the requirements of JSON Web Key [JWK] or does not contain all of the specified
255 // usages values, then throw a DataError.
256 jwk.check_key_ops(&usages)?;
257
258 // Step 2.9. If the ext field of jwk is present and has the value false and
259 // extractable is true, then throw a DataError.
260 if jwk.ext.is_some_and(|ext| !ext) && extractable {
261 return Err(Error::Data);
262 }
263 },
264 // Otherwise:
265 _ => {
266 // throw a NotSupportedError.
267 return Err(Error::NotSupported);
268 },
269 }
270
271 // Step 5. Let length be the length in bits of data.
272 let mut length = data.len() as u32 * 8;
273
274 // Step 6. If length is zero then throw a DataError.
275 if length == 0 {
276 return Err(Error::Data);
277 }
278
279 // Step 7. If the length member of normalizedAlgorithm is present:
280 if let Some(given_length) = normalized_algorithm.length {
281 // If the length member of normalizedAlgorithm is greater than length:
282 if given_length > length {
283 // throw a DataError.
284 return Err(Error::Data);
285 }
286 // Otherwise:
287 else {
288 // Set length equal to the length member of normalizedAlgorithm.
289 length = given_length;
290 }
291 }
292
293 // Step 10. Let algorithm be a new HmacKeyAlgorithm.
294 // Step 11. Set the name attribute of algorithm to "HMAC".
295 // Step 12. Set the length attribute of algorithm to length.
296 // Step 13. Set the hash attribute of algorithm to hash.
297 let algorithm = SubtleHmacKeyAlgorithm {
298 name: ALG_HMAC.to_string(),
299 hash,
300 length,
301 };
302
303 // Step 8. Let key be a new CryptoKey object representing an HMAC key with the first length
304 // bits of data.
305 // Step 9. Set the [[type]] internal slot of key to "secret".
306 // Step 14. Set the [[algorithm]] internal slot of key to algorithm.
307 let truncated_data = data[..length as usize / 8].to_vec();
308 let key = CryptoKey::new(
309 global,
310 KeyType::Secret,
311 extractable,
312 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(algorithm),
313 usages,
314 Handle::Hmac(truncated_data),
315 can_gc,
316 );
317
318 // Step 15. Return key.
319 Ok(key)
320}
321
322/// <https://w3c.github.io/webcrypto/#hmac-operations-export-key>
323pub(crate) fn export(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
324 match format {
325 KeyFormat::Raw => match key.handle() {
326 Handle::Hmac(key_data) => Ok(ExportedKey::Raw(key_data.as_slice().to_vec())),
327 _ => Err(Error::Operation),
328 },
329 KeyFormat::Jwk => {
330 let key_data = key.handle().as_bytes();
331 // Step 3. Set the k attribute of jwk to be a string containing data
332 let k = Base64UrlUnpadded::encode_string(key_data);
333 // Step 6.
334 let hash_algorithm = match key.algorithm() {
335 KeyAlgorithmAndDerivatives::HmacKeyAlgorithm(alg) => match &*alg.hash.name {
336 ALG_SHA1 => "HS1",
337 ALG_SHA256 => "HS256",
338 ALG_SHA384 => "HS384",
339 ALG_SHA512 => "HS512",
340 _ => return Err(Error::NotSupported),
341 },
342 _ => return Err(Error::NotSupported),
343 };
344
345 // Step 7. Set the key_ops attribute of jwk to the usages attribute of key.
346 let key_ops = key
347 .usages()
348 .iter()
349 .map(|usage| DOMString::from(usage.as_str()))
350 .collect::<Vec<DOMString>>();
351
352 // Step 1. Let jwk be a new JsonWebKey dictionary.
353 let jwk = JsonWebKey {
354 // Step 2. Set the kty attribute of jwk to the string "oct".
355 kty: Some(DOMString::from("oct")),
356 k: Some(DOMString::from(k)),
357 alg: Some(DOMString::from(hash_algorithm.to_string())),
358 // Step 7. Set the key_ops attribute of jwk to equal the usages attribute of key.
359 key_ops: Some(key_ops),
360 // Step 8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
361 ext: Some(key.Extractable()),
362 ..Default::default()
363 };
364
365 Ok(ExportedKey::Jwk(Box::new(jwk)))
366 },
367 _ => Err(Error::NotSupported),
368 }
369}
370
371/// <https://w3c.github.io/webcrypto/#hmac-operations-get-key-length>
372pub(crate) fn get_key_length(
373 normalized_derived_key_algorithm: &SubtleHmacImportParams,
374) -> Result<Option<u32>, Error> {
375 // Step 1.
376 let length = match normalized_derived_key_algorithm.length {
377 // If the length member of normalizedDerivedKeyAlgorithm is not present:
378 None => {
379 // Let length be the block size in bits of the hash function identified by the hash
380 // member of normalizedDerivedKeyAlgorithm.
381 match normalized_derived_key_algorithm.hash.name.as_str() {
382 ALG_SHA1 => 160,
383 ALG_SHA256 => 256,
384 ALG_SHA384 => 384,
385 ALG_SHA512 => 512,
386 _ => {
387 return Err(Error::Type("Unidentified hash member".to_string()));
388 },
389 }
390 },
391 // Otherwise, if the length member of normalizedDerivedKeyAlgorithm is non-zero:
392 Some(length) if length != 0 => {
393 // Let length be equal to the length member of normalizedDerivedKeyAlgorithm.
394 length
395 },
396 // Otherwise:
397 _ => {
398 // throw a TypeError.
399 return Err(Error::Type("[[length]] must not be zero".to_string()));
400 },
401 };
402
403 // Step 2. Return length.
404 Ok(Some(length))
405}