diff --git a/ext_federation/src/ap_client.rs b/ext_federation/src/ap_client.rs index 552712f..4731306 100644 --- a/ext_federation/src/ap_client.rs +++ b/ext_federation/src/ap_client.rs @@ -11,9 +11,10 @@ use url::Url; use magnetar_core::web_model::content_type::ContentActivityStreams; use crate::{ - ApClientService, - ApSignature, - ApSigningField, ApSigningHeaders, client::federation_client::{FederationClient, FederationClientError}, crypto::{ApSigningError, ApSigningKey, SigningAlgorithm}, SigningInput, SigningParts, + client::federation_client::{FederationClient, FederationClientError}, + crypto::{ApSigningError, ApSigningKey, SigningAlgorithm}, + ApClientService, ApSignature, ApSigningField, + ApSigningHeaders, SigningInput, SigningParts, }; pub struct ApClientServiceDefaultProvider { @@ -414,9 +415,9 @@ mod test { use crate::{ ap_client::ApClientServiceDefaultProvider, - ApClientService, client::federation_client::FederationClient, crypto::{ApHttpPrivateKey, SigningAlgorithm}, + ApClientService, }; #[tokio::test] @@ -440,7 +441,7 @@ mod test { let val = ap_client .signed_get( - ApHttpPrivateKey::Rsa(Box::new(Cow::Owned(rsa_key))) + ApHttpPrivateKey::Rsa(Cow::Owned(Box::new(rsa_key))) .create_signing_key(&key_id, SigningAlgorithm::RsaSha256) .into_diagnostic()?, SigningAlgorithm::RsaSha256, diff --git a/ext_federation/src/crypto.rs b/ext_federation/src/crypto.rs index 7c86a34..647cbdd 100644 --- a/ext_federation/src/crypto.rs +++ b/ext_federation/src/crypto.rs @@ -1,12 +1,16 @@ -use std::{borrow::Cow, fmt::Display}; - +use rsa::pkcs1::DecodeRsaPrivateKey; +use rsa::pkcs1::DecodeRsaPublicKey; +use rsa::pkcs8::DecodePrivateKey; +use rsa::pkcs8::DecodePublicKey; use rsa::signature::Verifier; use rsa::{ sha2::{Sha256, Sha512}, signature::Signer, }; - use serde::{Deserialize, Serialize}; +use std::fmt::Formatter; +use std::str::FromStr; +use std::{borrow::Cow, fmt::Display}; use strum::AsRefStr; use thiserror::Error; @@ -35,7 +39,7 @@ pub enum SigningAlgorithm { } impl Display for SigningAlgorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Hs2019 => write!(f, "hs2019"), Self::RsaSha256 => write!(f, "rsa-sha256"), @@ -61,6 +65,59 @@ pub enum ApHttpPublicKey<'a> { Ed25519(Cow<'a, ed25519_dalek::VerifyingKey>), } +#[derive(Debug, Copy, Clone, Error)] +pub struct ApHttpPublicKeyParseError; + +impl Display for ApHttpPublicKeyParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Failed to parse the public key: No available parser could decode the PEM string" + ) + } +} + +impl FromStr for ApHttpPublicKey<'_> { + type Err = ApHttpPublicKeyParseError; + + fn from_str(input_pem: &str) -> Result { + let pem = input_pem.trim(); + + let parse_pkcs1_rsa: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPublicKey::Rsa(Cow::Owned( + rsa::RsaPublicKey::from_pkcs1_pem(p).ok()?, + ))) + }; + let parse_spki_rsa: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPublicKey::Rsa(Cow::Owned( + rsa::RsaPublicKey::from_public_key_pem(p).ok()?, + ))) + }; + let parse_spki_ed25519: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPublicKey::Ed25519(Cow::Owned( + ed25519_dalek::VerifyingKey::from_public_key_pem(p).ok()?, + ))) + }; + + // Some heuristics + let parsers: &[_] = match pem { + p if p.starts_with("-----BEGIN PUBLIC KEY-----") => { + &[parse_spki_rsa, parse_spki_ed25519] + } + p if p.starts_with("-----BEGIN RSA PUBLIC KEY-----") => &[parse_pkcs1_rsa], + _ => &[parse_spki_rsa, parse_spki_ed25519, parse_pkcs1_rsa], + }; + + for parser in parsers { + if let Some(k) = parser(pem) { + return Ok(k); + } + } + + Err(ApHttpPublicKeyParseError) + } +} + impl ApHttpVerificationKey<'_> { pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), ApVerificationError> { match self { @@ -109,12 +166,10 @@ impl ApHttpPublicKey<'_> { )); Ok(verification_key.verify(message, signature)?) } - (_, SigningAlgorithm::RsaSha256) => { - return Err(ApVerificationError::KeyAlgorithmMismatch( - algorithm, - self.as_ref().to_owned(), - )); - } + (_, SigningAlgorithm::RsaSha256) => Err(ApVerificationError::KeyAlgorithmMismatch( + algorithm, + self.as_ref().to_owned(), + )), (Self::Ed25519(key), SigningAlgorithm::Hs2019) => { let verification_key = ApHttpVerificationKey::Ed25519(Cow::Borrowed(key.as_ref())); Ok(verification_key.verify(message, signature)?) @@ -126,17 +181,72 @@ impl ApHttpPublicKey<'_> { #[derive(Debug, Clone, AsRefStr)] pub enum ApHttpPrivateKey<'a> { #[strum(serialize = "rsa")] - Rsa(Box>), + Rsa(Cow<'a, Box>), #[strum(serialize = "ed25519")] Ed25519(Cow<'a, ed25519_dalek::SecretKey>), } +#[derive(Debug, Copy, Clone, Error)] +pub struct ApHttpPrivateKeyParseError; + +impl Display for ApHttpPrivateKeyParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Failed to parse the private key: No available parser could decode the PEM string" + ) + } +} + +impl FromStr for ApHttpPrivateKey<'_> { + type Err = ApHttpPrivateKeyParseError; + + fn from_str(input_pem: &str) -> Result { + let pem = input_pem.trim(); + + let parse_pkcs1_rsa: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPrivateKey::Rsa(Cow::Owned(Box::new( + rsa::RsaPrivateKey::from_pkcs1_pem(p).ok()?, + )))) + }; + let parse_pkcs8_rsa: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPrivateKey::Rsa(Cow::Owned(Box::new( + rsa::RsaPrivateKey::from_pkcs8_pem(p).ok()?, + )))) + }; + let parse_pkcs8_ed25519: &dyn Fn(_) -> _ = &|p| { + Some(ApHttpPrivateKey::Ed25519(Cow::Owned( + ed25519_dalek::SigningKey::from_pkcs8_pem(p) + .ok()? + .to_bytes(), + ))) + }; + + // Some heuristics + let parsers: &[_] = match pem { + p if p.contains("-----BEGIN PRIVATE KEY-----") => { + &[parse_pkcs8_rsa, parse_pkcs8_ed25519] + } + p if p.contains("-----BEGIN RSA PRIVATE KEY-----") => &[parse_pkcs1_rsa], + _ => &[parse_pkcs8_rsa, parse_pkcs8_ed25519, parse_pkcs1_rsa], + }; + + for parser in parsers { + if let Some(k) = parser(pem) { + return Ok(k); + } + } + + Err(ApHttpPrivateKeyParseError) + } +} + #[derive(Debug, Clone, AsRefStr)] pub enum ApHttpSigningKey<'a> { #[strum(serialize = "rsa-sha256")] - RsaSha256(Cow<'a, rsa::pkcs1v15::SigningKey>), + RsaSha256(Cow<'a, rsa::pkcs1v15::SigningKey>), #[strum(serialize = "rsa-sha512")] - RsaSha512(Cow<'a, rsa::pkcs1v15::SigningKey>), + RsaSha512(Cow<'a, rsa::pkcs1v15::SigningKey>), #[strum(serialize = "ed25519")] Ed25519(Cow<'a, ed25519_dalek::SigningKey>), } @@ -192,7 +302,7 @@ impl ApHttpPrivateKey<'_> { key: match (self, algorithm) { (Self::Rsa(key), SigningAlgorithm::RsaSha256 | SigningAlgorithm::Hs2019) => { ApHttpSigningKey::RsaSha256(Cow::Owned(rsa::pkcs1v15::SigningKey::new( - key.clone().into_owned(), + *key.as_ref().to_owned(), ))) } (Self::Ed25519(key), SigningAlgorithm::Hs2019) => ApHttpSigningKey::Ed25519(