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}