Skip to main content

aws_lc_rs/
ed25519.rs

1// Copyright 2015-2016 Brian Smith.
2// SPDX-License-Identifier: ISC
3// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0 OR ISC
5
6use core::fmt;
7use core::fmt::{Debug, Formatter};
8use std::marker::PhantomData;
9
10#[cfg(feature = "ring-sig-verify")]
11use untrusted::Input;
12
13use crate::aws_lc::{EVP_PKEY, EVP_PKEY_ED25519};
14
15use crate::buffer::Buffer;
16use crate::digest::Digest;
17use crate::encoding::{
18    AsBigEndian, AsDer, Curve25519SeedBin, Pkcs8V1Der, Pkcs8V2Der, PublicKeyX509Der,
19};
20use crate::error::{KeyRejected, Unspecified};
21use crate::evp_pkey::No_EVP_PKEY_CTX_consumer;
22use crate::pkcs8::{Document, Version};
23use crate::ptr::LcPtr;
24use crate::rand::SecureRandom;
25use crate::signature::{
26    KeyPair, ParsedPublicKey, ParsedVerificationAlgorithm, Signature, VerificationAlgorithm,
27};
28use crate::{constant_time, digest, hex, sealed};
29
30/// The length of an Ed25519 public key.
31pub const ED25519_PUBLIC_KEY_LEN: usize = crate::aws_lc::ED25519_PUBLIC_KEY_LEN as usize;
32const ED25519_SIGNATURE_LEN: usize = crate::aws_lc::ED25519_SIGNATURE_LEN as usize;
33const ED25519_SEED_LEN: usize = 32;
34
35/// Parameters for `EdDSA` signing and verification.
36#[derive(Debug, Eq, PartialEq)]
37pub struct EdDSAParameters;
38
39impl sealed::Sealed for EdDSAParameters {}
40
41impl ParsedVerificationAlgorithm for EdDSAParameters {
42    fn parsed_verify_sig(
43        &self,
44        public_key: &ParsedPublicKey,
45        msg: &[u8],
46        signature: &[u8],
47    ) -> Result<(), Unspecified> {
48        public_key
49            .key()
50            .verify(msg, None, No_EVP_PKEY_CTX_consumer, signature)
51    }
52
53    fn parsed_verify_digest_sig(
54        &self,
55        _public_key: &ParsedPublicKey,
56        _digest: &Digest,
57        _signature: &[u8],
58    ) -> Result<(), Unspecified> {
59        Err(Unspecified)
60    }
61}
62
63impl VerificationAlgorithm for EdDSAParameters {
64    #[inline]
65    #[cfg(feature = "ring-sig-verify")]
66    fn verify(
67        &self,
68        public_key: Input<'_>,
69        msg: Input<'_>,
70        signature: Input<'_>,
71    ) -> Result<(), Unspecified> {
72        self.verify_sig(
73            public_key.as_slice_less_safe(),
74            msg.as_slice_less_safe(),
75            signature.as_slice_less_safe(),
76        )
77    }
78
79    /// Verify `signature` for `msg` using `public_key`.
80    ///
81    /// # Errors
82    ///  Returns `Unspecified` if the `msg` cannot be verified using `public_key`.
83    fn verify_sig(
84        &self,
85        public_key: &[u8],
86        msg: &[u8],
87        signature: &[u8],
88    ) -> Result<(), Unspecified> {
89        let evp_pkey = parse_ed25519_public_key(public_key)?;
90        evp_pkey.verify(msg, None, No_EVP_PKEY_CTX_consumer, signature)
91    }
92
93    /// DO NOT USE. This function is required by `VerificationAlgorithm` but cannot be used w/ Ed25519.
94    ///
95    /// # Errors
96    /// Always returns `Unspecified`.
97    fn verify_digest_sig(
98        &self,
99        _public_key: &[u8],
100        _digest: &digest::Digest,
101        _signature: &[u8],
102    ) -> Result<(), Unspecified> {
103        Err(Unspecified)
104    }
105}
106
107pub(crate) fn parse_ed25519_public_key(key_bytes: &[u8]) -> Result<LcPtr<EVP_PKEY>, KeyRejected> {
108    // If the length of key bytes matches the raw public key size then it has to be that
109    if key_bytes.len() == ED25519_PUBLIC_KEY_LEN {
110        LcPtr::<EVP_PKEY>::parse_raw_public_key(key_bytes, EVP_PKEY_ED25519)
111    } else {
112        // Otherwise we support X.509 SubjectPublicKeyInfo formatted keys which are inherently larger
113        LcPtr::<EVP_PKEY>::parse_rfc5280_public_key(key_bytes, EVP_PKEY_ED25519)
114    }
115}
116
117/// An Ed25519 key pair, for signing.
118#[derive(Clone)]
119#[allow(clippy::module_name_repetitions)]
120pub struct Ed25519KeyPair {
121    evp_pkey: LcPtr<EVP_PKEY>,
122    public_key: PublicKey,
123}
124
125impl Debug for Ed25519KeyPair {
126    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
127        f.write_str(&format!(
128            "Ed25519KeyPair {{ public_key: PublicKey(\"{}\") }}",
129            hex::encode(&self.public_key)
130        ))
131    }
132}
133
134#[derive(Clone)]
135#[allow(clippy::module_name_repetitions)]
136/// The seed value for the `EdDSA` signature scheme using Curve25519
137pub struct Seed<'a> {
138    bytes: Box<[u8]>,
139    phantom: PhantomData<&'a [u8]>,
140}
141
142impl AsBigEndian<Curve25519SeedBin<'static>> for Seed<'_> {
143    /// Exposes the seed encoded as a big-endian fixed-length integer.
144    ///
145    /// For most use-cases, `EcdsaKeyPair::to_pkcs8()` should be preferred.
146    ///
147    /// # Errors
148    /// `error::Unspecified` if serialization failed.
149    fn as_be_bytes(&self) -> Result<Curve25519SeedBin<'static>, Unspecified> {
150        Ok(Curve25519SeedBin::new(self.bytes.to_vec()))
151    }
152}
153
154impl Debug for Seed<'_> {
155    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
156        f.write_str("Ed25519Seed()")
157    }
158}
159
160#[derive(Clone)]
161#[allow(clippy::module_name_repetitions)]
162/// Ed25519 Public Key
163pub struct PublicKey {
164    evp_pkey: LcPtr<EVP_PKEY>,
165    public_key_bytes: [u8; ED25519_PUBLIC_KEY_LEN],
166}
167
168impl AsRef<[u8]> for PublicKey {
169    #[inline]
170    /// Returns the "raw" bytes of the ED25519 public key
171    fn as_ref(&self) -> &[u8] {
172        &self.public_key_bytes
173    }
174}
175
176impl Debug for PublicKey {
177    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
178        f.write_str(&format!(
179            "PublicKey(\"{}\")",
180            hex::encode(self.public_key_bytes)
181        ))
182    }
183}
184
185unsafe impl Send for PublicKey {}
186unsafe impl Sync for PublicKey {}
187
188impl AsDer<PublicKeyX509Der<'static>> for PublicKey {
189    /// Provides the public key as a DER-encoded (X.509) `SubjectPublicKeyInfo` structure.
190    /// # Errors
191    /// Returns an error if the public key fails to marshal to X.509.
192    fn as_der(&self) -> Result<PublicKeyX509Der<'static>, crate::error::Unspecified> {
193        // Initial size of 44 based on:
194        // 0:d=0  hl=2 l=  42 cons: SEQUENCE
195        // 2:d=1  hl=2 l=   5 cons:  SEQUENCE
196        // 4:d=2  hl=2 l=   3 prim:   OBJECT            :ED25519
197        // 9:d=1  hl=2 l=  33 prim:  BIT STRING
198        let der = self.evp_pkey.as_const().marshal_rfc5280_public_key()?;
199        Ok(PublicKeyX509Der::from(Buffer::new(der)))
200    }
201}
202
203impl KeyPair for Ed25519KeyPair {
204    type PublicKey = PublicKey;
205    #[inline]
206    fn public_key(&self) -> &Self::PublicKey {
207        &self.public_key
208    }
209}
210
211unsafe impl Send for Ed25519KeyPair {}
212unsafe impl Sync for Ed25519KeyPair {}
213
214pub(crate) fn generate_key() -> Result<LcPtr<EVP_PKEY>, Unspecified> {
215    LcPtr::<EVP_PKEY>::generate(EVP_PKEY_ED25519, No_EVP_PKEY_CTX_consumer)
216}
217
218impl Ed25519KeyPair {
219    /// Generates a new key pair and returns the key pair.
220    ///
221    /// # Errors
222    /// `error::Unspecified` if key generation fails.
223    pub fn generate() -> Result<Self, Unspecified> {
224        let evp_pkey = generate_key()?;
225
226        let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN];
227        let out_len: usize = evp_pkey
228            .as_const()
229            .marshal_raw_public_to_buffer(&mut public_key)?;
230        debug_assert_eq!(public_key.len(), out_len);
231
232        Ok(Self {
233            public_key: PublicKey {
234                public_key_bytes: public_key,
235                evp_pkey: evp_pkey.clone(),
236            },
237            evp_pkey,
238        })
239    }
240
241    /// Generates a new key pair and returns the key pair serialized as a
242    /// PKCS#8 document.
243    ///
244    /// The PKCS#8 document will be a v2 `OneAsymmetricKey` with the public key,
245    /// as described in [RFC 5958 Section 2]; see [RFC 8410 Section 10.3] for an
246    /// example.
247    ///
248    /// [RFC 5958 Section 2]: https://tools.ietf.org/html/rfc5958#section-2
249    /// [RFC 8410 Section 10.3]: https://tools.ietf.org/html/rfc8410#section-10.3
250    ///
251    /// # *ring* Compatibility
252    /// The ring 0.16.x API did not produce encoded v2 documents that were compliant with RFC 5958.
253    /// The aws-lc-ring implementation produces PKCS#8 v2 encoded documents that are compliant per
254    /// the RFC specification.
255    ///
256    /// Our implementation ignores the `SecureRandom` parameter.
257    ///
258    // # FIPS
259    // This function must not be used.
260    //
261    /// # Errors
262    /// `error::Unspecified` if `rng` cannot provide enough bits or if there's an internal error.
263    pub fn generate_pkcs8(_rng: &dyn SecureRandom) -> Result<Document, Unspecified> {
264        let evp_pkey = generate_key()?;
265        Ok(Document::new(
266            evp_pkey
267                .as_const()
268                .marshal_rfc5208_private_key(Version::V2)?,
269        ))
270    }
271
272    /// Serializes this `Ed25519KeyPair` into a PKCS#8 v2 document.
273    ///
274    /// # Errors
275    /// `error::Unspecified` on internal error.
276    ///
277    pub fn to_pkcs8(&self) -> Result<Document, Unspecified> {
278        Ok(Document::new(
279            self.evp_pkey
280                .as_const()
281                .marshal_rfc5208_private_key(Version::V2)?,
282        ))
283    }
284
285    /// Generates a `Ed25519KeyPair` using the `rng` provided, then serializes that key as a
286    /// PKCS#8 document.
287    ///
288    /// The PKCS#8 document will be a v1 `PrivateKeyInfo` structure (RFC5208). Use this method
289    /// when needing to produce documents that are compatible with the OpenSSL CLI.
290    ///
291    /// # *ring* Compatibility
292    ///  Our implementation ignores the `SecureRandom` parameter.
293    ///
294    // # FIPS
295    // This function must not be used.
296    //
297    /// # Errors
298    /// `error::Unspecified` if `rng` cannot provide enough bits or if there's an internal error.
299    pub fn generate_pkcs8v1(_rng: &dyn SecureRandom) -> Result<Document, Unspecified> {
300        let evp_pkey = generate_key()?;
301        Ok(Document::new(
302            evp_pkey
303                .as_const()
304                .marshal_rfc5208_private_key(Version::V1)?,
305        ))
306    }
307
308    /// Serializes this `Ed25519KeyPair` into a PKCS#8 v1 document.
309    ///
310    /// # Errors
311    /// `error::Unspecified` on internal error.
312    ///
313    pub fn to_pkcs8v1(&self) -> Result<Document, Unspecified> {
314        Ok(Document::new(
315            self.evp_pkey
316                .as_const()
317                .marshal_rfc5208_private_key(Version::V1)?,
318        ))
319    }
320
321    /// Constructs an Ed25519 key pair from the private key seed `seed` and its
322    /// public key `public_key`.
323    ///
324    /// It is recommended to use `Ed25519KeyPair::from_pkcs8()` instead.
325    ///
326    /// The private and public keys will be verified to be consistent with each
327    /// other. This helps avoid misuse of the key (e.g. accidentally swapping
328    /// the private key and public key, or using the wrong private key for the
329    /// public key). This also detects any corruption of the public or private
330    /// key.
331    ///
332    /// # Errors
333    /// `error::KeyRejected` if parse error, or if key is otherwise unacceptable.
334    pub fn from_seed_and_public_key(seed: &[u8], public_key: &[u8]) -> Result<Self, KeyRejected> {
335        let this = Self::from_seed_unchecked(seed)?;
336
337        constant_time::verify_slices_are_equal(public_key, &this.public_key.public_key_bytes)
338            .map_err(|_| KeyRejected::inconsistent_components())?;
339        Ok(this)
340    }
341
342    /// Constructs an Ed25519 key pair from the private key seed `seed`.
343    ///
344    /// It is recommended to use `Ed25519KeyPair::from_pkcs8()` instead. If the public key is
345    /// available, prefer to use `Ed25519KeyPair::from_seed_and_public_key()` as it will verify
346    /// the validity of the key pair.
347    ///
348    /// CAUTION: Both an Ed25519 seed and its public key are 32-bytes. If the bytes of a public key
349    /// are provided this function will create an (effectively) invalid `Ed25519KeyPair`. This
350    /// problem is undetectable by the API.
351    ///
352    /// # Errors
353    /// `error::KeyRejected` if parse error, or if key is otherwise unacceptable.
354    pub fn from_seed_unchecked(seed: &[u8]) -> Result<Self, KeyRejected> {
355        if seed.len() < ED25519_SEED_LEN {
356            return Err(KeyRejected::inconsistent_components());
357        }
358
359        let evp_pkey = LcPtr::<EVP_PKEY>::parse_raw_private_key(seed, EVP_PKEY_ED25519)?;
360
361        let mut derived_public_key = [0u8; ED25519_PUBLIC_KEY_LEN];
362        let out_len: usize = evp_pkey
363            .as_const()
364            .marshal_raw_public_to_buffer(&mut derived_public_key)?;
365        debug_assert_eq!(derived_public_key.len(), out_len);
366
367        Ok(Self {
368            public_key: PublicKey {
369                public_key_bytes: derived_public_key,
370                evp_pkey: evp_pkey.clone(),
371            },
372            evp_pkey,
373        })
374    }
375
376    /// Constructs an Ed25519 key pair by parsing an unencrypted PKCS#8 v1 or v2
377    /// Ed25519 private key.
378    ///
379    /// `openssl genpkey -algorithm ED25519` generates PKCS#8 v1 keys.
380    ///
381    /// # Ring Compatibility
382    /// * This method accepts either v1 or v2 encoded keys, if a v2 encoded key is provided, with the
383    ///   public key component present, it will be verified to match the one derived from the
384    ///   encoded private key.
385    /// * The ring 0.16.x API did not produce encoded v2 documents that were compliant with RFC 5958.
386    ///   The aws-lc-ring implementation produces PKCS#8 v2 encoded documents that are compliant per
387    ///   the RFC specification.
388    ///
389    /// # Errors
390    /// `error::KeyRejected` on parse error, or if key is otherwise unacceptable.
391    pub fn from_pkcs8(pkcs8: &[u8]) -> Result<Self, KeyRejected> {
392        Self::parse_pkcs8(pkcs8)
393    }
394
395    /// Constructs an Ed25519 key pair by parsing an unencrypted PKCS#8 v1 or v2
396    /// Ed25519 private key.
397    ///
398    /// `openssl genpkey -algorithm ED25519` generates PKCS# v1 keys.
399    ///
400    /// # Ring Compatibility
401    /// * This method accepts either v1 or v2 encoded keys, if a v2 encoded key is provided, with the
402    ///   public key component present, it will be verified to match the one derived from the
403    ///   encoded private key.
404    /// * The ring 0.16.x API did not produce encoded v2 documents that were compliant with RFC 5958.
405    ///   The aws-lc-ring implementation produces PKCS#8 v2 encoded documents that are compliant per
406    ///   the RFC specification.
407    ///
408    /// # Errors
409    /// `error::KeyRejected` on parse error, or if key is otherwise unacceptable.
410    pub fn from_pkcs8_maybe_unchecked(pkcs8: &[u8]) -> Result<Self, KeyRejected> {
411        Self::parse_pkcs8(pkcs8)
412    }
413
414    fn parse_pkcs8(pkcs8: &[u8]) -> Result<Self, KeyRejected> {
415        let evp_pkey = LcPtr::<EVP_PKEY>::parse_rfc5208_private_key(pkcs8, EVP_PKEY_ED25519)?;
416
417        evp_pkey.as_const().validate_as_ed25519()?;
418
419        let mut public_key = [0u8; ED25519_PUBLIC_KEY_LEN];
420        let out_len: usize = evp_pkey
421            .as_const()
422            .marshal_raw_public_to_buffer(&mut public_key)?;
423        debug_assert_eq!(public_key.len(), out_len);
424
425        Ok(Self {
426            public_key: PublicKey {
427                public_key_bytes: public_key,
428                evp_pkey: evp_pkey.clone(),
429            },
430            evp_pkey,
431        })
432    }
433
434    /// Returns the signature of the message msg.
435    ///
436    // # FIPS
437    // This method must not be used.
438    //
439    /// # Panics
440    /// Panics if the message is unable to be signed
441    #[inline]
442    #[must_use]
443    pub fn sign(&self, msg: &[u8]) -> Signature {
444        Self::try_sign(self, msg).expect("ED25519 signing failed")
445    }
446
447    /// Returns the signature of the message `msg`.
448    ///
449    // # FIPS
450    // This method must not be used.
451    //
452    /// # Errors
453    /// Returns `error::Unspecified` if the signing operation fails.
454    #[inline]
455    pub fn try_sign(&self, msg: &[u8]) -> Result<Signature, Unspecified> {
456        let sig_bytes = self.evp_pkey.sign(msg, None, No_EVP_PKEY_CTX_consumer)?;
457
458        Ok(Signature::new(|slice| {
459            slice[0..ED25519_SIGNATURE_LEN].copy_from_slice(&sig_bytes);
460            ED25519_SIGNATURE_LEN
461        }))
462    }
463
464    /// Provides the private key "seed" for this `Ed25519` key pair.
465    ///
466    /// For serialization of the key pair, `Ed25519KeyPair::to_pkcs8()` is preferred.
467    ///
468    /// # Errors
469    /// Currently the function cannot fail, but it might in future implementations.
470    pub fn seed(&self) -> Result<Seed<'static>, Unspecified> {
471        Ok(Seed {
472            bytes: self
473                .evp_pkey
474                .as_const()
475                .marshal_raw_private_key()?
476                .into_boxed_slice(),
477            phantom: PhantomData,
478        })
479    }
480}
481
482impl AsDer<Pkcs8V1Der<'static>> for Ed25519KeyPair {
483    /// Serializes this `Ed25519KeyPair` into a PKCS#8 v1 document.
484    ///
485    /// # Errors
486    /// `error::Unspecified` on internal error.
487    fn as_der(&self) -> Result<Pkcs8V1Der<'static>, crate::error::Unspecified> {
488        Ok(Pkcs8V1Der::new(
489            self.evp_pkey
490                .as_const()
491                .marshal_rfc5208_private_key(Version::V1)?,
492        ))
493    }
494}
495
496impl AsDer<Pkcs8V2Der<'static>> for Ed25519KeyPair {
497    /// Serializes this `Ed25519KeyPair` into a PKCS#8 v1 document.
498    ///
499    /// # Errors
500    /// `error::Unspecified` on internal error.
501    fn as_der(&self) -> Result<Pkcs8V2Der<'static>, crate::error::Unspecified> {
502        Ok(Pkcs8V2Der::new(
503            self.evp_pkey
504                .as_const()
505                .marshal_rfc5208_private_key(Version::V2)?,
506        ))
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use crate::ed25519::Ed25519KeyPair;
513    use crate::encoding::{AsBigEndian, AsDer, Pkcs8V1Der, Pkcs8V2Der, PublicKeyX509Der};
514    use crate::rand::SystemRandom;
515    use crate::signature::{KeyPair, UnparsedPublicKey, ED25519};
516    use crate::{hex, test};
517
518    #[test]
519    fn test_generate() {
520        const MESSAGE: &[u8] = b"test message";
521        let key_pair = Ed25519KeyPair::generate().unwrap();
522        let public_key = key_pair.public_key();
523        let signature = key_pair.sign(MESSAGE);
524        let unparsed_public_key = UnparsedPublicKey::new(&ED25519, public_key.as_ref());
525        unparsed_public_key
526            .verify(MESSAGE, signature.as_ref())
527            .unwrap();
528    }
529
530    #[test]
531    fn test_generate_pkcs8() {
532        let rng = SystemRandom::new();
533        let document = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
534        let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap();
535        assert_eq!(
536            document.as_ref(),
537            AsDer::<Pkcs8V2Der>::as_der(&kp1).unwrap().as_ref()
538        );
539        let kp2: Ed25519KeyPair =
540            Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap();
541        assert_eq!(
542            kp1.seed().unwrap().as_be_bytes().unwrap().as_ref(),
543            kp2.seed().unwrap().as_be_bytes().unwrap().as_ref(),
544        );
545        assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref());
546
547        let document = Ed25519KeyPair::generate_pkcs8v1(&rng).unwrap();
548        let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap();
549        assert_eq!(
550            document.as_ref(),
551            AsDer::<Pkcs8V1Der>::as_der(&kp1).unwrap().as_ref()
552        );
553        let kp2: Ed25519KeyPair =
554            Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap();
555        assert_eq!(
556            kp1.seed().unwrap().as_be_bytes().unwrap().as_ref(),
557            kp2.seed().unwrap().as_be_bytes().unwrap().as_ref(),
558        );
559        assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref());
560        let seed = kp1.seed().unwrap();
561        assert_eq!("Ed25519Seed()", format!("{seed:?}"));
562    }
563
564    #[test]
565    fn test_from_pkcs8() {
566        struct TestCase {
567            key: &'static str,
568            expected_public: &'static str,
569        }
570
571        for case in [
572            TestCase {
573                key: "302e020100300506032b6570042204209d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
574                expected_public: "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
575            },
576            TestCase {
577                key: "3051020101300506032b657004220420756434bd5b824753007a138d27abbc14b5cc786adb78fb62435e6419a2b2e72b8121000faccd81e57de15fa6343a7fbb43b2b93f28be6435100ae8bd633c6dfee3d198",
578                expected_public: "0faccd81e57de15fa6343a7fbb43b2b93f28be6435100ae8bd633c6dfee3d198",
579            },
580            TestCase {
581                key: "304f020100300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842a01f301d060a2a864886f70d01090914310f0c0d437572646c6520436861697273",
582                expected_public: "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1",
583            },
584            TestCase {
585                key: "3072020101300506032b657004220420d4ee72dbf913584ad5b6d8f1f769f8ad3afe7c28cbf1d4fbe097a88f44755842a01f301d060a2a864886f70d01090914310f0c0d437572646c652043686169727381210019bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1",
586                expected_public: "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1",
587            }
588        ] {
589            let key_pair = Ed25519KeyPair::from_pkcs8(&test::from_dirty_hex(case.key)).unwrap();
590            assert_eq!(
591                format!(
592                    r#"Ed25519KeyPair {{ public_key: PublicKey("{}") }}"#,
593                    case.expected_public
594                ),
595                format!("{key_pair:?}")
596            );
597            let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&test::from_dirty_hex(case.key)).unwrap();
598            assert_eq!(
599                format!(
600                    r#"Ed25519KeyPair {{ public_key: PublicKey("{}") }}"#,
601                    case.expected_public
602                ),
603                format!("{key_pair:?}")
604            );
605        }
606    }
607
608    #[test]
609    fn test_public_key_as_der_x509() {
610        let key_pair = Ed25519KeyPair::from_pkcs8(&hex::decode("302e020100300506032b6570042204209d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap()).unwrap();
611        let public_key = key_pair.public_key();
612        let x509der = AsDer::<PublicKeyX509Der>::as_der(public_key).unwrap();
613        assert_eq!(
614            x509der.as_ref(),
615            &[
616                0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xd7, 0x5a,
617                0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a,
618                0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
619                0x51, 0x1a
620            ]
621        );
622    }
623}