1use std::time::Duration;
2
3use crate::browser::{AcsEndpoint, SloEndpoint, SsoEndpoint};
4use crate::entity::EntitySetting;
5use crate::error::SamlError;
6
7use super::algorithms::{name_id_format_uris, transform_algorithm_uris, NameIdFormat};
8use super::credentials::Credentials;
9use super::descriptors::{validate_entity_id, EntityId, IdpMetadataConfig, SpMetadataConfig};
10use super::policies::{
11 assertion_signature_required, audience_restriction_required, audience_validation_enabled,
12 authn_request_signature_required, authn_request_signing_enabled,
13 encrypted_cbc_response_signature_required, logout_signature_required, name_id_creation_allowed,
14 response_signature_required, verify_present_authn_request_signature, AlgorithmPolicy,
15 AssertionEncryptionPolicy, AuthnRequestSigningPolicy, IdpValidationPolicy, SpValidationPolicy,
16 TemplatePolicy, XmlPolicy,
17};
18#[cfg(not(any(
19 feature = "crypto-rustcrypto",
20 feature = "crypto-aws-lc",
21 feature = "crypto-fips"
22)))]
23use super::policies::{
24 AssertionSignaturePolicy, AuthnRequestValidationPolicy, LogoutPolicy, LogoutSignaturePolicy,
25 ResponseSignaturePolicy,
26};
27
28#[derive(Debug, Clone)]
45pub struct SpConfig {
46 pub entity_id: EntityId,
48 pub metadata: SpMetadataConfig,
50 pub credentials: Credentials,
52 pub validation: SpValidationPolicy,
54 pub algorithms: AlgorithmPolicy,
56 pub xml: XmlPolicy,
58 pub templates: TemplatePolicy,
60}
61
62impl SpConfig {
63 pub fn new(entity_id: EntityId, metadata: SpMetadataConfig) -> Self {
69 Self {
70 entity_id,
71 metadata,
72 credentials: Credentials::default(),
73 validation: SpValidationPolicy::default(),
74 algorithms: AlgorithmPolicy::default(),
75 xml: XmlPolicy::default(),
76 templates: TemplatePolicy::default(),
77 }
78 }
79
80 pub fn try_new(entity_id: EntityId, metadata: SpMetadataConfig) -> Result<Self, SamlError> {
87 let config = Self::new(entity_id, metadata);
88 config.validate()?;
89 Ok(config)
90 }
91
92 pub fn builder(entity_id: EntityId) -> SpConfigBuilder {
97 SpConfigBuilder::new(entity_id)
98 }
99
100 pub fn validate(&self) -> Result<(), SamlError> {
107 validate_entity_id(&self.entity_id)?;
108 self.metadata.validate()?;
109 validate_sp_policy(self)
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct SpConfigBuilder {
116 entity_id: EntityId,
117 metadata: SpMetadataConfig,
118 credentials: Credentials,
119 validation: SpValidationPolicy,
120 algorithms: AlgorithmPolicy,
121 xml: XmlPolicy,
122 templates: TemplatePolicy,
123}
124
125impl SpConfigBuilder {
126 fn new(entity_id: EntityId) -> Self {
127 Self {
128 entity_id,
129 metadata: SpMetadataConfig::new(Vec::new()),
130 credentials: Credentials::default(),
131 validation: deprecated_strict_sp_validation(),
132 algorithms: AlgorithmPolicy::default(),
133 xml: XmlPolicy::default(),
134 templates: TemplatePolicy::default(),
135 }
136 }
137
138 pub fn acs_endpoint(mut self, endpoint: AcsEndpoint) -> Self {
140 self.metadata.assertion_consumer_service.push(endpoint);
141 self
142 }
143
144 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
146 self.metadata.single_logout_service.push(endpoint);
147 self
148 }
149
150 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
152 self.metadata.name_id_format.push(format);
153 self
154 }
155
156 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
158 self.metadata.elements_order = Some(elements_order);
159 self
160 }
161
162 pub fn credentials(mut self, credentials: Credentials) -> Self {
164 self.credentials = credentials;
165 self
166 }
167
168 pub fn validation(mut self, validation: SpValidationPolicy) -> Self {
170 self.validation = validation;
171 self
172 }
173
174 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
176 self.algorithms = algorithms;
177 self
178 }
179
180 pub fn xml(mut self, xml: XmlPolicy) -> Self {
182 self.xml = xml;
183 self
184 }
185
186 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
188 self.templates = templates;
189 self
190 }
191
192 pub fn build(self) -> Result<SpConfig, SamlError> {
200 let config = SpConfig {
201 entity_id: self.entity_id,
202 metadata: self.metadata,
203 credentials: self.credentials,
204 validation: self.validation,
205 algorithms: self.algorithms,
206 xml: self.xml,
207 templates: self.templates,
208 };
209 config.validate()?;
210 Ok(config)
211 }
212}
213
214#[derive(Debug, Clone)]
236pub struct IdpConfig {
237 pub entity_id: EntityId,
239 pub metadata: IdpMetadataConfig,
241 pub credentials: Credentials,
243 pub issuance_lifetime: Duration,
249 pub validation: IdpValidationPolicy,
251 pub algorithms: AlgorithmPolicy,
253 pub xml: XmlPolicy,
255 pub templates: TemplatePolicy,
257}
258
259impl IdpConfig {
260 pub fn new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Self {
267 Self {
268 entity_id,
269 metadata,
270 credentials: Credentials::default(),
271 issuance_lifetime: Duration::from_secs(300),
272 validation: IdpValidationPolicy::default(),
273 algorithms: AlgorithmPolicy::default(),
274 xml: XmlPolicy::default(),
275 templates: TemplatePolicy::default(),
276 }
277 }
278
279 pub fn try_new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Result<Self, SamlError> {
287 let config = Self::new(entity_id, metadata);
288 config.validate()?;
289 Ok(config)
290 }
291
292 pub fn builder(entity_id: EntityId) -> IdpConfigBuilder {
295 IdpConfigBuilder::new(entity_id)
296 }
297
298 pub fn validate(&self) -> Result<(), SamlError> {
306 validate_entity_id(&self.entity_id)?;
307 self.metadata.validate()?;
308 validate_idp_policy(self)
309 }
310}
311
312#[derive(Debug, Clone)]
314pub struct IdpConfigBuilder {
315 entity_id: EntityId,
316 metadata: IdpMetadataConfig,
317 credentials: Credentials,
318 issuance_lifetime: Duration,
319 validation: IdpValidationPolicy,
320 algorithms: AlgorithmPolicy,
321 xml: XmlPolicy,
322 templates: TemplatePolicy,
323}
324
325impl IdpConfigBuilder {
326 fn new(entity_id: EntityId) -> Self {
327 Self {
328 entity_id,
329 metadata: IdpMetadataConfig::new(Vec::new()),
330 credentials: Credentials::default(),
331 issuance_lifetime: Duration::from_secs(300),
332 validation: deprecated_strict_idp_validation(),
333 algorithms: AlgorithmPolicy::default(),
334 xml: XmlPolicy::default(),
335 templates: TemplatePolicy::default(),
336 }
337 }
338
339 pub fn sso_endpoint(mut self, endpoint: SsoEndpoint) -> Self {
341 self.metadata.single_sign_on_service.push(endpoint);
342 self
343 }
344
345 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
347 self.metadata.single_logout_service.push(endpoint);
348 self
349 }
350
351 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
353 self.metadata.name_id_format.push(format);
354 self
355 }
356
357 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
359 self.metadata.elements_order = Some(elements_order);
360 self
361 }
362
363 pub fn credentials(mut self, credentials: Credentials) -> Self {
365 self.credentials = credentials;
366 self
367 }
368
369 pub fn issuance_lifetime(mut self, issuance_lifetime: Duration) -> Self {
374 self.issuance_lifetime = issuance_lifetime;
375 self
376 }
377
378 pub fn validation(mut self, validation: IdpValidationPolicy) -> Self {
380 self.validation = validation;
381 self
382 }
383
384 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
386 self.algorithms = algorithms;
387 self
388 }
389
390 pub fn xml(mut self, xml: XmlPolicy) -> Self {
392 self.xml = xml;
393 self
394 }
395
396 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
398 self.templates = templates;
399 self
400 }
401
402 pub fn build(self) -> Result<IdpConfig, SamlError> {
410 let config = IdpConfig {
411 entity_id: self.entity_id,
412 metadata: self.metadata,
413 credentials: self.credentials,
414 issuance_lifetime: self.issuance_lifetime,
415 validation: self.validation,
416 algorithms: self.algorithms,
417 xml: self.xml,
418 templates: self.templates,
419 };
420 config.validate()?;
421 Ok(config)
422 }
423}
424
425fn validate_common_credentials(credentials: &Credentials) -> Result<(), SamlError> {
426 if credentials.signing_key_passphrase.is_some() && credentials.signing_key.is_none() {
427 return Err(SamlError::MissingKey("signing_key".into()));
428 }
429 if credentials.decryption_key_passphrase.is_some() && credentials.decryption_key.is_none() {
430 return Err(SamlError::MissingKey("decryption_key".into()));
431 }
432 Ok(())
433}
434
435fn validate_sp_policy(config: &SpConfig) -> Result<(), SamlError> {
436 validate_sp_crypto_support(config)?;
437 validate_common_credentials(&config.credentials)?;
438 if matches!(
439 config.validation.authn_requests,
440 AuthnRequestSigningPolicy::Sign
441 ) {
442 validate_signing_credentials(&config.credentials)?;
443 }
444 if matches!(
445 config.xml.encryption.assertions,
446 AssertionEncryptionPolicy::EncryptAssertions
447 ) && config.credentials.decryption_key.is_none()
448 {
449 return Err(SamlError::MissingKey("decryption_key".into()));
450 }
451 Ok(())
452}
453
454fn validate_idp_policy(config: &IdpConfig) -> Result<(), SamlError> {
455 validated_idp_issuance_lifetime(config.issuance_lifetime)?;
456 validate_idp_crypto_support(config)?;
457 validate_common_credentials(&config.credentials)
458}
459
460pub(crate) fn validated_idp_issuance_lifetime(
461 issuance_lifetime: Duration,
462) -> Result<time::Duration, SamlError> {
463 if issuance_lifetime.is_zero() {
464 return Err(SamlError::Invalid(
465 "IdP issuance lifetime must be greater than zero".into(),
466 ));
467 }
468 time::Duration::try_from(issuance_lifetime).map_err(|_| {
469 SamlError::Invalid(
470 "IdP issuance lifetime must fit in time::Duration (at most i64::MAX seconds plus 999,999,999 nanoseconds)".into(),
471 )
472 })
473}
474
475fn validate_signing_credentials(credentials: &Credentials) -> Result<(), SamlError> {
476 if credentials.signing_key.is_none() {
477 return Err(SamlError::MissingKey("signing_key".into()));
478 }
479 if credentials.signing_certificate.is_none() {
480 return Err(SamlError::MissingKey("signing_certificate".into()));
481 }
482 Ok(())
483}
484
485#[cfg(any(
486 feature = "crypto-rustcrypto",
487 feature = "crypto-aws-lc",
488 feature = "crypto-fips"
489))]
490fn validate_sp_crypto_support(_config: &SpConfig) -> Result<(), SamlError> {
491 Ok(())
492}
493
494#[cfg(not(any(
495 feature = "crypto-rustcrypto",
496 feature = "crypto-aws-lc",
497 feature = "crypto-fips"
498)))]
499fn validate_sp_crypto_support(config: &SpConfig) -> Result<(), SamlError> {
500 if sp_config_requires_crypto(config) {
501 return Err(SamlError::Unsupported(
502 "selected SP config policy requires a crypto provider feature".into(),
503 ));
504 }
505 Ok(())
506}
507
508#[cfg(any(
509 feature = "crypto-rustcrypto",
510 feature = "crypto-aws-lc",
511 feature = "crypto-fips"
512))]
513fn validate_idp_crypto_support(_config: &IdpConfig) -> Result<(), SamlError> {
514 Ok(())
515}
516
517#[cfg(not(any(
518 feature = "crypto-rustcrypto",
519 feature = "crypto-aws-lc",
520 feature = "crypto-fips"
521)))]
522fn validate_idp_crypto_support(config: &IdpConfig) -> Result<(), SamlError> {
523 if idp_config_requires_crypto(config) {
524 return Err(SamlError::Unsupported(
525 "selected IdP config policy requires a crypto provider feature".into(),
526 ));
527 }
528 Ok(())
529}
530
531#[cfg(not(any(
532 feature = "crypto-rustcrypto",
533 feature = "crypto-aws-lc",
534 feature = "crypto-fips"
535)))]
536fn sp_config_requires_crypto(config: &SpConfig) -> bool {
537 matches!(
538 config.validation.assertions,
539 AssertionSignaturePolicy::RequireSigned
540 ) || matches!(
541 config.validation.responses,
542 ResponseSignaturePolicy::RequireSigned | ResponseSignaturePolicy::RequireForEncryptedCbc
543 ) || matches!(
544 config.validation.authn_requests,
545 AuthnRequestSigningPolicy::Sign
546 ) || logout_policy_requires_crypto(config.validation.logout)
547 || matches!(
548 config.xml.encryption.assertions,
549 AssertionEncryptionPolicy::EncryptAssertions
550 )
551}
552
553#[cfg(not(any(
554 feature = "crypto-rustcrypto",
555 feature = "crypto-aws-lc",
556 feature = "crypto-fips"
557)))]
558fn idp_config_requires_crypto(config: &IdpConfig) -> bool {
559 matches!(
560 config.validation.authn_requests,
561 AuthnRequestValidationPolicy::RequireSigned
562 ) || logout_policy_requires_crypto(config.validation.logout)
563 || matches!(
564 config.xml.encryption.assertions,
565 AssertionEncryptionPolicy::EncryptAssertions
566 )
567}
568
569#[cfg(not(any(
570 feature = "crypto-rustcrypto",
571 feature = "crypto-aws-lc",
572 feature = "crypto-fips"
573)))]
574fn logout_policy_requires_crypto(policy: LogoutPolicy) -> bool {
575 logout_signature_policy_requires_crypto(policy.requests)
576 || logout_signature_policy_requires_crypto(policy.responses)
577}
578
579#[cfg(not(any(
580 feature = "crypto-rustcrypto",
581 feature = "crypto-aws-lc",
582 feature = "crypto-fips"
583)))]
584fn logout_signature_policy_requires_crypto(policy: LogoutSignaturePolicy) -> bool {
585 matches!(policy, LogoutSignaturePolicy::RequireSigned)
586}
587
588fn apply_common_settings(
589 entity_id: &EntityId,
590 name_id_format: &[NameIdFormat],
591 credentials: &Credentials,
592 algorithms: &AlgorithmPolicy,
593 xml: &XmlPolicy,
594 templates: &TemplatePolicy,
595 setting: &mut EntitySetting,
596) {
597 setting.entity_id = Some(entity_id.as_str().to_string());
598 setting.request_signature_algorithm = algorithms.signature.as_uri().to_string();
599 setting.data_encryption_algorithm = algorithms.data_encryption.as_uri().to_string();
600 setting.key_encryption_algorithm = algorithms.key_encryption.as_uri().to_string();
601 setting.message_signing_order = algorithms.message_signing_order;
602 setting.is_assertion_encrypted = matches!(
603 xml.encryption.assertions,
604 AssertionEncryptionPolicy::EncryptAssertions
605 );
606 setting.allow_insecure_software_rsa_key_transport_decryption = xml
607 .encryption
608 .allows_insecure_software_rsa_key_transport_decryption();
609 setting.relay_state = templates.relay_state.clone();
610 setting.name_id_format = name_id_format_uris(name_id_format);
611 setting.private_key = credentials
612 .signing_key
613 .as_ref()
614 .map(|key| key.as_str().to_string());
615 setting.private_key_pass = credentials
616 .signing_key_passphrase
617 .as_ref()
618 .map(|passphrase| passphrase.as_str().to_string());
619 setting.signing_cert = credentials
620 .signing_certificate
621 .as_ref()
622 .map(|certificate| certificate.as_str().to_string());
623 setting.encrypt_cert = credentials
624 .encryption_certificate
625 .as_ref()
626 .map(|certificate| certificate.as_str().to_string());
627 setting.enc_private_key = credentials
628 .decryption_key
629 .as_ref()
630 .map(|key| key.as_str().to_string());
631 setting.enc_private_key_pass = credentials
632 .decryption_key_passphrase
633 .as_ref()
634 .map(|passphrase| passphrase.as_str().to_string());
635 setting.clock_drifts = xml.clock_drifts;
636 setting.redirect_inflate_max_bytes = xml.redirect_inflate_max_bytes;
637 setting.xml_limits = xml.limits;
638 setting.tag_prefix_protocol = templates.tag_prefix_protocol.clone();
639 setting.tag_prefix_assertion = templates.tag_prefix_assertion.clone();
640 setting.tag_prefix_encrypted_assertion = templates.tag_prefix_encrypted_assertion.clone();
641 setting.login_response_template = templates.login_response_template.clone();
642 setting.login_request_template = templates.login_request_template.clone();
643 setting.logout_request_template = templates.logout_request_template.clone();
644 setting.logout_response_template = templates.logout_response_template.clone();
645 setting.signature_config = templates.signature_config.clone();
646 setting.transformation_algorithms =
647 transform_algorithm_uris(&algorithms.signed_reference_transforms);
648}
649
650impl TryFrom<&SpConfig> for EntitySetting {
651 type Error = SamlError;
652
653 fn try_from(config: &SpConfig) -> Result<Self, Self::Error> {
654 config.validate()?;
655 let mut setting = Self::default();
656 apply_common_settings(
657 &config.entity_id,
658 &config.metadata.name_id_format,
659 &config.credentials,
660 &config.algorithms,
661 &config.xml,
662 &config.templates,
663 &mut setting,
664 );
665 setting.allow_create = name_id_creation_allowed(config.validation.name_id_creation);
666 setting.authn_requests_signed =
667 authn_request_signing_enabled(config.validation.authn_requests);
668 setting.want_assertions_signed = assertion_signature_required(config.validation.assertions);
669 setting.validate_audience = audience_validation_enabled(config.validation.audience);
670 setting.require_audience_restriction =
671 audience_restriction_required(config.validation.audience);
672 setting.want_message_signed = response_signature_required(config.validation.responses);
673 setting.strict_xml_signature_profile = matches!(
674 config.validation.xml_signatures,
675 super::XmlSignatureProfile::StrictRsaSha2
676 );
677 setting.want_encrypted_cbc_response_signed =
678 encrypted_cbc_response_signature_required(config.validation.responses);
679 setting.want_logout_request_signed =
680 logout_signature_required(config.validation.logout.requests)?;
681 setting.want_logout_response_signed =
682 logout_signature_required(config.validation.logout.responses)?;
683 Ok(setting)
684 }
685}
686
687impl TryFrom<&IdpConfig> for EntitySetting {
688 type Error = SamlError;
689
690 fn try_from(config: &IdpConfig) -> Result<Self, Self::Error> {
691 config.validate()?;
692 let mut setting = Self::default();
693 apply_common_settings(
694 &config.entity_id,
695 &config.metadata.name_id_format,
696 &config.credentials,
697 &config.algorithms,
698 &config.xml,
699 &config.templates,
700 &mut setting,
701 );
702 setting.want_authn_requests_signed =
703 authn_request_signature_required(config.validation.authn_requests);
704 setting.verify_authn_request_signature_if_present =
705 verify_present_authn_request_signature(config.validation.authn_requests);
706 setting.want_logout_request_signed =
707 logout_signature_required(config.validation.logout.requests)?;
708 setting.want_logout_response_signed =
709 logout_signature_required(config.validation.logout.responses)?;
710 Ok(setting)
711 }
712}
713
714#[expect(
715 deprecated,
716 reason = "config builders stay on the deprecated strict preset in this release"
717)]
718fn deprecated_strict_sp_validation() -> SpValidationPolicy {
719 SpValidationPolicy::strict()
720}
721
722#[expect(
723 deprecated,
724 reason = "config builders stay on the deprecated strict preset in this release"
725)]
726fn deprecated_strict_idp_validation() -> IdpValidationPolicy {
727 IdpValidationPolicy::strict()
728}