diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 989e219af..830b8d3bd 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -293,6 +293,88 @@ where } } +#[cfg(feature = "pem")] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum PemParseError { + ///Indicates invalid PEM string + Pem(pem_rfc7468::Error), + ///Indicates invalid pkcs8 EC key + Pkcs8(::pkcs8::Error), + ///Indicates invalid Sec1 EC key + Sec1(::sec1::Error), + ///Unable to recognize document label + UnknownLabel, +} + +#[cfg(feature = "pem")] +impl From for PemParseError { + #[inline(always)] + fn from(error: pem_rfc7468::Error) -> Self { + Self::Pem(error) + } +} + +#[cfg(feature = "pem")] +impl From<::pkcs8::Error> for PemParseError { + #[inline(always)] + fn from(error: ::pkcs8::Error) -> Self { + Self::Pkcs8(error) + } +} + +#[cfg(feature = "pem")] +impl From<::sec1::Error> for PemParseError { + #[inline(always)] + fn from(error: ::sec1::Error) -> Self { + Self::Sec1(error) + } +} + +#[cfg(feature = "pem")] +impl fmt::Display for PemParseError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pem(error) => fmt.write_fmt(format_args!("Failed to parse PEM: {error}")), + Self::UnknownLabel => fmt.write_str("Unrecognized key label"), + Self::Pkcs8(error) => fmt.write_fmt(format_args!("Faoled to parse Pkcs8 key: {error}")), + Self::Sec1(error) => fmt.write_fmt(format_args!("Faoled to parse SEC1 key: {error}")), + } + } +} + +#[cfg(feature = "pem")] +impl core::error::Error for PemParseError {} + +#[cfg(feature = "pem")] +impl SecretKey +where + C: AssociatedOid + Curve + ValidatePublicKey, + FieldBytesSize: ModulusSize, +{ + /// Parse [`SecretKey`] from PEM-encoded private key. + /// + /// Supported formats: + /// - `SEC1` - requires feature `sec1` + /// - `PKCS #8` - requires feature `pkcs8` + /// + /// # Errors + /// - If `pem` is not valid PEM encoded private key + /// - If label within `pem` is not known valid label + /// - If label is valid, but unable to decode DER content of the PEM file + #[cfg(feature = "pem")] + pub fn from_pem(pem: &str) -> ::core::result::Result { + let label = pem_rfc7468::decode_label(pem.as_bytes()).map_err(PemParseError::Pem)?; + + if ::pkcs8::PrivateKeyInfoRef::validate_pem_label(label).is_ok() { + return ::pkcs8::DecodePrivateKey::from_pkcs8_pem(pem).map_err(PemParseError::Pkcs8); + } else if ::sec1::EcPrivateKey::validate_pem_label(label).is_ok() { + return ::sec1::DecodeEcPrivateKey::from_sec1_pem(pem).map_err(PemParseError::Sec1); + } + + Err(PemParseError::UnknownLabel) + } +} + impl ConstantTimeEq for SecretKey where C: Curve, diff --git a/elliptic-curve/tests/pkcs8.rs b/elliptic-curve/tests/pkcs8.rs index 7320de2b6..5291e481c 100644 --- a/elliptic-curve/tests/pkcs8.rs +++ b/elliptic-curve/tests/pkcs8.rs @@ -23,17 +23,34 @@ const EXAMPLE_SCALAR: [u8; 32] = hex!("AABBCCDDEEFF0000000000000000000000000000000000000000000000000001"); /// Example PKCS#8 private key -fn example_private_key() -> der::SecretDocument { +fn example_private_key_der() -> der::SecretDocument { SecretKey::from_slice(&EXAMPLE_SCALAR) .unwrap() .to_pkcs8_der() .unwrap() } +#[cfg(feature = "pem")] +/// Example PKCS#8 private key +fn example_private_key_pem() -> impl AsRef { + SecretKey::from_slice(&EXAMPLE_SCALAR) + .unwrap() + .to_pkcs8_pem(Default::default()) + .unwrap() +} + #[test] fn decode_pkcs8_private_key_from_der() { - dbg!(example_private_key().as_bytes()); - let secret_key = SecretKey::from_pkcs8_der(example_private_key().as_bytes()).unwrap(); + dbg!(example_private_key_der().as_bytes()); + let secret_key = SecretKey::from_pkcs8_der(example_private_key_der().as_bytes()).unwrap(); + assert_eq!(secret_key.to_bytes().as_slice(), &EXAMPLE_SCALAR); +} + +#[cfg(feature = "pem")] +#[test] +fn decode_pkcs8_private_key_from_pem() { + dbg!(example_private_key_pem().as_ref()); + let secret_key = SecretKey::from_pem(example_private_key_pem().as_ref()).unwrap(); assert_eq!(secret_key.to_bytes().as_slice(), &EXAMPLE_SCALAR); }