tower_image_xform/
signed.rs

1use base64::{engine::general_purpose::URL_SAFE, Engine};
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4use url::Url;
5
6use crate::{
7    transformation_params::{Height, TransformationParams, Width},
8    Key,
9};
10
11/// Verifier of signatures.
12#[derive(Debug, Clone)]
13pub struct Verifier {
14    key: Key,
15}
16
17impl Verifier {
18    /// Create a new [`Verifier`] with the provided [`Key`].
19    pub const fn new(key: Key) -> Self {
20        Self { key }
21    }
22
23    /// Verify a given signature and value.
24    ///
25    /// # Example
26    ///
27    /// ```rust
28    /// use tower_image_xform::{Key, Verifier};
29    ///
30    /// # /*
31    /// let key = { /* a cryptographically random key >= 64 bytes */ };
32    /// # */
33    /// # let key: &Vec<u8> = &(0..64).collect();
34    ///
35    /// let key = Key::from(key);
36    /// let verifier = Verifier::new(key);
37    ///
38    /// # /*
39    /// let sig = { /* a signature produced by signing a value */ };
40    /// let val = { /* a signed value, e.g. transform parameters and URL */ };
41    /// # */
42    /// # let sig = "ZkGOa8OrigopaLapeyNwVkREmYauORdo9OYh3-2rvQY=";
43    /// # let val = "foobar";
44    ///
45    /// assert!(verifier.verify(sig, val));
46    /// ```
47    pub fn verify(&self, signature: &str, value: &str) -> bool {
48        let Ok(digest) = URL_SAFE.decode(signature) else {
49            tracing::warn!("could not Base64 decode signature");
50            return false;
51        };
52
53        let mut mac = Hmac::<Sha256>::new_from_slice(self.key.as_slice())
54            .expect("HMAC can take key of any size");
55        mac.update(value.as_bytes());
56        mac.verify_slice(&digest).is_ok()
57    }
58}
59
60/// Signed URL.
61#[derive(Debug)]
62pub struct SignedUrl {
63    base: Url,
64    key: Key,
65    params: TransformationParams,
66    target: Url,
67}
68
69impl SignedUrl {
70    const fn new(key: Key, base: Url, target: Url, params: TransformationParams) -> Self {
71        Self {
72            base,
73            key,
74            params,
75            target,
76        }
77    }
78
79    fn sign(&self, data: &[u8]) -> String {
80        let mut mac = Hmac::<Sha256>::new_from_slice(self.key.as_slice())
81            .expect("HMAC can take key of any size");
82        mac.update(data);
83        URL_SAFE.encode(mac.finalize().into_bytes())
84    }
85
86    /// Generates a signed URL.
87    ///
88    /// The signature is based on the parameters and encoded URL.
89    pub fn generate_signed_url(&self) -> Result<Url, url::ParseError> {
90        let params_encoded = self.params.to_string();
91        let url_encoded = urlencoding::encode(self.target.as_ref());
92        let combined_encoded = format!("{params_encoded}{url_encoded}");
93        let signature = self.sign(combined_encoded.as_bytes());
94
95        self.base
96            .join(&format!("{signature}/{params_encoded}/{url_encoded}"))
97    }
98}
99
100/// Builder for [`SignedUrl`].
101#[derive(Debug)]
102pub struct SignedUrlBuilder<K, B, P, T> {
103    key: K,
104    base: B,
105    params: P,
106    target: T,
107}
108
109impl SignedUrlBuilder<(), (), (), ()> {
110    /// Create a new [`SignedUrlBuilder`].
111    pub const fn new() -> Self {
112        Self {
113            key: (),
114            base: (),
115            params: (),
116            target: (),
117        }
118    }
119
120    /// Set signing key.
121    pub const fn key(self, key: Key) -> SignedUrlBuilder<Key, (), (), ()> {
122        let Self {
123            base,
124            params,
125            target,
126            ..
127        } = self;
128        SignedUrlBuilder {
129            key,
130            base,
131            params,
132            target,
133        }
134    }
135}
136
137impl SignedUrlBuilder<Key, (), (), ()> {
138    /// Set base URL.
139    pub const fn base(self, base: Url) -> SignedUrlBuilder<Key, Url, (), ()> {
140        let Self {
141            key,
142            params,
143            target,
144            ..
145        } = self;
146        SignedUrlBuilder {
147            key,
148            base,
149            params,
150            target,
151        }
152    }
153}
154
155impl SignedUrlBuilder<Key, Url, (), ()> {
156    /// Returns a builder on which parameters may be set.
157    pub fn params(self) -> SignedUrlBuilder<Key, Url, TransformationParams, ()> {
158        let Self {
159            key, base, target, ..
160        } = self;
161        let params = TransformationParams::default();
162        SignedUrlBuilder {
163            key,
164            base,
165            target,
166            params,
167        }
168    }
169}
170
171impl SignedUrlBuilder<Key, Url, TransformationParams, ()> {
172    /// Set resize height.
173    pub fn height(self, height: Height) -> Self {
174        let Self {
175            key,
176            base,
177            target,
178            mut params,
179            ..
180        } = self;
181        params.height = Some(height);
182        SignedUrlBuilder {
183            key,
184            base,
185            target,
186            params,
187        }
188    }
189
190    /// Set resize width.
191    pub fn width(self, width: Width) -> Self {
192        let Self {
193            key,
194            base,
195            target,
196            mut params,
197            ..
198        } = self;
199        params.width = Some(width);
200        SignedUrlBuilder {
201            key,
202            base,
203            target,
204            params,
205        }
206    }
207
208    /// Set image target URL.
209    pub fn target(self, target: Url) -> SignedUrlBuilder<Key, Url, TransformationParams, Url> {
210        let Self {
211            key, base, params, ..
212        } = self;
213        SignedUrlBuilder {
214            key,
215            base,
216            target,
217            params,
218        }
219    }
220}
221
222impl SignedUrlBuilder<Key, Url, TransformationParams, Url> {
223    /// Returns a [`SignedUrl`].
224    pub fn build(self) -> SignedUrl {
225        SignedUrl::new(self.key, self.base, self.target, self.params)
226    }
227}
228
229impl Default for SignedUrlBuilder<(), (), (), ()> {
230    fn default() -> Self {
231        Self::new()
232    }
233}