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};
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(None))
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
181 .iter()
182 .filter(|usage| matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
183 .cloned()
184 .collect(),
185 Handle::X448PrivateKey(private_key),
186 );
187
188 // Step 15. Let result be a new CryptoKeyPair dictionary.
189 // Step 16. Set the publicKey attribute of result to be publicKey.
190 // Step 17. Set the privateKey attribute of result to be privateKey.
191 let result = CryptoKeyPair {
192 publicKey: Some(public_key),
193 privateKey: Some(private_key),
194 };
195
196 // Step 18. Return the result of converting result to an ECMAScript Object, as defined by
197 // [WebIDL].
198 // NOTE: The conversion of result to an ECMAScript Object is done in SubtleCrypto::Generate.
199 Ok(result)
200}
201
202/// <https://wicg.github.io/webcrypto-secure-curves/#x448-description>
203pub(crate) fn import_key(
204 cx: &mut JSContext,
205 global: &GlobalScope,
206 format: KeyFormat,
207 key_data: &[u8],
208 extractable: bool,
209 usages: Vec<KeyUsage>,
210) -> Result<DomRoot<CryptoKey>, Error> {
211 // Step 1. Let keyData be the key data to be imported.
212
213 // Step 2.
214 let key = match format {
215 // If format is "spki":
216 KeyFormat::Spki => {
217 // Step 2.1. If usages is not empty then throw a SyntaxError.
218 if !usages.is_empty() {
219 return Err(Error::Syntax(Some("Usages is not empty".into())));
220 }
221
222 // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
223 // algorithm over keyData.
224 // Step 2.3. If an error occurred while parsing, then throw a DataError.
225 let spki = SubjectPublicKeyInfoRef::from_der(key_data).map_err(|_| {
226 Error::Data(Some(
227 "Failed to parse the X448 public key in SPKI format".into(),
228 ))
229 })?;
230
231 // Step 2.4. If the algorithm object identifier field of the algorithm
232 // AlgorithmIdentifier field of spki is not equal to the id-X448 object identifier
233 // defined in [RFC8410], then throw a DataError.
234 if spki.algorithm.oid != ObjectIdentifier::new_unwrap(X448_OID_STRING) {
235 return Err(Error::Data(Some(
236 "The algorithm object identifier field of the algorithm field of spki \
237 is not equal to the id-X448 object identifier"
238 .into(),
239 )));
240 }
241
242 // Step 2.5. If the parameters field of the algorithm AlgorithmIdentifier field of spki
243 // is present, then throw a DataError.
244 if spki.algorithm.parameters.is_some() {
245 return Err(Error::Data(Some(
246 "The parameters field of the algorithm field of spki is present".into(),
247 )));
248 }
249
250 // Step 2.6. Let publicKey be the X448 public key identified by the subjectPublicKey
251 // field of spki.
252 let key_bytes = spki.subject_public_key.as_bytes().ok_or(Error::Data(Some(
253 "The subjectPublicKey field in spki is not octet aligned".into(),
254 )))?;
255 let public_key = PublicKey::from_bytes_unchecked(key_bytes).ok_or(Error::Data(
256 Some("The length of the subjectPublicKey in spki is not 56 bytes".into()),
257 ))?;
258
259 // Step 2.7. Let key be a new CryptoKey associated with the relevant global object of
260 // this [HTML], and that represents publicKey.
261 // Step 2.8. Set the [[type]] internal slot of key to "public"
262 // Step 2.9. Let algorithm be a new KeyAlgorithm.
263 // Step 2.10. Set the name attribute of algorithm to "X448".
264 // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
265 let algorithm = SubtleKeyAlgorithm {
266 name: CryptoAlgorithm::X448,
267 };
268 CryptoKey::new(
269 cx,
270 global,
271 KeyType::Public,
272 extractable,
273 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
274 usages,
275 Handle::X448PublicKey(public_key),
276 )
277 },
278 // If format is "pkcs8":
279 KeyFormat::Pkcs8 => {
280 // Step 2.1. If usages contains an entry which is not "deriveKey" or "deriveBits" then
281 // throw a SyntaxError.
282 if usages
283 .iter()
284 .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
285 {
286 return Err(Error::Syntax(Some(
287 "Usages contains an entry which is not \"deriveKey\" or \"deriveBits\"".into(),
288 )));
289 }
290
291 // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
292 // algorithm over keyData.
293 // Step 2.3. If an error occurs while parsing, then throw a DataError.
294 let private_key_info = PrivateKeyInfoRef::from_der(key_data).map_err(|_| {
295 Error::Data(Some(
296 "Failed to parse the X448 private key to PKCS#8 document".into(),
297 ))
298 })?;
299
300 // Step 2.4. If the algorithm object identifier field of the privateKeyAlgorithm
301 // PrivateKeyAlgorithm field of privateKeyInfo is not equal to the id-X448 object
302 // identifier defined in [RFC8410], then throw a DataError.
303 if private_key_info.algorithm.oid != ObjectIdentifier::new_unwrap(X448_OID_STRING) {
304 return Err(Error::Data(Some(
305 "The algorithm object identifier field of the privateKeyAlgorithm field of \
306 privateKeyInfo is not equal to the id-X448 object identifier"
307 .into(),
308 )));
309 }
310
311 // Step 2.5. If the parameters field of the privateKeyAlgorithm
312 // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
313 // DataError.
314 if private_key_info.algorithm.parameters.is_some() {
315 return Err(Error::Data(Some(
316 "The parameters field of the privateKeyAlgorithm field of privateKeyInfo \
317 is present"
318 .into(),
319 )));
320 }
321
322 // Step 2.6. Let curvePrivateKey be the result of performing the parse an ASN.1
323 // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
324 // as the ASN.1 CurvePrivateKey structure specified in Section 7 of [RFC8410], and
325 // exactData set to true.
326 // Step 7. If an error occurred while parsing, then throw a DataError.
327 let curve_private_key = private_key_info
328 .private_key
329 .decode_into::<&OctetStringRef>()
330 .map_err(|_| {
331 Error::Data(Some(
332 "Failed to decode the privateKey field of PrivateKeyInfo ASN.1 structure"
333 .into(),
334 ))
335 })?;
336 let key_bytes: [u8; PRIVATE_KEY_LENGTH] =
337 curve_private_key.as_bytes().try_into().map_err(|_| {
338 Error::Data(Some(
339 "Failed to extract the raw bytes from the CurvePrivateKey ASN.1 structure"
340 .into(),
341 ))
342 })?;
343 let curve_private_key = StaticSecret::from(key_bytes);
344
345 // Step 2.8. Let key be a new CryptoKey associated with the relevant global object of
346 // this [HTML], and that represents the X448 private key identified by curvePrivateKey.
347 // Step 2.9. Set the [[type]] internal slot of key to "private"
348 // Step 2.10. Let algorithm be a new KeyAlgorithm.
349 // Step 2.11. Set the name attribute of algorithm to "X448".
350 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
351 let algorithm = SubtleKeyAlgorithm {
352 name: CryptoAlgorithm::X448,
353 };
354 CryptoKey::new(
355 cx,
356 global,
357 KeyType::Private,
358 extractable,
359 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
360 usages,
361 Handle::X448PrivateKey(curve_private_key),
362 )
363 },
364 // If format is "jwk":
365 KeyFormat::Jwk => {
366 // Step 2.1.
367 // If keyData is a JsonWebKey dictionary:
368 // Let jwk equal keyData.
369 // Otherwise:
370 // Throw a DataError.
371 let jwk = JsonWebKey::parse(cx, key_data)?;
372
373 // Step 2.2. If the d field is present and if usages contains an entry which is not
374 // "deriveKey" or "deriveBits" then throw a SyntaxError.
375 if jwk.d.is_some() &&
376 usages
377 .iter()
378 .any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
379 {
380 return Err(Error::Syntax(Some(
381 "The d field is present and if usages contains an entry which is not \
382 \"deriveKey\" or \"deriveBits\""
383 .into(),
384 )));
385 }
386
387 // Step 2.3. If the d field is not present and if usages is not empty then throw a
388 // SyntaxError.
389 if jwk.d.is_none() && !usages.is_empty() {
390 return Err(Error::Syntax(Some(
391 "The d field is not present and if usages is not empty".into(),
392 )));
393 }
394
395 // Step 2.4. If the kty field of jwk is not "OKP", then throw a DataError.
396 if jwk.kty.as_ref().is_none_or(|kty| kty != "OKP") {
397 return Err(Error::Data(Some(
398 "The kty field of jwk is not \"OKP\"".into(),
399 )));
400 }
401
402 // Step 2.5. If the crv field of jwk is not "X448", then throw a DataError.
403 if jwk.crv.as_ref().is_none_or(|crv| crv != "X448") {
404 return Err(Error::Data(Some(
405 "The crv field of jwk is not \"X448\"".into(),
406 )));
407 }
408
409 // Step 2.6. If usages is non-empty and the use field of jwk is present and is not equal
410 // to "enc" then throw a DataError.
411 if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
412 return Err(Error::Data(Some(
413 "Usages is non-empty and the use field of jwk is present and is not equal to \
414 \"enc\""
415 .into(),
416 )));
417 }
418
419 // Step 2.7. If the key_ops field of jwk is present, and is invalid according to the
420 // requirements of JSON Web Key [JWK], or it does not contain all of the specified
421 // usages values, then throw a DataError.
422 jwk.check_key_ops(&usages)?;
423
424 // Step 2.8. If the ext field of jwk is present and has the value false and extractable
425 // is true, then throw a DataError.
426 if jwk.ext.is_some_and(|ext| !ext) && extractable {
427 return Err(Error::Data(Some(
428 "the ext field of jwk is present and has the value false \
429 and extractable is true"
430 .into(),
431 )));
432 }
433
434 // Step 2.9.
435 // If the d field is present:
436 let (handle, key_type) = if jwk.d.is_some() {
437 // Step 2.9.1. If jwk does not meet the requirements of the JWK private key format
438 // described in Section 2 of [RFC8037], then throw a DataError.
439 let d = jwk.decode_required_string_field(JwkStringField::D)?;
440 let x = jwk.decode_required_string_field(JwkStringField::X)?;
441 let private_key_bytes: [u8; PRIVATE_KEY_LENGTH] =
442 d.as_slice().try_into().map_err(|_| {
443 Error::Data(Some("Invalid length of private key in 'd' field".into()))
444 })?;
445 let public_key_bytes = x.as_slice();
446 let private_key = StaticSecret::from(private_key_bytes);
447 let public_key = PublicKey::from_bytes_unchecked(public_key_bytes).ok_or(
448 Error::Data(Some("Invalid length of private key in 'x' field".into())),
449 )?;
450 if PublicKey::from(&private_key) != public_key {
451 return Err(Error::Data(Some(
452 "Public key in 'x' field does not match private key in 'd' field".into(),
453 )));
454 }
455
456 // Step 2.9.2. Let key be a new CryptoKey object that represents the X448 private
457 // key identified by interpreting jwk according to Section 2 of [RFC8037].
458 // NOTE: CryptoKey is created in Step 2.10 - 2.12.
459 let handle = Handle::X448PrivateKey(private_key);
460
461 // Step 2.9.3. Set the [[type]] internal slot of Key to "private".
462 let key_type = KeyType::Private;
463
464 (handle, key_type)
465 }
466 // Otherwise:
467 else {
468 // Step 2.9.1. If jwk does not meet the requirements of the JWK public key format
469 // described in Section 2 of [RFC8037], then throw a DataError.
470 let x = jwk.decode_required_string_field(JwkStringField::X)?;
471 let public_key_bytes = x.as_slice();
472 let public_key = PublicKey::from_bytes_unchecked(public_key_bytes).ok_or(
473 Error::Data(Some("Invalid length of private key in 'x' field".into())),
474 )?;
475
476 // Step 2.9.2. Let key be a new CryptoKey object that represents the X448 public key
477 // identified by interpreting jwk according to Section 2 of [RFC8037].
478 // NOTE: CryptoKey is created in Step 2.10 - 2.12.
479 let handle = Handle::X448PublicKey(public_key);
480
481 // Step 2.9.3. Set the [[type]] internal slot of Key to "public".
482 let key_type = KeyType::Public;
483
484 (handle, key_type)
485 };
486
487 // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
488 // Step 2.11. Set the name attribute of algorithm to "X448".
489 // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
490 let algorithm = SubtleKeyAlgorithm {
491 name: CryptoAlgorithm::X448,
492 };
493 CryptoKey::new(
494 cx,
495 global,
496 key_type,
497 extractable,
498 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
499 usages,
500 handle,
501 )
502 },
503 // If format is "raw":
504 KeyFormat::Raw | KeyFormat::Raw_public => {
505 // Step 2.1. If usages is not empty then throw a SyntaxError.
506 if !usages.is_empty() {
507 return Err(Error::Syntax(Some("Usages is not empty".into())));
508 }
509
510 // Step 2.2. Let data be keyData.
511 let data = key_data;
512
513 // Step 2.3. If the length in bits of data is not 448 then throw a DataError.
514 if data.len() != 56 {
515 return Err(Error::Data(Some("The key length is not 448 bits".into())));
516 }
517
518 // Step 2.4. Let algorithm be a new KeyAlgorithm object.
519 // Step 2.5. Set the name attribute of algorithm to "X448".
520 let algorithm = SubtleKeyAlgorithm {
521 name: CryptoAlgorithm::X448,
522 };
523
524 // Step 2.6. Let key be a new CryptoKey associated with the relevant global object of
525 // this [HTML], and that represents data.
526 // Step 2.7. Set the [[type]] internal slot of key to "public"
527 // Step 2.8. Set the [[algorithm]] internal slot of key to algorithm.
528 let public_key = PublicKey::from_bytes_unchecked(data).ok_or(Error::Data(Some(
529 "Failed to import public key from raw bytes".into(),
530 )))?;
531 CryptoKey::new(
532 cx,
533 global,
534 KeyType::Public,
535 extractable,
536 KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
537 usages,
538 Handle::X448PublicKey(public_key),
539 )
540 },
541 // Otherwise:
542 _ => {
543 // throw a NotSupportedError.
544 return Err(Error::NotSupported(Some(
545 "Unsupported import key format for X448".into(),
546 )));
547 },
548 };
549
550 // Step 3. Return key
551 Ok(key)
552}
553
554/// <https://wicg.github.io/webcrypto-secure-curves/#x448>
555pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
556 // Step 1. Let key be the CryptoKey to be exported.
557
558 // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
559 // slot of key cannot be accessed, then throw an OperationError.
560 // NOTE: Done in Step 3.
561
562 // Step 3.
563 let result = match format {
564 // If format is "spki":
565 KeyFormat::Spki => {
566 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
567 // InvalidAccessError.
568 if key.Type() != KeyType::Public {
569 return Err(Error::InvalidAccess(Some(
570 "[[type]] internal slot of key is not \"public\"".into(),
571 )));
572 }
573
574 // Step 3.2. Let data be an instance of the subjectPublicKeyInfo ASN.1 structure defined
575 // in [RFC5280] with the following properties:
576 // * Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
577 // properties:
578 // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410].
579 // * Set the subjectPublicKey field to keyData.
580 let Handle::X448PublicKey(public_key) = key.handle() else {
581 return Err(Error::Operation(Some(
582 "[[handle]] internal slot of key is not an X448 public key".into(),
583 )));
584 };
585 let data = SubjectPublicKeyInfoRef {
586 algorithm: AlgorithmIdentifierRef {
587 oid: ObjectIdentifier::new_unwrap(X448_OID_STRING),
588 parameters: None,
589 },
590 subject_public_key: public_key.as_bytes().try_into().map_err(|_| {
591 Error::Data(Some(
592 "Failed to construct the subjectPublicKey field of subjectPublicKeyInfo \
593 ASN.1 structure"
594 .into(),
595 ))
596 })?,
597 };
598
599 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
600 // of this [HTML], and containing data.
601 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
602 ExportedKey::new_bytes(data.to_der().map_err(|_| {
603 Error::Operation(Some(
604 "Failed to encode the subjectPublicKeyInfo ASN.1 structure in DER-encoding"
605 .into(),
606 ))
607 })?)
608 },
609 // If format is "pkcs8":
610 KeyFormat::Pkcs8 => {
611 // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
612 // InvalidAccessError.
613 if key.Type() != KeyType::Private {
614 return Err(Error::InvalidAccess(Some(
615 "[[type]] internal slot of key is not \"private\"".into(),
616 )));
617 }
618
619 // Step 3.2. Let data be an instance of the privateKeyInfo ASN.1 structure defined in
620 // [RFC5208] with the following properties:
621 // * Set the version field to 0.
622 // * Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
623 // with the following properties:
624 // * Set the algorithm object identifier to the id-X448 OID defined in [RFC8410].
625 // * Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1
626 // type, as defined in Section 7 of [RFC8410], that represents the X448 private key
627 // represented by the [[handle]] internal slot of key
628 let Handle::X448PrivateKey(private_key) = key.handle() else {
629 return Err(Error::Operation(Some(
630 "[[handle]] internal slot of key is not an X448 private key".into(),
631 )));
632 };
633 let curve_private_key = OctetStringRef::new(private_key.as_bytes()).map_err(|_| {
634 Error::Operation(Some(
635 "Failed to construct CurvePrivateKey ASN.1 structure".into(),
636 ))
637 })?;
638 let encoded_curve_private_key: Zeroizing<Vec<u8>> = curve_private_key
639 .to_der()
640 .map_err(|_| {
641 Error::Operation(Some(
642 "Failed to encode CurvePrivateKey ASN.1 structure in DER-encoding".into(),
643 ))
644 })?
645 .into();
646 let private_key_field =
647 OctetStringRef::new(&encoded_curve_private_key).map_err(|_| {
648 Error::Operation(Some(
649 "Failed to construct privateKey field of privateKeyInfo ASN.1 structure"
650 .into(),
651 ))
652 })?;
653 let data = PrivateKeyInfoRef {
654 algorithm: AlgorithmIdentifierRef {
655 oid: ObjectIdentifier::new_unwrap(X448_OID_STRING),
656 parameters: None,
657 },
658 private_key: private_key_field,
659 public_key: None,
660 };
661
662 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
663 // of this [HTML], and containing data.
664 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
665 ExportedKey::new_bytes(data.to_der().map_err(|_| {
666 Error::Operation(Some(
667 "Failed to encode privateKeyInfo ASN.1 structure in DER-encoding".into(),
668 ))
669 })?)
670 },
671 // If format is "jwk":
672 KeyFormat::Jwk => {
673 // Step 3.1. Let jwk be a new JsonWebKey dictionary.
674 let mut jwk = JsonWebKey::default();
675
676 // Step 3.2. Set the kty attribute of jwk to "OKP".
677 jwk.kty = Some(DOMString::from("OKP"));
678
679 // Step 3.3. Set the crv attribute of jwk to "X448".
680 jwk.crv = Some(DOMString::from("X448"));
681
682 // Step 3.4. Set the x attribute of jwk according to the definition in Section 2 of
683 // [RFC8037].
684 match key.handle() {
685 Handle::X448PrivateKey(private_key) => {
686 let public_key = PublicKey::from(private_key);
687 jwk.encode_string_field(JwkStringField::X, public_key.as_bytes());
688 },
689 Handle::X448PublicKey(public_key) => {
690 jwk.encode_string_field(JwkStringField::X, public_key.as_bytes());
691 },
692 _ => {
693 return Err(Error::Operation(Some(
694 "[[handle]] internal slot of key is not an X448 key".into(),
695 )));
696 },
697 }
698
699 // Step 3.5. If the [[type]] internal slot of key is "private"
700 // Set the d attribute of jwk according to the definition in Section 2 of [RFC8037].
701 if key.Type() == KeyType::Private {
702 if let Handle::X448PrivateKey(private_key) = key.handle() {
703 jwk.encode_string_field(JwkStringField::D, private_key.as_bytes());
704 } else {
705 return Err(Error::Operation(None));
706 }
707 let Handle::X448PrivateKey(private_key) = key.handle() else {
708 return Err(Error::Operation(Some(
709 "[[handle]] internal slot of key is not an X448 private key".into(),
710 )));
711 };
712 jwk.encode_string_field(JwkStringField::D, private_key.as_bytes().as_slice());
713 }
714
715 // Step 3.6. Set the key_ops attribute of jwk to the usages attribute of key.
716 jwk.set_key_ops(&key.usages());
717
718 // Step 3.7. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
719 jwk.ext = Some(key.Extractable());
720
721 // Step 3.8. Let result be the result of converting jwk to an ECMAScript Object, as
722 // defined by [WebIDL].
723 // NOTE: The conversion to an ECMAScript Object is done by SubtleCrypto::ExportKey.
724 ExportedKey::new_jwk(jwk)
725 },
726 // If format is "raw":
727 KeyFormat::Raw | KeyFormat::Raw_public => {
728 // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
729 // InvalidAccessError.
730 if key.Type() != KeyType::Public {
731 return Err(Error::InvalidAccess(Some(
732 "[[type]] internal slot of key is not \"public\"".into(),
733 )));
734 }
735
736 // Step 3.2. Let data be an octet string representing the X448 public key represented by
737 // the [[handle]] internal slot of key.
738 let Handle::X448PublicKey(public_key) = key.handle() else {
739 return Err(Error::Operation(Some(
740 "[[handle]] internal slot of key is not an X448 public key".into(),
741 )));
742 };
743 let data = public_key.as_bytes();
744
745 // Step 3.3. Let result be a new ArrayBuffer associated with the relevant global object
746 // of this [HTML], and containing data.
747 // NOTE: The conversion to a new ArrayBuffer is done in SubtleCrypto::ExportKey.
748 ExportedKey::new_bytes(data.to_vec())
749 },
750 // Otherwise:
751 _ => {
752 // throw a NotSupportedError.
753 return Err(Error::NotSupported(Some(
754 "Unsupported export key format for X448".into(),
755 )));
756 },
757 };
758
759 // Step 4. Return result.
760 Ok(result)
761}
762
763/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
764/// Step 9 - 15, for X448
765pub(crate) fn get_public_key(
766 cx: &mut JSContext,
767 global: &GlobalScope,
768 key: &CryptoKey,
769 algorithm: &KeyAlgorithmAndDerivatives,
770 usages: Vec<KeyUsage>,
771) -> Result<DomRoot<CryptoKey>, Error> {
772 // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
773 // identified by algorithm, then throw a SyntaxError.
774 //
775 // NOTE: See "importKey" operation for supported usages
776 if !usages.is_empty() {
777 return Err(Error::Syntax(Some("Usages is not empty".to_string())));
778 }
779
780 // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
781 // private key represented by the [[handle]] internal slot of key.
782 // Step 11. If an error occurred, then throw a OperationError.
783 // Step 12. Set the [[type]] internal slot of publicKey to "public".
784 // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
785 // Step 14. Set the [[extractable]] internal slot of publicKey to true.
786 // Step 15. Set the [[usages]] internal slot of publicKey to usages.
787 let Handle::X448PrivateKey(private_key) = key.handle() else {
788 return Err(Error::Operation(Some(
789 "[[handle]] internal slot of key is not an X448 private key".into(),
790 )));
791 };
792 let public_key = CryptoKey::new(
793 cx,
794 global,
795 KeyType::Public,
796 true,
797 algorithm.clone(),
798 usages,
799 Handle::X448PublicKey(PublicKey::from(private_key)),
800 );
801
802 Ok(public_key)
803}