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}