Skip to main content

mauth_core/
verifier.rs

1use crate::{error::Error, signable::Signable};
2use base64::{engine::general_purpose, Engine as _};
3use rsa::pkcs1v15::Signature;
4use rsa::pkcs8::DecodePublicKey;
5use rsa::RsaPublicKey;
6use sha2::Sha512;
7
8/// Used to verify incoming requests. Struct can be initialized once and used to verify many requests.
9#[derive(Debug, Clone)]
10pub struct Verifier {
11    app_uuid: String,
12    public_key: RsaPublicKey,
13    verifying_key: rsa::pkcs1v15::VerifyingKey<Sha512>,
14}
15
16impl Verifier {
17    /// Initialize a new verifier with the source app UUID and the public key. The format of the public key
18    /// should be a raw RSA public key in the public key PEM format, as generated by `openssl rsa -pubout -out`.
19    /// An error will be returned if the input data is unable to be parsed as a public key. The `app_uuid`
20    /// is expected to be a valid UUID, however this is not checked. If you pass something other than
21    /// a valid UUID, no error will be returned, but none of the signatures will be able to be validated.
22    ///
23    /// ```
24    /// # use mauth_core::verifier::Verifier;
25    /// # let public_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key-pub").unwrap();
26    /// let verifier = Verifier::new("101c139a-236c-11ef-b5e3-125eb8485a60", public_key);
27    /// assert!(verifier.is_ok());
28    /// ```
29    pub fn new(app_uuid: impl Into<String>, public_key_data: String) -> Result<Self, Error> {
30        let public_key = RsaPublicKey::from_public_key_pem(&public_key_data)?;
31        let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha512>::new(public_key.to_owned());
32
33        Ok(Self {
34            app_uuid: app_uuid.into(),
35            public_key,
36            verifying_key,
37        })
38    }
39
40    /// This function will verify that a provided signature is valid given the uuid and public key the
41    /// struct was constructed with, the request properties passed into the function, and the signature
42    /// passed in. It will return Ok(()) if the signature validates successfully, and Err if it does
43    /// not. It is the responsibility of the consuming crate and application to use these cases to
44    /// determine whether to process a request further, or return error information.
45    ///
46    /// ```
47    /// # use mauth_core::verifier::Verifier;
48    /// # use mauth_core::error::Error;
49    /// # let public_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key-pub").unwrap();
50    /// # let verifier = Verifier::new("101c139a-236c-11ef-b5e3-125eb8485a60", public_key).unwrap();
51    /// let result = verifier.verify_signature(2, "GET", "/item", "page=2", b"", "2024-01-28T19:11:35.000", "");
52    /// // Passing in an empty signature, so it will result in a verification error
53    /// assert!(matches!(result, Err(Error::SignatureVerifyError(_))));
54    /// ```
55    #[allow(clippy::too_many_arguments)]
56    pub fn verify_signature(
57        &self,
58        version: u8,
59        verb: impl Into<String>,
60        path: impl Into<String>,
61        query: impl Into<String>,
62        body: &[u8],
63        timestamp: impl Into<String>,
64        signature: impl Into<String>,
65    ) -> Result<(), Error> {
66        let signable = Signable::new(verb, path, query, body, timestamp, &self.app_uuid);
67
68        match version {
69            1 => self.verify_signature_v1(&signable, signature.into()),
70            2 => self.verify_signature_v2(&signable, signature.into()),
71            v => Err(Error::UnsupportedVersion(v)),
72        }
73    }
74
75    fn verify_signature_v1(&self, signable: &Signable, signature: String) -> Result<(), Error> {
76        self.public_key.verify(
77            rsa::Pkcs1v15Sign::new_unprefixed(),
78            &signable.signing_string_v1()?,
79            &general_purpose::STANDARD.decode(signature)?,
80        )?;
81
82        Ok(())
83    }
84
85    fn verify_signature_v2(&self, signable: &Signable, signature: String) -> Result<(), Error> {
86        use rsa::signature::Verifier;
87
88        let signature =
89            Signature::try_from(general_purpose::STANDARD.decode(signature)?.as_slice())?;
90        self.verifying_key
91            .verify(&signable.signing_string_v2()?, &signature)?;
92
93        Ok(())
94    }
95}