apub_rustcrypto/
lib.rs

1//! rustcrypto-backed cryptography for apub
2
3#![deny(missing_docs)]
4
5use rsa::{
6    hash::Hash,
7    pkcs8::{FromPrivateKey, FromPublicKey, ToPrivateKey},
8    PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
9};
10use sha2::{Digest, Sha256};
11use std::fmt::Debug;
12
13/// Errors that can be produced when signing, verifying, decoding, or otherwise interacting with
14/// rustcrypto types
15#[derive(Debug, thiserror::Error)]
16pub enum RustcryptoError {
17    /// There was an error producing a key from PKCS8
18    #[error(transparent)]
19    Pkcs8(#[from] rsa::pkcs8::Error),
20
21    /// There was an error signing or verifying a message
22    #[error(transparent)]
23    Rsa(#[from] rsa::errors::Error),
24
25    /// There was an error decoding a signature
26    #[error(transparent)]
27    Base64(#[from] base64::DecodeError),
28}
29
30/// A sha256 digest
31#[derive(Debug, Clone)]
32pub struct Sha256Digest {
33    digest: Sha256,
34}
35
36/// An RSA private key message signer
37pub struct RsaSigner {
38    private_key: RsaPrivateKey,
39}
40
41/// An RSA public key message verifier
42pub struct RsaVerifier {
43    public_key: RsaPublicKey,
44}
45
46/// A type representing an actor's cryptography information
47///
48/// It includes a private key, plus its ID
49#[derive(Clone, serde::Deserialize, serde::Serialize)]
50pub struct Rustcrypto {
51    key_id: String,
52    #[serde(with = "rsa_signer")]
53    private_key: RsaPrivateKey,
54}
55
56impl Rustcrypto {
57    /// Create a new rustcrypto cryptography type from a private key and ID
58    pub fn new(key_id: String, private_key: RsaPrivateKey) -> Self {
59        Self {
60            key_id,
61            private_key,
62        }
63    }
64}
65
66impl apub_core::digest::Digest for Sha256Digest {
67    const NAME: &'static str = "SHA-256";
68
69    fn digest(&mut self, input: &[u8]) -> String {
70        self.digest.update(input);
71        let bytes = self.digest.finalize_reset();
72
73        base64::encode(&bytes)
74    }
75
76    fn update(&mut self, input: &[u8]) {
77        self.digest.update(input);
78    }
79
80    fn verify(&mut self, encoded: &str) -> bool {
81        let bytes = self.digest.finalize_reset();
82
83        base64::encode(&bytes) == encoded
84    }
85}
86
87impl apub_core::digest::DigestBuilder for Sha256Digest {
88    fn build() -> Self {
89        Sha256Digest {
90            digest: Sha256::new(),
91        }
92    }
93}
94
95impl apub_core::signature::Sign for RsaSigner {
96    type Error = RustcryptoError;
97
98    fn sign(&self, signing_string: &str) -> Result<String, Self::Error> {
99        let hashed = Sha256::digest(signing_string.as_bytes());
100        let bytes = self.private_key.sign(
101            PaddingScheme::PKCS1v15Sign {
102                hash: Some(Hash::SHA2_256),
103            },
104            &hashed,
105        )?;
106        Ok(base64::encode(bytes))
107    }
108}
109
110impl apub_core::signature::Verify for RsaVerifier {
111    type Error = RustcryptoError;
112
113    fn verify(&self, signing_string: &str, signature: &str) -> Result<bool, Self::Error> {
114        let decoded = base64::decode(signature)?;
115        let hashed = Sha256::digest(signing_string.as_bytes());
116        self.public_key.verify(
117            PaddingScheme::PKCS1v15Sign {
118                hash: Some(Hash::SHA2_256),
119            },
120            &hashed,
121            &decoded,
122        )?;
123        Ok(true)
124    }
125}
126
127impl apub_core::signature::VerifyBuilder for RsaVerifier {
128    fn build(public_key_pem: &str) -> Result<Self, Self::Error>
129    where
130        Self: Sized,
131    {
132        Ok(RsaVerifier {
133            public_key: RsaPublicKey::from_public_key_pem(public_key_pem)?,
134        })
135    }
136}
137
138impl apub_core::digest::DigestFactory for Rustcrypto {
139    type Digest = Sha256Digest;
140}
141
142impl apub_core::signature::PrivateKey for Rustcrypto {
143    type Signer = RsaSigner;
144
145    fn key_id(&self) -> String {
146        self.key_id.clone()
147    }
148
149    fn signer(&self) -> Self::Signer {
150        RsaSigner {
151            private_key: self.private_key.clone(),
152        }
153    }
154}
155
156impl apub_core::signature::PrivateKeyBuilder for Rustcrypto {
157    type Error = RustcryptoError;
158
159    fn build(key_id: String, private_key_pem: &str) -> Result<Self, Self::Error>
160    where
161        Self: Sized,
162    {
163        Ok(Rustcrypto {
164            key_id,
165            private_key: RsaPrivateKey::from_pkcs8_pem(private_key_pem)?,
166        })
167    }
168
169    fn private_key_pem(&self) -> Result<String, Self::Error> {
170        self.private_key
171            .to_pkcs8_pem()
172            .map(|s| (*s).clone())
173            .map_err(RustcryptoError::from)
174    }
175}
176
177impl Debug for RsaSigner {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        f.debug_struct("RsaSigner")
180            .field("private_key", &"hidden")
181            .finish()
182    }
183}
184
185impl Debug for Rustcrypto {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("Rustcrypto")
188            .field("key_id", &self.key_id)
189            .field("private_key", &"hidden")
190            .finish()
191    }
192}
193
194mod rsa_signer {
195    use rsa::{
196        pkcs1::{FromRsaPrivateKey, ToRsaPrivateKey},
197        RsaPrivateKey,
198    };
199    use serde::{
200        de::{Deserialize, Deserializer},
201        ser::{Serialize, Serializer},
202    };
203
204    pub(super) fn serialize<S: Serializer>(
205        private_key: &RsaPrivateKey,
206        serializer: S,
207    ) -> Result<S::Ok, S::Error> {
208        use serde::ser::Error;
209
210        let der = private_key.to_pkcs1_der().map_err(S::Error::custom)?;
211        let der_string = base64::encode(der.as_der());
212
213        String::serialize(&der_string, serializer)
214    }
215
216    pub(super) fn deserialize<'de, D: Deserializer<'de>>(
217        deserializer: D,
218    ) -> Result<RsaPrivateKey, D::Error> {
219        use serde::de::Error;
220
221        let der_string = String::deserialize(deserializer)?;
222        let der = base64::decode(&der_string).map_err(D::Error::custom)?;
223
224        RsaPrivateKey::from_pkcs1_der(&der).map_err(D::Error::custom)
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::Rustcrypto;
231    use apub_core::signature::{PrivateKey, Sign};
232    use rsa::RsaPrivateKey;
233
234    #[test]
235    fn round_trip() {
236        let private_key = RsaPrivateKey::new(&mut rand::thread_rng(), 1024).unwrap();
237        let crypto = Rustcrypto::new("key-id".into(), private_key);
238        let signer = crypto.signer();
239
240        let first_sign = signer.sign("hello").unwrap();
241
242        let s = serde_json::to_string(&crypto).unwrap();
243        let crypto2: Rustcrypto = serde_json::from_str(&s).unwrap();
244        let signer2 = crypto2.signer();
245
246        let second_sign = signer2.sign("hello").unwrap();
247
248        let s2 = serde_json::to_string(&crypto2).unwrap();
249
250        assert_eq!(s, s2);
251        assert_eq!(first_sign, second_sign);
252    }
253}