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}