Skip to main content

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}