apub-rustcrypto 0.2.0

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

#![deny(missing_docs)]

use rsa::{
    hash::Hash,
    pkcs8::{FromPrivateKey, FromPublicKey, ToPrivateKey},
    PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
};
use sha2::{Digest, Sha256};
use std::fmt::Debug;

/// Errors that can be produced when signing, verifying, decoding, or otherwise interacting with
/// rustcrypto types
#[derive(Debug, thiserror::Error)]
pub enum RustcryptoError {
    /// There was an error producing a key from PKCS8
    #[error(transparent)]
    Pkcs8(#[from] rsa::pkcs8::Error),

    /// There was an error signing or verifying a message
    #[error(transparent)]
    Rsa(#[from] rsa::errors::Error),

    /// There was an error decoding a signature
    #[error(transparent)]
    Base64(#[from] base64::DecodeError),
}

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

/// An RSA private key message signer
pub struct RsaSigner {
    private_key: RsaPrivateKey,
}

/// An RSA public key message verifier
pub struct RsaVerifier {
    public_key: RsaPublicKey,
}

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

impl Rustcrypto {
    /// Create a new rustcrypto cryptography type from a private key and ID
    pub fn new(key_id: String, private_key: RsaPrivateKey) -> Self {
        Self {
            key_id,
            private_key,
        }
    }
}

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

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

        base64::encode(&bytes)
    }

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

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

        base64::encode(&bytes) == encoded
    }
}

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

impl apub_core::signature::Sign for RsaSigner {
    type Error = RustcryptoError;

    fn sign(&self, signing_string: &str) -> Result<String, Self::Error> {
        let hashed = Sha256::digest(signing_string.as_bytes());
        let bytes = self.private_key.sign(
            PaddingScheme::PKCS1v15Sign {
                hash: Some(Hash::SHA2_256),
            },
            &hashed,
        )?;
        Ok(base64::encode(bytes))
    }
}

impl apub_core::signature::Verify for RsaVerifier {
    type Error = RustcryptoError;

    fn verify(&self, signing_string: &str, signature: &str) -> Result<bool, Self::Error> {
        let decoded = base64::decode(signature)?;
        let hashed = Sha256::digest(signing_string.as_bytes());
        self.public_key.verify(
            PaddingScheme::PKCS1v15Sign {
                hash: Some(Hash::SHA2_256),
            },
            &hashed,
            &decoded,
        )?;
        Ok(true)
    }
}

impl apub_core::signature::VerifyBuilder for RsaVerifier {
    fn build(public_key_pem: &str) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        Ok(RsaVerifier {
            public_key: RsaPublicKey::from_public_key_pem(public_key_pem)?,
        })
    }
}

impl apub_core::digest::DigestFactory for Rustcrypto {
    type Digest = Sha256Digest;
}

impl apub_core::signature::PrivateKey for Rustcrypto {
    type Signer = RsaSigner;

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

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

impl apub_core::signature::PrivateKeyBuilder for Rustcrypto {
    type Error = RustcryptoError;

    fn build(key_id: String, private_key_pem: &str) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        Ok(Rustcrypto {
            key_id,
            private_key: RsaPrivateKey::from_pkcs8_pem(private_key_pem)?,
        })
    }

    fn private_key_pem(&self) -> Result<String, Self::Error> {
        self.private_key
            .to_pkcs8_pem()
            .map(|s| (*s).clone())
            .map_err(RustcryptoError::from)
    }
}

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

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

mod rsa_signer {
    use rsa::{
        pkcs1::{FromRsaPrivateKey, ToRsaPrivateKey},
        RsaPrivateKey,
    };
    use serde::{
        de::{Deserialize, Deserializer},
        ser::{Serialize, Serializer},
    };

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

        let der = private_key.to_pkcs1_der().map_err(S::Error::custom)?;
        let der_string = base64::encode(der.as_der());

        String::serialize(&der_string, serializer)
    }

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

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

        RsaPrivateKey::from_pkcs1_der(&der).map_err(D::Error::custom)
    }
}

#[cfg(test)]
mod tests {
    use super::Rustcrypto;
    use apub_core::signature::{PrivateKey, Sign};
    use rsa::RsaPrivateKey;

    #[test]
    fn round_trip() {
        let private_key = RsaPrivateKey::new(&mut rand::thread_rng(), 1024).unwrap();
        let crypto = Rustcrypto::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: Rustcrypto = 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);
    }
}