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}