django_signing/
lib.rs

1use base62::base62;
2use base64;
3use digest::{Digest, Mac};
4use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression};
5use hmac::Hmac;
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use sha2::Sha256;
9use std::io::{Read, Write};
10pub use time::Duration; // re-export
11use time::OffsetDateTime;
12
13type HmacSha256 = Hmac<Sha256>;
14
15#[derive(Debug)]
16pub enum SignatureError {
17    MissingSeparator,
18    FormatError,
19    InvalidSignature,
20    MissingTimestamp,
21    TimestampFormatError,
22    SignatureExpired,
23    ObjectFormatError,
24}
25
26pub trait Signer {
27    fn sign(&self, value: String) -> String;
28
29    fn unsign(&self, signed_value: String) -> Result<String, SignatureError>;
30
31    fn sign_object<T>(&self, obj: T, compress: bool) -> String
32    where
33        T: Serialize;
34
35    fn unsign_object<T>(&self, signed_object: String) -> Result<T, SignatureError>
36    where
37        T: DeserializeOwned;
38}
39
40pub trait TimedSigner: Signer {
41    fn unsign_with_age(
42        &self,
43        signed_value: String,
44        max_age: Duration,
45    ) -> Result<String, SignatureError>;
46
47    fn unsign_object_with_age<T>(
48        &self,
49        signed_value: String,
50        max_age: Duration,
51    ) -> Result<T, SignatureError>
52    where
53        T: DeserializeOwned;
54}
55
56pub struct BaseSigner {
57    key: Vec<u8>,
58}
59
60impl BaseSigner {
61    pub fn new(key: &[u8], salt: &[u8]) -> Self {
62        // https://github.com/django/django/blob/ca04659b4b3f042c1bc7e557c25ed91e3c56c745/django/core/signing.py#L160
63        let mut new_salt = Vec::with_capacity(salt.len() + 6);
64        new_salt.extend_from_slice(salt);
65        new_salt.extend(b"signer");
66
67        let mut inner_hasher = Sha256::new();
68        inner_hasher.update(&new_salt[..]);
69        inner_hasher.update(key);
70
71        Self {
72            key: inner_hasher.finalize().to_vec(),
73        }
74    }
75    fn get_mac_with_value(&self, value: &[u8]) -> HmacSha256 {
76        let mut mac = HmacSha256::new_from_slice(&self.key[..]).unwrap();
77        mac.update(value);
78        mac
79    }
80    fn encoded_signature(&self, value: &[u8]) -> String {
81        let mac = self.get_mac_with_value(value);
82        base64::encode_config(mac.finalize().into_bytes(), base64::URL_SAFE_NO_PAD)
83    }
84
85    fn decode_object<T>(&self, value: String) -> Result<T, SignatureError>
86    where
87        T: DeserializeOwned,
88    {
89        let (decompress, encoded_value) = match value.strip_prefix(".") {
90            Some(remainder) => (true, remainder.as_bytes()),
91            None => (false, value.as_bytes()),
92        };
93        let mut decoded_value =
94            base64::decode_config(encoded_value, base64::URL_SAFE_NO_PAD).unwrap();
95        if decompress {
96            let mut decoder = ZlibDecoder::new(&decoded_value[..]);
97            let mut unpacked = String::new();
98            decoder.read_to_string(&mut unpacked).unwrap();
99            decoded_value = unpacked.into();
100        }
101        match serde_json::from_slice(&decoded_value[..]) {
102            Ok(obj) => Ok(obj),
103            Err(_) => Err(SignatureError::ObjectFormatError),
104        }
105    }
106
107    fn encode_object<T>(&self, obj: T, compress: bool) -> String
108    where
109        T: Serialize,
110    {
111        let mut value = serde_json::to_vec(&obj).unwrap();
112        let mut is_compressed = false;
113        if compress {
114            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
115            encoder.write(&value[..]).unwrap();
116            let compressed = encoder.finish().unwrap();
117            if compressed.len() < value.len() - 1 {
118                value = compressed;
119                is_compressed = true;
120            }
121        }
122        let mut value = base64::encode_config(value, base64::URL_SAFE_NO_PAD);
123        if is_compressed {
124            value.insert(0, '.');
125        }
126        value
127    }
128}
129
130impl Signer for BaseSigner {
131    fn sign(&self, value: String) -> String {
132        format!("{}:{}", value, self.encoded_signature(value.as_bytes()))
133    }
134    fn unsign(&self, signed_value: String) -> Result<String, SignatureError> {
135        if let Some((value, sig)) = signed_value.rsplit_once(":") {
136            if let Ok(decoded_sig) = base64::decode_config(sig, base64::URL_SAFE_NO_PAD) {
137                let mac = self.get_mac_with_value(value.as_bytes());
138                if let Ok(_) = mac.verify_slice(&decoded_sig[..]) {
139                    Ok(value.to_string())
140                } else {
141                    Err(SignatureError::InvalidSignature)
142                }
143            } else {
144                Err(SignatureError::FormatError)
145            }
146        } else {
147            Err(SignatureError::MissingSeparator)
148        }
149    }
150
151    fn sign_object<T>(&self, obj: T, compress: bool) -> String
152    where
153        T: Serialize,
154    {
155        let value = self.encode_object(obj, compress);
156        self.sign(value)
157    }
158    fn unsign_object<T>(&self, signed_object: String) -> Result<T, SignatureError>
159    where
160        T: DeserializeOwned,
161    {
162        let unsigned = self.unsign(signed_object);
163        match unsigned {
164            Ok(value) => self.decode_object(value),
165            Err(e) => Err(e),
166        }
167    }
168}
169
170pub struct TimestampSigner {
171    inner: BaseSigner,
172}
173
174impl TimestampSigner {
175    pub fn new(key: &[u8], salt: &[u8]) -> Self {
176        Self {
177            inner: BaseSigner::new(key, salt),
178        }
179    }
180}
181
182impl Signer for TimestampSigner {
183    fn sign(&self, value: String) -> String {
184        let timestamp = OffsetDateTime::now_utc().unix_timestamp() as u64;
185        let value = format!("{}:{}", value, base62::encode(timestamp));
186        self.inner.sign(value)
187    }
188    fn unsign(&self, signed_value: String) -> Result<String, SignatureError> {
189        let unsigned = self.inner.unsign(signed_value);
190        match unsigned {
191            Err(e) => Err(e),
192            Ok(timestamped_value) => {
193                if let Some((value, _)) = timestamped_value.rsplit_once(":") {
194                    Ok(value.to_string())
195                } else {
196                    Err(SignatureError::MissingTimestamp)
197                }
198            }
199        }
200    }
201
202    fn sign_object<T>(&self, obj: T, compress: bool) -> String
203    where
204        T: Serialize,
205    {
206        let value = self.inner.encode_object(obj, compress);
207        self.sign(value)
208    }
209
210    fn unsign_object<T>(&self, signed_object: String) -> Result<T, SignatureError>
211    where
212        T: DeserializeOwned,
213    {
214        match self.unsign(signed_object) {
215            Ok(value) => self.inner.decode_object(value),
216            Err(e) => Err(e),
217        }
218    }
219}
220
221impl TimedSigner for TimestampSigner {
222    fn unsign_with_age(
223        &self,
224        signed_value: String,
225        max_age: Duration,
226    ) -> Result<String, SignatureError> {
227        let unsigned = self.inner.unsign(signed_value);
228        match unsigned {
229            Err(e) => Err(e),
230            Ok(timestamped_value) => {
231                if let Some((value, timestamp)) = timestamped_value.rsplit_once(":") {
232                    if let Ok(timestamp) = base62::decode(timestamp) {
233                        if let Ok(timestamp) = OffsetDateTime::from_unix_timestamp(timestamp as i64)
234                        {
235                            let distance = OffsetDateTime::now_utc() - timestamp;
236                            if distance <= max_age {
237                                Ok(value.to_string())
238                            } else {
239                                Err(SignatureError::SignatureExpired)
240                            }
241                        } else {
242                            Err(SignatureError::TimestampFormatError)
243                        }
244                    } else {
245                        Err(SignatureError::TimestampFormatError)
246                    }
247                } else {
248                    Err(SignatureError::MissingTimestamp)
249                }
250            }
251        }
252    }
253
254    fn unsign_object_with_age<T>(
255        &self,
256        signed_value: String,
257        max_age: Duration,
258    ) -> Result<T, SignatureError>
259    where
260        T: DeserializeOwned,
261    {
262        match self.unsign_with_age(signed_value, max_age) {
263            Ok(value) => self.inner.decode_object(value),
264            Err(e) => Err(e),
265        }
266    }
267}
268
269pub fn dumps<T>(obj: T, key: &[u8], salt: &[u8], compress: bool) -> String
270where
271    T: Serialize,
272{
273    let signer = TimestampSigner::new(key, salt);
274    signer.sign_object(obj, compress)
275}
276
277pub fn loads<T>(
278    signed_value: String,
279    key: &[u8],
280    salt: &[u8],
281    max_age: Duration,
282) -> Result<T, SignatureError>
283where
284    T: DeserializeOwned,
285{
286    let signer = TimestampSigner::new(key, salt);
287    signer.unsign_object_with_age(signed_value, max_age)
288}