//! Encode Rsa's Public Key as a Magic Public Key //! //! This implementation has been reverse-engineered from Mastodon's implementation, since no //! documentation for the Magic Public Key format could be found online (Maybe I didn't look hard //! enough). //! //! ### Examples //! From private key //! ```rust //! # let mut rng = rand::thread_rng(); //! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap(); //! use rsa_magic_public_key::AsMagicPublicKey; //! let string = private_key.as_magic_public_key(); //! ``` //! From public key //! ```rust //! # let mut rng = rand::thread_rng(); //! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap(); //! # let public_key = private_key.to_public_key(); //! use rsa_magic_public_key::AsMagicPublicKey; //! let string = public_key.as_magic_public_key(); //! ``` //! Parsing //! ```rust //! # use rsa_magic_public_key::AsMagicPublicKey; //! # let mut rng = rand::thread_rng(); //! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap(); //! # let magic_public_key = private_key.as_magic_public_key(); //! use rsa::RsaPublicKey; //! use rsa_magic_public_key::FromMagicPublicKey; //! let public_key = RsaPublicKey::from_magic_public_key(&magic_public_key).unwrap(); //! ``` use base64::{engine::general_purpose::URL_SAFE, Engine}; use num_bigint_dig::BigUint; use rsa::{traits::PublicKeyParts, RsaPublicKey}; use thiserror::Error; /// Helper trait to add functionality to Rsa types pub trait AsMagicPublicKey { /// Produce a magic-public-key formatted string fn as_magic_public_key(&self) -> String; } /// Helper trait to add functionality to Rsa types pub trait FromMagicPublicKey { /// Parse a type from a magic-public-key formatted string fn from_magic_public_key(magic_public_key: &str) -> Result where Self: Sized; } #[derive(Debug, Error)] /// Parsing errors pub enum KeyError { /// The magic-public-key is not properly formatted #[error("The provided key is malformed")] Malformed, /// The magic-public-key is not Rsa #[error("The provided key is of the wrong kind")] Kind, } impl AsMagicPublicKey for T where T: PublicKeyParts, { fn as_magic_public_key(&self) -> String { let n = URL_SAFE.encode(&self.n().to_bytes_be()); let e = URL_SAFE.encode(&self.e().to_bytes_be()); format!("Rsa.{}.{}", n, e) } } impl FromMagicPublicKey for RsaPublicKey { fn from_magic_public_key(magic_public_key: &str) -> Result { let mut iter = magic_public_key.split('.'); let kind = iter.next().ok_or(KeyError::Malformed)?; match kind { "Rsa" => (), _ => return Err(KeyError::Kind), }; let n = iter.next().ok_or(KeyError::Malformed)?; let e = iter.next().ok_or(KeyError::Malformed)?; let n = URL_SAFE.decode(n)?; let e = URL_SAFE.decode(e)?; let n = BigUint::from_bytes_be(&n); let e = BigUint::from_bytes_be(&e); RsaPublicKey::new(n, e).map_err(|_| KeyError::Malformed) } } impl From for KeyError { fn from(_: base64::DecodeError) -> Self { KeyError::Malformed } } #[cfg(test)] mod tests { use crate::{AsMagicPublicKey, FromMagicPublicKey}; use rsa::{RsaPrivateKey, RsaPublicKey}; #[test] fn can_complete_cycle() { let mut rng = rand::thread_rng(); let rsa = RsaPrivateKey::new(&mut rng, 2048).unwrap(); let string = rsa.as_magic_public_key(); let res = RsaPublicKey::from_magic_public_key(&string); assert!(res.is_ok()); } #[test] fn asonix_key_is_valid() { key_is_valid("Rsa.wEvEsHqM3twoC2F3KYMQ9YOialfVQX4StkLvhLUwFv8qpmY7ZSHHl2TWpnzlo5iWS5Pi2vC41HUGYz9XT5G74IUOyuIkjTL1FIcPJDcUFCzQjN3QZcHLPJPJVNOOOEiOk8__paOyrqJTq9ikcJDMJ8KTWQgk1leOxUVEN5uaQ-p9IBFbXC76-RqabfEoqLZagVMDSOfeC2uR9xZ1q5HkFveRTGs84QLR7FJVvx078nszx4UQGnmP0M-0sOeRJGK17IoJmhaok1XBpP6XFQ45vYeIRiaFj0Pc9GNISCW70dVXKMhv-K07orQJm6PwP8USyhq4tLkq6tcPbGRqEk3ZXw==.AQAB"); } #[test] fn sir_boops_key_is_valid() { key_is_valid("Rsa.vwDujxmxoYHs64MyVB3LG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ-3Zb6CI8zOO-nM-Q2llrVRYjZa4ZFnOLvMTq_Kf-Zf5wy2aCRer88gX-MsJOAtItSi412y0a_rKOuFaDYLOLeTkRvmGLgZWbsrZJOp-YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBOfOHggSt1-eAIKGIsCmINEMzs1mG9D75xKtC_sM8GfbvBclQcBstGkHAEj1VHPW0ch6Bok5_QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Qaw==.AQAB"); } fn key_is_valid(key: &str) { let res = RsaPublicKey::from_magic_public_key(key); assert!(res.is_ok()); assert_eq!(res.unwrap().as_magic_public_key(), key); } }