fastcrypto/
rsa.rs

1// Copyright (c) 2022, Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Implementation of a verifier following RSASSA-PKCS1-v1_5 using SHA-256 (see https://datatracker.ietf.org/doc/rfc3447/).
5
6use crate::error::{FastCryptoError, FastCryptoResult};
7use crate::hash::{HashFunction, Sha256};
8pub use base64ct::{Base64UrlUnpadded, Encoding};
9use rsa::pkcs1::DecodeRsaPublicKey;
10use rsa::pkcs1v15::Signature as ExternalSignature;
11use rsa::{BigUint, RsaPublicKey as ExternalPublicKey};
12use rsa::{Pkcs1v15Sign, PublicKey};
13
14#[derive(Clone)]
15pub struct RSAPublicKey(pub ExternalPublicKey);
16
17#[derive(Clone, PartialEq, Eq)]
18pub struct RSASignature(pub ExternalSignature);
19
20impl RSAPublicKey {
21    /// Parse an `RSAPublicKey` from an ASN.1 DER (Distinguished Encoding Rules) PKCS #1 encoding.
22    pub fn from_der(der: &[u8]) -> FastCryptoResult<Self> {
23        Ok(RSAPublicKey(
24            rsa::RsaPublicKey::from_pkcs1_der(der).map_err(|_| FastCryptoError::InvalidInput)?,
25        ))
26    }
27
28    /// Parse an `RSAPublicKey` from its components, eg. the modulus (n) and the exponent (e) from a binary big-endian representation.
29    pub fn from_raw_components(modulus: &[u8], exponent: &[u8]) -> FastCryptoResult<Self> {
30        // The Base64 encodings in a JSON Web Key is big-endian encoded (see RFC 7517 and 7518), so we expect the same here.
31        Ok(RSAPublicKey(
32            rsa::RsaPublicKey::new(
33                BigUint::from_bytes_be(modulus),
34                BigUint::from_bytes_be(exponent),
35            )
36            .map_err(|_| FastCryptoError::InvalidInput)?,
37        ))
38    }
39
40    /// Verify a signed message. The verification uses SHA-256 for hashing.
41    pub fn verify(&self, msg: &[u8], signature: &RSASignature) -> FastCryptoResult<()> {
42        self.verify_prehash(&Sha256::digest(msg).digest, signature)
43    }
44
45    /// Verify a signed message. The message, `hashed`, must be the output of a cryptographic hash function.
46    pub fn verify_prehash(&self, hashed: &[u8], signature: &RSASignature) -> FastCryptoResult<()> {
47        self.0
48            .verify(
49                Pkcs1v15Sign::new::<sha2::Sha256>(),
50                hashed,
51                signature.0.as_ref(),
52            )
53            .map_err(|_| FastCryptoError::InvalidSignature)
54    }
55}
56
57impl RSASignature {
58    /// Parse signature from binary representation according to https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.
59    pub fn from_bytes(bytes: &[u8]) -> FastCryptoResult<Self> {
60        Ok(Self(
61            ExternalSignature::try_from(bytes).map_err(|_| FastCryptoError::InvalidInput)?,
62        ))
63    }
64}
65
66#[cfg(test)]
67mod test {
68    use crate::hash::{HashFunction, Sha256};
69    use crate::rsa::{Base64UrlUnpadded, Encoding};
70    use crate::rsa::{RSAPublicKey, RSASignature};
71
72    #[test]
73    fn jwt_test() {
74        // Test vector from with RFC 7515 section A.2.1.
75        let n_base64 = "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ";
76        let n_bytes = Base64UrlUnpadded::decode_vec(n_base64).unwrap();
77
78        let e_base64 = "AQAB";
79        let e_bytes = Base64UrlUnpadded::decode_vec(e_base64).unwrap();
80
81        let pk = RSAPublicKey::from_raw_components(&n_bytes, &e_bytes).unwrap();
82
83        let msg = b"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ";
84        let digest = Sha256::digest(msg).digest;
85
86        let signature_base64 = "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw";
87        let signature_bytes = Base64UrlUnpadded::decode_vec(signature_base64).unwrap();
88        let signature = RSASignature::from_bytes(&signature_bytes).unwrap();
89
90        // Valid signature
91        assert!(pk.verify_prehash(&digest, &signature).is_ok());
92        assert!(pk.verify(msg, &signature).is_ok());
93
94        // Invalid digest
95        let mut other_digest = digest;
96        other_digest[0] += 1;
97        assert!(pk.verify_prehash(&other_digest, &signature).is_err());
98
99        // Invalid message
100        let mut other_msg = *msg;
101        other_msg[0] += 1;
102        assert!(pk.verify(&other_msg, &signature).is_err());
103
104        // Invalid signature
105        let mut other_signature_bytes = signature_bytes;
106        other_signature_bytes[7] += 1;
107        let other_signature = RSASignature::from_bytes(&other_signature_bytes).unwrap();
108        assert!(pk.verify_prehash(&other_digest, &signature).is_err());
109        assert!(pk.verify(msg, &other_signature).is_err());
110    }
111}