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(None));
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(None))?;
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(None));
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(None));
97    }
98    let key_pair =
99        Ed25519KeyPair::from_seed_unchecked(&seed).map_err(|_| Error::Operation(None))?;
100
101    // Step 3. Let algorithm be a new KeyAlgorithm object.
102    // Step 4. Set the name attribute of algorithm to "Ed25519".
103    let algorithm = SubtleKeyAlgorithm {
104        name: ALG_ED25519.to_string(),
105    };
106
107    // Step 5. Let publicKey be a new CryptoKey representing the public key of the generated key pair.
108    // Step 6. Set the [[type]] internal slot of publicKey to "public"
109    // Step 7. Set the [[algorithm]] internal slot of publicKey to algorithm.
110    // Step 8. Set the [[extractable]] internal slot of publicKey to true.
111    // Step 9. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages
112    // and [ "verify" ].
113    let public_key = CryptoKey::new(
114        global,
115        KeyType::Public,
116        true,
117        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
118        usages
119            .iter()
120            .filter(|&usage| *usage == KeyUsage::Verify)
121            .cloned()
122            .collect(),
123        Handle::Ed25519(key_pair.public_key().as_ref().to_vec()),
124        can_gc,
125    );
126
127    // Step 10. Let privateKey be a new CryptoKey representing the private key of the generated key pair.
128    // Step 11. Set the [[type]] internal slot of privateKey to "private"
129    // Step 12. Set the [[algorithm]] internal slot of privateKey to algorithm.
130    // Step 13. Set the [[extractable]] internal slot of privateKey to extractable.
131    // Step 14. Set the [[usages]] internal slot of privateKey to be the usage intersection of
132    // usages and [ "sign" ].
133    let private_key = CryptoKey::new(
134        global,
135        KeyType::Private,
136        extractable,
137        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
138        usages
139            .iter()
140            .filter(|&usage| *usage == KeyUsage::Sign)
141            .cloned()
142            .collect(),
143        Handle::Ed25519(seed),
144        can_gc,
145    );
146
147    // Step 16. Let result be a new CryptoKeyPair dictionary.
148    // Step 17. Set the publicKey attribute of result to be publicKey.
149    // Step 18. Set the privateKey attribute of result to be privateKey.
150    let result = CryptoKeyPair {
151        publicKey: Some(public_key),
152        privateKey: Some(private_key),
153    };
154
155    // Step 19. Return result.
156    Ok(result)
157}
158
159/// <https://w3c.github.io/webcrypto/#ed25519-operations-import-key>
160pub(crate) fn import_key(
161    global: &GlobalScope,
162    format: KeyFormat,
163    key_data: &[u8],
164    extractable: bool,
165    usages: Vec<KeyUsage>,
166    can_gc: CanGc,
167) -> Result<DomRoot<CryptoKey>, Error> {
168    // Step 1. Let keyData be the key data to be imported.
169    // NOTE: It is given as a method parameter.
170
171    // Step 2.
172    let key = match format {
173        // If format is "spki":
174        KeyFormat::Spki => {
175            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
176            if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
177                return Err(Error::Syntax(None));
178            }
179
180            // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
181            // algorithm over keyData.
182            // Step 2.3. If an error occurred while parsing, then throw a DataError.
183            // Step 2.4. If the algorithm object identifier field of the algorithm
184            // AlgorithmIdentifier field of spki is not equal to the id-Ed25519 object identifier
185            // defined in [RFC8410], then throw a DataError.
186            // Step 2.5. If the parameters field of the algorithm AlgorithmIdentifier field of spki
187            // is present, then throw a DataError.
188            // Step 2.6. Let publicKey be the Ed25519 public key identified by the subjectPublicKey
189            // field of spki.
190            let public_key =
191                ParsedPublicKey::new(&ED25519, key_data).map_err(|_| Error::Data(None))?;
192
193            // Step 2.9. Let algorithm be a new KeyAlgorithm.
194            // Step 2.10. Set the name attribute of algorithm to "Ed25519".
195            let algorithm = SubtleKeyAlgorithm {
196                name: ALG_ED25519.to_string(),
197            };
198
199            // Step 2.7. Let key be a new CryptoKey that represents publicKey.
200            // Step 2.8. Set the [[type]] internal slot of key to "public"
201            // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
202            CryptoKey::new(
203                global,
204                KeyType::Public,
205                extractable,
206                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
207                usages,
208                Handle::Ed25519(public_key.as_ref().to_vec()),
209                can_gc,
210            )
211        },
212        // If format is "pkcs8":
213        KeyFormat::Pkcs8 => {
214            // Step 2.1. If usages contains a value which is not "sign" then throw a SyntaxError.
215            if usages.iter().any(|usage| *usage != KeyUsage::Sign) {
216                return Err(Error::Syntax(None));
217            }
218
219            // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
220            // algorithm over keyData.
221            // Step 2.3. If an error occurs while parsing, then throw a DataError.
222            // Step 2.4. If the algorithm object identifier field of the privateKeyAlgorithm
223            // PrivateKeyAlgorithm field of privateKeyInfo is not equal to the id-Ed25519 object
224            // identifier defined in [RFC8410], then throw a DataError.
225            // Step 2.5. If the parameters field of the privateKeyAlgorithm
226            // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
227            // DataError.
228            let private_key_info =
229                Ed25519KeyPair::from_pkcs8(key_data).map_err(|_| Error::Data(None))?;
230
231            // Step 2.6. Let curvePrivateKey be the result of performing the parse an ASN.1
232            // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
233            // as the ASN.1 CurvePrivateKey structure specified in Section 7 of [RFC8410], and
234            // exactData set to true.
235            // Step 2.7. If an error occurred while parsing, then throw a DataError.
236            let curve_private_key = private_key_info
237                .seed()
238                .map_err(|_| Error::Data(None))?
239                .as_be_bytes()
240                .map_err(|_| Error::Data(None))?
241                .as_ref()
242                .to_vec();
243
244            // Step 2.10. Let algorithm be a new KeyAlgorithm.
245            // Step 2.11. Set the name attribute of algorithm to "Ed25519".
246            let algorithm = SubtleKeyAlgorithm {
247                name: ALG_ED25519.to_string(),
248            };
249
250            // Step 2.8. Let key be a new CryptoKey that represents the Ed25519 private key
251            // identified by curvePrivateKey.
252            // Step 2.9. Set the [[type]] internal slot of key to "private"
253            // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
254            CryptoKey::new(
255                global,
256                KeyType::Private,
257                extractable,
258                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
259                usages,
260                Handle::Ed25519(curve_private_key),
261                can_gc,
262            )
263        },
264        // If format is "jwk":
265        KeyFormat::Jwk => {
266            // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData.
267            // Otherwise: Throw a DataError.
268            let cx = GlobalScope::get_cx();
269            let jwk = JsonWebKey::parse(cx, key_data)?;
270
271            // Step 2.2 If the d field is present and usages contains a value which is not "sign",
272            // or, if the d field is not present and usages contains a value which is not "verify"
273            // then throw a SyntaxError.
274            if (jwk.d.as_ref().is_some() && usages.iter().any(|usage| *usage != KeyUsage::Sign)) ||
275                (jwk.d.as_ref().is_none() &&
276                    usages.iter().any(|usage| *usage != KeyUsage::Verify))
277            {
278                return Err(Error::Syntax(None));
279            }
280
281            // Step 2.3 If the kty field of jwk is not "OKP", then throw a DataError.
282            if jwk.kty.as_ref().is_none_or(|kty| kty != "OKP") {
283                return Err(Error::Data(None));
284            }
285
286            // Step 2.4 If the crv field of jwk is not "Ed25519", then throw a DataError.
287            if jwk.crv.as_ref().is_none_or(|crv| crv != ALG_ED25519) {
288                return Err(Error::Data(None));
289            }
290
291            // Step 2.5 If the alg field of jwk is present and is not "Ed25519" or "EdDSA", then
292            // throw a DataError.
293            if jwk
294                .alg
295                .as_ref()
296                .is_some_and(|alg| !matches!(alg.str().as_ref(), ALG_ED25519 | "EdDSA"))
297            {
298                return Err(Error::Data(None));
299            }
300
301            // Step 2.6 If usages is non-empty and the use field of jwk is present and is not
302            // "sig", then throw a DataError.
303            if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") {
304                return Err(Error::Data(None));
305            }
306
307            // Step 2.7 If the key_ops field of jwk is present, and is invalid according to the
308            // requirements of JSON Web Key [JWK], or it does not contain all of the specified
309            // usages values, then throw a DataError.
310            jwk.check_key_ops(&usages)?;
311
312            // Step 2.8 If the ext field of jwk is present and has the value false and extractable
313            // is true, then throw a DataError.
314            if jwk.ext.as_ref().is_some_and(|ext| !ext) && extractable {
315                return Err(Error::Data(None));
316            }
317
318            // Step 2.10. Let algorithm be a new instance of a KeyAlgorithm object.
319            // Step 2.11. Set the name attribute of algorithm to "Ed25519".
320            let algorithm = SubtleKeyAlgorithm {
321                name: ALG_ED25519.to_string(),
322            };
323
324            // Step 2.9
325            match jwk.d {
326                // If the d field is present:
327                Some(d) => {
328                    // Step 2.9.1. If jwk does not meet the requirements of the JWK private key
329                    // format described in Section 2 of [RFC8037], then throw a DataError.
330                    let x = jwk.x.ok_or(Error::Data(None))?;
331
332                    // Step 2.9.2. Let key be a new CryptoKey object that represents the Ed25519
333                    // private key identified by interpreting jwk according to Section
334                    // 2 of [RFC8037]
335                    // Step 2.9.3. Set the [[type]] internal slot of Key to "private".
336                    let public_key_bytes =
337                        Base64UrlUnpadded::decode_vec(&x.str()).map_err(|_| Error::Data(None))?;
338                    let private_key_bytes =
339                        Base64UrlUnpadded::decode_vec(&d.str()).map_err(|_| Error::Data(None))?;
340                    let _ = Ed25519KeyPair::from_seed_and_public_key(
341                        &private_key_bytes,
342                        &public_key_bytes,
343                    )
344                    .map_err(|_| Error::Data(None))?;
345                    CryptoKey::new(
346                        global,
347                        KeyType::Private,
348                        extractable,
349                        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
350                        usages,
351                        Handle::Ed25519(private_key_bytes),
352                        can_gc,
353                    )
354                },
355                // Otherwise:
356                None => {
357                    // Step 2.9.1. If jwk does not meet the requirements of the JWK public key
358                    // format described in Section 2 of [RFC8037], then throw a DataError.
359                    let x = jwk.x.ok_or(Error::Data(None))?;
360
361                    // Step 2.9.2. Let key be a new CryptoKey object that represents the Ed25519
362                    // public key identified by interpreting jwk according to Section 2 of
363                    // [RFC8037].
364                    // Step 2.9.3. Set the [[type]] internal slot of Key to "public".
365                    let public_key_bytes =
366                        Base64UrlUnpadded::decode_vec(&x.str()).map_err(|_| Error::Data(None))?;
367                    CryptoKey::new(
368                        global,
369                        KeyType::Public,
370                        extractable,
371                        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
372                        usages,
373                        Handle::Ed25519(public_key_bytes),
374                        can_gc,
375                    )
376                },
377            }
378
379            // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
380            // NOTE: Done in Step 2.9
381        },
382        // If format is "raw":
383        KeyFormat::Raw | KeyFormat::Raw_public => {
384            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
385            if usages.iter().any(|usage| *usage != KeyUsage::Verify) {
386                return Err(Error::Syntax(None));
387            }
388
389            // Step 2.2. If the length in bits of keyData is not 256 then throw a DataError.
390            if key_data.len() * 8 != 256 {
391                return Err(Error::Data(None));
392            }
393
394            // Step 2.3. Let algorithm be a new KeyAlgorithm object.
395            // Step 2.4. Set the name attribute of algorithm to "Ed25519".
396            let algorithm = SubtleKeyAlgorithm {
397                name: ALG_ED25519.to_string(),
398            };
399
400            // Step 2.5. Let key be a new CryptoKey representing the key data provided in keyData.
401            // Step 2.6. Set the [[type]] internal slot of key to "public"
402            // Step 2.7. Set the [[algorithm]] internal slot of key to algorithm.
403            CryptoKey::new(
404                global,
405                KeyType::Public,
406                extractable,
407                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
408                usages,
409                Handle::Ed25519(key_data.to_vec()),
410                can_gc,
411            )
412        },
413        // Otherwise:
414        _ => {
415            // throw a NotSupportedError.
416            return Err(Error::NotSupported(None));
417        },
418    };
419
420    // Step 3. Return key
421    Ok(key)
422}
423
424/// <https://w3c.github.io/webcrypto/#ed25519-operations-export-key>
425pub(crate) fn export_key(format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
426    // Step 1. Let key be the CryptoKey to be exported.
427    // NOTE: It is given as a method parameter.
428
429    // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
430    // slot of key cannot be accessed, then throw an OperationError.
431    // NOTE: key.handle() guarantees access.
432    let key_data = key.handle().as_bytes();
433
434    // Step 3.
435    let result = match format {
436        // If format is "spki":
437        KeyFormat::Spki => {
438            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
439            // InvalidAccessError.
440            if key.Type() != KeyType::Public {
441                return Err(Error::InvalidAccess(None));
442            }
443
444            // Step 3.2. Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure
445            // defined in [RFC5280] with the following properties:
446            //     Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
447            //     properties:
448            //         Set the algorithm object identifier to the id-Ed25519 OID defined in
449            //         [RFC8410].
450            //     Set the subjectPublicKey field to keyData.
451            let data =
452                ParsedPublicKey::new(&ED25519, key_data).map_err(|_| Error::Operation(None))?;
453
454            // Step 3.3. Let result be the result of DER-encoding data.
455            ExportedKey::Bytes(
456                data.as_der()
457                    .map_err(|_| Error::Operation(None))?
458                    .as_ref()
459                    .to_vec(),
460            )
461        },
462        // If format is "pkcs8":
463        KeyFormat::Pkcs8 => {
464            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
465            // InvalidAccessError.
466            if key.Type() != KeyType::Private {
467                return Err(Error::InvalidAccess(None));
468            }
469
470            // Step 3.2. Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in
471            // [RFC5208] with the following properties:
472            //     Set the version field to 0.
473            //     Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
474            //     with the following properties:
475            //         Set the algorithm object identifier to the id-Ed25519 OID defined in
476            //         [RFC8410].
477            //     Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1
478            //     type, as defined in Section 7 of [RFC8410], that represents the Ed25519 private
479            //     key represented by the [[handle]] internal slot of key
480            let data = Ed25519KeyPair::from_seed_unchecked(key_data)
481                .map_err(|_| Error::Operation(None))?
482                .to_pkcs8v1()
483                .map_err(|_| Error::Operation(None))?;
484
485            // Step 3.3. Let result be the result of DER-encoding data.
486            ExportedKey::Bytes(data.as_ref().to_vec())
487        },
488        // If format is "jwk":
489        KeyFormat::Jwk => {
490            // Step 3.5. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037].
491            // Step 3.6.
492            // If the [[type]] internal slot of key is "private"
493            //     Set the d attribute of jwk according to the definition in Section 2 of [RFC8037].
494            let (x, d) = match key.Type() {
495                KeyType::Public => {
496                    let public_key = Base64UrlUnpadded::encode_string(key_data);
497                    (Some(DOMString::from(public_key)), None)
498                },
499                KeyType::Private => {
500                    let key_pair = Ed25519KeyPair::from_seed_unchecked(key_data)
501                        .map_err(|_| Error::Data(None))?;
502                    let public_key =
503                        Base64UrlUnpadded::encode_string(key_pair.public_key().as_ref());
504                    let private_key = Base64UrlUnpadded::encode_string(key_data);
505                    (
506                        Some(DOMString::from(public_key)),
507                        Some(DOMString::from(private_key)),
508                    )
509                },
510                KeyType::Secret => {
511                    return Err(Error::Data(None));
512                },
513            };
514
515            // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
516            let key_ops = Some(
517                key.usages()
518                    .iter()
519                    .map(|usage| DOMString::from(usage.as_str()))
520                    .collect::<Vec<DOMString>>(),
521            );
522
523            // Step 3.1. Let jwk be a new JsonWebKey dictionary.
524            // Step 3.2. Set the kty attribute of jwk to "OKP".
525            // Step 3.3. Set the alg attribute of jwk to "Ed25519".
526            // Step 3.4. Set the crv attribute of jwk to "Ed25519".
527            // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
528            let jwk = JsonWebKey {
529                kty: Some(DOMString::from("OKP")),
530                alg: Some(DOMString::from(ALG_ED25519)),
531                crv: Some(DOMString::from(ALG_ED25519)),
532                x,
533                d,
534                key_ops,
535                ext: Some(key.Extractable()),
536                ..Default::default()
537            };
538
539            // Step 9. Let result be jwk.
540            ExportedKey::Jwk(Box::new(jwk))
541        },
542        // If format is "raw":
543        KeyFormat::Raw | KeyFormat::Raw_public => {
544            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
545            // InvalidAccessError.
546            if key.Type() != KeyType::Public {
547                return Err(Error::InvalidAccess(None));
548            }
549
550            // Step 3.2. Let data be a byte sequence representing the Ed25519 public key
551            // represented by the [[handle]] internal slot of key.
552            // Step 3.3. Let result be data.
553            ExportedKey::Bytes(key_data.to_vec())
554        },
555        // Otherwise:
556        _ => {
557            // throw a NotSupportedError.
558            return Err(Error::NotSupported(None));
559        },
560    };
561
562    // Step 4. Return result.
563    Ok(result)
564}