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