Skip to main content

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