#![deny(missing_docs)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

//! `rsa_export` allows you to export your RSA key, generated via the [rsa crate](https://crates.io/crates/rsa), with PKCS#1 or PKCS#8 encoding  
//!
//! **Note**: Multi-prime keys are not supported
//!
//! The keys can also be exported into the PEM format by enabling the feature `pem`
//!
//! Example:  
//!
//! ```rust
//! use rsa::RSAPrivateKey;
//! use rand::rngs::OsRng;
//! use rsa_export::Encode;
//!
//! let mut rng = OsRng;
//! let private_key = RSAPrivateKey::new(&mut rng, 2048).unwrap();
//! let public_key = private_key.to_public_key();
//!
//! let pkcs1_encoded_private = private_key.as_pkcs1().unwrap();
//! let pkcs1_encoded_public = public_key.as_pkcs1().unwrap();
//!
//! let pkcs8_encoded_private = private_key.as_pkcs8().unwrap();
//! let pkcs8_encoded_public = public_key.as_pkcs8().unwrap();
//! ```
//!
//! Encode PKCS#1 or PKCS#8 encoded keys into the PEM format
//!
//! ```rust
//! use rsa::RSAPrivateKey;
//! use rand::rngs::OsRng;
//! use rsa_export::PemEncode;
//!
//! let mut rng = OsRng;
//! let private_key = RSAPrivateKey::new(&mut rng, 2048).unwrap();
//! let public_key = private_key.to_public_key();
//!
//! let pkcs1_encoded_private_pem = private_key.as_pkcs1_pem().unwrap();
//! let pkcs1_encoded_public_pem = public_key.as_pkcs1_pem().unwrap();
//!
//! let pkcs8_encoded_private_pem = private_key.as_pkcs8_pem().unwrap();
//! let pkcs8_encoded_public_pem = public_key.as_pkcs8_pem().unwrap();
//! ```
//!
//! Encode PKCS#1 or PKCS#8 encoded keys into the PEM format with a custom line ending
//!
//! ```rust
//! use rsa::RSAPrivateKey;
//! use rsa_export::{PemEncode, LineEnding};
//! use rand::rngs::OsRng;
//!
//! let mut rng = OsRng;
//! let private_key = RSAPrivateKey::new(&mut rng, 2048).unwrap();
//! let public_key = private_key.to_public_key();
//!
//! let pkcs1_encoded_private_pem = private_key
//!     .as_pkcs1_pem_custom_ending(LineEnding::CRLF)
//!     .unwrap();
//! let pkcs1_encoded_public_pem = public_key
//!     .as_pkcs1_pem_custom_ending(LineEnding::CRLF)
//!     .unwrap();
//!
//! let pkcs8_encoded_private_pem = private_key
//!     .as_pkcs1_pem_custom_ending(LineEnding::CRLF)
//!     .unwrap();
//! let pkcs8_encoded_public_pem = public_key
//!    .as_pkcs1_pem_custom_ending(LineEnding::CRLF)
//!    .unwrap();
//! ```
//!

#[cfg(feature = "pem")]
extern crate pem as pem_crate;

use {
    crate::error::Error,
    num_bigint_dig::ToBigInt,
    rsa::{RSAPrivateKey, RSAPublicKey},
};

fn rsa_oid() -> simple_asn1::OID {
    use simple_asn1::{BigUint, OID};

    simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1)
}

fn to_bigint(biguint: &rsa::BigUint) -> simple_asn1::BigInt {
    simple_asn1::BigInt::from_signed_bytes_le(
        biguint.to_bigint().unwrap().to_signed_bytes_le().as_slice(),
    )
}

/// Trait for encoding the keys
pub trait Encode: sealed::Sealed {
    /// Encode in the PKCS#1 format
    fn as_pkcs1(&self) -> Result<Vec<u8>, Error>;
    /// Encode in the PKCS#8 format
    fn as_pkcs8(&self) -> Result<Vec<u8>, Error>;
}

impl Encode for RSAPrivateKey {
    fn as_pkcs1(&self) -> Result<Vec<u8>, Error> {
        pkcs1::private_key(self)
    }

    fn as_pkcs8(&self) -> Result<Vec<u8>, Error> {
        pkcs8::private_key(self)
    }
}

impl Encode for RSAPublicKey {
    fn as_pkcs1(&self) -> Result<Vec<u8>, Error> {
        pkcs1::public_key(self)
    }

    fn as_pkcs8(&self) -> Result<Vec<u8>, Error> {
        pkcs8::public_key(self)
    }
}

/// Trait for encoding the keys and wrapping them in the PEM format  
pub trait PemEncode: sealed::Sealed + Encode {
    /// Encode in the PKCS#1 format, PEM encoded
    fn as_pkcs1_pem(&self) -> Result<String, Error>;
    /// Encode in the PKCS#8 format, PEM encoded
    fn as_pkcs8_pem(&self) -> Result<String, Error>;

    /// Encode in the PKCS#1 format, PEM encoded with a custom line ending
    fn as_pkcs1_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error>;
    /// Encode in the PKCS#8 format, PEM encoded with a custom line ending
    fn as_pkcs8_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error>;
}

impl PemEncode for RSAPrivateKey {
    /// Line endings default to Unix-style LF  
    fn as_pkcs1_pem(&self) -> Result<String, Error> {
        self.as_pkcs1_pem_custom_ending(LineEnding::LF)
    }

    /// Line endings default to Unix-style LF
    fn as_pkcs8_pem(&self) -> Result<String, Error> {
        self.as_pkcs8_pem_custom_ending(LineEnding::LF)
    }

    /// Encode with a different line ending
    fn as_pkcs1_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error> {
        let pkcs1_der = self.as_pkcs1()?;

        Ok(pem::encode(
            pem::EncodingScheme::PKCS1,
            pem::KeyType::Private,
            line_ending,
            pkcs1_der,
        ))
    }

    /// Encode with a different line ending
    fn as_pkcs8_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error> {
        let pkcs8_der = self.as_pkcs8()?;

        Ok(pem::encode(
            pem::EncodingScheme::PKCS8,
            pem::KeyType::Private,
            line_ending,
            pkcs8_der,
        ))
    }
}

impl PemEncode for RSAPublicKey {
    /// Line endings default to Unix-style LF  
    fn as_pkcs1_pem(&self) -> Result<String, Error> {
        self.as_pkcs1_pem_custom_ending(LineEnding::LF)
    }

    /// Line endings default to Unix-style LF
    fn as_pkcs8_pem(&self) -> Result<String, Error> {
        self.as_pkcs8_pem_custom_ending(LineEnding::LF)
    }

    /// Encode with a different line ending
    fn as_pkcs1_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error> {
        let pkcs1_der = self.as_pkcs1()?;

        Ok(pem::encode(
            pem::EncodingScheme::PKCS1,
            pem::KeyType::Public,
            line_ending,
            pkcs1_der,
        ))
    }

    /// Encode with a different line ending
    fn as_pkcs8_pem_custom_ending(&self, line_ending: LineEnding) -> Result<String, Error> {
        let pkcs8_der = self.as_pkcs8()?;

        Ok(pem::encode(
            pem::EncodingScheme::PKCS8,
            pem::KeyType::Public,
            line_ending,
            pkcs8_der,
        ))
    }
}

#[cfg(feature = "pem")]
pub use pem_crate::LineEnding;

mod sealed {
    pub trait Sealed {}

    impl Sealed for rsa::RSAPrivateKey {}
    impl Sealed for rsa::RSAPublicKey {}
}

/// Error type
pub mod error;

/// Encode a public/private key with PKCS#1
mod pkcs1;
/// Encode a public/private key with PKCS#8
mod pkcs8;

#[cfg(feature = "pem")]
/// Encode a public/private key encoded with PKCS#1 or PKCS#8 into the PEM format
mod pem;

#[cfg(test)]
mod test;
