Skip to main content

saml_rs/config/
policies.rs

1use crate::binding::MAX_DEFLATE_RAW_DECODE_BYTES;
2use crate::constants::MessageSignatureOrder;
3use crate::entity::SignatureConfig;
4use crate::error::SamlError;
5use crate::template::LoginResponseTemplate;
6use crate::xml::XmlLimits;
7
8use super::algorithms::{
9    DataEncryptionAlgorithm, KeyEncryptionAlgorithm, SignatureAlgorithm, TransformAlgorithm,
10};
11
12/// Whether SPs require assertion-level signatures.
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub enum AssertionSignaturePolicy {
15    /// Reject unsigned assertions.
16    RequireSigned,
17    /// Accept unsigned assertions for legacy interoperability.
18    #[default]
19    AllowUnsignedForCompatibility,
20}
21
22/// Whether SPs require message-level signatures.
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24pub enum MessageSignaturePolicy {
25    /// Reject unsigned protocol messages.
26    RequireSigned,
27    /// Accept unsigned protocol messages for legacy interoperability.
28    #[default]
29    AllowUnsignedForCompatibility,
30}
31
32/// Whether an SP signs outgoing AuthnRequests.
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub enum AuthnRequestSigningPolicy {
35    /// Sign outgoing AuthnRequests.
36    Sign,
37    /// Send unsigned AuthnRequests for legacy interoperability.
38    #[default]
39    DoNotSignForCompatibility,
40}
41
42/// Whether an IdP requires signed inbound AuthnRequests.
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
44pub enum AuthnRequestValidationPolicy {
45    /// Reject unsigned AuthnRequests.
46    RequireSigned,
47    /// Accept unsigned AuthnRequests for legacy interoperability.
48    #[default]
49    AllowUnsignedForCompatibility,
50}
51
52/// Whether logout messages require signatures.
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
54pub enum LogoutSignaturePolicy {
55    /// Reject unsigned logout messages.
56    #[default]
57    RequireSigned,
58    /// Accept unsigned logout messages for legacy interoperability.
59    AllowUnsignedForCompatibility,
60}
61
62/// Whether an SP validates assertion audience restrictions.
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub enum AudienceValidationPolicy {
65    /// Require this SP's entity ID in assertion audiences.
66    #[default]
67    Validate,
68    /// Skip audience validation for legacy interoperability.
69    SkipForCompatibility,
70}
71
72/// Whether SP AuthnRequests allow IdPs to create a new identifier.
73#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
74pub enum NameIdCreationPolicy {
75    /// Set `AllowCreate="true"` in AuthnRequests.
76    AllowCreate,
77    /// Set `AllowCreate="false"` in AuthnRequests.
78    #[default]
79    DoNotAllowCreate,
80}
81
82/// SP-side validation and outbound signing policy.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct SpValidationPolicy {
85    /// Assertion signature requirement.
86    pub assertions: AssertionSignaturePolicy,
87    /// Response/message signature requirement.
88    pub messages: MessageSignaturePolicy,
89    /// Outbound AuthnRequest signing behavior.
90    pub authn_requests: AuthnRequestSigningPolicy,
91    /// Audience validation behavior.
92    pub audience: AudienceValidationPolicy,
93    /// NameID creation behavior for AuthnRequests.
94    pub name_id_creation: NameIdCreationPolicy,
95    /// Logout signature validation behavior.
96    pub logout: LogoutPolicy,
97}
98
99impl SpValidationPolicy {
100    /// Strict SP validation and outbound signing defaults.
101    pub fn strict() -> Self {
102        Self {
103            assertions: AssertionSignaturePolicy::RequireSigned,
104            messages: MessageSignaturePolicy::RequireSigned,
105            authn_requests: AuthnRequestSigningPolicy::Sign,
106            audience: AudienceValidationPolicy::Validate,
107            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
108            logout: LogoutPolicy::strict(),
109        }
110    }
111
112    /// Legacy interoperability policy with unsigned behavior made explicit.
113    pub fn compatibility() -> Self {
114        Self {
115            assertions: AssertionSignaturePolicy::AllowUnsignedForCompatibility,
116            messages: MessageSignaturePolicy::AllowUnsignedForCompatibility,
117            authn_requests: AuthnRequestSigningPolicy::DoNotSignForCompatibility,
118            audience: AudienceValidationPolicy::SkipForCompatibility,
119            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
120            logout: LogoutPolicy::compatibility(),
121        }
122    }
123}
124
125impl Default for SpValidationPolicy {
126    fn default() -> Self {
127        Self::compatibility()
128    }
129}
130
131/// IdP-side validation policy.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct IdpValidationPolicy {
134    /// Inbound AuthnRequest signature requirement.
135    pub authn_requests: AuthnRequestValidationPolicy,
136    /// Logout signature validation behavior.
137    pub logout: LogoutPolicy,
138}
139
140impl IdpValidationPolicy {
141    /// Strict IdP validation defaults.
142    pub fn strict() -> Self {
143        Self {
144            authn_requests: AuthnRequestValidationPolicy::RequireSigned,
145            logout: LogoutPolicy::strict(),
146        }
147    }
148
149    /// Legacy interoperability policy with unsigned behavior made explicit.
150    pub fn compatibility() -> Self {
151        Self {
152            authn_requests: AuthnRequestValidationPolicy::AllowUnsignedForCompatibility,
153            logout: LogoutPolicy::compatibility(),
154        }
155    }
156}
157
158impl Default for IdpValidationPolicy {
159    fn default() -> Self {
160        Self::compatibility()
161    }
162}
163
164/// Logout request and response signature policy.
165#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
166pub struct LogoutPolicy {
167    /// LogoutRequest signature behavior.
168    pub requests: LogoutSignaturePolicy,
169    /// LogoutResponse signature behavior.
170    pub responses: LogoutSignaturePolicy,
171}
172
173impl LogoutPolicy {
174    /// Require signed logout requests and responses.
175    pub fn strict() -> Self {
176        Self {
177            requests: LogoutSignaturePolicy::RequireSigned,
178            responses: LogoutSignaturePolicy::RequireSigned,
179        }
180    }
181
182    /// Accept unsigned logout requests and responses for legacy interoperability.
183    pub fn compatibility() -> Self {
184        Self {
185            requests: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
186            responses: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
187        }
188    }
189}
190
191/// Whether assertions are encrypted in generated responses.
192#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
193pub enum AssertionEncryptionPolicy {
194    /// Do not encrypt assertions.
195    #[default]
196    PlaintextAssertions,
197    /// Encrypt assertions.
198    EncryptAssertions,
199}
200
201/// XML encryption policy.
202///
203/// # Examples
204///
205/// Use typed configuration to request encrypted assertions in generated
206/// responses. This only configures policy; actual encryption uses the crate's
207/// XML-Enc backend and deployment credentials.
208///
209/// ```
210/// use saml_rs::{EntityId, IdpConfig, SsoEndpoint, XmlEncryptionPolicy, XmlPolicy};
211///
212/// let xml = XmlPolicy {
213///     encryption: XmlEncryptionPolicy::encrypt_assertions(),
214///     ..XmlPolicy::default()
215/// };
216/// let idp_builder = IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
217///     .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
218///     .xml(xml);
219/// # let _ = idp_builder;
220/// # Ok::<(), saml_rs::SamlError>(())
221/// ```
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
223pub struct XmlEncryptionPolicy {
224    /// Assertion encryption behavior.
225    pub assertions: AssertionEncryptionPolicy,
226    allow_insecure_software_rsa_key_transport_decryption: bool,
227}
228
229impl XmlEncryptionPolicy {
230    /// Enable assertion encryption.
231    pub fn encrypt_assertions() -> Self {
232        Self {
233            assertions: AssertionEncryptionPolicy::EncryptAssertions,
234            ..Self::default()
235        }
236    }
237
238    /// Explicitly allow software RSA key-transport decryption despite
239    /// `RUSTSEC-2023-0071` timing-risk concerns in the bundled backend.
240    pub fn allow_insecure_software_rsa_key_transport_decryption() -> Self {
241        Self {
242            allow_insecure_software_rsa_key_transport_decryption: true,
243            ..Self::default()
244        }
245    }
246
247    /// Return a copy with the software RSA key-transport risk explicitly allowed.
248    pub fn with_insecure_software_rsa_key_transport_decryption_allowed(mut self) -> Self {
249        self.allow_insecure_software_rsa_key_transport_decryption = true;
250        self
251    }
252
253    pub(super) fn allows_insecure_software_rsa_key_transport_decryption(self) -> bool {
254        self.allow_insecure_software_rsa_key_transport_decryption
255    }
256}
257
258/// XML parser, redirect decompression, clock, and XML encryption policy.
259///
260/// # Examples
261///
262/// Software RSA key-transport decryption is disabled by default because the
263/// bundled RustCrypto RSA backend, reached through `bergshamra` / `kryptering`,
264/// is affected by `RUSTSEC-2023-0071`. Enable it only as an explicit
265/// compatibility exception for a deployment that accepts that risk.
266///
267/// ```
268/// use saml_rs::{AcsEndpoint, EntityId, SpConfig, XmlEncryptionPolicy, XmlPolicy};
269///
270/// let xml = XmlPolicy {
271///     encryption: XmlEncryptionPolicy::default()
272///         .with_insecure_software_rsa_key_transport_decryption_allowed(),
273///     ..XmlPolicy::default()
274/// };
275/// let sp_builder = SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
276///     .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
277///     .xml(xml);
278/// # let _ = sp_builder;
279/// # Ok::<(), saml_rs::SamlError>(())
280/// ```
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub struct XmlPolicy {
283    /// Clock drift tolerance `(not_before_ms, not_on_or_after_ms)`.
284    pub clock_drifts: (i64, i64),
285    /// Maximum decoded compressed and inflated raw-DEFLATE bytes accepted for
286    /// HTTP-Redirect input.
287    pub redirect_inflate_max_bytes: usize,
288    /// XML parser resource limits.
289    pub limits: XmlLimits,
290    /// XML encryption behavior.
291    pub encryption: XmlEncryptionPolicy,
292}
293
294impl Default for XmlPolicy {
295    fn default() -> Self {
296        Self {
297            clock_drifts: (0, 0),
298            redirect_inflate_max_bytes: MAX_DEFLATE_RAW_DECODE_BYTES,
299            limits: XmlLimits::default(),
300            encryption: XmlEncryptionPolicy::default(),
301        }
302    }
303}
304
305/// Algorithm choices used by outgoing SAML messages.
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct AlgorithmPolicy {
308    /// Signature algorithm URI.
309    pub signature: SignatureAlgorithm,
310    /// Data encryption algorithm URI.
311    pub data_encryption: DataEncryptionAlgorithm,
312    /// Key encryption algorithm URI.
313    pub key_encryption: KeyEncryptionAlgorithm,
314    /// Sign/encrypt operation order for messages that do both.
315    pub message_signing_order: MessageSignatureOrder,
316    /// XML-DSig reference transforms.
317    pub signed_reference_transforms: Vec<TransformAlgorithm>,
318}
319
320impl Default for AlgorithmPolicy {
321    fn default() -> Self {
322        Self {
323            signature: SignatureAlgorithm::default(),
324            data_encryption: DataEncryptionAlgorithm::default(),
325            key_encryption: KeyEncryptionAlgorithm::default(),
326            message_signing_order: MessageSignatureOrder::SignThenEncrypt,
327            signed_reference_transforms: vec![
328                TransformAlgorithm::EnvelopedSignature,
329                TransformAlgorithm::ExclusiveCanonicalization,
330            ],
331        }
332    }
333}
334
335/// Template and XML tag-prefix customization.
336#[derive(Debug, Clone)]
337pub struct TemplatePolicy {
338    /// Default RelayState.
339    pub relay_state: String,
340    /// IdP protocol tag prefix for generated messages.
341    pub tag_prefix_protocol: String,
342    /// IdP assertion tag prefix for generated messages.
343    pub tag_prefix_assertion: String,
344    /// IdP tag prefix for generated `<EncryptedAssertion>` elements.
345    pub tag_prefix_encrypted_assertion: String,
346    /// IdP login response template and attributes.
347    pub login_response_template: Option<LoginResponseTemplate>,
348    /// SP login request template.
349    pub login_request_template: Option<String>,
350    /// Logout request template.
351    pub logout_request_template: Option<String>,
352    /// Logout response template.
353    pub logout_response_template: Option<String>,
354    /// Embedded-signature placement and prefix.
355    pub signature_config: Option<SignatureConfig>,
356}
357
358impl Default for TemplatePolicy {
359    fn default() -> Self {
360        Self {
361            relay_state: String::new(),
362            tag_prefix_protocol: "samlp".to_string(),
363            tag_prefix_assertion: "saml".to_string(),
364            tag_prefix_encrypted_assertion: "saml".to_string(),
365            login_response_template: None,
366            login_request_template: None,
367            logout_request_template: None,
368            logout_response_template: None,
369            signature_config: None,
370        }
371    }
372}
373pub(super) fn authn_request_signing_enabled(policy: AuthnRequestSigningPolicy) -> bool {
374    matches!(policy, AuthnRequestSigningPolicy::Sign)
375}
376
377pub(super) fn authn_request_signature_required(policy: AuthnRequestValidationPolicy) -> bool {
378    matches!(policy, AuthnRequestValidationPolicy::RequireSigned)
379}
380
381pub(super) fn assertion_signature_required(policy: AssertionSignaturePolicy) -> bool {
382    matches!(policy, AssertionSignaturePolicy::RequireSigned)
383}
384
385pub(super) fn message_signature_required(policy: MessageSignaturePolicy) -> bool {
386    matches!(policy, MessageSignaturePolicy::RequireSigned)
387}
388
389pub(super) fn logout_signature_required(policy: LogoutSignaturePolicy) -> Result<bool, SamlError> {
390    match policy {
391        LogoutSignaturePolicy::RequireSigned => Ok(true),
392        LogoutSignaturePolicy::AllowUnsignedForCompatibility => Ok(false),
393    }
394}
395pub(super) fn name_id_creation_allowed(policy: NameIdCreationPolicy) -> bool {
396    matches!(policy, NameIdCreationPolicy::AllowCreate)
397}
398
399pub(super) fn audience_validation_enabled(policy: AudienceValidationPolicy) -> bool {
400    matches!(policy, AudienceValidationPolicy::Validate)
401}