From 5b75703e74b4f00554809095aa9ebcc2bd494da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Fri, 7 Jun 2024 18:40:21 +0900 Subject: [PATCH] feat: short-hand API to export keys into PKCS1 PEM --- .../Devolutions.Picky/Generated/PrivateKey.cs | 30 +++++ .../Devolutions.Picky/Generated/PublicKey.cs | 30 +++++ .../Generated/RawPrivateKey.cs | 9 ++ .../Generated/RawPublicKey.cs | 9 ++ ffi/src/key.rs | 16 +++ ffi/wasm/src/key.rs | 16 +++ picky-asn1-x509/src/private_key_info.rs | 9 +- picky/src/key/mod.rs | 117 ++++++++++++------ 8 files changed, 196 insertions(+), 40 deletions(-) diff --git a/ffi/dotnet/Devolutions.Picky/Generated/PrivateKey.cs b/ffi/dotnet/Devolutions.Picky/Generated/PrivateKey.cs index 855049e9..762e67b1 100644 --- a/ffi/dotnet/Devolutions.Picky/Generated/PrivateKey.cs +++ b/ffi/dotnet/Devolutions.Picky/Generated/PrivateKey.cs @@ -225,6 +225,36 @@ public Pem ToPem() } } + /// + /// Exports the private key into a PEM object using the PKCS 1 format + /// + /// + /// This format can only be used for RSA keys. + /// + /// + /// + /// A Pem allocated on Rust side. + /// + public Pem ToPkcs1Pem() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("PrivateKey"); + } + IntPtr resultPtr = Raw.PrivateKey.ToPkcs1Pem(_inner); + Raw.KeyFfiResultBoxPemBoxPickyError result = Marshal.PtrToStructure(resultPtr); + Raw.KeyFfiResultBoxPemBoxPickyError.Destroy(resultPtr); + if (!result.isOk) + { + throw new PickyException(new PickyError(result.Err)); + } + Raw.Pem* retVal = result.Ok; + return new Pem(retVal); + } + } + /// /// Extracts the public part of this private key /// diff --git a/ffi/dotnet/Devolutions.Picky/Generated/PublicKey.cs b/ffi/dotnet/Devolutions.Picky/Generated/PublicKey.cs index eff40033..634f88d7 100644 --- a/ffi/dotnet/Devolutions.Picky/Generated/PublicKey.cs +++ b/ffi/dotnet/Devolutions.Picky/Generated/PublicKey.cs @@ -147,6 +147,36 @@ public Pem ToPem() } } + /// + /// Exports the public key into a PEM object using the PKCS 1 format + /// + /// + /// This format can only be used for RSA keys. + /// + /// + /// + /// A Pem allocated on Rust side. + /// + public Pem ToPkcs1Pem() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("PublicKey"); + } + IntPtr resultPtr = Raw.PublicKey.ToPkcs1Pem(_inner); + Raw.KeyFfiResultBoxPemBoxPickyError result = Marshal.PtrToStructure(resultPtr); + Raw.KeyFfiResultBoxPemBoxPickyError.Destroy(resultPtr); + if (!result.isOk) + { + throw new PickyException(new PickyError(result.Err)); + } + Raw.Pem* retVal = result.Ok; + return new Pem(retVal); + } + } + /// /// Retrieves the key kind for this public key. /// diff --git a/ffi/dotnet/Devolutions.Picky/Generated/RawPrivateKey.cs b/ffi/dotnet/Devolutions.Picky/Generated/RawPrivateKey.cs index b5128141..676a4297 100644 --- a/ffi/dotnet/Devolutions.Picky/Generated/RawPrivateKey.cs +++ b/ffi/dotnet/Devolutions.Picky/Generated/RawPrivateKey.cs @@ -66,6 +66,15 @@ public partial struct PrivateKey [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PrivateKey_to_pem", ExactSpelling = true)] public static unsafe extern IntPtr ToPem(PrivateKey* self); + /// + /// Exports the private key into a PEM object using the PKCS 1 format + /// + /// + /// This format can only be used for RSA keys. + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PrivateKey_to_pkcs1_pem", ExactSpelling = true)] + public static unsafe extern IntPtr ToPkcs1Pem(PrivateKey* self); + /// /// Extracts the public part of this private key /// diff --git a/ffi/dotnet/Devolutions.Picky/Generated/RawPublicKey.cs b/ffi/dotnet/Devolutions.Picky/Generated/RawPublicKey.cs index f4836ba4..275af8a8 100644 --- a/ffi/dotnet/Devolutions.Picky/Generated/RawPublicKey.cs +++ b/ffi/dotnet/Devolutions.Picky/Generated/RawPublicKey.cs @@ -44,6 +44,15 @@ public partial struct PublicKey [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PublicKey_to_pem", ExactSpelling = true)] public static unsafe extern IntPtr ToPem(PublicKey* self); + /// + /// Exports the public key into a PEM object using the PKCS 1 format + /// + /// + /// This format can only be used for RSA keys. + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PublicKey_to_pkcs1_pem", ExactSpelling = true)] + public static unsafe extern IntPtr ToPkcs1Pem(PublicKey* self); + /// /// Retrieves the key kind for this public key. /// diff --git a/ffi/src/key.rs b/ffi/src/key.rs index dced84df..f944382e 100644 --- a/ffi/src/key.rs +++ b/ffi/src/key.rs @@ -146,6 +146,14 @@ pub mod ffi { Ok(Box::new(Pem(pem))) } + /// Exports the private key into a PEM object using the PKCS 1 format + /// + /// This format can only be used for RSA keys. + pub fn to_pkcs1_pem(&self) -> Result, Box> { + let pem = self.0.to_pkcs1_pem()?; + Ok(Box::new(Pem(pem))) + } + /// Extracts the public part of this private key pub fn to_public_key(&self) -> Result, Box> { let key = self.0.to_public_key()?; @@ -186,6 +194,14 @@ pub mod ffi { Ok(Box::new(Pem(pem))) } + /// Exports the public key into a PEM object using the PKCS 1 format + /// + /// This format can only be used for RSA keys. + pub fn to_pkcs1_pem(&self) -> Result, Box> { + let pem = self.0.to_pkcs1_pem()?; + Ok(Box::new(Pem(pem))) + } + /// Retrieves the key kind for this public key. pub fn get_kind(&self) -> KeyKind { self.0.kind().into() diff --git a/ffi/wasm/src/key.rs b/ffi/wasm/src/key.rs index 4a4ae6d4..b5783750 100644 --- a/ffi/wasm/src/key.rs +++ b/ffi/wasm/src/key.rs @@ -109,6 +109,14 @@ impl PrivateKey { Ok(Pem(pem)) } + /// Exports the private key into a PEM object using the PKCS 1 format + /// + /// This format can only be used for RSA keys. + pub fn to_pkcs1_pem(&self) -> Result { + let pem = self.0.to_pkcs1_pem()?; + Ok(Pem(pem)) + } + /// Extracts the public part of this private key pub fn to_public_key(&self) -> Result { Ok(PublicKey(self.0.to_public_key()?)) @@ -137,4 +145,12 @@ impl PublicKey { let pem = self.0.to_pem()?; Ok(Pem(pem)) } + + /// Exports the public key into a PEM object using the PKCS 1 format + /// + /// This format can only be used for RSA keys. + pub fn to_pkcs1_pem(&self) -> Result { + let pem = self.0.to_pkcs1_pem()?; + Ok(Pem(pem)) + } } diff --git a/picky-asn1-x509/src/private_key_info.rs b/picky-asn1-x509/src/private_key_info.rs index 4d6ba99c..e233998e 100644 --- a/picky-asn1-x509/src/private_key_info.rs +++ b/picky-asn1-x509/src/private_key_info.rs @@ -95,7 +95,7 @@ impl PrivateKeyInfo { exponents: (IntegerAsn1, IntegerAsn1), coefficient: IntegerAsn1, ) -> Self { - let private_key = PrivateKeyValue::RSA( + let private_key = PrivateKeyValue::Rsa( RsaPrivateKey { version: vec![0].into(), modulus, @@ -212,7 +212,7 @@ impl<'de> de::Deserialize<'de> for PrivateKeyInfo { seq_next_element!(seq, PrivateKeyInfo, "private key algorithm"); let private_key = if private_key_algorithm.is_a(oids::rsa_encryption()) { - PrivateKeyValue::RSA(seq_next_element!(seq, PrivateKeyInfo, "rsa oid")) + PrivateKeyValue::Rsa(seq_next_element!(seq, PrivateKeyInfo, "rsa oid")) } else if matches!(private_key_algorithm.parameters(), AlgorithmIdentifierParameters::Ec(_)) { PrivateKeyValue::EC(seq_next_element!(seq, PrivateKeyInfo, "ec private key")) } else if private_key_algorithm.is_one_of([oids::ed25519(), oids::x25519()]) { @@ -262,7 +262,7 @@ impl<'de> de::Deserialize<'de> for PrivateKeyInfo { #[derive(Debug, PartialEq, Eq, Clone)] pub enum PrivateKeyValue { - RSA(OctetStringAsn1Container), + Rsa(OctetStringAsn1Container), EC(OctetStringAsn1Container), // Used by Ed25519, Ed448, X25519, and X448 keys ED(OctetStringAsn1Container), @@ -274,7 +274,7 @@ impl ser::Serialize for PrivateKeyValue { S: ser::Serializer, { match self { - PrivateKeyValue::RSA(rsa) => rsa.serialize(serializer), + PrivateKeyValue::Rsa(rsa) => rsa.serialize(serializer), PrivateKeyValue::EC(ec) => ec.serialize(serializer), PrivateKeyValue::ED(ed) => ed.serialize(serializer), } @@ -488,7 +488,6 @@ impl RsaPrivateKey { /// publicKey [1] BIT STRING OPTIONAL /// } /// ``` - #[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct ECPrivateKey { pub version: IntegerAsn1, diff --git a/picky/src/key/mod.rs b/picky/src/key/mod.rs index 4682ec95..91e1cbb9 100644 --- a/picky/src/key/mod.rs +++ b/picky/src/key/mod.rs @@ -161,7 +161,7 @@ impl TryFrom<&'_ PrivateKey> for RsaPrivateKey { fn try_from(v: &PrivateKey) -> Result { match &v.as_inner().private_key { - private_key_info::PrivateKeyValue::RSA(OctetStringAsn1Container(key)) => { + private_key_info::PrivateKeyValue::Rsa(OctetStringAsn1Container(key)) => { let p1 = BigUint::from_bytes_be(key.prime_1.as_unsigned_bytes_be()); let p2 = BigUint::from_bytes_be(key.prime_2.as_unsigned_bytes_be()); @@ -187,7 +187,7 @@ impl TryFrom<&'_ PrivateKey> for RsaPublicKey { fn try_from(v: &PrivateKey) -> Result { match &v.as_inner().private_key { - private_key_info::PrivateKeyValue::RSA(OctetStringAsn1Container(key)) => { + private_key_info::PrivateKeyValue::Rsa(OctetStringAsn1Container(key)) => { Ok(RsaPublicKey::new_with_max_size( BigUint::from_bytes_be(key.modulus.as_unsigned_bytes_be()), BigUint::from_bytes_be(key.public_exponent.as_unsigned_bytes_be()), @@ -356,7 +356,7 @@ impl PrivateKey { pub fn from_pem(pem: &Pem) -> Result { match pem.label() { PRIVATE_KEY_PEM_LABEL => Self::from_pkcs8(pem.data()), - RSA_PRIVATE_KEY_PEM_LABEL => Self::from_rsa_der(pem.data()), + RSA_PRIVATE_KEY_PEM_LABEL => Self::from_pkcs1(pem.data()), EC_PRIVATE_KEY_LABEL => Self::from_ec_der(pem.data()), _ => Err(KeyError::InvalidPemLabel { label: pem.label().to_owned(), @@ -377,7 +377,7 @@ impl PrivateKey { })?; match &inner.private_key { - PrivateKeyValue::RSA(_) => Ok(Self { + PrivateKeyValue::Rsa(_) => Ok(Self { kind: PrivateKeyKind::Rsa, inner, }), @@ -440,7 +440,8 @@ impl PrivateKey { } } - pub fn from_rsa_der>(der: &T) -> Result { + /// Decodes a DER-encoded RSA private key + pub fn from_pkcs1>(der: &T) -> Result { use picky_asn1_x509::{AlgorithmIdentifier, RsaPrivateKey}; let private_key = @@ -452,7 +453,7 @@ impl PrivateKey { let inner = PrivateKeyInfo { version: PRIVATE_KEY_INFO_VERSION_1, private_key_algorithm: AlgorithmIdentifier::new_rsa_encryption(), - private_key: PrivateKeyValue::RSA(private_key.into()), + private_key: PrivateKeyValue::Rsa(private_key.into()), public_key: None, }; @@ -567,6 +568,20 @@ impl PrivateKey { }) } + pub fn to_pkcs1(&self) -> Result, KeyError> { + let picky_asn1_x509::PrivateKeyValue::Rsa(OctetStringAsn1Container(rsa_private_key)) = &self.inner.private_key + else { + return Err(KeyError::Rsa { + context: String::from("can’t export a non-RSA key to PKCS#1 format"), + }); + }; + + picky_asn1_der::to_vec(rsa_private_key).map_err(|e| KeyError::Asn1Serialization { + source: e, + element: "RSA private key (pkcs1)", + }) + } + pub fn to_pem(&self) -> Result, KeyError> { let pkcs8 = self.to_pkcs8()?; Ok(Pem::new(PRIVATE_KEY_PEM_LABEL, pkcs8)) @@ -576,10 +591,19 @@ impl PrivateKey { self.to_pem().map(|pem| pem.to_string()) } + pub fn to_pkcs1_pem(&self) -> Result, KeyError> { + let pkcs1 = self.to_pkcs1()?; + Ok(Pem::new(RSA_PRIVATE_KEY_PEM_LABEL, pkcs1)) + } + + pub fn to_pkcs1_pem_str(&self) -> Result { + self.to_pkcs1_pem().map(|pem| pem.to_string()) + } + pub fn to_public_key(&self) -> Result { let key = match &self.kind { PrivateKeyKind::Rsa => match &self.inner.private_key { - PrivateKeyValue::RSA(OctetStringAsn1Container(key)) => { + PrivateKeyValue::Rsa(OctetStringAsn1Container(key)) => { SubjectPublicKeyInfo::new_rsa_key(key.modulus.clone(), key.public_exponent.clone()).into() } _ => unreachable!("BUG: Non-RSA key data in RSA private key"), @@ -933,6 +957,15 @@ impl PublicKey { self.to_pem().map(|pem| pem.to_string()) } + pub fn to_pkcs1_pem(&self) -> Result, KeyError> { + let pkcs1 = self.to_pkcs1()?; + Ok(Pem::new(RSA_PUBLIC_KEY_PEM_LABEL, pkcs1)) + } + + pub fn to_pkcs1_pem_str(&self) -> Result { + self.to_pkcs1_pem().map(|pem| pem.to_string()) + } + pub fn from_pem(pem: &Pem) -> Result { match pem.label() { PUBLIC_KEY_PEM_LABEL | EC_PUBLIC_KEY_PEM_LABEL => Self::from_der(pem.data()), @@ -1035,37 +1068,51 @@ mod tests { generate_certificate_from_pk(private_key); } - const RSA_PRIVATE_KEY_PEM: &str = "-----BEGIN RSA PRIVATE KEY-----\n\ - MIIEpAIBAAKCAQEA5Kz4i/+XZhiE+fyrgtx/4yI3i6C6HXbC4QJYpDuSUEKN2bO9\n\ - RsE+Fnds/FizHtJVWbvya9ktvKdDPBdy58+CIM46HEKJhYLnBVlkEcg9N2RNgR3x\n\ - HnpRbKfv+BmWjOpSmWrmJSDLY0dbw5X5YL8TU69ImoouCUfStyCgrpwkctR0GD3G\n\ - fcGjbZRucV7VvVH9bS1jyaT/9yORyzPOSTwb+K9vOr6XlJX0CGvzQeIOcOimejHx\n\ - ACFOCnhEKXiwMsmL8FMz0drkGeMuCODY/OHVmAdXDE5UhroL0oDhSmIrdZ8CxngO\n\ - xHr1WD2yC0X0jAVP/mrxjSSfBwmmqhSMmONlvQIDAQABAoIBAQCJrBl3L8nWjayB\n\ - VL1ta5MTC+alCX8DfhyVmvQC7FqKN4dvKecqUe0vWXcj9cLhK4B3JdAtXfNLQOgZ\n\ - pYRoS2XsmjwiB20EFGtBrS+yBPvV/W0r7vrbfojHAdRXahBZhjl0ZAdrEvNgMfXt\n\ - Kr2YoXDhUQZFBCvzKmqSFfKnLRpEhsCBOsp+Sx0ZbP3yVPASXnqiZmKblpY4qcE5\n\ - KfYUO0nUWBSzY8I5c/29IY5oBbOUGS1DTMkx3R7V0BzbH/xmskVACn+cMzf467vp\n\ - yupTKG9hIX8ff0QH4Ggx88uQTRTI9IvfrAMnICFtR6U7g70hLN6j9ujXkPNhmycw\n\ - E5nQCmuBAoGBAPVbYtGBvnlySN73UrlyJ1NItUmOGhBt/ezpRjMIdMkJ6dihq7i2\n\ - RpE76sRvwHY9Tmw8oxR/V1ITK3dM2jZP1SRcm1mn5Y1D3K38jwFS0C47AXzIN2N+\n\ - LExekI1J4YOPV9o378vUKQuWpbQrQOOvylQBkRJ0Cd8DI3xhiBT/AVGbAoGBAO6Y\n\ - WBP3GMloO2v6PHijhRqrNdaI0qht8tDhO5L1troFLst3sfpK9fUP/KTlhHOzNVBF\n\ - fIJnNdcYAe9BISBbfSat+/R9F+GoUvpoC4j8ygHTQkT6ZMcMDfR8RQ4BlqGHIDKZ\n\ - YaAJoPZVkg7hNRMcvIruYpzFrheDE/4xvnC51GeHAoGAHzCFyFIw72lKwCU6e956\n\ - B0lH2ljZEVuaGuKwjM43YlMDSgmLNcjeAZpXRq9aDO3QKUwwAuwJIqLTNLAtURgm\n\ - 5R9slCIWuTV2ORvQ5f8r/aR8lOsyt1ATu4WN5JgOtdWj+laAAi4vJYz59YRGFGuF\n\ - UdZ9JZZgptvUR/xx+xFLjp8CgYBMRzghaeXqvgABTUb36o8rL4FOzP9MCZqPXPKG\n\ - 0TdR0UZcli+4LS7k4e+LaDUoKCrrNsvPhN+ZnHtB2jiU96rTKtxaFYQFCKM+mvTV\n\ - HrwWSUvucX62hAwSFYieKbPWgDSy+IZVe76SAllnmGg3bAB7CitMo4Y8zhMeORkB\n\ - QOe/EQKBgQDgeNgRud7S9BvaT3iT7UtizOr0CnmMfoF05Ohd9+VE4ogvLdAoDTUF\n\ - JFtdOT/0naQk0yqIwLDjzCjhe8+Ji5Y/21pjau8bvblTnASq26FRRjv5+hV8lmcR\n\ - zzk3Y05KXvJL75ksJdomkzZZb0q+Omf3wyjMR8Xl5WueJH1fh4hpBw==\n\ - -----END RSA PRIVATE KEY-----"; + const PKCS1_PEM: &str = "-----BEGIN RSA PRIVATE KEY-----\n\ + MIIEpAIBAAKCAQEA5Kz4i/+XZhiE+fyrgtx/4yI3i6C6HXbC4QJYpDuSUEKN2bO9\n\ + RsE+Fnds/FizHtJVWbvya9ktvKdDPBdy58+CIM46HEKJhYLnBVlkEcg9N2RNgR3x\n\ + HnpRbKfv+BmWjOpSmWrmJSDLY0dbw5X5YL8TU69ImoouCUfStyCgrpwkctR0GD3G\n\ + fcGjbZRucV7VvVH9bS1jyaT/9yORyzPOSTwb+K9vOr6XlJX0CGvzQeIOcOimejHx\n\ + ACFOCnhEKXiwMsmL8FMz0drkGeMuCODY/OHVmAdXDE5UhroL0oDhSmIrdZ8CxngO\n\ + xHr1WD2yC0X0jAVP/mrxjSSfBwmmqhSMmONlvQIDAQABAoIBAQCJrBl3L8nWjayB\n\ + VL1ta5MTC+alCX8DfhyVmvQC7FqKN4dvKecqUe0vWXcj9cLhK4B3JdAtXfNLQOgZ\n\ + pYRoS2XsmjwiB20EFGtBrS+yBPvV/W0r7vrbfojHAdRXahBZhjl0ZAdrEvNgMfXt\n\ + Kr2YoXDhUQZFBCvzKmqSFfKnLRpEhsCBOsp+Sx0ZbP3yVPASXnqiZmKblpY4qcE5\n\ + KfYUO0nUWBSzY8I5c/29IY5oBbOUGS1DTMkx3R7V0BzbH/xmskVACn+cMzf467vp\n\ + yupTKG9hIX8ff0QH4Ggx88uQTRTI9IvfrAMnICFtR6U7g70hLN6j9ujXkPNhmycw\n\ + E5nQCmuBAoGBAPVbYtGBvnlySN73UrlyJ1NItUmOGhBt/ezpRjMIdMkJ6dihq7i2\n\ + RpE76sRvwHY9Tmw8oxR/V1ITK3dM2jZP1SRcm1mn5Y1D3K38jwFS0C47AXzIN2N+\n\ + LExekI1J4YOPV9o378vUKQuWpbQrQOOvylQBkRJ0Cd8DI3xhiBT/AVGbAoGBAO6Y\n\ + WBP3GMloO2v6PHijhRqrNdaI0qht8tDhO5L1troFLst3sfpK9fUP/KTlhHOzNVBF\n\ + fIJnNdcYAe9BISBbfSat+/R9F+GoUvpoC4j8ygHTQkT6ZMcMDfR8RQ4BlqGHIDKZ\n\ + YaAJoPZVkg7hNRMcvIruYpzFrheDE/4xvnC51GeHAoGAHzCFyFIw72lKwCU6e956\n\ + B0lH2ljZEVuaGuKwjM43YlMDSgmLNcjeAZpXRq9aDO3QKUwwAuwJIqLTNLAtURgm\n\ + 5R9slCIWuTV2ORvQ5f8r/aR8lOsyt1ATu4WN5JgOtdWj+laAAi4vJYz59YRGFGuF\n\ + UdZ9JZZgptvUR/xx+xFLjp8CgYBMRzghaeXqvgABTUb36o8rL4FOzP9MCZqPXPKG\n\ + 0TdR0UZcli+4LS7k4e+LaDUoKCrrNsvPhN+ZnHtB2jiU96rTKtxaFYQFCKM+mvTV\n\ + HrwWSUvucX62hAwSFYieKbPWgDSy+IZVe76SAllnmGg3bAB7CitMo4Y8zhMeORkB\n\ + QOe/EQKBgQDgeNgRud7S9BvaT3iT7UtizOr0CnmMfoF05Ohd9+VE4ogvLdAoDTUF\n\ + JFtdOT/0naQk0yqIwLDjzCjhe8+Ji5Y/21pjau8bvblTnASq26FRRjv5+hV8lmcR\n\ + zzk3Y05KXvJL75ksJdomkzZZb0q+Omf3wyjMR8Xl5WueJH1fh4hpBw==\n\ + -----END RSA PRIVATE KEY-----"; #[test] fn private_key_from_rsa_pem() { - PrivateKey::from_pem(&RSA_PRIVATE_KEY_PEM.parse::().expect("pem")).expect("private key"); + PrivateKey::from_pem(&PKCS1_PEM.parse::().expect("pem")).expect("private key"); + } + + #[test] + fn check_pkcs1() { + let private_pkcs1_pem = PKCS1_PEM.parse::().expect("pem"); + let private = PrivateKey::from_pem(&private_pkcs1_pem).expect("private key"); + + let private_pkcs1 = private.to_pkcs1().unwrap(); + PrivateKey::from_pkcs1(&private_pkcs1).unwrap(); + assert_eq!(private_pkcs1, private_pkcs1_pem.data()); + + let public = private.to_public_key().unwrap(); + let public_pkcs1 = public.to_pkcs1().unwrap(); + PublicKey::from_pkcs1(&public_pkcs1).unwrap(); } const PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----\n\