Skip to main content

cloud_sdk/authentication/signing/
output.rs

1use core::fmt;
2
3use cloud_sdk_sanitization::sanitize_bytes;
4
5use crate::transport::TransportRequest;
6
7use super::SigningContext;
8
9/// Caller-provided request-signing implementation.
10pub trait RequestSigner {
11    /// Signing failure.
12    type Error;
13
14    /// Signs the exact versioned canonical input into caller-owned output.
15    fn sign(&self, input: &[u8], output: &mut [u8]) -> Result<usize, Self::Error>;
16}
17
18/// Validated signing-output failure.
19pub enum SigningOutputError<E> {
20    /// The caller-provided signer rejected the canonical input.
21    Signer(E),
22    /// A successful signer returned zero output bytes.
23    Empty,
24    /// A successful signer returned a length beyond the supplied output.
25    TooLong,
26}
27
28impl<E> fmt::Debug for SigningOutputError<E> {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        formatter.write_str(match self {
31            Self::Signer(_) => "SigningOutputError::Signer([redacted])",
32            Self::Empty => "SigningOutputError::Empty",
33            Self::TooLong => "SigningOutputError::TooLong",
34        })
35    }
36}
37
38impl<E> fmt::Display for SigningOutputError<E> {
39    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40        formatter.write_str(match self {
41            Self::Signer(_) => "request signer rejected the canonical input",
42            Self::Empty => "request signer returned empty output",
43            Self::TooLong => "request signer returned an out-of-bounds length",
44        })
45    }
46}
47
48impl<E> core::error::Error for SigningOutputError<E>
49where
50    E: core::error::Error + 'static,
51{
52    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
53        match self {
54            Self::Signer(error) => Some(error),
55            Self::Empty | Self::TooLong => None,
56        }
57    }
58}
59
60/// Exact request and validated signature produced from one canonical snapshot.
61pub struct SignedRequest<'output, 'request> {
62    request: TransportRequest<'request>,
63    context: SigningContext<'request>,
64    storage: &'output mut [u8],
65    len: usize,
66}
67
68impl<'output, 'request> SignedRequest<'output, 'request> {
69    pub(super) fn sign<S: RequestSigner>(
70        request: TransportRequest<'request>,
71        context: SigningContext<'request>,
72        input: &[u8],
73        signer: &S,
74        output: &'output mut [u8],
75    ) -> Result<Self, SigningOutputError<S::Error>> {
76        sanitize_bytes(output);
77        let mut signed = Self {
78            request,
79            context,
80            storage: output,
81            len: 0,
82        };
83        let len = signer
84            .sign(input, signed.storage)
85            .map_err(SigningOutputError::Signer)?;
86        if len == 0 {
87            return Err(SigningOutputError::Empty);
88        }
89        if len > signed.storage.len() {
90            return Err(SigningOutputError::TooLong);
91        }
92        signed.len = len;
93        Ok(signed)
94    }
95
96    /// Returns the exact request whose body and metadata were signed.
97    #[must_use]
98    pub const fn request(&self) -> TransportRequest<'request> {
99        self.request
100    }
101
102    /// Returns the exact security domain whose bytes were signed.
103    #[must_use]
104    pub const fn context(&self) -> SigningContext<'request> {
105        self.context
106    }
107
108    /// Returns the validated signature or MAC bytes.
109    #[must_use]
110    pub fn signature(&self) -> &[u8] {
111        self.storage.get(..self.len).unwrap_or_default()
112    }
113}
114
115impl fmt::Debug for SignedRequest<'_, '_> {
116    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117        formatter
118            .debug_struct("SignedRequest")
119            .field("request", &self.request)
120            .field("context", &self.context)
121            .field("signature_len", &self.len)
122            .field("signature", &"[redacted]")
123            .finish()
124    }
125}
126
127impl Drop for SignedRequest<'_, '_> {
128    fn drop(&mut self) {
129        sanitize_bytes(self.storage);
130    }
131}