uselesskey_ecdsa/keypair.rs
1use std::fmt;
2use std::sync::Arc;
3
4use elliptic_curve::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
5use rand_chacha::ChaCha20Rng;
6use rand_core::SeedableRng;
7use uselesskey_core::negative::CorruptPem;
8use uselesskey_core::sink::TempArtifact;
9use uselesskey_core::{Error, Factory};
10use uselesskey_core_keypair_material::Pkcs8SpkiKeyMaterial;
11
12use crate::EcdsaSpec;
13
14/// Cache domain for ECDSA keypair fixtures.
15///
16/// Keep this stable: changing it changes deterministic outputs.
17pub const DOMAIN_ECDSA_KEYPAIR: &str = "uselesskey:ecdsa:keypair";
18
19/// An ECDSA keypair fixture with various output formats.
20///
21/// Created via [`EcdsaFactoryExt::ecdsa()`]. Provides access to:
22/// - Private key in PKCS#8 PEM and DER formats
23/// - Public key in SPKI PEM and DER formats
24/// - Negative fixtures (corrupted PEM, truncated DER, mismatched keys)
25/// - JWK output (with the `jwk` feature)
26///
27/// # Examples
28///
29/// ```
30/// use uselesskey_core::Factory;
31/// use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
32///
33/// let fx = Factory::random();
34/// let keypair = fx.ecdsa("my-service", EcdsaSpec::es256());
35///
36/// let private_pem = keypair.private_key_pkcs8_pem();
37/// let public_der = keypair.public_key_spki_der();
38///
39/// assert!(private_pem.contains("BEGIN PRIVATE KEY"));
40/// assert!(!public_der.is_empty());
41/// ```
42#[derive(Clone)]
43pub struct EcdsaKeyPair {
44 factory: Factory,
45 label: String,
46 spec: EcdsaSpec,
47 inner: Arc<Inner>,
48}
49
50/// Inner storage for computed key material.
51struct Inner {
52 /// Kept for potential use; not currently read outside JWK feature.
53 #[allow(dead_code)]
54 spec: EcdsaSpec,
55 material: Pkcs8SpkiKeyMaterial,
56 /// Raw public key bytes (uncompressed point, for JWK).
57 #[cfg_attr(not(feature = "jwk"), allow(dead_code))]
58 public_key_bytes: Vec<u8>,
59 /// Raw private scalar bytes (for private JWK).
60 #[cfg_attr(not(feature = "jwk"), allow(dead_code))]
61 private_key_bytes: Vec<u8>,
62}
63
64impl fmt::Debug for EcdsaKeyPair {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.debug_struct("EcdsaKeyPair")
67 .field("label", &self.label)
68 .field("spec", &self.spec)
69 .finish_non_exhaustive()
70 }
71}
72
73/// Extension trait to hang ECDSA helpers off the core [`Factory`].
74pub trait EcdsaFactoryExt {
75 /// Generate (or retrieve from cache) an ECDSA keypair fixture.
76 ///
77 /// The `label` identifies this keypair within your test suite.
78 /// In deterministic mode, `seed + label + spec` always produces the same key.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use uselesskey_core::{Factory, Seed};
84 /// use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
85 ///
86 /// let seed = Seed::from_env_value("test-seed").unwrap();
87 /// let fx = Factory::deterministic(seed);
88 /// let keypair = fx.ecdsa("auth-service", EcdsaSpec::es256());
89 ///
90 /// let pem = keypair.private_key_pkcs8_pem();
91 /// assert!(pem.contains("BEGIN PRIVATE KEY"));
92 /// ```
93 fn ecdsa(&self, label: impl AsRef<str>, spec: EcdsaSpec) -> EcdsaKeyPair;
94}
95
96impl EcdsaFactoryExt for Factory {
97 fn ecdsa(&self, label: impl AsRef<str>, spec: EcdsaSpec) -> EcdsaKeyPair {
98 EcdsaKeyPair::new(self.clone(), label.as_ref(), spec)
99 }
100}
101
102impl EcdsaKeyPair {
103 fn new(factory: Factory, label: &str, spec: EcdsaSpec) -> Self {
104 let inner = load_inner(&factory, label, spec, "good");
105 Self {
106 factory,
107 label: label.to_string(),
108 spec,
109 inner,
110 }
111 }
112
113 fn load_variant(&self, variant: &str) -> Arc<Inner> {
114 load_inner(&self.factory, &self.label, self.spec, variant)
115 }
116
117 /// Returns the spec used to create this keypair.
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// # use uselesskey_core::Factory;
123 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
124 /// let fx = Factory::random();
125 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
126 /// assert_eq!(kp.spec(), EcdsaSpec::es256());
127 /// ```
128 pub fn spec(&self) -> EcdsaSpec {
129 self.spec
130 }
131
132 /// PKCS#8 DER-encoded private key bytes.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// # use uselesskey_core::{Factory, Seed};
138 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
139 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
140 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
141 /// let der = kp.private_key_pkcs8_der();
142 /// assert!(!der.is_empty());
143 /// ```
144 pub fn private_key_pkcs8_der(&self) -> &[u8] {
145 self.inner.material.private_key_pkcs8_der()
146 }
147
148 /// PKCS#8 PEM-encoded private key.
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// # use uselesskey_core::{Factory, Seed};
154 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
155 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
156 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
157 /// let pem = kp.private_key_pkcs8_pem();
158 /// assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----"));
159 /// ```
160 pub fn private_key_pkcs8_pem(&self) -> &str {
161 self.inner.material.private_key_pkcs8_pem()
162 }
163
164 /// SPKI DER-encoded public key bytes.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// # use uselesskey_core::{Factory, Seed};
170 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
171 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
172 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
173 /// let der = kp.public_key_spki_der();
174 /// assert!(!der.is_empty());
175 /// ```
176 pub fn public_key_spki_der(&self) -> &[u8] {
177 self.inner.material.public_key_spki_der()
178 }
179
180 /// SPKI PEM-encoded public key.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// # use uselesskey_core::{Factory, Seed};
186 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
187 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
188 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
189 /// let pem = kp.public_key_spki_pem();
190 /// assert!(pem.starts_with("-----BEGIN PUBLIC KEY-----"));
191 /// ```
192 pub fn public_key_spki_pem(&self) -> &str {
193 self.inner.material.public_key_spki_pem()
194 }
195
196 /// Write the PKCS#8 PEM private key to a tempfile and return the handle.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// # use uselesskey_core::Factory;
202 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
203 /// let fx = Factory::random();
204 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
205 /// let temp = kp.write_private_key_pkcs8_pem().unwrap();
206 /// assert!(temp.path().exists());
207 /// ```
208 pub fn write_private_key_pkcs8_pem(&self) -> Result<TempArtifact, Error> {
209 self.inner.material.write_private_key_pkcs8_pem()
210 }
211
212 /// Write the SPKI PEM public key to a tempfile and return the handle.
213 ///
214 /// # Examples
215 ///
216 /// ```
217 /// # use uselesskey_core::Factory;
218 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
219 /// let fx = Factory::random();
220 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
221 /// let temp = kp.write_public_key_spki_pem().unwrap();
222 /// assert!(temp.path().exists());
223 /// ```
224 pub fn write_public_key_spki_pem(&self) -> Result<TempArtifact, Error> {
225 self.inner.material.write_public_key_spki_pem()
226 }
227
228 /// Produce a corrupted variant of the PKCS#8 PEM.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// # use uselesskey_core::{Factory, Seed};
234 /// # use uselesskey_core::negative::CorruptPem;
235 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
236 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
237 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
238 /// let bad = kp.private_key_pkcs8_pem_corrupt(CorruptPem::BadHeader);
239 /// assert!(bad.contains("CORRUPTED"));
240 /// ```
241 pub fn private_key_pkcs8_pem_corrupt(&self, how: CorruptPem) -> String {
242 self.inner.material.private_key_pkcs8_pem_corrupt(how)
243 }
244
245 /// Produce a deterministic corrupted PKCS#8 PEM using a variant string.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// # use uselesskey_core::Factory;
251 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
252 /// let fx = Factory::random();
253 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
254 /// let bad = kp.private_key_pkcs8_pem_corrupt_deterministic("corrupt:v1");
255 /// assert!(!bad.is_empty());
256 /// ```
257 pub fn private_key_pkcs8_pem_corrupt_deterministic(&self, variant: &str) -> String {
258 self.inner
259 .material
260 .private_key_pkcs8_pem_corrupt_deterministic(variant)
261 }
262
263 /// Produce a truncated variant of the PKCS#8 DER.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// # use uselesskey_core::{Factory, Seed};
269 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
270 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
271 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
272 /// let truncated = kp.private_key_pkcs8_der_truncated(10);
273 /// assert_eq!(truncated.len(), 10);
274 /// ```
275 pub fn private_key_pkcs8_der_truncated(&self, len: usize) -> Vec<u8> {
276 self.inner.material.private_key_pkcs8_der_truncated(len)
277 }
278
279 /// Produce a deterministic corrupted PKCS#8 DER using a variant string.
280 ///
281 /// # Examples
282 ///
283 /// ```
284 /// # use uselesskey_core::Factory;
285 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
286 /// let fx = Factory::random();
287 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
288 /// let bad = kp.private_key_pkcs8_der_corrupt_deterministic("corrupt:v1");
289 /// assert!(!bad.is_empty());
290 /// ```
291 pub fn private_key_pkcs8_der_corrupt_deterministic(&self, variant: &str) -> Vec<u8> {
292 self.inner
293 .material
294 .private_key_pkcs8_der_corrupt_deterministic(variant)
295 }
296
297 /// Return a valid (parseable) public key that does *not* match this private key.
298 ///
299 /// # Examples
300 ///
301 /// ```
302 /// # use uselesskey_core::{Factory, Seed};
303 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
304 /// let fx = Factory::deterministic(Seed::from_env_value("test-seed").unwrap());
305 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
306 /// let wrong_pub = kp.mismatched_public_key_spki_der();
307 /// assert_ne!(wrong_pub, kp.public_key_spki_der());
308 /// ```
309 pub fn mismatched_public_key_spki_der(&self) -> Vec<u8> {
310 let other = self.load_variant("mismatch");
311 other.material.public_key_spki_der().to_vec()
312 }
313
314 /// A stable key identifier derived from the public key (base64url blake3 hash prefix).
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// # use uselesskey_core::Factory;
320 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
321 /// let fx = Factory::random();
322 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
323 /// let kid = kp.kid();
324 /// assert!(!kid.is_empty());
325 /// ```
326 #[cfg(feature = "jwk")]
327 pub fn kid(&self) -> String {
328 self.inner.material.kid()
329 }
330
331 /// Alias for [`Self::public_jwk`].
332 ///
333 /// Requires the `jwk` feature.
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// # use uselesskey_core::Factory;
339 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
340 /// let fx = Factory::random();
341 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
342 /// let jwk = kp.public_key_jwk();
343 /// assert_eq!(jwk.to_value()["kty"], "EC");
344 /// ```
345 #[cfg(feature = "jwk")]
346 pub fn public_key_jwk(&self) -> uselesskey_jwk::PublicJwk {
347 self.public_jwk()
348 }
349
350 /// Public JWK for this keypair (kty=EC, crv=P-256 or P-384, alg=ES256 or ES384).
351 ///
352 /// Requires the `jwk` feature.
353 ///
354 /// # Examples
355 ///
356 /// ```
357 /// # use uselesskey_core::Factory;
358 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
359 /// let fx = Factory::random();
360 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
361 /// let jwk = kp.public_jwk();
362 /// let val = jwk.to_value();
363 /// assert_eq!(val["kty"], "EC");
364 /// assert_eq!(val["crv"], "P-256");
365 /// ```
366 #[cfg(feature = "jwk")]
367 pub fn public_jwk(&self) -> uselesskey_jwk::PublicJwk {
368 use base64::Engine as _;
369 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
370 use uselesskey_jwk::{EcPublicJwk, PublicJwk};
371
372 // Public key bytes are in uncompressed form: 0x04 || x || y
373 let bytes = &self.inner.public_key_bytes;
374 assert_eq!(bytes[0], 0x04, "expected uncompressed point");
375 let coord_len = self.spec.coordinate_len_bytes();
376 assert_eq!(
377 bytes.len(),
378 1 + (coord_len * 2),
379 "unexpected EC point length for {:?}",
380 self.spec
381 );
382 let x = &bytes[1..1 + coord_len];
383 let y = &bytes[1 + coord_len..];
384
385 PublicJwk::Ec(EcPublicJwk {
386 kty: "EC",
387 use_: "sig",
388 alg: self.spec.alg_name(),
389 crv: self.spec.curve_name(),
390 kid: self.kid(),
391 x: URL_SAFE_NO_PAD.encode(x),
392 y: URL_SAFE_NO_PAD.encode(y),
393 })
394 }
395
396 /// Private JWK for this keypair (kty=EC, crv=..., alg=..., d=...).
397 ///
398 /// Requires the `jwk` feature.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// # use uselesskey_core::Factory;
404 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
405 /// let fx = Factory::random();
406 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
407 /// let jwk = kp.private_key_jwk();
408 /// let val = jwk.to_value();
409 /// assert_eq!(val["kty"], "EC");
410 /// assert!(val["d"].is_string());
411 /// ```
412 #[cfg(feature = "jwk")]
413 pub fn private_key_jwk(&self) -> uselesskey_jwk::PrivateJwk {
414 use base64::Engine as _;
415 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
416 use uselesskey_jwk::{EcPrivateJwk, PrivateJwk};
417
418 // Public key bytes are in uncompressed form: 0x04 || x || y
419 let bytes = &self.inner.public_key_bytes;
420 assert_eq!(bytes[0], 0x04, "expected uncompressed point");
421 let coord_len = self.spec.coordinate_len_bytes();
422 assert_eq!(
423 bytes.len(),
424 1 + (coord_len * 2),
425 "unexpected EC point length for {:?}",
426 self.spec
427 );
428 let x = &bytes[1..1 + coord_len];
429 let y = &bytes[1 + coord_len..];
430
431 PrivateJwk::Ec(EcPrivateJwk {
432 kty: "EC",
433 use_: "sig",
434 alg: self.spec.alg_name(),
435 crv: self.spec.curve_name(),
436 kid: self.kid(),
437 x: URL_SAFE_NO_PAD.encode(x),
438 y: URL_SAFE_NO_PAD.encode(y),
439 d: URL_SAFE_NO_PAD.encode(&self.inner.private_key_bytes),
440 })
441 }
442
443 /// JWKS containing a single public key.
444 ///
445 /// # Examples
446 ///
447 /// ```
448 /// # use uselesskey_core::Factory;
449 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
450 /// let fx = Factory::random();
451 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
452 /// let jwks = kp.public_jwks();
453 /// assert!(jwks.to_value()["keys"].is_array());
454 /// ```
455 #[cfg(feature = "jwk")]
456 pub fn public_jwks(&self) -> uselesskey_jwk::Jwks {
457 use uselesskey_jwk::JwksBuilder;
458
459 let mut builder = JwksBuilder::new();
460 builder.push_public(self.public_jwk());
461 builder.build()
462 }
463
464 /// Public JWK serialized to `serde_json::Value`.
465 ///
466 /// Requires the `jwk` feature.
467 ///
468 /// # Examples
469 ///
470 /// ```
471 /// # use uselesskey_core::Factory;
472 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
473 /// let fx = Factory::random();
474 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
475 /// let val = kp.public_jwk_json();
476 /// assert_eq!(val["kty"], "EC");
477 /// ```
478 #[cfg(feature = "jwk")]
479 pub fn public_jwk_json(&self) -> serde_json::Value {
480 self.public_jwk().to_value()
481 }
482
483 /// JWKS serialized to `serde_json::Value`.
484 ///
485 /// Requires the `jwk` feature.
486 ///
487 /// # Examples
488 ///
489 /// ```
490 /// # use uselesskey_core::Factory;
491 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
492 /// let fx = Factory::random();
493 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
494 /// let val = kp.public_jwks_json();
495 /// assert!(val["keys"].is_array());
496 /// ```
497 #[cfg(feature = "jwk")]
498 pub fn public_jwks_json(&self) -> serde_json::Value {
499 self.public_jwks().to_value()
500 }
501
502 /// Private JWK serialized to `serde_json::Value`.
503 ///
504 /// Requires the `jwk` feature.
505 ///
506 /// # Examples
507 ///
508 /// ```
509 /// # use uselesskey_core::Factory;
510 /// # use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
511 /// let fx = Factory::random();
512 /// let kp = fx.ecdsa("svc", EcdsaSpec::es256());
513 /// let val = kp.private_key_jwk_json();
514 /// assert_eq!(val["kty"], "EC");
515 /// assert!(val["d"].is_string());
516 /// ```
517 #[cfg(feature = "jwk")]
518 pub fn private_key_jwk_json(&self) -> serde_json::Value {
519 self.private_key_jwk().to_value()
520 }
521}
522
523fn load_inner(factory: &Factory, label: &str, spec: EcdsaSpec, variant: &str) -> Arc<Inner> {
524 let spec_bytes = spec.stable_bytes();
525
526 factory.get_or_init(DOMAIN_ECDSA_KEYPAIR, label, &spec_bytes, variant, |seed| {
527 let mut rng = ChaCha20Rng::from_seed(*seed.bytes());
528 match spec {
529 EcdsaSpec::Es256 => generate_p256(spec, &mut rng),
530 EcdsaSpec::Es384 => generate_p384(spec, &mut rng),
531 }
532 })
533}
534
535fn generate_p256(spec: EcdsaSpec, rng: &mut impl rand_core::CryptoRngCore) -> Inner {
536 use p256::ecdsa::SigningKey;
537
538 let signing_key = SigningKey::random(rng);
539 let verifying_key = signing_key.verifying_key();
540
541 let pkcs8_der_doc = signing_key
542 .to_pkcs8_der()
543 .expect("failed to encode P-256 private key as PKCS#8 DER");
544 let pkcs8_der: Arc<[u8]> = Arc::from(pkcs8_der_doc.as_bytes());
545
546 let pkcs8_pem = signing_key
547 .to_pkcs8_pem(LineEnding::LF)
548 .expect("failed to encode P-256 private key as PKCS#8 PEM")
549 .to_string();
550
551 let spki_der_doc = verifying_key
552 .to_public_key_der()
553 .expect("failed to encode P-256 public key as SPKI DER");
554 let spki_der: Arc<[u8]> = Arc::from(spki_der_doc.as_bytes());
555
556 let spki_pem = verifying_key
557 .to_public_key_pem(LineEnding::LF)
558 .expect("failed to encode P-256 public key as SPKI PEM");
559
560 // Get uncompressed point for JWK
561 let point = verifying_key.to_encoded_point(false);
562 let public_key_bytes = point.as_bytes().to_vec();
563 let private_key_bytes = signing_key.to_bytes().to_vec();
564
565 let material = Pkcs8SpkiKeyMaterial::new(pkcs8_der, pkcs8_pem, spki_der, spki_pem);
566
567 Inner {
568 spec,
569 material,
570 public_key_bytes,
571 private_key_bytes,
572 }
573}
574
575fn generate_p384(spec: EcdsaSpec, rng: &mut impl rand_core::CryptoRngCore) -> Inner {
576 use p384::ecdsa::SigningKey;
577
578 let signing_key = SigningKey::random(rng);
579 let verifying_key = signing_key.verifying_key();
580
581 let pkcs8_der_doc = signing_key
582 .to_pkcs8_der()
583 .expect("failed to encode P-384 private key as PKCS#8 DER");
584 let pkcs8_der: Arc<[u8]> = Arc::from(pkcs8_der_doc.as_bytes());
585
586 let pkcs8_pem = signing_key
587 .to_pkcs8_pem(LineEnding::LF)
588 .expect("failed to encode P-384 private key as PKCS#8 PEM")
589 .to_string();
590
591 let spki_der_doc = verifying_key
592 .to_public_key_der()
593 .expect("failed to encode P-384 public key as SPKI DER");
594 let spki_der: Arc<[u8]> = Arc::from(spki_der_doc.as_bytes());
595
596 let spki_pem = verifying_key
597 .to_public_key_pem(LineEnding::LF)
598 .expect("failed to encode P-384 public key as SPKI PEM");
599
600 // Get uncompressed point for JWK
601 let point = verifying_key.to_encoded_point(false);
602 let public_key_bytes = point.as_bytes().to_vec();
603 let private_key_bytes = signing_key.to_bytes().to_vec();
604
605 let material = Pkcs8SpkiKeyMaterial::new(pkcs8_der, pkcs8_pem, spki_der, spki_pem);
606
607 Inner {
608 spec,
609 material,
610 public_key_bytes,
611 private_key_bytes,
612 }
613}