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