rsa-magic-public-key/src/lib.rs

140 lines
4.6 KiB
Rust
Raw Normal View History

2021-08-01 20:08:54 +00:00
//! Encode Rsa's Public Key as a Magic Public Key
2020-04-21 18:31:54 +00:00
//!
//! 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();
2021-08-01 20:08:54 +00:00
//! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap();
2020-04-21 18:31:54 +00:00
//! use rsa_magic_public_key::AsMagicPublicKey;
//! let string = private_key.as_magic_public_key();
//! ```
//! From public key
//! ```rust
//! # let mut rng = rand::thread_rng();
2021-08-01 20:08:54 +00:00
//! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap();
2020-04-21 18:31:54 +00:00
//! # 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();
2021-08-01 20:08:54 +00:00
//! # let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).unwrap();
2020-04-21 18:31:54 +00:00
//! # let magic_public_key = private_key.as_magic_public_key();
2021-08-01 20:08:54 +00:00
//! use rsa::RsaPublicKey;
2020-04-21 18:31:54 +00:00
//! use rsa_magic_public_key::FromMagicPublicKey;
2021-08-01 20:08:54 +00:00
//! let public_key = RsaPublicKey::from_magic_public_key(&magic_public_key).unwrap();
2020-04-21 18:31:54 +00:00
//! ```
2023-01-23 14:43:26 +00:00
use base64::{engine::general_purpose::URL_SAFE, Engine};
2019-10-01 00:23:36 +00:00
use num_bigint_dig::BigUint;
2023-04-28 00:02:04 +00:00
use rsa::{traits::PublicKeyParts, RsaPublicKey};
2020-04-21 18:31:54 +00:00
use thiserror::Error;
2019-10-01 00:23:36 +00:00
2021-08-01 20:08:54 +00:00
/// Helper trait to add functionality to Rsa types
2019-10-01 00:23:36 +00:00
pub trait AsMagicPublicKey {
2020-04-21 18:31:54 +00:00
/// Produce a magic-public-key formatted string
2019-10-01 00:23:36 +00:00
fn as_magic_public_key(&self) -> String;
}
2021-08-01 20:08:54 +00:00
/// Helper trait to add functionality to Rsa types
2019-10-01 00:23:36 +00:00
pub trait FromMagicPublicKey {
2020-04-21 18:31:54 +00:00
/// Parse a type from a magic-public-key formatted string
2019-10-01 00:23:36 +00:00
fn from_magic_public_key(magic_public_key: &str) -> Result<Self, KeyError>
where
Self: Sized;
}
2019-12-31 03:09:18 +00:00
#[derive(Debug, Error)]
2020-04-21 18:31:54 +00:00
/// Parsing errors
2019-10-01 00:23:36 +00:00
pub enum KeyError {
2020-04-21 18:31:54 +00:00
/// The magic-public-key is not properly formatted
2019-12-31 03:09:18 +00:00
#[error("The provided key is malformed")]
2019-10-01 00:23:36 +00:00
Malformed,
2021-08-01 20:08:54 +00:00
/// The magic-public-key is not Rsa
2019-12-31 03:09:18 +00:00
#[error("The provided key is of the wrong kind")]
2019-10-01 00:23:36 +00:00
Kind,
}
impl<T> AsMagicPublicKey for T
where
2023-04-28 00:02:04 +00:00
T: PublicKeyParts,
2019-10-01 00:23:36 +00:00
{
fn as_magic_public_key(&self) -> String {
2023-01-23 14:43:26 +00:00
let n = URL_SAFE.encode(&self.n().to_bytes_be());
let e = URL_SAFE.encode(&self.e().to_bytes_be());
2019-10-01 00:23:36 +00:00
2021-08-01 20:08:54 +00:00
format!("Rsa.{}.{}", n, e)
2019-10-01 00:23:36 +00:00
}
}
2021-08-01 20:08:54 +00:00
impl FromMagicPublicKey for RsaPublicKey {
2019-10-01 00:23:36 +00:00
fn from_magic_public_key(magic_public_key: &str) -> Result<Self, KeyError> {
let mut iter = magic_public_key.split('.');
let kind = iter.next().ok_or(KeyError::Malformed)?;
match kind {
2021-08-01 20:08:54 +00:00
"Rsa" => (),
2019-10-01 00:23:36 +00:00
_ => return Err(KeyError::Kind),
};
let n = iter.next().ok_or(KeyError::Malformed)?;
let e = iter.next().ok_or(KeyError::Malformed)?;
2023-01-23 14:43:26 +00:00
let n = URL_SAFE.decode(n)?;
let e = URL_SAFE.decode(e)?;
2019-10-01 00:23:36 +00:00
let n = BigUint::from_bytes_be(&n);
let e = BigUint::from_bytes_be(&e);
2021-08-01 20:08:54 +00:00
RsaPublicKey::new(n, e).map_err(|_| KeyError::Malformed)
2019-10-01 00:23:36 +00:00
}
}
impl From<base64::DecodeError> for KeyError {
fn from(_: base64::DecodeError) -> Self {
KeyError::Malformed
}
}
#[cfg(test)]
mod tests {
use crate::{AsMagicPublicKey, FromMagicPublicKey};
2021-08-01 20:08:54 +00:00
use rsa::{RsaPrivateKey, RsaPublicKey};
2019-10-01 00:23:36 +00:00
#[test]
fn can_complete_cycle() {
let mut rng = rand::thread_rng();
2021-08-01 20:08:54 +00:00
let rsa = RsaPrivateKey::new(&mut rng, 2048).unwrap();
2019-10-01 00:23:36 +00:00
let string = rsa.as_magic_public_key();
2021-08-01 20:08:54 +00:00
let res = RsaPublicKey::from_magic_public_key(&string);
2019-10-01 00:23:36 +00:00
assert!(res.is_ok());
}
#[test]
fn asonix_key_is_valid() {
2021-08-01 20:08:54 +00:00
key_is_valid("Rsa.wEvEsHqM3twoC2F3KYMQ9YOialfVQX4StkLvhLUwFv8qpmY7ZSHHl2TWpnzlo5iWS5Pi2vC41HUGYz9XT5G74IUOyuIkjTL1FIcPJDcUFCzQjN3QZcHLPJPJVNOOOEiOk8__paOyrqJTq9ikcJDMJ8KTWQgk1leOxUVEN5uaQ-p9IBFbXC76-RqabfEoqLZagVMDSOfeC2uR9xZ1q5HkFveRTGs84QLR7FJVvx078nszx4UQGnmP0M-0sOeRJGK17IoJmhaok1XBpP6XFQ45vYeIRiaFj0Pc9GNISCW70dVXKMhv-K07orQJm6PwP8USyhq4tLkq6tcPbGRqEk3ZXw==.AQAB");
2019-10-01 00:23:36 +00:00
}
#[test]
fn sir_boops_key_is_valid() {
2021-08-01 20:08:54 +00:00
key_is_valid("Rsa.vwDujxmxoYHs64MyVB3LG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ-3Zb6CI8zOO-nM-Q2llrVRYjZa4ZFnOLvMTq_Kf-Zf5wy2aCRer88gX-MsJOAtItSi412y0a_rKOuFaDYLOLeTkRvmGLgZWbsrZJOp-YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBOfOHggSt1-eAIKGIsCmINEMzs1mG9D75xKtC_sM8GfbvBclQcBstGkHAEj1VHPW0ch6Bok5_QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Qaw==.AQAB");
2019-10-01 00:23:36 +00:00
}
fn key_is_valid(key: &str) {
2021-08-01 20:08:54 +00:00
let res = RsaPublicKey::from_magic_public_key(key);
2019-10-01 00:23:36 +00:00
assert!(res.is_ok());
assert_eq!(res.unwrap().as_magic_public_key(), key);
}
}