Skip to main content

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}