apub-openssl 0.2.0

Utilities for building activitypub servers
Documentation
//! OpenSSL-backed cryptography for apub

#![deny(missing_docs)]

use openssl::{
    error::ErrorStack,
    hash::MessageDigest,
    pkey::{PKey, Private, Public},
    sha::Sha256,
    sign::{Signer, Verifier},
};
use std::fmt::Debug;

/// A sha256 digest
#[derive(Clone)]
pub struct OpenSslDigest {
    digest: Sha256,
}

/// An RSA private key message signer
pub struct OpenSslSigner {
    private_key: PKey<Private>,
}

/// An RSA public key message verifier
pub struct OpenSslVerifier {
    public_key: PKey<Public>,
}

/// A type representing an actor's cryptography information
///
/// It includes a private key, plus its ID
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct OpenSsl {
    key_id: String,
    #[serde(with = "openssl_private_key")]
    private_key: PKey<Private>,
}

impl OpenSsl {
    /// Create a new OpenSsl cryptography type from a given key and ID
    pub fn new(key_id: String, private_key: PKey<Private>) -> Self {
        Self {
            key_id,
            private_key,
        }
    }
}

impl apub_core::digest::Digest for OpenSslDigest {
    const NAME: &'static str = "SHA-256";

    fn digest(&mut self, input: &[u8]) -> String {
        self.digest.update(input);
        let bytes = self.digest.clone().finish();

        openssl::base64::encode_block(&bytes)
    }

    fn update(&mut self, input: &[u8]) {
        self.digest.update(input);
    }

    fn verify(&mut self, encoded: &str) -> bool {
        let bytes = self.digest.clone().finish();

        openssl::base64::encode_block(&bytes) == encoded
    }
}

impl apub_core::digest::DigestBuilder for OpenSslDigest {
    fn build() -> Self {
        OpenSslDigest {
            digest: Sha256::new(),
        }
    }
}

impl apub_core::signature::Sign for OpenSslSigner {
    type Error = ErrorStack;

    fn sign(&self, signing_string: &str) -> Result<String, Self::Error> {
        let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?;
        signer.update(signing_string.as_bytes())?;

        Ok(openssl::base64::encode_block(&signer.sign_to_vec()?))
    }
}

impl apub_core::signature::Verify for OpenSslVerifier {
    type Error = ErrorStack;

    fn verify(&self, signing_string: &str, signature: &str) -> Result<bool, Self::Error> {
        let mut verifier = Verifier::new(MessageDigest::sha256(), &self.public_key)?;
        verifier.update(signing_string.as_bytes())?;
        verifier.verify(&openssl::base64::decode_block(signature)?)
    }
}

impl apub_core::signature::VerifyBuilder for OpenSslVerifier {
    fn build(public_key_pem: &str) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        Ok(OpenSslVerifier {
            public_key: PKey::public_key_from_pem(public_key_pem.as_bytes())?,
        })
    }
}

impl apub_core::digest::DigestFactory for OpenSsl {
    type Digest = OpenSslDigest;
}

impl apub_core::signature::PrivateKey for OpenSsl {
    type Signer = OpenSslSigner;

    fn key_id(&self) -> String {
        self.key_id.clone()
    }

    fn signer(&self) -> Self::Signer {
        OpenSslSigner {
            private_key: self.private_key.clone(),
        }
    }
}

impl apub_core::signature::PrivateKeyBuilder for OpenSsl {
    type Error = ErrorStack;

    fn build(key_id: String, private_key_pem: &str) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        Ok(Self {
            key_id,
            private_key: PKey::private_key_from_pem(private_key_pem.as_bytes())?,
        })
    }

    fn private_key_pem(&self) -> Result<String, Self::Error> {
        self.private_key
            .private_key_to_pem_pkcs8()
            .map(|v| String::from_utf8_lossy(&v).to_string())
    }
}

impl Debug for OpenSslDigest {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OpenSslDigest")
            .field("digest", &"Sha256")
            .finish()
    }
}

impl Debug for OpenSslSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OpenSslSigner")
            .field("private_key", &"hidden")
            .finish()
    }
}

impl Debug for OpenSsl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OpenSsl")
            .field("key_id", &self.key_id)
            .field("private_key", &"hidden")
            .finish()
    }
}

mod openssl_private_key {
    use openssl::pkey::{PKey, Private};
    use serde::{
        de::{Deserialize, Deserializer},
        ser::{Serialize, Serializer},
    };

    pub(super) fn serialize<S: Serializer>(
        private_key: &PKey<Private>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        use serde::ser::Error;

        let der = private_key.private_key_to_der().map_err(S::Error::custom)?;
        let der_string = openssl::base64::encode_block(&der);

        String::serialize(&der_string, serializer)
    }

    pub(super) fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<PKey<Private>, D::Error> {
        use serde::de::Error;

        let der_string = String::deserialize(deserializer)?;
        let der = openssl::base64::decode_block(&der_string).map_err(D::Error::custom)?;

        PKey::<Private>::private_key_from_der(&der).map_err(D::Error::custom)
    }
}

#[cfg(test)]
mod tests {
    use super::OpenSsl;
    use apub_core::signature::{PrivateKey, Sign};
    use openssl::{pkey::PKey, rsa::Rsa};

    #[test]
    fn round_trip() {
        let private_key = PKey::from_rsa(Rsa::generate(1024).unwrap()).unwrap();
        let crypto = OpenSsl::new("key-id".into(), private_key);
        let signer = crypto.signer();

        let first_sign = signer.sign("hello").unwrap();

        let s = serde_json::to_string(&crypto).unwrap();
        let crypto2: OpenSsl = serde_json::from_str(&s).unwrap();
        let signer2 = crypto2.signer();

        let second_sign = signer2.sign("hello").unwrap();

        let s2 = serde_json::to_string(&crypto2).unwrap();

        assert_eq!(s, s2);
        assert_eq!(first_sign, second_sign);
    }
}