salvo-csrf 0.96.0

CSRF support for salvo web server framework.
Documentation
use std::fmt::Debug;

use aead::array::Array;
use aead::consts::U12;
use aead::{Aead, Key, KeyInit};
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chacha20poly1305::ChaCha20Poly1305;
use subtle::ConstantTimeEq;

use super::CsrfCipher;

/// CcpCipher is a CSRF protection implementation that uses [`ChaCha20Poly1305`](https://datatracker.ietf.org/doc/html/rfc8439).
#[derive(Debug, Clone)]
pub struct CcpCipher {
    aead_key: [u8; 32],
    token_size: usize,
}

impl CcpCipher {
    /// Given an AEAD key, returns a `CcpCipher` instance.
    #[inline]
    #[must_use]
    pub fn new(aead_key: [u8; 32]) -> Self {
        Self {
            aead_key,
            token_size: 32,
        }
    }

    /// Sets the length of the token.
    #[inline]
    #[must_use]
    pub fn token_size(mut self, token_size: usize) -> Self {
        assert!(token_size >= 8, "length must be larger than 8");
        self.token_size = token_size;
        self
    }

    #[inline]
    fn aead(&self) -> ChaCha20Poly1305 {
        let key = Key::<ChaCha20Poly1305>::try_from(&self.aead_key[..]).expect("invalid key length");
        ChaCha20Poly1305::new(&key)
    }
}

impl CsrfCipher for CcpCipher {
    fn verify(&self, token: &str, proof: &str) -> bool {
        if let (Ok(token), Ok(proof)) = (
            URL_SAFE_NO_PAD.decode(token.as_bytes()),
            URL_SAFE_NO_PAD.decode(proof.as_bytes()),
        ) {
            if token.len() < 8 || proof.len() < 20 {
                false
            } else {
                let nonce = Array::<u8, U12>::try_from(&proof[0..12]).expect("invalid nonce");
                let aead = self.aead();
                aead.decrypt(&nonce, &proof[12..])
                    .map(|p| {
                        // Compare lengths first, then use constant-time compare
                        // on the recovered plaintext vs. the client-supplied
                        // token. Avoids leaking the prefix length of a valid
                        // token through timing.
                        p.len() == token.len() && bool::from(p.ct_eq(&token))
                    })
                    .unwrap_or(false)
            }
        } else {
            false
        }
    }
    fn generate(&self) -> (String, String) {
        let token = self.random_bytes(self.token_size);
        let aead = self.aead();
        let mut proof = self.random_bytes(12);
        let nonce = Array::<u8, U12>::try_from(&proof[..]).expect("invalid nonce");
        proof.append(
            &mut aead
                .encrypt(&nonce, token.as_slice())
                .expect("encryption failed"),
        );
        (URL_SAFE_NO_PAD.encode(token), URL_SAFE_NO_PAD.encode(proof))
    }
}