csrf/
core.rs

1//! Module containing the core functionality for CSRF protection
2
3use std::{borrow::Cow, error::Error, fmt, io::Cursor};
4
5use aead::{generic_array::GenericArray, Aead, AeadCore, Key, KeyInit};
6use aes_gcm::Aes256Gcm;
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use chacha20poly1305::ChaCha20Poly1305;
9use chrono::{prelude::*, Duration};
10use data_encoding::{BASE64, BASE64URL};
11use hmac::{Hmac, Mac};
12use rand::RngCore;
13use sha2::Sha256;
14
15type HmacSha256 = Hmac<Sha256>;
16
17/// An `enum` of all CSRF related errors.
18#[derive(Debug, Hash, Eq, PartialEq, Clone)]
19pub enum CsrfError {
20    /// There was an internal error.
21    InternalError,
22    /// There was CSRF token validation failure.
23    ValidationFailure(String),
24    /// There was a CSRF token encryption failure.
25    EncryptionFailure(String),
26}
27
28impl Error for CsrfError {}
29
30impl fmt::Display for CsrfError {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        match self {
33            CsrfError::InternalError => write!(f, "Library error"),
34            CsrfError::ValidationFailure(err) => write!(f, "Validation failed: {err}"),
35            CsrfError::EncryptionFailure(err) => write!(f, "Encryption failed: {err}"),
36        }
37    }
38}
39
40/// A signed, encrypted CSRF token that is suitable to be displayed to end users.
41#[derive(Eq, PartialEq, Debug, Clone, Hash)]
42pub struct CsrfToken {
43    bytes: Vec<u8>,
44}
45
46impl CsrfToken {
47    /// Create a new token from the given bytes.
48    pub fn new(bytes: Vec<u8>) -> Self {
49        // TODO make this return a Result and check that bytes is long enough
50        CsrfToken { bytes }
51    }
52
53    /// Retrieve the CSRF token as a base64 encoded string.
54    pub fn b64_string(&self) -> String {
55        BASE64.encode(&self.bytes)
56    }
57
58    /// Retrieve the CSRF token as a URL safe base64 encoded string.
59    pub fn b64_url_string(&self) -> String {
60        BASE64URL.encode(&self.bytes)
61    }
62
63    /// Get be raw value of this token.
64    pub fn value(&self) -> &[u8] {
65        &self.bytes
66    }
67}
68
69/// A signed, encrypted CSRF cookie that is suitable to be displayed to end users.
70#[derive(Debug, Eq, PartialEq, Clone, Hash)]
71pub struct CsrfCookie {
72    bytes: Vec<u8>,
73}
74
75impl CsrfCookie {
76    /// Create a new cookie from the given token bytes.
77    pub fn new(bytes: Vec<u8>) -> Self {
78        // TODO make this return a Result and check that bytes is long enough
79        CsrfCookie { bytes }
80    }
81
82    /// Get the base64 value of this cookie.
83    pub fn b64_string(&self) -> String {
84        BASE64.encode(&self.bytes)
85    }
86
87    /// Get be raw value of this cookie.
88    pub fn value(&self) -> &[u8] {
89        &self.bytes
90    }
91}
92
93/// Internal represenation of an unencrypted CSRF token. This is not suitable to send to end users.
94#[derive(Clone, Debug, Eq, PartialEq, Hash)]
95pub struct UnencryptedCsrfToken {
96    token: Vec<u8>,
97}
98
99impl UnencryptedCsrfToken {
100    /// Create a new unenrypted token.
101    pub fn new(token: Vec<u8>) -> Self {
102        UnencryptedCsrfToken { token }
103    }
104
105    /// Retrieve the token value as bytes.
106    #[deprecated]
107    pub fn token(&self) -> &[u8] {
108        &self.token
109    }
110
111    /// Retrieve the token value as bytes.
112    pub fn value(&self) -> &[u8] {
113        &self.token
114    }
115}
116
117/// Internal represenation of an unencrypted CSRF cookie. This is not suitable to send to end users.
118#[derive(Clone, Debug, Eq, PartialEq, Hash)]
119pub struct UnencryptedCsrfCookie {
120    expires: i64,
121    token: Vec<u8>,
122}
123
124impl UnencryptedCsrfCookie {
125    /// Create a new unenrypted cookie.
126    pub fn new(expires: i64, token: Vec<u8>) -> Self {
127        UnencryptedCsrfCookie { expires, token }
128    }
129
130    /// Retrieve the token value as bytes.
131    pub fn value(&self) -> &[u8] {
132        &self.token
133    }
134}
135
136/// The base trait that allows a developer to add CSRF protection to an application.
137pub trait CsrfProtection: Send + Sync {
138    /// Given a nonce and a time to live (TTL), create a cookie to send to the end user.
139    fn generate_cookie(
140        &self,
141        token_value: &[u8; 64],
142        ttl_seconds: i64,
143    ) -> Result<CsrfCookie, CsrfError>;
144
145    /// Given a nonce, create a token to send to the end user.
146    fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError>;
147
148    /// Given a decoded byte array, deserialize, decrypt, and verify the cookie.
149    fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError>;
150
151    /// Given a decoded byte array, deserialize, decrypt, and verify the token.
152    fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError>;
153
154    /// Given a token pair that has been parsed, decoded, decrypted, and verified, return whether
155    /// or not the token matches the cookie and they have not expired.
156    fn verify_token_pair(
157        &self,
158        token: &UnencryptedCsrfToken,
159        cookie: &UnencryptedCsrfCookie,
160    ) -> Result<(), CsrfError> {
161        if token.token != cookie.token {
162            return Err(CsrfError::ValidationFailure(format!(
163                "Token did not match cookie: T: {:?}, C: {:?}",
164                BASE64.encode(&token.token),
165                BASE64.encode(&cookie.token)
166            )));
167        }
168
169        let now = Utc::now().timestamp();
170        if cookie.expires <= now {
171            return Err(CsrfError::ValidationFailure(format!(
172                "Cookie expired. Expiration: {}, Current time: {}",
173                cookie.expires, now
174            )));
175        }
176
177        Ok(())
178    }
179
180    /// Given a buffer, fill it with random bytes or error if this is not possible.
181    fn random_bytes(&self, buf: &mut [u8]) -> Result<(), CsrfError> {
182        // TODO We had to get rid of `ring` because of `gcc` conflicts with `rust-crypto`, and
183        // `ring`'s RNG didn't require mutability. Now create a new one per call which is not a
184        // great idea.
185        rand::rngs::OsRng.fill_bytes(buf);
186        Ok(())
187    }
188
189    /// Given an optional previous token and a TTL, generate a matching token and cookie pair.
190    fn generate_token_pair(
191        &self,
192        previous_token_value: Option<&[u8; 64]>,
193        ttl_seconds: i64,
194    ) -> Result<(CsrfToken, CsrfCookie), CsrfError> {
195        let token: Cow<[u8; 64]> = match previous_token_value {
196            Some(v) => Cow::Borrowed(v),
197            None => {
198                let mut new_token = [0; 64];
199                self.random_bytes(&mut new_token)
200                    .expect("Error filling random bytes");
201                Cow::Owned(new_token)
202            }
203        };
204
205        let generated_token = self.generate_token(&token)?;
206        let generated_cookie = self.generate_cookie(&token, ttl_seconds)?;
207        Ok((generated_token, generated_cookie))
208    }
209}
210
211/// Uses HMAC to provide authenticated CSRF tokens and cookies.
212#[derive(Clone)]
213pub struct HmacCsrfProtection {
214    hmac: HmacSha256,
215}
216
217impl HmacCsrfProtection {
218    /// Returns n `HmacCsrfProtection` instance with auto generated key.
219    pub fn new() -> Self {
220        HmacCsrfProtection {
221            // Infallible
222            hmac: <HmacSha256 as Mac>::new_from_slice(&HmacSha256::generate_key(
223                &mut rand::rngs::OsRng,
224            ))
225            .unwrap(),
226        }
227    }
228    /// Given an HMAC key, return an `HmacCsrfProtection` instance.
229    pub fn from_key(hmac_key: [u8; 32]) -> Self {
230        HmacCsrfProtection {
231            // Infallible
232            hmac: <HmacSha256 as Mac>::new_from_slice(&hmac_key).unwrap(),
233        }
234    }
235}
236
237impl Default for HmacCsrfProtection {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243impl CsrfProtection for HmacCsrfProtection {
244    fn generate_cookie(
245        &self,
246        token_value: &[u8; 64],
247        ttl_seconds: i64,
248    ) -> Result<CsrfCookie, CsrfError> {
249        let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
250        let mut expires_bytes = [0u8; 8];
251        (&mut expires_bytes[..])
252            .write_i64::<BigEndian>(expires)
253            .map_err(|_| CsrfError::InternalError)?;
254
255        let mut hmac = self.hmac.clone();
256        hmac.update(&expires_bytes);
257        hmac.update(token_value);
258        let mac = hmac.finalize();
259        let code = mac.into_bytes();
260
261        let mut transport = [0; 104];
262        transport[0..32].copy_from_slice(&code);
263        transport[32..40].copy_from_slice(&expires_bytes);
264        transport[40..].copy_from_slice(token_value);
265
266        Ok(CsrfCookie::new(transport.to_vec()))
267    }
268
269    fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
270        let mut hmac = self.hmac.clone();
271        hmac.update(token_value);
272        let mac = hmac.finalize();
273        let code = mac.into_bytes();
274
275        let mut transport = [0; 96];
276        transport[0..32].copy_from_slice(&code);
277        transport[32..].copy_from_slice(token_value);
278
279        Ok(CsrfToken::new(transport.to_vec()))
280    }
281
282    fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
283        if cookie.len() != 104 {
284            return Err(CsrfError::ValidationFailure(format!(
285                "Cookie wrong size. Not parsed. Cookie length {} != 104",
286                cookie.len()
287            )));
288        }
289
290        let mut hmac = self.hmac.clone();
291        hmac.update(&cookie[32..]);
292
293        hmac.verify_slice(&cookie[0..32])
294            .map_err(|err| CsrfError::ValidationFailure(format!("Cookie had bad MAC: {err}")))?;
295
296        let mut cur = Cursor::new(&cookie[32..40]);
297        let expires = cur
298            .read_i64::<BigEndian>()
299            .map_err(|_| CsrfError::InternalError)?;
300        Ok(UnencryptedCsrfCookie::new(expires, cookie[40..].to_vec()))
301    }
302
303    fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
304        if token.len() != 96 {
305            return Err(CsrfError::ValidationFailure(format!(
306                "Token too small. Not parsed. Token length {} != 96",
307                token.len()
308            )));
309        }
310
311        let mut hmac = self.hmac.clone();
312        hmac.update(&token[32..]);
313
314        hmac.verify_slice(&token[0..32])
315            .map_err(|err| CsrfError::ValidationFailure(format!("Token had bad MAC: {err}")))?;
316
317        Ok(UnencryptedCsrfToken::new(token[32..].to_vec()))
318    }
319}
320
321/// Uses AES-GCM to provide signed, encrypted CSRF tokens and cookies.
322#[derive(Clone)]
323pub struct AesGcmCsrfProtection {
324    aead: Aes256Gcm,
325}
326
327impl AesGcmCsrfProtection {
328    /// Returns an `AesGcmCsrfProtection` instance with auto generated key.
329    pub fn new() -> Self {
330        AesGcmCsrfProtection {
331            aead: {
332                let key = Aes256Gcm::generate_key(&mut rand::rngs::OsRng);
333                Aes256Gcm::new(&key)
334            },
335        }
336    }
337    /// Given an AES256 key, return an `AesGcmCsrfProtection` instance.
338    pub fn from_key(aead_key: [u8; 32]) -> Self {
339        AesGcmCsrfProtection {
340            aead: {
341                let key = Key::<Aes256Gcm>::from_slice(&aead_key);
342                Aes256Gcm::new(key)
343            },
344        }
345    }
346}
347
348impl Default for AesGcmCsrfProtection {
349    fn default() -> Self {
350        Self::new()
351    }
352}
353
354impl CsrfProtection for AesGcmCsrfProtection {
355    fn generate_cookie(
356        &self,
357        token_value: &[u8; 64],
358        ttl_seconds: i64,
359    ) -> Result<CsrfCookie, CsrfError> {
360        let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
361        let mut expires_bytes = [0u8; 8];
362        (&mut expires_bytes[..])
363            .write_i64::<BigEndian>(expires)
364            .map_err(|_| CsrfError::InternalError)?;
365
366        let mut plaintext = [0; 104];
367        self.random_bytes(&mut plaintext[0..32])?; // padding
368        plaintext[32..40].copy_from_slice(&expires_bytes);
369        plaintext[40..].copy_from_slice(token_value);
370
371        let nonce = Aes256Gcm::generate_nonce(&mut rand::rngs::OsRng);
372
373        let ciphertext = self
374            .aead
375            .encrypt(&nonce, plaintext.as_ref())
376            .map_err(|err| {
377                CsrfError::EncryptionFailure(format!("Failed to encrypt cookie: {err}"))
378            })?;
379
380        let mut transport = [0; 132];
381        transport[0..12].copy_from_slice(&nonce);
382        transport[12..].copy_from_slice(&ciphertext);
383
384        Ok(CsrfCookie::new(transport.to_vec()))
385    }
386
387    fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
388        let mut plaintext = [0; 96];
389        self.random_bytes(&mut plaintext[0..32])?; // padding
390        plaintext[32..].copy_from_slice(token_value);
391
392        let nonce = Aes256Gcm::generate_nonce(&mut rand::rngs::OsRng);
393
394        let ciphertext = self
395            .aead
396            .encrypt(&nonce, plaintext.as_ref())
397            .map_err(|err| {
398                CsrfError::EncryptionFailure(format!("Failed to encrypt token: {err}"))
399            })?;
400
401        let mut transport = [0; 124];
402        transport[0..12].copy_from_slice(&nonce);
403        transport[12..].copy_from_slice(&ciphertext);
404
405        Ok(CsrfToken::new(transport.to_vec()))
406    }
407
408    fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
409        if cookie.len() != 132 {
410            return Err(CsrfError::ValidationFailure(format!(
411                "Cookie wrong size. Not parsed. Cookie length {} != 132",
412                cookie.len()
413            )));
414        }
415
416        let nonce = GenericArray::from_slice(&cookie[0..12]);
417
418        let plaintext = self
419            .aead
420            .decrypt(nonce, cookie[12..].as_ref())
421            .map_err(|err| {
422                CsrfError::ValidationFailure(format!("Failed to decrypt cookie: {err}"))
423            })?;
424
425        let mut cur = Cursor::new(&plaintext[32..40]);
426        let expires = cur
427            .read_i64::<BigEndian>()
428            .map_err(|_| CsrfError::InternalError)?;
429        Ok(UnencryptedCsrfCookie::new(
430            expires,
431            plaintext[40..].to_vec(),
432        ))
433    }
434
435    fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
436        if token.len() != 124 {
437            return Err(CsrfError::ValidationFailure(format!(
438                "Token too small. Not parsed. Token length {} != 124",
439                token.len()
440            )));
441        }
442
443        let nonce = GenericArray::from_slice(&token[0..12]);
444
445        let plaintext = self
446            .aead
447            .decrypt(nonce, token[12..].as_ref())
448            .map_err(|err| {
449                CsrfError::ValidationFailure(format!("Failed to decrypt token: {err}"))
450            })?;
451
452        Ok(UnencryptedCsrfToken::new(plaintext[32..].to_vec()))
453    }
454}
455
456/// Uses ChaCha20Poly1305 to provide signed, encrypted CSRF tokens and cookies.
457#[derive(Clone)]
458pub struct ChaCha20Poly1305CsrfProtection {
459    aead: ChaCha20Poly1305,
460}
461
462impl ChaCha20Poly1305CsrfProtection {
463    /// Return a new `ChaCha20Poly1305CsrfProtection` instance with auto generated key.
464    pub fn new() -> Self {
465        ChaCha20Poly1305CsrfProtection {
466            aead: ChaCha20Poly1305::new(&ChaCha20Poly1305::generate_key(&mut rand::rngs::OsRng)),
467        }
468    }
469    /// Given a key, return a `ChaCha20Poly1305CsrfProtection` instance.
470    pub fn from_key(aead_key: [u8; 32]) -> Self {
471        ChaCha20Poly1305CsrfProtection {
472            // Infallibale
473            aead: ChaCha20Poly1305::new_from_slice(&aead_key).unwrap(),
474        }
475    }
476}
477
478impl Default for ChaCha20Poly1305CsrfProtection {
479    fn default() -> Self {
480        Self::new()
481    }
482}
483
484impl CsrfProtection for ChaCha20Poly1305CsrfProtection {
485    fn generate_cookie(
486        &self,
487        token_value: &[u8; 64],
488        ttl_seconds: i64,
489    ) -> Result<CsrfCookie, CsrfError> {
490        let expires = (Utc::now() + Duration::seconds(ttl_seconds)).timestamp();
491        let mut expires_bytes = [0u8; 8];
492        (&mut expires_bytes[..])
493            .write_i64::<BigEndian>(expires)
494            .map_err(|_| CsrfError::InternalError)?;
495
496        let mut plaintext = [0; 104];
497        self.random_bytes(&mut plaintext[0..32])?; // padding
498        plaintext[32..40].copy_from_slice(&expires_bytes);
499        plaintext[40..].copy_from_slice(token_value);
500
501        let nonce = ChaCha20Poly1305::generate_nonce(&mut rand::rngs::OsRng);
502
503        let ciphertext = self
504            .aead
505            .encrypt(&nonce, plaintext.as_ref())
506            .map_err(|err| {
507                CsrfError::EncryptionFailure(format!("Failed to encrypt cookie: {err}"))
508            })?;
509
510        let mut transport = [0; 132];
511        transport[0..12].copy_from_slice(&nonce);
512        transport[12..].copy_from_slice(&ciphertext);
513
514        Ok(CsrfCookie::new(transport.to_vec()))
515    }
516
517    fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
518        let mut plaintext = [0; 96];
519        self.random_bytes(&mut plaintext[0..32])?; // padding
520        plaintext[32..].copy_from_slice(token_value);
521
522        let nonce = ChaCha20Poly1305::generate_nonce(&mut rand::rngs::OsRng);
523
524        let ciphertext = self
525            .aead
526            .encrypt(&nonce, plaintext.as_ref())
527            .map_err(|err| {
528                CsrfError::EncryptionFailure(format!("Failed to encrypt token: {err}"))
529            })?;
530
531        let mut transport = [0; 124];
532        transport[0..12].copy_from_slice(&nonce);
533        transport[12..].copy_from_slice(&ciphertext);
534
535        Ok(CsrfToken::new(transport.to_vec()))
536    }
537
538    fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
539        if cookie.len() != 132 {
540            return Err(CsrfError::ValidationFailure(format!(
541                "Cookie wrong size. Not parsed. Cookie length {} != 132",
542                cookie.len()
543            )));
544        }
545
546        let nonce = GenericArray::from_slice(&cookie[0..12]);
547
548        let plaintext = self
549            .aead
550            .decrypt(nonce, cookie[12..].as_ref())
551            .map_err(|err| {
552                CsrfError::ValidationFailure(format!("Failed to decrypt cookie: {err}"))
553            })?;
554
555        let mut cur = Cursor::new(&plaintext[32..40]);
556        let expires = cur
557            .read_i64::<BigEndian>()
558            .map_err(|_| CsrfError::InternalError)?;
559        Ok(UnencryptedCsrfCookie::new(
560            expires,
561            plaintext[40..].to_vec(),
562        ))
563    }
564
565    fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
566        if token.len() != 124 {
567            return Err(CsrfError::ValidationFailure(format!(
568                "Token too small. Not parsed. Token length {} != 124",
569                token.len()
570            )));
571        }
572
573        let nonce = GenericArray::from_slice(&token[0..12]);
574
575        let plaintext = self
576            .aead
577            .decrypt(nonce, token[12..].as_ref())
578            .map_err(|err| {
579                CsrfError::ValidationFailure(format!("Failed to decrypt token: {err}"))
580            })?;
581
582        Ok(UnencryptedCsrfToken::new(plaintext[32..].to_vec()))
583    }
584}
585
586/// This is used when one wants to rotate keys or switch from implementation to another.
587///
588/// It accepts `1 + N` instances of `CsrfProtection` and uses only the first to generate tokens and cookies.
589/// The `N` remaining instances are used only for parsing.
590pub struct MultiCsrfProtection {
591    current: Box<dyn CsrfProtection>,
592    previous: Vec<Box<dyn CsrfProtection>>,
593}
594
595impl MultiCsrfProtection {
596    /// Create a new `MultiCsrfProtection` from one current `CsrfProtection` and some `N` previous
597    /// instances of `CsrfProtection`.
598    pub fn new(current: Box<dyn CsrfProtection>, previous: Vec<Box<dyn CsrfProtection>>) -> Self {
599        Self { current, previous }
600    }
601}
602
603impl CsrfProtection for MultiCsrfProtection {
604    fn generate_cookie(
605        &self,
606        token_value: &[u8; 64],
607        ttl_seconds: i64,
608    ) -> Result<CsrfCookie, CsrfError> {
609        self.current.generate_cookie(token_value, ttl_seconds)
610    }
611
612    fn generate_token(&self, token_value: &[u8; 64]) -> Result<CsrfToken, CsrfError> {
613        self.current.generate_token(token_value)
614    }
615
616    fn parse_cookie(&self, cookie: &[u8]) -> Result<UnencryptedCsrfCookie, CsrfError> {
617        match self.current.parse_cookie(cookie) {
618            ok @ Ok(_) => ok,
619            Err(_) => {
620                for protection in self.previous.iter() {
621                    if let ok @ Ok(_) = protection.parse_cookie(cookie) {
622                        return ok;
623                    }
624                }
625                Err(CsrfError::ValidationFailure(
626                    "Failed to validate the cookie against all provided keys".to_owned(),
627                ))
628            }
629        }
630    }
631
632    fn parse_token(&self, token: &[u8]) -> Result<UnencryptedCsrfToken, CsrfError> {
633        match self.current.parse_token(token) {
634            ok @ Ok(_) => ok,
635            Err(_) => {
636                for protection in self.previous.iter() {
637                    if let ok @ Ok(_) = protection.parse_token(token) {
638                        return ok;
639                    }
640                }
641                Err(CsrfError::ValidationFailure(
642                    "Failed to validate the token against all provided keys".to_owned(),
643                ))
644            }
645        }
646    }
647}
648
649#[cfg(feature = "iron")]
650impl typemap::Key for CsrfToken {
651    type Value = CsrfToken;
652}
653
654#[cfg(test)]
655mod tests {
656    // TODO write test that ensures encrypted messages don't contain the plaintext
657    // TODO test that checks tokens are repeated when given Some
658
659    const KEY_32: [u8; 32] = *b"01234567012345670123456701234567";
660    const KEY2_32: [u8; 32] = *b"76543210765432107654321076543210";
661
662    macro_rules! test_cases {
663        ($strct: ident, $md: ident) => {
664            mod $md {
665                use super::KEY_32;
666                use data_encoding::BASE64;
667                use $crate::{$strct, CsrfProtection};
668
669                #[test]
670                fn verification_succeeds() {
671                    let protect = $strct::from_key(KEY_32);
672                    let (token, cookie) = protect
673                        .generate_token_pair(None, 300)
674                        .expect("couldn't generate token/cookie pair");
675                    let token = &BASE64
676                        .decode(token.b64_string().as_bytes())
677                        .expect("token not base64");
678                    let token = protect.parse_token(&token).expect("token not parsed");
679                    let cookie = &BASE64
680                        .decode(cookie.b64_string().as_bytes())
681                        .expect("cookie not base64");
682                    let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
683                    assert!(
684                        protect.verify_token_pair(&token, &cookie).is_ok(),
685                        "could not verify token/cookie pair"
686                    );
687                }
688
689                #[test]
690                fn modified_cookie_value_fails() {
691                    let protect = $strct::from_key(KEY_32);
692                    let (_, mut cookie) = protect
693                        .generate_token_pair(None, 300)
694                        .expect("couldn't generate token/cookie pair");
695                    cookie.bytes[0] ^= 0x01;
696                    let cookie = &BASE64
697                        .decode(cookie.b64_string().as_bytes())
698                        .expect("cookie not base64");
699                    assert!(protect.parse_cookie(&cookie).is_err());
700                }
701
702                #[test]
703                fn modified_token_value_fails() {
704                    let protect = $strct::from_key(KEY_32);
705                    let (mut token, _) = protect
706                        .generate_token_pair(None, 300)
707                        .expect("couldn't generate token/token pair");
708                    token.bytes[0] ^= 0x01;
709                    let token = &BASE64
710                        .decode(token.b64_string().as_bytes())
711                        .expect("token not base64");
712                    assert!(protect.parse_token(&token).is_err());
713                }
714
715                #[test]
716                fn mismatched_cookie_token_fail() {
717                    let protect = $strct::from_key(KEY_32);
718                    let (token, _) = protect
719                        .generate_token_pair(None, 300)
720                        .expect("couldn't generate token/token pair");
721                    let (_, cookie) = protect
722                        .generate_token_pair(None, 300)
723                        .expect("couldn't generate token/token pair");
724
725                    let token = &BASE64
726                        .decode(token.b64_string().as_bytes())
727                        .expect("token not base64");
728                    let token = protect.parse_token(&token).expect("token not parsed");
729                    let cookie = &BASE64
730                        .decode(cookie.b64_string().as_bytes())
731                        .expect("cookie not base64");
732                    let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
733                    assert!(
734                        !protect.verify_token_pair(&token, &cookie).is_ok(),
735                        "verified token/cookie pair when failure expected"
736                    );
737                }
738
739                #[test]
740                fn expired_token_fail() {
741                    let protect = $strct::from_key(KEY_32);
742                    let (token, cookie) = protect
743                        .generate_token_pair(None, -1)
744                        .expect("couldn't generate token/cookie pair");
745                    let token = &BASE64
746                        .decode(token.b64_string().as_bytes())
747                        .expect("token not base64");
748                    let token = protect.parse_token(&token).expect("token not parsed");
749                    let cookie = &BASE64
750                        .decode(cookie.b64_string().as_bytes())
751                        .expect("cookie not base64");
752                    let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
753                    assert!(
754                        !protect.verify_token_pair(&token, &cookie).is_ok(),
755                        "verified token/cookie pair when failure expected"
756                    );
757                }
758            }
759        };
760    }
761
762    test_cases!(AesGcmCsrfProtection, aesgcm);
763    test_cases!(ChaCha20Poly1305CsrfProtection, chacha20poly1305);
764    test_cases!(HmacCsrfProtection, hmac);
765
766    mod multi {
767        macro_rules! test_cases {
768            ($strct1: ident, $strct2: ident, $name: ident) => {
769                mod $name {
770                    use super::super::{super::*, KEY2_32, KEY_32};
771                    use data_encoding::BASE64;
772
773                    #[test]
774                    fn no_previous() {
775                        let protect = $strct1::from_key(KEY_32);
776                        let mut pairs = vec![];
777                        let pair = protect
778                            .generate_token_pair(None, 300)
779                            .expect("couldn't generate token/cookie pair");
780                        pairs.push(pair);
781
782                        let protect = MultiCsrfProtection::new(Box::new(protect), vec![]);
783                        let pair = protect
784                            .generate_token_pair(None, 300)
785                            .expect("couldn't generate token/cookie pair");
786                        pairs.push(pair);
787
788                        for &(ref token, ref cookie) in pairs.iter() {
789                            let token = &BASE64
790                                .decode(token.b64_string().as_bytes())
791                                .expect("token not base64");
792                            let token = protect.parse_token(&token).expect("token not parsed");
793                            let cookie = &BASE64
794                                .decode(cookie.b64_string().as_bytes())
795                                .expect("cookie not base64");
796                            let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
797                            assert!(
798                                protect.verify_token_pair(&token, &cookie).is_ok(),
799                                "could not verify token/cookie pair"
800                            );
801                        }
802                    }
803
804                    #[test]
805                    fn $name() {
806                        let protect_1 = $strct1::from_key(KEY_32);
807                        let mut pairs = vec![];
808                        let pair = protect_1
809                            .generate_token_pair(None, 300)
810                            .expect("couldn't generate token/cookie pair");
811                        pairs.push(pair);
812
813                        let protect_2 = $strct2::from_key(KEY2_32);
814                        let mut pairs = vec![];
815                        let pair = protect_2
816                            .generate_token_pair(None, 300)
817                            .expect("couldn't generate token/cookie pair");
818                        pairs.push(pair);
819
820                        let protect = MultiCsrfProtection::new(
821                            Box::new(protect_1),
822                            vec![Box::new(protect_2)],
823                        );
824                        let pair = protect
825                            .generate_token_pair(None, 300)
826                            .expect("couldn't generate token/cookie pair");
827                        pairs.push(pair);
828
829                        for &(ref token, ref cookie) in pairs.iter() {
830                            let token = &BASE64
831                                .decode(token.b64_string().as_bytes())
832                                .expect("token not base64");
833                            let token = protect.parse_token(&token).expect("token not parsed");
834                            let cookie = &BASE64
835                                .decode(cookie.b64_string().as_bytes())
836                                .expect("cookie not base64");
837                            let cookie = protect.parse_cookie(&cookie).expect("cookie not parsed");
838                            assert!(
839                                protect.verify_token_pair(&token, &cookie).is_ok(),
840                                "could not verify token/cookie pair"
841                            );
842                        }
843                    }
844                }
845            };
846        }
847
848        test_cases!(
849            AesGcmCsrfProtection,
850            AesGcmCsrfProtection,
851            aesgcm_then_aesgcm
852        );
853
854        test_cases!(
855            ChaCha20Poly1305CsrfProtection,
856            ChaCha20Poly1305CsrfProtection,
857            chacha20poly1305_then_chacha20poly1305
858        );
859
860        test_cases!(HmacCsrfProtection, HmacCsrfProtection, hmac_then_hmac);
861
862        test_cases!(
863            ChaCha20Poly1305CsrfProtection,
864            AesGcmCsrfProtection,
865            chacha20poly1305_then_aesgcm
866        );
867
868        test_cases!(HmacCsrfProtection, AesGcmCsrfProtection, hmac_then_aesgcm);
869
870        test_cases!(
871            AesGcmCsrfProtection,
872            ChaCha20Poly1305CsrfProtection,
873            aesgcm_then_chacha20poly1305
874        );
875        test_cases!(
876            HmacCsrfProtection,
877            ChaCha20Poly1305CsrfProtection,
878            hmac_then_chacha20poly1305
879        );
880
881        test_cases!(AesGcmCsrfProtection, HmacCsrfProtection, aesgcm_then_hmac);
882        test_cases!(
883            ChaCha20Poly1305CsrfProtection,
884            HmacCsrfProtection,
885            chacha20poly1305_then_hmac
886        );
887    }
888}