cashweb_auth_wrapper/
lib.rs

1#![warn(
2    missing_debug_implementations,
3    missing_docs,
4    rust_2018_idioms,
5    unreachable_pub
6)]
7
8//! `cashweb-auth-wrapper` is a library providing deserialization, parsing, and verification needed within the [`Authorization Wrapper Framework`].
9//!
10//! [`Authorization Wrapper Framework`]: https://github.com/cashweb/specifications/blob/master/authorization-wrapper/specification.mediawiki
11
12#[allow(unreachable_pub)]
13mod models;
14
15use std::convert::TryInto;
16
17use ring::digest::{digest, SHA256};
18use secp256k1::{key::PublicKey, Error as SecpError, Message, Secp256k1, Signature};
19use thiserror::Error;
20
21pub use models::{auth_wrapper::SignatureScheme, AuthWrapper};
22
23/// Represents an [`AuthWrapper`] post-parsing.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ParsedAuthWrapper {
26    /// The public key associated with the signature.
27    pub public_key: PublicKey,
28    /// The signature by public key covering the payload.
29    pub signature: Signature,
30    /// The signature scheme used for signing.
31    pub scheme: SignatureScheme,
32    /// The payload covered by the signature.
33    pub payload: Vec<u8>,
34    /// The SHA256 digest of the payload.
35    pub payload_digest: [u8; 32],
36}
37
38/// Error associated with validation and parsing of the [`AuthWrapper`].
39#[derive(Debug, Clone, PartialEq, Eq, Error)]
40pub enum ParseError {
41    /// The public key provided was invalid.
42    #[error(transparent)]
43    PublicKey(SecpError),
44    /// The signature provided was an invalid format.
45    #[error(transparent)]
46    Signature(SecpError),
47    /// The signature scheme provided is unsupported.
48    #[error("unsupported signature scheme")]
49    UnsupportedScheme,
50    /// The `payload_digest` provided was fraudulent.
51    #[error("fraudulent digest")]
52    FraudulentDigest,
53    /// Both the digest and the payload were missing.
54    #[error("digest and payload missing")]
55    DigestAndPayloadMissing,
56    /// The `payload_digest` was not 32 bytes long.
57    #[error("unexpected length digest")]
58    UnexpectedLengthDigest,
59}
60
61impl AuthWrapper {
62    /// Parse the [`AuthWrapper`] to construct a [`ParsedAuthWrapper`].
63    ///
64    /// The involves deserialization of both public keys, calculation of the payload digest, and coercion of byte fields
65    /// into fixed-length arrays.
66    #[inline]
67    pub fn parse(self) -> Result<ParsedAuthWrapper, ParseError> {
68        // Parse public key
69        let public_key = PublicKey::from_slice(&self.public_key).map_err(ParseError::PublicKey)?;
70
71        // Parse scheme
72        let scheme = SignatureScheme::from_i32(self.scheme).ok_or(ParseError::UnsupportedScheme)?;
73
74        // Parse signature
75        let signature = Signature::from_compact(&self.signature).map_err(ParseError::Signature)?;
76
77        // Construct and validate payload digest
78        let payload_digest = match self.payload_digest.len() {
79            0 => {
80                if self.payload.is_empty() {
81                    return Err(ParseError::DigestAndPayloadMissing);
82                } else {
83                    let payload_digest = digest(&SHA256, &self.payload);
84                    let digest_arr: [u8; 32] = payload_digest.as_ref().try_into().unwrap();
85                    digest_arr
86                }
87            }
88            32 => {
89                let payload_digest = digest(&SHA256, &self.payload);
90                if *payload_digest.as_ref() != self.payload_digest[..] {
91                    return Err(ParseError::FraudulentDigest);
92                }
93                let digest_arr: [u8; 32] = self.payload_digest[..].try_into().unwrap();
94                digest_arr
95            }
96            _ => return Err(ParseError::UnexpectedLengthDigest),
97        };
98
99        Ok(ParsedAuthWrapper {
100            public_key,
101            scheme,
102            signature,
103            payload_digest,
104            payload: self.payload,
105        })
106    }
107}
108
109/// Error associated with verifying the signature of an [`AuthWrapper`].
110#[derive(Debug, Clone, PartialEq, Eq, Error)]
111pub enum VerifyError {
112    /// The signature failed verification.
113    #[error(transparent)]
114    InvalidSignature(SecpError),
115    /// The signature scheme provided is unsupported.
116    #[error("unsupported signature scheme")]
117    UnsupportedScheme,
118}
119
120impl ParsedAuthWrapper {
121    /// Verify the signature on [`ParsedAuthWrapper`].
122    #[inline]
123    pub fn verify(&self) -> Result<(), VerifyError> {
124        if self.scheme == SignatureScheme::Schnorr {
125            // TODO: Support Schnorr
126            return Err(VerifyError::UnsupportedScheme);
127        }
128        // Verify signature on the message
129        let msg = Message::from_slice(self.payload_digest.as_ref()).unwrap(); // This is safe
130        let secp = Secp256k1::verification_only();
131        secp.verify(&msg, &self.signature, &self.public_key)
132            .map_err(VerifyError::InvalidSignature)?;
133        Ok(())
134    }
135}