biscotti/
config.rs

1//! Configuration for a [`Processor`].
2//!
3//! Check out the [`ProcessorConfig`] struct for more information.
4//!
5//! [`Processor`]: crate::Processor
6//! [`ProcessorConfig`]: crate::ProcessorConfig
7pub use inner::{CryptoAlgorithm, CryptoRule, FallbackConfig};
8
9pub(crate) mod inner {
10    use crate::Key;
11
12    /// `Config` specifies how the server should handle incoming and outgoing cookies
13    /// with respect to security and encoding.
14    ///
15    /// In particular, it specifies whether cookie values and names should be
16    /// percent-encoded, and whether cookie values should be encrypted or signed.
17    ///
18    /// Check out the documentation for the fields of this struct for more information.
19    ///
20    /// # [`Processor`]
21    ///
22    /// To action the rules specified in this struct, you must convert it into a [`Processor`]:
23    ///
24    /// ```rust
25    /// use biscotti::{Processor, ProcessorConfig, Key};
26    /// use biscotti::config::{CryptoRule, CryptoAlgorithm};
27    ///
28    /// let mut config = ProcessorConfig::default();
29    /// config.crypto_rules.push(CryptoRule {
30    ///     cookie_names: vec!["session".to_string()],
31    ///     algorithm: CryptoAlgorithm::Encryption,
32    ///     // You'll use a key loaded from *somewhere* in production—e.g.
33    ///     // from a file, environment variable, or a secret management service.
34    ///     key: Key::generate(),
35    ///     fallbacks: vec![],
36    /// });
37    /// let processor: Processor = config.into();
38    /// ```
39    ///
40    /// [`Processor`]: crate::Processor
41    #[derive(Debug, Clone)]
42    #[non_exhaustive]
43    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
44    pub struct ProcessorConfig {
45        /// If `true`, all cookie values and names are automatically:
46        ///
47        /// - percent-decoded, when parsing request cookies out of the `Cookie` header.
48        /// - percent-encoded, when building the `Set-Cookie` header from response cookies.
49        ///
50        /// If `false`, cookie values and names are used as is.
51        ///
52        /// By default, this field is `true`.
53        #[cfg_attr(feature = "serde", serde(default = "percent_encode_default"))]
54        pub percent_encode: bool,
55        /// By default:
56        ///
57        /// - Values for response cookies are sent to the client unencrypted and unsigned
58        /// - Values for request cookies are assumed to be unencrypted and unsigned
59        ///
60        /// You can opt into higher cryptographic guarantees for specific cookies using
61        /// one or more [`CryptoRule`]s.
62        #[cfg_attr(feature = "serde", serde(default = "crypto_rules_default"))]
63        pub crypto_rules: Vec<CryptoRule>,
64    }
65
66    fn percent_encode_default() -> bool {
67        true
68    }
69
70    fn crypto_rules_default() -> Vec<CryptoRule> {
71        vec![]
72    }
73
74    /// `CryptoRule` specifies whether certain cookies should be encrypted or signed.
75    #[derive(Debug, Clone)]
76    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
77    pub struct CryptoRule {
78        /// The names of the cookies to which this rule applies.
79        pub cookie_names: Vec<String>,
80        /// How the cookies should be secured: either encryption or signing.
81        pub algorithm: CryptoAlgorithm,
82        /// The key to use for encryption or signing.
83        ///
84        /// # Requirements
85        ///
86        /// The key must be at least 64 bytes long and should be generated using a
87        /// cryptographically secure random number generator.
88        pub key: Key,
89        /// Fallbacks are used to decrypt/verify request cookies that failed to
90        /// be decrypted/verified using the primary key.
91        /// Fallbacks are never used to encrypt/sign response cookies.
92        ///
93        /// # Key rotation
94        ///
95        /// Fallbacks exist to enable **key and algorithm rotation**.
96        /// From time to time, you may want to change the key used to sign or encrypt cookies, or update
97        /// the algorithm.
98        /// If you do this naively
99        /// (e.g. change [`CryptoRule::key`] or [`CryptoRule::algorithm`] to a new value),
100        /// the server will immediately start rejecting all existing cookies
101        /// because they were signed/encrypted with the old key/algorithm.
102        ///
103        /// With fallbacks, you can start using the new configuration _without_ invalidating all existing
104        /// cookies.
105        /// The process for key rotation goes as follows:
106        ///
107        /// 1. Generate a new key
108        /// 2. Set `key` to the new key,
109        ///    and add the old key to the `fallbacks` vector, using the same algorithm
110        /// 3. Wait for the expiration of all cookies signed/encrypted with the old key
111        /// 4. Remove the old key from the `fallbacks` vector
112        #[cfg_attr(feature = "serde", serde(default))]
113        pub fallbacks: Vec<FallbackConfig>,
114    }
115
116    #[derive(Debug, Clone)]
117    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
118    /// A fallback configuration for either decrypting or verifying a cookie.
119    ///
120    /// Check out [`CryptoRule::fallbacks`] for more information.
121    pub struct FallbackConfig {
122        /// The key to use for encryption or signing.
123        pub key: Key,
124        /// How the cookies should be secured.
125        pub algorithm: CryptoAlgorithm,
126    }
127
128    /// The two cryptographic processes that can be applied to a cookie value.
129    #[derive(Debug, Clone, Copy)]
130    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
131    #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
132    #[non_exhaustive]
133    pub enum CryptoAlgorithm {
134        /// The cookie value will be encrypted using [AEAD-AES-256-GCM-SIV](https://www.rfc-editor.org/rfc/rfc8452.html).
135        ///
136        /// Encryption guarantees **confidentiality** of the value as well as its
137        /// **integrity**.
138        Encryption,
139        /// The cookie will be signed using [HMAC-SHA256](https://www.rfc-editor.org/rfc/rfc2104.html).
140        ///
141        /// Signing guarantees **integrity** of the value.
142        Signing,
143    }
144
145    impl Default for ProcessorConfig {
146        fn default() -> Self {
147            ProcessorConfig {
148                percent_encode: percent_encode_default(),
149                crypto_rules: crypto_rules_default(),
150            }
151        }
152    }
153}