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