Skip to main content

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