1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Configuration for a [`Processor`].
//!
//! Check out the [`Config`] struct for more information.
//!
//! [`Processor`]: crate::Processor
use crate::Key;

/// `Config` specifies how the server should handle incoming and outgoing cookies
/// with respect to security and encoding.  
///
/// In particular, it specifies whether cookie values and names should be
/// percent-encoded, and whether cookie values should be encrypted or signed.
///
/// Check out the documentation for the fields of this struct for more information.
///
/// # [`Processor`]
///
/// To action the rules specified in this struct, you must convert it into a [`Processor`]:
///
/// ```rust
/// use biscotti::{Processor, Key};
/// use biscotti::config::{Config, CryptoRule, CryptoType};
///
/// let mut config = Config::default();
/// config.crypto_rules.push(CryptoRule {
///     cookie_names: vec!["session".to_string()],
///     r#type: CryptoType::Encryption,
///     // You'll use a key loaded from *somewhere* in production—e.g.
///     // from a file, environment variable, or a secret management service.
///     key: Key::generate(),
///     secondary_keys: vec![],
/// });
/// let processor: Processor = config.into();
/// ```
///
/// [`Processor`]: crate::Processor
#[derive(Debug, Clone)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct Config {
    /// If `true`, all cookie values and names are automatically:
    ///
    /// - percent-decoded, when parsing request cookies out of the `Cookie` header.
    /// - percent-encoded, when building the `Set-Cookie` header from response cookies.
    ///
    /// If `false`, cookie values and names are used as is.
    ///
    /// By default, this field is `true`.
    pub percent_encode: bool,
    /// By default:
    ///
    /// - Values for response cookies are sent to the client unencrypted and unsigned
    /// - Values for request cookies are assumed to be unencrypted and unsigned
    ///
    /// You can opt into higher cryptographic guarantees for specific cookies using
    /// one or more [`CryptoRule`]s.
    pub crypto_rules: Vec<CryptoRule>,
}

/// `CryptoRule` specifies whether certain cookies should be encrypted or signed.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct CryptoRule {
    /// The names of the cookies to which this rule applies.
    pub cookie_names: Vec<String>,
    /// How the cookies should be secured: either encryption or signing.
    pub r#type: CryptoType,
    /// The key to use for encryption or signing.
    ///
    /// # Requirements
    ///
    /// The key must be at least 64 bytes long and should be generated using a
    /// cryptographically secure random number generator.
    pub key: Key,
    /// Secondary keys are used to decrypt/verify request cookies that failed to
    /// be decrypted/verified using the primary key.  
    /// Secondary keys are never used to encrypt/sign response cookies.
    ///
    /// # Key rotation
    ///
    /// Secondary keys exist to enable **key rotation**.  
    /// From time to time, you may want to change the key used to sign or encrypt cookies.  
    /// If you do this naively (i.e. change [`CryptoRule::key`] to a new value), the server  
    /// will immediately start rejecting all existing cookies
    /// because they were signed/encrypted with the old key.
    ///
    /// Using secondary keys, you can start using the new key _without_ invalidating all existing
    /// cookies.
    /// The process is as follows:
    ///
    /// 1. Generate a new key
    /// 2. Set `key` to the new key, and add the old key to the `secondary_keys` vector
    /// 3. Wait for the expiration of all cookies signed/encrypted with the old key
    /// 4. Remove the old key from the `secondary_keys` vector
    #[cfg_attr(feature = "serde", serde(default))]
    pub secondary_keys: Vec<Key>,
}

/// The two cryptographic processes that can be applied to a cookie value.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub enum CryptoType {
    /// The cookie value should be encrypted.  
    /// Encryption guarantees **confidentiality** of the value as well as its
    /// **integrity**.
    Encryption,
    /// The cookie value should be signed using this key.
    /// Signing guarantees **integrity** of the value.
    Signing,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            percent_encode: true,
            crypto_rules: vec![],
        }
    }
}