#![deny(missing_docs)]
use rsa::{
hash::Hash,
pkcs8::{FromPrivateKey, FromPublicKey, ToPrivateKey},
PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
};
use sha2::{Digest, Sha256};
use std::fmt::Debug;
#[derive(Debug, thiserror::Error)]
pub enum RustcryptoError {
#[error(transparent)]
Pkcs8(#[from] rsa::pkcs8::Error),
#[error(transparent)]
Rsa(#[from] rsa::errors::Error),
#[error(transparent)]
Base64(#[from] base64::DecodeError),
}
#[derive(Debug, Clone)]
pub struct Sha256Digest {
digest: Sha256,
}
pub struct RsaSigner {
private_key: RsaPrivateKey,
}
pub struct RsaVerifier {
public_key: RsaPublicKey,
}
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct Rustcrypto {
key_id: String,
#[serde(with = "rsa_signer")]
private_key: RsaPrivateKey,
}
impl Rustcrypto {
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);
}
}