script/dom/webcrypto/subtlecrypto/x448_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 elliptic_curve::ctutils::CtEq;
6use js::context::JSContext;
7use pkcs8::der::asn1::OctetStringRef;
8use pkcs8::der::{Decode, Encode};
9use pkcs8::{AlgorithmIdentifierRef, ObjectIdentifier, PrivateKeyInfoRef, SubjectPublicKeyInfoRef};
10use x448::{PublicKey, StaticSecret};
11use zeroize::Zeroizing;
12
13use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
14 CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
15};
16use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
17use crate::dom::bindings::error::Error;
18use crate::dom::bindings::root::DomRoot;
19use crate::dom::bindings::str::DOMString;
20use crate::dom::cryptokey::{CryptoKey, Handle, KeyUsageVecHelper};
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::subtlecrypto::{
23 CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField, KeyAlgorithmAndDerivatives,
24 SubtleEcdhKeyDeriveParams, SubtleKeyAlgorithm,
25};
26
27/// `id-X448` object identifier defined in [RFC8410]
28const X448_OID_STRING: &str = "1.3.101.111";
29
30const PRIVATE_KEY_LENGTH: usize = 56;
31pub(crate) const SECRET_LENGTH: usize = 56;
32
33/// <https://wicg.github.io/webcrypto-secure-curves/#x448>
34pub(crate) fn derive_bits(
35 normalized_algorithm: &SubtleEcdhKeyDeriveParams,
36 key: &CryptoKey,
37 length: Option<u32>,
38) -> Result<Vec<u8>, Error> {
39 // Step 1. If the [[type]] internal slot of key is not "private", then throw an
40 // InvalidAccessError.
41 if key.Type() != KeyType::Private {
42 return Err(Error::InvalidAccess(Some(
43 "[[type]] internal slot of key is not \"private\"".into(),
44 )));
45 }
46
47 // Step 2. Let publicKey be the public member of normalizedAlgorithm.
48 let public_key = normalized_algorithm.public.root();
49
50 // Step 3. If the [[type]] internal slot of publicKey is not "public", then throw an
51 // InvalidAccessError.
52 if public_key.Type() != KeyType::Public {
53 return Err(Error::InvalidAccess(Some(
54 "[[type]] internal slot of publicKey is not \"public\"".into(),
55 )));
56 }
57
58 // Step 4. If the name attribute of the [[algorithm]] internal slot of publicKey is not equal to
59 // the name property of the [[algorithm]] internal slot of key, then throw an
60 // InvalidAccessError.
61 if public_key.algorithm().name() != key.algorithm().name() {
62 return Err(Error::InvalidAccess(Some(
63 "[[algorithm]] internal slot of publicKey does not match \
64 [[algorithm]] internal slot of key"
65 .into(),
66 )));
67 }
68
69 // Step 5. Let secret be the result of performing the X448 function specified in [RFC7748]
70 // Section 5 with key as the X448 private key k and the X448 public key represented by the
71 // [[handle]] internal slot of publicKey as the X448 public key u.
72 let Handle::X448PrivateKey(private_key) = key.handle() else {
73 return Err(Error::Operation(Some(
74 "[[handle]] internal slot of key is not an X448 private key".into(),
75 )));
76 };
77 let Handle::X448PublicKey(public_key) = public_key.handle() else {
78 return Err(Error::Operation(Some(
79 "[[handle]] internal slot of publicKey is not an X448 public key".into(),
80 )));
81 };
82 let secret = private_key.diffie_hellman(public_key);
83
84 // Step 6. If secret is the all-zero value, then throw a OperationError. This check must be
85 // performed in constant-time, as per [RFC7748] Section 6.2.
86 if secret.as_bytes().ct_eq(&[0u8; SECRET_LENGTH]).into() {
87 return Err(Error::Operation(Some(
88 "The secret is the all-zero value".into(),
89 )));
90 }
91
92 // Step 7.
93 // If length is null:
94 // Return secret
95 // Otherwise:
96 // If the length of secret in bits is less than length:
97 // throw an OperationError.
98 // Otherwise:
99 // Return an octet string containing the first length bits of secret.
100 let secret_slice = secret.as_bytes();
101 match length {
102 None => Ok(secret_slice.to_vec()),
103 Some(length) => {
104 if secret_slice.len() * 8 < length as usize {
105 Err(Error::Operation(Some("Derived secret is too short".into())))
106 } else {
107 let mut secret = secret_slice[..length.div_ceil(8) as usize].to_vec();
108 if length % 8 != 0 {
109 // Clean excess bits in last byte of secret.
110 let mask = u8::MAX << (8 - length % 8);
111 if let Some(last_byte) = secret.last_mut() {
112 *last_byte &= mask;
113 }
114 }
115 Ok(secret)
116 }
117 },
118 }
119}
120
121/// <https://wicg.github.io/webcrypto-secure-curves/#x448-description>
122pub(crate) fn generate_key(
123 cx: &mut JSContext,
124 global: &GlobalScope,
125 extractable: bool,
126 usages: Vec<KeyUsage>,
127) -> Result<CryptoKeyPair, Error> {
128 // Step 1. If usages contains an entry which is not "deriveKey" or "deriveBits" then throw a
129 // SyntaxError.
130 if usages
131 .iter()
132 .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
133 {
134 return Err(Error::Syntax(Some(
135 "Usages contains an entry which is not \"deriveKey\" or \"deriveBits\"".into(),
136 )));
137 }
138
139 // Step 2. Generate an X448 key pair, with the private key being 56 random bytes, and the public
140 // key being X448(a, 5), as defined in [RFC7748], section 6.2.
141 let mut rng = rand::rng();
142 let private_key = StaticSecret::random_from_rng(&mut rng);
143 let public_key = PublicKey::from(&private_key);
144
145 // Step 3. Let algorithm be a new KeyAlgorithm object.
146 // Step 4. Set the name attribute of algorithm to "X448".
147 let algorithm = SubtleKeyAlgorithm {
148 name: CryptoAlgorithm::X448,
149 };
150
151 // Step 5. Let publicKey be a new CryptoKey associated with the relevant global object of this
152 // [HTML], and representing the public key of the generated key pair.
153 // Step 6. Set the [[type]] internal slot of publicKey to "public"
154 // Step 7. Set the [[algorithm]] internal slot of publicKey to algorithm.
155 // Step 8. Set the [[extractable]] internal slot of publicKey to true.
156 // Step 9. Set the [[usages]] internal slot of publicKey to be the empty list.
157 let public_key = CryptoKey::new(
158 cx,
159 global,
160 KeyType::Public,
161 true,
162 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
163 Vec::new(),
164 Handle::X448PublicKey(public_key),
165 );
166
167 // Step 10. Let privateKey be a new CryptoKey associated with the relevant global object of this
168 // [HTML], and representing the private key of the generated key pair.
169 // Step 11. Set the [[type]] internal slot of privateKey to "private"
170 // Step 12. Set the [[algorithm]] internal slot of privateKey to algorithm.
171 // Step 13. Set the [[extractable]] internal slot of privateKey to extractable.
172 // Step 14. Set the [[usages]] internal slot of privateKey to be the usage intersection of
173 // usages and [ "deriveKey", "deriveBits" ].
174 let private_key = CryptoKey::new(
175 cx,
176 global,
177 KeyType::Private,
178 extractable,
179 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
180 usages.usage_intersection(&[KeyUsage::DeriveKey, KeyUsage::DeriveBits]),
181 Handle::X448PrivateKey(private_key),
182 );
183
184 // Step 15. Let result be a new CryptoKeyPair dictionary.
185 // Step 16. Set the publicKey attribute of result to be publicKey.
186 // Step 17. Set the privateKey attribute of result to be privateKey.
187 let result = CryptoKeyPair {
188 publicKey: Some(public_key),
189 privateKey: Some(private_key),
190 };
191
192 // Step 18. Return the result of converting result to an ECMAScript Object, as defined by
193 // [WebIDL].
194 // NOTE: The conversion of result to an ECMAScript Object is done in SubtleCrypto::Generate.
195 Ok(result)
196}
197
198/// <https://wicg.github.io/webcrypto-secure-curves/#x448-description>
199pub(crate) fn import_key(
200 cx: &mut JSContext,
201 global: &GlobalScope,
202 format: KeyFormat,
203 key_data: &[u8],
204 extractable: bool,
205 usages: Vec<KeyUsage>,
206) -> Result<DomRoot<CryptoKey>, Error> {
207 // Step 1. Let keyData be the key data to be imported.
208
209 // Step 2.
210 let key = match format {
211 // If format is "spki":
212 KeyFormat::Spki => {
213 // Step 2.1. If usages is not empty then throw a SyntaxError.
214 if !usages.is_empty() {
215 return Err(Error::Syntax(Some("Usages is not empty".into())));
216 }
217
218 // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
219 // algorithm over keyData.
220 // Step 2.3. If an error occurred while parsing, then throw a DataError.
221 let spki = SubjectPublicKeyInfoRef::from_der(key_data).map_err(|_| {
222 Error::Data(Some(
223 "Failed to parse the X448 public key in SPKI format".into(),
224 ))
225 })?;
226
227 // Step 2.4. If the algorithm object identifier field of the algorithm
228 // AlgorithmIdentifier field of spki is not equal to the id-X448 object identifier
229 // defined in [RFC8410], then throw a DataError.
230 if spki.algorithm.oid != ObjectIdentifier::new_unwrap(X448_OID_STRING) {
231 return Err(Error::Data(Some(
232 "The algorithm object identifier field of the algorithm field of spki \
233 is not equal to the id-X448 object identifier"
234 .into(),
235 )));
236 }
237
238 // Step 2.5. If the parameters field of the algorithm AlgorithmIdentifier field of spki
239 // is present, then throw a DataError.
240 if spki.algorithm.parameters.is_some() {
241 return Err(Error::Data(Some(
242 "The parameters field of the algorithm field of spki is present".into(),
243 )));
244 }
245
246 // Step 2.6. Let publicKey be the X448 public key identified by the subjectPublicKey
247 // field of spki.
248 let key_bytes = spki.subject_public_key.as_bytes().ok_or(Error::Data(Some(
249 "The subjectPublicKey field in spki is not octet aligned".into(),
250 )))?;
251 let public_key = PublicKey::from_bytes_unchecked(key_bytes).ok_or(Error::Data(
252 Some("The length of the subjectPublicKey in spki is not 56 bytes".into()),
253 ))?;
254
255 // Step 2.7. Let key be a new CryptoKey associated with the relevant global object of
256 // this [HTML], and that represents publicKey.
257 // Step 2.8. Set the [[type]] internal slot of key to "public"
258 // Step 2.9. Let algorithm be a new KeyAlgorithm.
259 // Step 2.10. Set the name attribute of algorithm to "X448".
260 // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
261 let algorithm = SubtleKeyAlgorithm {
262 name: CryptoAlgorithm::X448,
263 };
264 CryptoKey::new(
265 cx,
266 global,
267 KeyType::Public,
268 extractable,
269 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
270 usages.normalized_value(),
271 Handle::X448PublicKey(public_key),
272 )
273 },
274 // If format is "pkcs8":
275 KeyFormat::Pkcs8 => {
276 // Step 2.1. If usages contains an entry which is not "deriveKey" or "deriveBits" then
277 // throw a SyntaxError.
278 if usages
279 .iter()
280 .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
281 {
282 return Err(Error::Syntax(Some(
283 "Usages contains an entry which is not \"deriveKey\" or \"deriveBits\"".into(),
284 )));
285 }
286
287 // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
288 // algorithm over keyData.
289 // Step 2.3. If an error occurs while parsing, then throw a DataError.
290 let private_key_info = PrivateKeyInfoRef::from_der(key_data).map_err(|_| {
291 Error::Data(Some(
292 "Failed to parse the X448 private key to PKCS#8 document".into(),
293 ))
294 })?;
295
296 // Step 2.4. If the algorithm object identifier field of the privateKeyAlgorithm
297 // PrivateKeyAlgorithm field of privateKeyInfo is not equal to the id-X448 object
298 // identifier defined in [RFC8410], then throw a DataError.
299 if private_key_info.algorithm.oid != ObjectIdentifier::new_unwrap(X448_OID_STRING) {
300 return Err(Error::Data(Some(
301 "The algorithm object identifier field of the privateKeyAlgorithm field of \
302 privateKeyInfo is not equal to the id-X448 object identifier"
303 .into(),
304 )));
305 }
306
307 // Step 2.5. If the parameters field of the privateKeyAlgorithm
308 // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
309 // DataError.
310 if private_key_info.algorithm.parameters.is_some() {
311 return Err(Error::Data(Some(
312 "The parameters field of the privateKeyAlgorithm field of privateKeyInfo \
313 is present"
314 .into(),
315 )));
316 }
317
318 // Step 2.6. Let curvePrivateKey be the result of performing the parse an ASN.1
319 // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
320 // as the ASN.1 CurvePrivateKey structure specified in Section 7 of [RFC8410], and
321 // exactData set to true.
322 // Step 7. If an error occurred while parsing, then throw a DataError.
323 let curve_private_key = private_key_info
324 .private_key
325 .decode_into::<&OctetStringRef>()
326 .map_err(|_| {
327 Error::Data(Some(
328 "Failed to decode the privateKey field of PrivateKeyInfo ASN.1 structure"
329 .into(),
330 ))
331 })?;
332 let key_bytes: [u8; PRIVATE_KEY_LENGTH] =
333 curve_private_key.as_bytes().try_into().map_err(|_| {
334 Error::Data(Some(
335 "Failed to extract the raw bytes from the CurvePrivateKey ASN.1 structure"
336 .into(),
337 ))
338 })?;
339 let curve_private_key = StaticSecret::from(key_bytes);
340
341 // Step 2.8. Let key be a new CryptoKey associated with the relevant global object of
342 // this [HTML], and that represents the X448 private key identified by curvePrivateKey.
343 // Step 2.9. Set the [[type]] internal slot of key to "private"
344 // Step 2.10. Let algorithm be a new KeyAlgorithm.
345 // Step 2.11. Set the name attribute of algorithm to "X448".
346 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
347 let algorithm = SubtleKeyAlgorithm {
348 name: CryptoAlgorithm::X448,
349 };
350 CryptoKey::new(
351 cx,
352 global,
353 KeyType::Private,
354 extractable,
355 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
356 usages.normalized_value(),
357 Handle::X448PrivateKey(curve_private_key),
358 )
359 },
360 // If format is "jwk":
361 KeyFormat::Jwk => {
362 // Step 2.1.
363 // If keyData is a JsonWebKey dictionary:
364 // Let jwk equal keyData.
365 // Otherwise:
366 // Throw a DataError.
367 let jwk = JsonWebKey::parse(cx, key_data)?;
368
369 // Step 2.2. If the d field is present and if usages contains an entry which is not
370 // "deriveKey" or "deriveBits" then throw a SyntaxError.
371 if jwk.d.is_some() &&
372 usages
373 .iter()
374 .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
375 {
376 return Err(Error::Syntax(Some(
377 "The d field is present and if usages contains an entry which is not \
378 \"deriveKey\" or \"deriveBits\""
379 .into(),
380 )));
381 }
382
383 // Step 2.3. If the d field is not present and if usages is not empty then throw a
384 // SyntaxError.
385 if jwk.d.is_none() && !usages.is_empty() {
386 return Err(Error::Syntax(Some(
387 "The d field is not present and if usages is not empty".into(),
388 )));
389 }
390
391 // Step 2.4. If the kty field of jwk is not "OKP", then throw a DataError.
392 if jwk.kty.as_ref().is_none_or(|kty| kty != "OKP") {
393 return Err(Error::Data(Some(
394 "The kty field of jwk is not \"OKP\"".into(),
395 )));
396 }
397
398 // Step 2.5. If the crv field of jwk is not "X448", then throw a DataError.
399 if jwk.crv.as_ref().is_none_or(|crv| crv != "X448") {
400 return Err(Error::Data(Some(
401 "The crv field of jwk is not \"X448\"".into(),
402 )));
403 }
404
405 // Step 2.6. If usages is non-empty and the use field of jwk is present and is not equal
406 // to "enc" then throw a DataError.
407 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
408 return Err(Error::Data(Some(
409 "Usages is non-empty and the use field of jwk is present and is not equal to \
410 \"enc\""
411 .into(),
412 )));
413 }
414
415 // Step 2.7. If the key_ops field of jwk is present, and is invalid according to the
416 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
417 // usages values, then throw a DataError.
418 jwk.check_key_ops(&usages)?;
419
420 // Step 2.8. If the ext field of jwk is present and has the value false and extractable
421 // is true, then throw a DataError.
422 if jwk.ext.is_some_and(|ext| !ext) && extractable {
423 return Err(Error::Data(Some(
424 "the ext field of jwk is present and has the value false \
425 and extractable is true"
426 .into(),
427 )));
428 }
429
430 // Step 2.9.
431 // If the d field is present:
432 let (handle, key_type) = if jwk.d.is_some() {
433 // Step 2.9.1. If jwk does not meet the requirements of the JWK private key format
434 // described in Section 2 of [RFC8037], then throw a DataError.
435 let d = jwk.decode_required_string_field(JwkStringField::D)?;
436 let x = jwk.decode_required_string_field(JwkStringField::X)?;
437 let private_key_bytes: [u8; PRIVATE_KEY_LENGTH] =
438 d.as_slice().try_into().map_err(|_| {
439 Error::Data(Some("Invalid length of private key in 'd' field".into()))
440 })?;
441 let public_key_bytes = x.as_slice();
442 let private_key = StaticSecret::from(private_key_bytes);
443 let public_key = PublicKey::from_bytes_unchecked(public_key_bytes).ok_or(
444 Error::Data(Some("Invalid length of private key in 'x' field".into())),
445 )?;
446 if PublicKey::from(&private_key) != public_key {
447 return Err(Error::Data(Some(
448 "Public key in 'x' field does not match private key in 'd' field".into(),
449 )));
450 }
451
452 // Step 2.9.2. Let key be a new CryptoKey object that represents the X448 private
453 // key identified by interpreting jwk according to Section 2 of [RFC8037].
454 // NOTE: CryptoKey is created in Step 2.10 - 2.12.
455 let handle = Handle::X448PrivateKey(private_key);
456
457 // Step 2.9.3. Set the [[type]] internal slot of Key to "private".
458 let key_type = KeyType::Private;
459
460 (handle, key_type)
461 }
462 // Otherwise:
463 else {
464 // Step 2.9.1. If jwk does not meet the requirements of the JWK public key format
465 // described in Section 2 of [RFC8037], then throw a DataError.
466 let x = jwk.decode_required_string_field(JwkStringField::X)?;
467 let public_key_bytes = x.as_slice();
468 let public_key = PublicKey::from_bytes_unchecked(public_key_bytes).ok_or(
469 Error::Data(Some("Invalid length of private key in 'x' field".into())),
470 )?;
471
472 // Step 2.9.2. Let key be a new CryptoKey object that represents the X448 public key
473 // identified by interpreting jwk according to Section 2 of [RFC8037].
474 // NOTE: CryptoKey is created in Step 2.10 - 2.12.
475 let handle = Handle::X448PublicKey(public_key);
476
477 // Step 2.9.3. Set the [[type]] internal slot of Key to "public".
478 let key_type = KeyType::Public;
479
480 (handle, key_type)
481 };
482
483 // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
484 // Step 2.11. Set the name attribute of algorithm to "X448".
485 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
486 let algorithm = SubtleKeyAlgorithm {
487 name: CryptoAlgorithm::X448,
488 };
489 CryptoKey::new(
490 cx,
491 global,
492 key_type,
493 extractable,
494 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
495 usages.normalized_value(),
496 handle,
497 )
498 },
499 // If format is "raw":
500 KeyFormat::Raw | KeyFormat::Raw_public => {
501 // Step 2.1. If usages is not empty then throw a SyntaxError.
502 if !usages.is_empty() {
503 return Err(Error::Syntax(Some("Usages is not empty".into())));
504 }
505
506 // Step 2.2. Let data be keyData.
507 let data = key_data;
508
509 // Step 2.3. If the length in bits of data is not 448 then throw a DataError.
510 if data.len() != 56 {
511 return Err(Error::Data(Some("The key length is not 448 bits".into())));
512 }
513
514 // Step 2.4. Let algorithm be a new KeyAlgorithm object.
515 // Step 2.5. Set the name attribute of algorithm to "X448".
516 let algorithm = SubtleKeyAlgorithm {
517 name: CryptoAlgorithm::X448,
518 };
519
520 // Step 2.6. Let key be a new CryptoKey associated with the relevant global object of
521 // this [HTML], and that represents data.
522 // Step 2.7. Set the [[type]] internal slot of key to "public"
523 // Step 2.8. Set the [[algorithm]] internal slot of key to algorithm.
524 let public_key = PublicKey::from_bytes_unchecked(data).ok_or(Error::Data(Some(
525 "Failed to import public key from raw bytes".into(),
526 )))?;
527 CryptoKey::new(
528 cx,
529 global,
530 KeyType::Public,
531 extractable,
532 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
533 usages.normalized_value(),
534 Handle::X448PublicKey(public_key),
535 )
536 },
537 // Otherwise:
538 _ => {
539 // throw a NotSupportedError.
540 return Err(Error::NotSupported(Some(
541 "Unsupported import key format for X448".into(),
542 )));
543 },
544 };
545
546 // Step 3. Return key
547 Ok(key)
548}
549
550/// <https://wicg.github.io/webcrypto-secure-curves/#x448>
551pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
552 // Step 1. Let key be the CryptoKey to be exported.
553
554 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
555 // slot of key cannot be accessed, then throw an OperationError.
556 // NOTE: Done in Step 3.
557
558 // Step 3.
559 let result = match format {
560 // If format is "spki":
561 KeyFormat::Spki => {
562 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
563 // InvalidAccessError.
564 if key.Type() != KeyType::Public {
565 return Err(Error::InvalidAccess(Some(
566 "[[type]] internal slot of key is not \"public\"".into(),
567 )));
568 }
569
570 // Step 3.2. Let data be an instance of the subjectPublicKeyInfo ASN.1 structure defined
571 // in [RFC5280] with the following properties:
572 // * Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
573 // properties:
574 // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410].
575 // * Set the subjectPublicKey field to keyData.
576 let Handle::X448PublicKey(public_key) = key.handle() else {
577 return Err(Error::Operation(Some(
578 "[[handle]] internal slot of key is not an X448 public key".into(),
579 )));
580 };
581 let data = SubjectPublicKeyInfoRef {
582 algorithm: AlgorithmIdentifierRef {
583 oid: ObjectIdentifier::new_unwrap(X448_OID_STRING),
584 parameters: None,
585 },
586 subject_public_key: public_key.as_bytes().try_into().map_err(|_| {
587 Error::Data(Some(
588 "Failed to construct the subjectPublicKey field of subjectPublicKeyInfo \
589 ASN.1 structure"
590 .into(),
591 ))
592 })?,
593 };
594
595 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
596 // of this [HTML], and containing data.
597 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
598 ExportedKey::new_bytes(data.to_der().map_err(|_| {
599 Error::Operation(Some(
600 "Failed to encode the subjectPublicKeyInfo ASN.1 structure in DER-encoding"
601 .into(),
602 ))
603 })?)
604 },
605 // If format is "pkcs8":
606 KeyFormat::Pkcs8 => {
607 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
608 // InvalidAccessError.
609 if key.Type() != KeyType::Private {
610 return Err(Error::InvalidAccess(Some(
611 "[[type]] internal slot of key is not \"private\"".into(),
612 )));
613 }
614
615 // Step 3.2. Let data be an instance of the privateKeyInfo ASN.1 structure defined in
616 // [RFC5208] with the following properties:
617 // * Set the version field to 0.
618 // * Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
619 // with the following properties:
620 // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410].
621 // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1
622 // type, as defined in Section 7 of [RFC8410], that represents the X448 private key
623 // represented by the [[handle]] internal slot of key
624 let Handle::X448PrivateKey(private_key) = key.handle() else {
625 return Err(Error::Operation(Some(
626 "[[handle]] internal slot of key is not an X448 private key".into(),
627 )));
628 };
629 let curve_private_key = OctetStringRef::new(private_key.as_bytes()).map_err(|_| {
630 Error::Operation(Some(
631 "Failed to construct CurvePrivateKey ASN.1 structure".into(),
632 ))
633 })?;
634 let encoded_curve_private_key: Zeroizing<Vec<u8>> = curve_private_key
635 .to_der()
636 .map_err(|_| {
637 Error::Operation(Some(
638 "Failed to encode CurvePrivateKey ASN.1 structure in DER-encoding".into(),
639 ))
640 })?
641 .into();
642 let private_key_field =
643 OctetStringRef::new(&encoded_curve_private_key).map_err(|_| {
644 Error::Operation(Some(
645 "Failed to construct privateKey field of privateKeyInfo ASN.1 structure"
646 .into(),
647 ))
648 })?;
649 let data = PrivateKeyInfoRef {
650 algorithm: AlgorithmIdentifierRef {
651 oid: ObjectIdentifier::new_unwrap(X448_OID_STRING),
652 parameters: None,
653 },
654 private_key: private_key_field,
655 public_key: None,
656 };
657
658 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
659 // of this [HTML], and containing data.
660 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
661 ExportedKey::new_bytes(data.to_der().map_err(|_| {
662 Error::Operation(Some(
663 "Failed to encode privateKeyInfo ASN.1 structure in DER-encoding".into(),
664 ))
665 })?)
666 },
667 // If format is "jwk":
668 KeyFormat::Jwk => {
669 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
670 let mut jwk = JsonWebKey::default();
671
672 // Step 3.2. Set the kty attribute of jwk to "OKP".
673 jwk.kty = Some(DOMString::from("OKP"));
674
675 // Step 3.3. Set the crv attribute of jwk to "X448".
676 jwk.crv = Some(DOMString::from("X448"));
677
678 // Step 3.4. Set the x attribute of jwk according to the definition in Section 2 of
679 // [RFC8037].
680 match key.handle() {
681 Handle::X448PrivateKey(private_key) => {
682 let public_key = PublicKey::from(private_key);
683 jwk.encode_string_field(JwkStringField::X, public_key.as_bytes());
684 },
685 Handle::X448PublicKey(public_key) => {
686 jwk.encode_string_field(JwkStringField::X, public_key.as_bytes());
687 },
688 _ => {
689 return Err(Error::Operation(Some(
690 "[[handle]] internal slot of key is not an X448 key".into(),
691 )));
692 },
693 }
694
695 // Step 3.5. If the [[type]] internal slot of key is "private"
696 // Set the d attribute of jwk according to the definition in Section 2 of [RFC8037].
697 if key.Type() == KeyType::Private {
698 if let Handle::X448PrivateKey(private_key) = key.handle() {
699 jwk.encode_string_field(JwkStringField::D, private_key.as_bytes());
700 } else {
701 return Err(Error::Operation(Some(
702 "[[handle]] internal slot of key is not an X448 private key".into(),
703 )));
704 }
705 let Handle::X448PrivateKey(private_key) = key.handle() else {
706 return Err(Error::Operation(Some(
707 "[[handle]] internal slot of key is not an X448 private key".into(),
708 )));
709 };
710 jwk.encode_string_field(JwkStringField::D, private_key.as_bytes().as_slice());
711 }
712
713 // Step 3.6. Set the key_ops attribute of jwk to the usages attribute of key.
714 jwk.set_key_ops(&key.usages());
715
716 // Step 3.7. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
717 jwk.ext = Some(key.Extractable());
718
719 // Step 3.8. Let result be the result of converting jwk to an ECMAScript Object, as
720 // defined by [WebIDL].
721 // NOTE: The conversion to an ECMAScript Object is done by SubtleCrypto::ExportKey.
722 ExportedKey::new_jwk(jwk)
723 },
724 // If format is "raw":
725 KeyFormat::Raw | KeyFormat::Raw_public => {
726 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
727 // InvalidAccessError.
728 if key.Type() != KeyType::Public {
729 return Err(Error::InvalidAccess(Some(
730 "[[type]] internal slot of key is not \"public\"".into(),
731 )));
732 }
733
734 // Step 3.2. Let data be an octet string representing the X448 public key represented by
735 // the [[handle]] internal slot of key.
736 let Handle::X448PublicKey(public_key) = key.handle() else {
737 return Err(Error::Operation(Some(
738 "[[handle]] internal slot of key is not an X448 public key".into(),
739 )));
740 };
741 let data = public_key.as_bytes();
742
743 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
744 // of this [HTML], and containing data.
745 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
746 ExportedKey::new_bytes(data.to_vec())
747 },
748 // Otherwise:
749 _ => {
750 // throw a NotSupportedError.
751 return Err(Error::NotSupported(Some(
752 "Unsupported export key format for X448".into(),
753 )));
754 },
755 };
756
757 // Step 4. Return result.
758 Ok(result)
759}
760
761/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
762/// Step 9 - 15, for X448
763pub(crate) fn get_public_key(
764 cx: &mut JSContext,
765 global: &GlobalScope,
766 key: &CryptoKey,
767 algorithm: &KeyAlgorithmAndDerivatives,
768 usages: Vec<KeyUsage>,
769) -> Result<DomRoot<CryptoKey>, Error> {
770 // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
771 // identified by algorithm, then throw a SyntaxError.
772 //
773 // NOTE: See "importKey" operation for supported usages
774 if !usages.is_empty() {
775 return Err(Error::Syntax(Some("Usages is not empty".to_string())));
776 }
777
778 // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
779 // private key represented by the [[handle]] internal slot of key.
780 // Step 11. If an error occurred, then throw a OperationError.
781 // Step 12. Set the [[type]] internal slot of publicKey to "public".
782 // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
783 // Step 14. Set the [[extractable]] internal slot of publicKey to true.
784 // Step 15. Set the [[usages]] internal slot of publicKey to usages.
785 let Handle::X448PrivateKey(private_key) = key.handle() else {
786 return Err(Error::Operation(Some(
787 "[[handle]] internal slot of key is not an X448 private key".into(),
788 )));
789 };
790 let public_key = CryptoKey::new(
791 cx,
792 global,
793 KeyType::Public,
794 true,
795 algorithm.clone(),
796 usages,
797 Handle::X448PublicKey(PublicKey::from(private_key)),
798 );
799
800 Ok(public_key)
801}