script/dom/subtlecrypto/
ed25519_operation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use aws_lc_rs::encoding::{AsBigEndian, AsDer};
6use aws_lc_rs::signature::{ED25519, Ed25519KeyPair, KeyPair, ParsedPublicKey, UnparsedPublicKey};
7use base64ct::{Base64UrlUnpadded, Encoding};
8use rand::TryRngCore;
9use rand::rngs::OsRng;
10
11use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
12    CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
13};
14use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
15use crate::dom::bindings::error::Error;
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::bindings::str::DOMString;
18use crate::dom::cryptokey::{CryptoKey, Handle};
19use crate::dom::globalscope::GlobalScope;
20use crate::dom::subtlecrypto::{
21    ALG_ED25519, ExportedKey, JsonWebKeyExt, KeyAlgorithmAndDerivatives, SubtleKeyAlgorithm,
22};
23use crate::script_runtime::CanGc;
24
25const ED25519_SEED_LENGTH: usize = 32;
26
27/// <https://w3c.github.io/webcrypto/#ed25519-operations-sign>
28pub(crate) fn sign(key: &CryptoKey, message: &[u8]) -> Result<Vec<u8>, Error> {
29    // Step 1. If the [[type]] internal slot of key is not "private", then throw an
30    // InvalidAccessError.
31    if key.Type() != KeyType::Private {
32        return Err(Error::InvalidAccess);
33    }
34
35    // Step 2. Let result be the result of performing the Ed25519 signing process, as specified in
36    // [RFC8032], Section 5.1.6, with message as M, using the Ed25519 private key associated with
37    // key.
38    let key_pair = Ed25519KeyPair::from_seed_unchecked(key.handle().as_bytes())
39        .map_err(|_| Error::Operation)?;
40    let result = key_pair.sign(message).as_ref().to_vec();
41
42    // Step 3. Return result.
43    Ok(result)
44}
45
46/// <https://w3c.github.io/webcrypto/#ed25519-operations-verify>
47pub(crate) fn verify(key: &CryptoKey, message: &[u8], signature: &[u8]) -> Result<bool, Error> {
48    // Step 1. If the [[type]] internal slot of key is not "public", then throw an
49    // InvalidAccessError.
50    if key.Type() != KeyType::Public {
51        return Err(Error::InvalidAccess);
52    }
53
54    // Step 2. If the key data of key represents an invalid point or a small-order element on the
55    // Elliptic Curve of Ed25519, return false.
56    // NOTE: Not all implementations perform this check. See WICG/webcrypto-secure-curves issue 27.
57
58    // Step 3. If the point R, encoded in the first half of signature, represents an invalid point
59    // or a small-order element on the Elliptic Curve of Ed25519, return false.
60    // NOTE: Not all implementations perform this check. See WICG/webcrypto-secure-curves issue 27.
61
62    // Step 4. Perform the Ed25519 verification steps, as specified in [RFC8032], Section
63    // 5.1.7, using the cofactorless (unbatched) equation, [S]B = R + [k]A', on the signature, with
64    // message as M, using the Ed25519 public key associated with key.
65    // Step 5. Let result be a boolean with the value true if the signature is valid and the value
66    // false otherwise.
67    let public_key = UnparsedPublicKey::new(&ED25519, key.handle().as_bytes());
68    let result = match public_key.verify(message, signature) {
69        Ok(()) => true,
70        Err(aws_lc_rs::error::Unspecified) => false,
71    };
72
73    // Step 6. Return result.
74    Ok(result)
75}
76
77/// <https://w3c.github.io/webcrypto/#ed25519-operations-generate-key>
78pub(crate) fn generate_key(
79    global: &GlobalScope,
80    extractable: bool,
81    usages: Vec<KeyUsage>,
82    can_gc: CanGc,
83) -> Result<CryptoKeyPair, Error> {
84    // Step 1. If usages contains any entry which is not "sign" or "verify", then throw a
85    // SyntaxError.
86    if usages
87        .iter()
88        .any(|usage| !matches!(usage, KeyUsage::Sign | KeyUsage::Verify))
89    {
90        return Err(Error::Syntax(None));
91    }
92
93    // Step 2. Generate an Ed25519 key pair, as defined in [RFC8032], section 5.1.5.
94    let mut seed = vec![0u8; ED25519_SEED_LENGTH];
95    if OsRng.try_fill_bytes(&mut seed).is_err() {
96        return Err(Error::Operation);
97    }
98    let key_pair = Ed25519KeyPair::from_seed_unchecked(&seed).map_err(|_| Error::Operation)?;
99
100    // Step 3. Let algorithm be a new KeyAlgorithm object.
101    // Step 4. Set the name attribute of algorithm to "Ed25519".
102    let algorithm = SubtleKeyAlgorithm {
103        name: ALG_ED25519.to_string(),
104    };
105
106    // Step 5. Let publicKey be a new CryptoKey representing the public key of the generated key pair.
107    // Step 6. Set the [[type]] internal slot of publicKey to "public"
108    // Step 7. Set the [[algorithm]] internal slot of publicKey to algorithm.
109    // Step 8. Set the [[extractable]] internal slot of publicKey to true.
110    // Step 9. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages
111    // and [ "verify" ].
112    let public_key = CryptoKey::new(
113        global,
114        KeyType::Public,
115        true,
116        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
117        usages
118            .iter()
119            .filter(|&usage| *usage == KeyUsage::Verify)
120            .cloned()
121            .collect(),
122        Handle::Ed25519(key_pair.public_key().as_ref().to_vec()),
123        can_gc,
124    );
125
126    // Step 10. Let privateKey be a new CryptoKey representing the private key of the generated key pair.
127    // Step 11. Set the [[type]] internal slot of privateKey to "private"
128    // Step 12. Set the [[algorithm]] internal slot of privateKey to algorithm.
129    // Step 13. Set the [[extractable]] internal slot of privateKey to extractable.
130    // Step 14. Set the [[usages]] internal slot of privateKey to be the usage intersection of
131    // usages and [ "sign" ].
132    let private_key = CryptoKey::new(
133        global,
134        KeyType::Private,
135        extractable,
136        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
137        usages
138            .iter()
139            .filter(|&usage| *usage == KeyUsage::Sign)
140            .cloned()
141            .collect(),
142        Handle::Ed25519(seed),
143        can_gc,
144    );
145
146    // Step 16. Let result be a new CryptoKeyPair dictionary.
147    // Step 17. Set the publicKey attribute of result to be publicKey.
148    // Step 18. Set the privateKey attribute of result to be privateKey.
149    let result = CryptoKeyPair {
150        publicKey: Some(public_key),
151        privateKey: Some(private_key),
152    };
153
154    // Step 19. Return result.
155    Ok(result)
156}
157
158/// <https://w3c.github.io/webcrypto/#ed25519-operations-import-key>
159pub(crate) fn import_key(
160    global: &GlobalScope,
161    format: KeyFormat,
162    key_data: &[u8],
163    extractable: bool,
164    usages: Vec<KeyUsage>,
165    can_gc: CanGc,
166) -> Result<DomRoot<CryptoKey>, Error> {
167    // Step 1. Let keyData be the key data to be imported.
168    // NOTE: It is given as a method parameter.
169
170    // Step 2.
171    let key = match format {
172        // If format is "spki":
173        KeyFormat::Spki => {
174            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
175            if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
176                return Err(Error::Syntax(None));
177            }
178
179            // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
180            // algorithm over keyData.
181            // Step 2.3. If an error occurred while parsing, then throw a DataError.
182            // Step 2.4. If the algorithm object identifier field of the algorithm
183            // AlgorithmIdentifier field of spki is not equal to the id-Ed25519 object identifier
184            // defined in [RFC8410], then throw a DataError.
185            // Step 2.5. If the parameters field of the algorithm AlgorithmIdentifier field of spki
186            // is present, then throw a DataError.
187            // Step 2.6. Let publicKey be the Ed25519 public key identified by the subjectPublicKey
188            // field of spki.
189            let public_key = ParsedPublicKey::new(&ED25519, key_data).map_err(|_| Error::Data)?;
190
191            // Step 2.9. Let algorithm be a new KeyAlgorithm.
192            // Step 2.10. Set the name attribute of algorithm to "Ed25519".
193            let algorithm = SubtleKeyAlgorithm {
194                name: ALG_ED25519.to_string(),
195            };
196
197            // Step 2.7. Let key be a new CryptoKey that represents publicKey.
198            // Step 2.8. Set the [[type]] internal slot of key to "public"
199            // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
200            CryptoKey::new(
201                global,
202                KeyType::Public,
203                extractable,
204                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
205                usages,
206                Handle::Ed25519(public_key.as_ref().to_vec()),
207                can_gc,
208            )
209        },
210        // If format is "pkcs8":
211        KeyFormat::Pkcs8 => {
212            // Step 2.1. If usages contains a value which is not "sign" then throw a SyntaxError.
213            if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
214                return Err(Error::Syntax(None));
215            }
216
217            // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
218            // algorithm over keyData.
219            // Step 2.3. If an error occurs while parsing, then throw a DataError.
220            // Step 2.4. If the algorithm object identifier field of the privateKeyAlgorithm
221            // PrivateKeyAlgorithm field of privateKeyInfo is not equal to the id-Ed25519 object
222            // identifier defined in [RFC8410], then throw a DataError.
223            // Step 2.5. If the parameters field of the privateKeyAlgorithm
224            // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
225            // DataError.
226            let private_key_info = Ed25519KeyPair::from_pkcs8(key_data).map_err(|_| Error::Data)?;
227
228            // Step 2.6. Let curvePrivateKey be the result of performing the parse an ASN.1
229            // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
230            // as the ASN.1 CurvePrivateKey structure specified in Section 7 of [RFC8410], and
231            // exactData set to true.
232            // Step 2.7. If an error occurred while parsing, then throw a DataError.
233            let curve_private_key = private_key_info
234                .seed()
235                .map_err(|_| Error::Data)?
236                .as_be_bytes()
237                .map_err(|_| Error::Data)?
238                .as_ref()
239                .to_vec();
240
241            // Step 2.10. Let algorithm be a new KeyAlgorithm.
242            // Step 2.11. Set the name attribute of algorithm to "Ed25519".
243            let algorithm = SubtleKeyAlgorithm {
244                name: ALG_ED25519.to_string(),
245            };
246
247            // Step 2.8. Let key be a new CryptoKey that represents the Ed25519 private key
248            // identified by curvePrivateKey.
249            // Step 2.9. Set the [[type]] internal slot of key to "private"
250            // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
251            CryptoKey::new(
252                global,
253                KeyType::Private,
254                extractable,
255                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
256                usages,
257                Handle::Ed25519(curve_private_key),
258                can_gc,
259            )
260        },
261        // If format is "jwk":
262        KeyFormat::Jwk => {
263            // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData.
264            // Otherwise: Throw a DataError.
265            let cx = GlobalScope::get_cx();
266            let jwk = JsonWebKey::parse(cx, key_data)?;
267
268            // Step 2.2 If the d field is present and usages contains a value which is not "sign",
269            // or, if the d field is not present and usages contains a value which is not "verify"
270            // then throw a SyntaxError.
271            if (jwk.d.as_ref().is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign)) ||
272                (jwk.d.as_ref().is_none() &&
273                    usages.iter().any(|usage| *usage != KeyUsage::Verify))
274            {
275                return Err(Error::Syntax(None));
276            }
277
278            // Step 2.3 If the kty field of jwk is not "OKP", then throw a DataError.
279            if jwk.kty.as_ref().is_none_or(|kty| kty != "OKP") {
280                return Err(Error::Data);
281            }
282
283            // Step 2.4 If the crv field of jwk is not "Ed25519", then throw a DataError.
284            if jwk.crv.as_ref().is_none_or(|crv| crv != ALG_ED25519) {
285                return Err(Error::Data);
286            }
287
288            // Step 2.5 If the alg field of jwk is present and is not "Ed25519" or "EdDSA", then
289            // throw a DataError.
290            if jwk
291                .alg
292                .as_ref()
293                .is_some_and(|alg| !matches!(alg.str().as_ref(), ALG_ED25519 | "EdDSA"))
294            {
295                return Err(Error::Data);
296            }
297
298            // Step 2.6 If usages is non-empty and the use field of jwk is present and is not
299            // "sig", then throw a DataError.
300            if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
301                return Err(Error::Data);
302            }
303
304            // Step 2.7 If the key_ops field of jwk is present, and is invalid according to the
305            // requirements of JSON Web Key [JWK], or it does not contain all of the specified
306            // usages values, then throw a DataError.
307            jwk.check_key_ops(&usages)?;
308
309            // Step 2.8 If the ext field of jwk is present and has the value false and extractable
310            // is true, then throw a DataError.
311            if jwk.ext.as_ref().is_some_and(|ext| !ext) && extractable {
312                return Err(Error::Data);
313            }
314
315            // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
316            // Step 2.11. Set the name attribute of algorithm to "Ed25519".
317            let algorithm = SubtleKeyAlgorithm {
318                name: ALG_ED25519.to_string(),
319            };
320
321            // Step 2.9
322            match jwk.d {
323                // If the d field is present:
324                Some(d) => {
325                    // Step 2.9.1. If jwk does not meet the requirements of the JWK private key
326                    // format described in Section 2 of [RFC8037], then throw a DataError.
327                    let x = jwk.x.ok_or(Error::Data)?;
328
329                    // Step 2.9.2. Let key be a new CryptoKey object that represents the Ed25519
330                    // private key identified by interpreting jwk according to Section
331                    // 2 of [RFC8037]
332                    // Step 2.9.3. Set the [[type]] internal slot of Key to "private".
333                    let public_key_bytes =
334                        Base64UrlUnpadded::decode_vec(&x.str()).map_err(|_| Error::Data)?;
335                    let private_key_bytes =
336                        Base64UrlUnpadded::decode_vec(&d.str()).map_err(|_| Error::Data)?;
337                    let _ = Ed25519KeyPair::from_seed_and_public_key(
338                        &private_key_bytes,
339                        &public_key_bytes,
340                    )
341                    .map_err(|_| Error::Data)?;
342                    CryptoKey::new(
343                        global,
344                        KeyType::Private,
345                        extractable,
346                        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
347                        usages,
348                        Handle::Ed25519(private_key_bytes),
349                        can_gc,
350                    )
351                },
352                // Otherwise:
353                None => {
354                    // Step 2.9.1. If jwk does not meet the requirements of the JWK public key
355                    // format described in Section 2 of [RFC8037], then throw a DataError.
356                    let x = jwk.x.ok_or(Error::Data)?;
357
358                    // Step 2.9.2. Let key be a new CryptoKey object that represents the Ed25519
359                    // public key identified by interpreting jwk according to Section 2 of
360                    // [RFC8037].
361                    // Step 2.9.3. Set the [[type]] internal slot of Key to "public".
362                    let public_key_bytes =
363                        Base64UrlUnpadded::decode_vec(&x.str()).map_err(|_| Error::Data)?;
364                    CryptoKey::new(
365                        global,
366                        KeyType::Public,
367                        extractable,
368                        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
369                        usages,
370                        Handle::Ed25519(public_key_bytes),
371                        can_gc,
372                    )
373                },
374            }
375
376            // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
377            // NOTE: Done in Step 2.9
378        },
379        // If format is "raw":
380        KeyFormat::Raw => {
381            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
382            if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
383                return Err(Error::Syntax(None));
384            }
385
386            // Step 2.2. If the length in bits of keyData is not 256 then throw a DataError.
387            if key_data.len() * 8 != 256 {
388                return Err(Error::Data);
389            }
390
391            // Step 2.3. Let algorithm be a new KeyAlgorithm object.
392            // Step 2.4. Set the name attribute of algorithm to "Ed25519".
393            let algorithm = SubtleKeyAlgorithm {
394                name: ALG_ED25519.to_string(),
395            };
396
397            // Step 2.5. Let key be a new CryptoKey representing the key data provided in keyData.
398            // Step 2.6. Set the [[type]] internal slot of key to "public"
399            // Step 2.7. Set the [[algorithm]] internal slot of key to algorithm.
400            CryptoKey::new(
401                global,
402                KeyType::Public,
403                extractable,
404                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
405                usages,
406                Handle::Ed25519(key_data.to_vec()),
407                can_gc,
408            )
409        },
410        // Otherwise: throw a NotSupportedError. (Unreachable)
411    };
412
413    // Step 3. Return key
414    Ok(key)
415}
416
417/// <https://w3c.github.io/webcrypto/#ed25519-operations-export-key>
418pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
419    // Step 1. Let key be the CryptoKey to be exported.
420    // NOTE: It is given as a method parameter.
421
422    // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
423    // slot of key cannot be accessed, then throw an OperationError.
424    // NOTE: key.handle() guarantees access.
425    let key_data = key.handle().as_bytes();
426
427    // Step 3.
428    let result = match format {
429        // If format is "spki":
430        KeyFormat::Spki => {
431            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
432            // InvalidAccessError.
433            if key.Type() != KeyType::Public {
434                return Err(Error::InvalidAccess);
435            }
436
437            // Step 3.2. Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure
438            // defined in [RFC5280] with the following properties:
439            //     Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
440            //     properties:
441            //         Set the algorithm object identifier to the id-Ed25519 OID defined in
442            //         [RFC8410].
443            //     Set the subjectPublicKey field to keyData.
444            let data = ParsedPublicKey::new(&ED25519, key_data).map_err(|_| Error::Operation)?;
445
446            // Step 3.3. Let result be the result of DER-encoding data.
447            ExportedKey::Raw(
448                data.as_der()
449                    .map_err(|_| Error::Operation)?
450                    .as_ref()
451                    .to_vec(),
452            )
453        },
454        // If format is "pkcs8":
455        KeyFormat::Pkcs8 => {
456            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
457            // InvalidAccessError.
458            if key.Type() != KeyType::Private {
459                return Err(Error::InvalidAccess);
460            }
461
462            // Step 3.2. Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in
463            // [RFC5208] with the following properties:
464            //     Set the version field to 0.
465            //     Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
466            //     with the following properties:
467            //         Set the algorithm object identifier to the id-Ed25519 OID defined in
468            //         [RFC8410].
469            //     Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1
470            //     type, as defined in Section 7 of [RFC8410], that represents the Ed25519 private
471            //     key represented by the [[handle]] internal slot of key
472            let data = Ed25519KeyPair::from_seed_unchecked(key_data)
473                .map_err(|_| Error::Operation)?
474                .to_pkcs8v1()
475                .map_err(|_| Error::Operation)?;
476
477            // Step 3.3. Let result be the result of DER-encoding data.
478            ExportedKey::Raw(data.as_ref().to_vec())
479        },
480        // If format is "jwk":
481        KeyFormat::Jwk => {
482            // Step 3.5. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037].
483            // Step 3.6.
484            // If the [[type]] internal slot of key is "private"
485            //     Set the d attribute of jwk according to the definition in Section 2 of [RFC8037].
486            let (x, d) = match key.Type() {
487                KeyType::Public => {
488                    let public_key = Base64UrlUnpadded::encode_string(key_data);
489                    (Some(DOMString::from(public_key)), None)
490                },
491                KeyType::Private => {
492                    let key_pair =
493                        Ed25519KeyPair::from_seed_unchecked(key_data).map_err(|_| Error::Data)?;
494                    let public_key =
495                        Base64UrlUnpadded::encode_string(key_pair.public_key().as_ref());
496                    let private_key = Base64UrlUnpadded::encode_string(key_data);
497                    (
498                        Some(DOMString::from(public_key)),
499                        Some(DOMString::from(private_key)),
500                    )
501                },
502                KeyType::Secret => {
503                    return Err(Error::Data);
504                },
505            };
506
507            // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
508            let key_ops = Some(
509                key.usages()
510                    .iter()
511                    .map(|usage| DOMString::from(usage.as_str()))
512                    .collect::<Vec<DOMString>>(),
513            );
514
515            // Step 3.1. Let jwk be a new JsonWebKey dictionary.
516            // Step 3.2. Set the kty attribute of jwk to "OKP".
517            // Step 3.3. Set the alg attribute of jwk to "Ed25519".
518            // Step 3.4. Set the crv attribute of jwk to "Ed25519".
519            // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
520            let jwk = JsonWebKey {
521                kty: Some(DOMString::from("OKP")),
522                alg: Some(DOMString::from(ALG_ED25519)),
523                crv: Some(DOMString::from(ALG_ED25519)),
524                x,
525                d,
526                key_ops,
527                ext: Some(key.Extractable()),
528                ..Default::default()
529            };
530
531            // Step 9. Let result be jwk.
532            ExportedKey::Jwk(Box::new(jwk))
533        },
534        // If format is "raw":
535        KeyFormat::Raw => {
536            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
537            // InvalidAccessError.
538            if key.Type() != KeyType::Public {
539                return Err(Error::InvalidAccess);
540            }
541
542            // Step 3.2. Let data be a byte sequence representing the Ed25519 public key
543            // represented by the [[handle]] internal slot of key.
544            // Step 3.3. Let result be data.
545            ExportedKey::Raw(key_data.to_vec())
546        },
547        // Otherwise: throw a NotSupportedError. (Unreachable)
548    };
549
550    // Step 4. Return result.
551    Ok(result)
552}