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_validation_enabled, authn_request_signature_required,
12 authn_request_signing_enabled, encrypted_cbc_response_signature_required,
13 logout_signature_required, name_id_creation_allowed, response_signature_required,
14 AlgorithmPolicy, AssertionEncryptionPolicy, AuthnRequestSigningPolicy, IdpValidationPolicy,
15 SpValidationPolicy, TemplatePolicy, XmlPolicy,
16};
17#[cfg(not(any(
18 feature = "crypto-rustcrypto",
19 feature = "crypto-aws-lc",
20 feature = "crypto-fips"
21)))]
22use super::policies::{
23 AssertionSignaturePolicy, AuthnRequestValidationPolicy, LogoutPolicy, LogoutSignaturePolicy,
24 ResponseSignaturePolicy,
25};
26
27#[derive(Debug, Clone)]
44pub struct SpConfig {
45 pub entity_id: EntityId,
47 pub metadata: SpMetadataConfig,
49 pub credentials: Credentials,
51 pub validation: SpValidationPolicy,
53 pub algorithms: AlgorithmPolicy,
55 pub xml: XmlPolicy,
57 pub templates: TemplatePolicy,
59}
60
61impl SpConfig {
62 pub fn new(entity_id: EntityId, metadata: SpMetadataConfig) -> Self {
68 Self {
69 entity_id,
70 metadata,
71 credentials: Credentials::default(),
72 validation: SpValidationPolicy::default(),
73 algorithms: AlgorithmPolicy::default(),
74 xml: XmlPolicy::default(),
75 templates: TemplatePolicy::default(),
76 }
77 }
78
79 pub fn try_new(entity_id: EntityId, metadata: SpMetadataConfig) -> Result<Self, SamlError> {
86 let config = Self::new(entity_id, metadata);
87 config.validate()?;
88 Ok(config)
89 }
90
91 pub fn builder(entity_id: EntityId) -> SpConfigBuilder {
93 SpConfigBuilder::new(entity_id)
94 }
95
96 pub fn validate(&self) -> Result<(), SamlError> {
103 validate_entity_id(&self.entity_id)?;
104 self.metadata.validate()?;
105 validate_sp_policy(self)
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct SpConfigBuilder {
112 entity_id: EntityId,
113 metadata: SpMetadataConfig,
114 credentials: Credentials,
115 validation: SpValidationPolicy,
116 algorithms: AlgorithmPolicy,
117 xml: XmlPolicy,
118 templates: TemplatePolicy,
119}
120
121impl SpConfigBuilder {
122 fn new(entity_id: EntityId) -> Self {
123 Self {
124 entity_id,
125 metadata: SpMetadataConfig::new(Vec::new()),
126 credentials: Credentials::default(),
127 validation: SpValidationPolicy::strict(),
128 algorithms: AlgorithmPolicy::default(),
129 xml: XmlPolicy::default(),
130 templates: TemplatePolicy::default(),
131 }
132 }
133
134 pub fn acs_endpoint(mut self, endpoint: AcsEndpoint) -> Self {
136 self.metadata.assertion_consumer_service.push(endpoint);
137 self
138 }
139
140 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
142 self.metadata.single_logout_service.push(endpoint);
143 self
144 }
145
146 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
148 self.metadata.name_id_format.push(format);
149 self
150 }
151
152 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
154 self.metadata.elements_order = Some(elements_order);
155 self
156 }
157
158 pub fn credentials(mut self, credentials: Credentials) -> Self {
160 self.credentials = credentials;
161 self
162 }
163
164 pub fn validation(mut self, validation: SpValidationPolicy) -> Self {
166 self.validation = validation;
167 self
168 }
169
170 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
172 self.algorithms = algorithms;
173 self
174 }
175
176 pub fn xml(mut self, xml: XmlPolicy) -> Self {
178 self.xml = xml;
179 self
180 }
181
182 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
184 self.templates = templates;
185 self
186 }
187
188 pub fn build(self) -> Result<SpConfig, SamlError> {
196 let config = SpConfig {
197 entity_id: self.entity_id,
198 metadata: self.metadata,
199 credentials: self.credentials,
200 validation: self.validation,
201 algorithms: self.algorithms,
202 xml: self.xml,
203 templates: self.templates,
204 };
205 config.validate()?;
206 Ok(config)
207 }
208}
209
210#[derive(Debug, Clone)]
232pub struct IdpConfig {
233 pub entity_id: EntityId,
235 pub metadata: IdpMetadataConfig,
237 pub credentials: Credentials,
239 pub issuance_lifetime: Duration,
245 pub validation: IdpValidationPolicy,
247 pub algorithms: AlgorithmPolicy,
249 pub xml: XmlPolicy,
251 pub templates: TemplatePolicy,
253}
254
255impl IdpConfig {
256 pub fn new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Self {
263 Self {
264 entity_id,
265 metadata,
266 credentials: Credentials::default(),
267 issuance_lifetime: Duration::from_secs(300),
268 validation: IdpValidationPolicy::default(),
269 algorithms: AlgorithmPolicy::default(),
270 xml: XmlPolicy::default(),
271 templates: TemplatePolicy::default(),
272 }
273 }
274
275 pub fn try_new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Result<Self, SamlError> {
283 let config = Self::new(entity_id, metadata);
284 config.validate()?;
285 Ok(config)
286 }
287
288 pub fn builder(entity_id: EntityId) -> IdpConfigBuilder {
291 IdpConfigBuilder::new(entity_id)
292 }
293
294 pub fn validate(&self) -> Result<(), SamlError> {
302 validate_entity_id(&self.entity_id)?;
303 self.metadata.validate()?;
304 validate_idp_policy(self)
305 }
306}
307
308#[derive(Debug, Clone)]
310pub struct IdpConfigBuilder {
311 entity_id: EntityId,
312 metadata: IdpMetadataConfig,
313 credentials: Credentials,
314 issuance_lifetime: Duration,
315 validation: IdpValidationPolicy,
316 algorithms: AlgorithmPolicy,
317 xml: XmlPolicy,
318 templates: TemplatePolicy,
319}
320
321impl IdpConfigBuilder {
322 fn new(entity_id: EntityId) -> Self {
323 Self {
324 entity_id,
325 metadata: IdpMetadataConfig::new(Vec::new()),
326 credentials: Credentials::default(),
327 issuance_lifetime: Duration::from_secs(300),
328 validation: IdpValidationPolicy::strict(),
329 algorithms: AlgorithmPolicy::default(),
330 xml: XmlPolicy::default(),
331 templates: TemplatePolicy::default(),
332 }
333 }
334
335 pub fn sso_endpoint(mut self, endpoint: SsoEndpoint) -> Self {
337 self.metadata.single_sign_on_service.push(endpoint);
338 self
339 }
340
341 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
343 self.metadata.single_logout_service.push(endpoint);
344 self
345 }
346
347 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
349 self.metadata.name_id_format.push(format);
350 self
351 }
352
353 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
355 self.metadata.elements_order = Some(elements_order);
356 self
357 }
358
359 pub fn credentials(mut self, credentials: Credentials) -> Self {
361 self.credentials = credentials;
362 self
363 }
364
365 pub fn issuance_lifetime(mut self, issuance_lifetime: Duration) -> Self {
370 self.issuance_lifetime = issuance_lifetime;
371 self
372 }
373
374 pub fn validation(mut self, validation: IdpValidationPolicy) -> Self {
376 self.validation = validation;
377 self
378 }
379
380 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
382 self.algorithms = algorithms;
383 self
384 }
385
386 pub fn xml(mut self, xml: XmlPolicy) -> Self {
388 self.xml = xml;
389 self
390 }
391
392 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
394 self.templates = templates;
395 self
396 }
397
398 pub fn build(self) -> Result<IdpConfig, SamlError> {
406 let config = IdpConfig {
407 entity_id: self.entity_id,
408 metadata: self.metadata,
409 credentials: self.credentials,
410 issuance_lifetime: self.issuance_lifetime,
411 validation: self.validation,
412 algorithms: self.algorithms,
413 xml: self.xml,
414 templates: self.templates,
415 };
416 config.validate()?;
417 Ok(config)
418 }
419}
420
421fn validate_common_credentials(credentials: &Credentials) -> Result<(), SamlError> {
422 if credentials.signing_key_passphrase.is_some() && credentials.signing_key.is_none() {
423 return Err(SamlError::MissingKey("signing_key".into()));
424 }
425 if credentials.decryption_key_passphrase.is_some() && credentials.decryption_key.is_none() {
426 return Err(SamlError::MissingKey("decryption_key".into()));
427 }
428 Ok(())
429}
430
431fn validate_sp_policy(config: &SpConfig) -> Result<(), SamlError> {
432 validate_sp_crypto_support(config)?;
433 validate_common_credentials(&config.credentials)?;
434 if matches!(
435 config.validation.authn_requests,
436 AuthnRequestSigningPolicy::Sign
437 ) {
438 validate_signing_credentials(&config.credentials)?;
439 }
440 if matches!(
441 config.xml.encryption.assertions,
442 AssertionEncryptionPolicy::EncryptAssertions
443 ) && config.credentials.decryption_key.is_none()
444 {
445 return Err(SamlError::MissingKey("decryption_key".into()));
446 }
447 Ok(())
448}
449
450fn validate_idp_policy(config: &IdpConfig) -> Result<(), SamlError> {
451 validated_idp_issuance_lifetime(config.issuance_lifetime)?;
452 validate_idp_crypto_support(config)?;
453 validate_common_credentials(&config.credentials)
454}
455
456pub(crate) fn validated_idp_issuance_lifetime(
457 issuance_lifetime: Duration,
458) -> Result<time::Duration, SamlError> {
459 if issuance_lifetime.is_zero() {
460 return Err(SamlError::Invalid(
461 "IdP issuance lifetime must be greater than zero".into(),
462 ));
463 }
464 time::Duration::try_from(issuance_lifetime).map_err(|_| {
465 SamlError::Invalid(
466 "IdP issuance lifetime must fit in time::Duration (at most i64::MAX seconds plus 999,999,999 nanoseconds)".into(),
467 )
468 })
469}
470
471fn validate_signing_credentials(credentials: &Credentials) -> Result<(), SamlError> {
472 if credentials.signing_key.is_none() {
473 return Err(SamlError::MissingKey("signing_key".into()));
474 }
475 if credentials.signing_certificate.is_none() {
476 return Err(SamlError::MissingKey("signing_certificate".into()));
477 }
478 Ok(())
479}
480
481#[cfg(any(
482 feature = "crypto-rustcrypto",
483 feature = "crypto-aws-lc",
484 feature = "crypto-fips"
485))]
486fn validate_sp_crypto_support(_config: &SpConfig) -> Result<(), SamlError> {
487 Ok(())
488}
489
490#[cfg(not(any(
491 feature = "crypto-rustcrypto",
492 feature = "crypto-aws-lc",
493 feature = "crypto-fips"
494)))]
495fn validate_sp_crypto_support(config: &SpConfig) -> Result<(), SamlError> {
496 if sp_config_requires_crypto(config) {
497 return Err(SamlError::Unsupported(
498 "selected SP config policy requires a crypto provider feature".into(),
499 ));
500 }
501 Ok(())
502}
503
504#[cfg(any(
505 feature = "crypto-rustcrypto",
506 feature = "crypto-aws-lc",
507 feature = "crypto-fips"
508))]
509fn validate_idp_crypto_support(_config: &IdpConfig) -> Result<(), SamlError> {
510 Ok(())
511}
512
513#[cfg(not(any(
514 feature = "crypto-rustcrypto",
515 feature = "crypto-aws-lc",
516 feature = "crypto-fips"
517)))]
518fn validate_idp_crypto_support(config: &IdpConfig) -> Result<(), SamlError> {
519 if idp_config_requires_crypto(config) {
520 return Err(SamlError::Unsupported(
521 "selected IdP config policy requires a crypto provider feature".into(),
522 ));
523 }
524 Ok(())
525}
526
527#[cfg(not(any(
528 feature = "crypto-rustcrypto",
529 feature = "crypto-aws-lc",
530 feature = "crypto-fips"
531)))]
532fn sp_config_requires_crypto(config: &SpConfig) -> bool {
533 matches!(
534 config.validation.assertions,
535 AssertionSignaturePolicy::RequireSigned
536 ) || matches!(
537 config.validation.responses,
538 ResponseSignaturePolicy::RequireSigned | ResponseSignaturePolicy::RequireForEncryptedCbc
539 ) || matches!(
540 config.validation.authn_requests,
541 AuthnRequestSigningPolicy::Sign
542 ) || logout_policy_requires_crypto(config.validation.logout)
543 || matches!(
544 config.xml.encryption.assertions,
545 AssertionEncryptionPolicy::EncryptAssertions
546 )
547}
548
549#[cfg(not(any(
550 feature = "crypto-rustcrypto",
551 feature = "crypto-aws-lc",
552 feature = "crypto-fips"
553)))]
554fn idp_config_requires_crypto(config: &IdpConfig) -> bool {
555 matches!(
556 config.validation.authn_requests,
557 AuthnRequestValidationPolicy::RequireSigned
558 ) || logout_policy_requires_crypto(config.validation.logout)
559 || matches!(
560 config.xml.encryption.assertions,
561 AssertionEncryptionPolicy::EncryptAssertions
562 )
563}
564
565#[cfg(not(any(
566 feature = "crypto-rustcrypto",
567 feature = "crypto-aws-lc",
568 feature = "crypto-fips"
569)))]
570fn logout_policy_requires_crypto(policy: LogoutPolicy) -> bool {
571 logout_signature_policy_requires_crypto(policy.requests)
572 || logout_signature_policy_requires_crypto(policy.responses)
573}
574
575#[cfg(not(any(
576 feature = "crypto-rustcrypto",
577 feature = "crypto-aws-lc",
578 feature = "crypto-fips"
579)))]
580fn logout_signature_policy_requires_crypto(policy: LogoutSignaturePolicy) -> bool {
581 matches!(policy, LogoutSignaturePolicy::RequireSigned)
582}
583
584fn apply_common_settings(
585 entity_id: &EntityId,
586 name_id_format: &[NameIdFormat],
587 credentials: &Credentials,
588 algorithms: &AlgorithmPolicy,
589 xml: &XmlPolicy,
590 templates: &TemplatePolicy,
591 setting: &mut EntitySetting,
592) {
593 setting.entity_id = Some(entity_id.as_str().to_string());
594 setting.request_signature_algorithm = algorithms.signature.as_uri().to_string();
595 setting.data_encryption_algorithm = algorithms.data_encryption.as_uri().to_string();
596 setting.key_encryption_algorithm = algorithms.key_encryption.as_uri().to_string();
597 setting.message_signing_order = algorithms.message_signing_order;
598 setting.is_assertion_encrypted = matches!(
599 xml.encryption.assertions,
600 AssertionEncryptionPolicy::EncryptAssertions
601 );
602 setting.allow_insecure_software_rsa_key_transport_decryption = xml
603 .encryption
604 .allows_insecure_software_rsa_key_transport_decryption();
605 setting.relay_state = templates.relay_state.clone();
606 setting.name_id_format = name_id_format_uris(name_id_format);
607 setting.private_key = credentials
608 .signing_key
609 .as_ref()
610 .map(|key| key.as_str().to_string());
611 setting.private_key_pass = credentials
612 .signing_key_passphrase
613 .as_ref()
614 .map(|passphrase| passphrase.as_str().to_string());
615 setting.signing_cert = credentials
616 .signing_certificate
617 .as_ref()
618 .map(|certificate| certificate.as_str().to_string());
619 setting.encrypt_cert = credentials
620 .encryption_certificate
621 .as_ref()
622 .map(|certificate| certificate.as_str().to_string());
623 setting.enc_private_key = credentials
624 .decryption_key
625 .as_ref()
626 .map(|key| key.as_str().to_string());
627 setting.enc_private_key_pass = credentials
628 .decryption_key_passphrase
629 .as_ref()
630 .map(|passphrase| passphrase.as_str().to_string());
631 setting.clock_drifts = xml.clock_drifts;
632 setting.redirect_inflate_max_bytes = xml.redirect_inflate_max_bytes;
633 setting.xml_limits = xml.limits;
634 setting.tag_prefix_protocol = templates.tag_prefix_protocol.clone();
635 setting.tag_prefix_assertion = templates.tag_prefix_assertion.clone();
636 setting.tag_prefix_encrypted_assertion = templates.tag_prefix_encrypted_assertion.clone();
637 setting.login_response_template = templates.login_response_template.clone();
638 setting.login_request_template = templates.login_request_template.clone();
639 setting.logout_request_template = templates.logout_request_template.clone();
640 setting.logout_response_template = templates.logout_response_template.clone();
641 setting.signature_config = templates.signature_config.clone();
642 setting.transformation_algorithms =
643 transform_algorithm_uris(&algorithms.signed_reference_transforms);
644}
645
646impl TryFrom<&SpConfig> for EntitySetting {
647 type Error = SamlError;
648
649 fn try_from(config: &SpConfig) -> Result<Self, Self::Error> {
650 config.validate()?;
651 let mut setting = Self::default();
652 apply_common_settings(
653 &config.entity_id,
654 &config.metadata.name_id_format,
655 &config.credentials,
656 &config.algorithms,
657 &config.xml,
658 &config.templates,
659 &mut setting,
660 );
661 setting.allow_create = name_id_creation_allowed(config.validation.name_id_creation);
662 setting.authn_requests_signed =
663 authn_request_signing_enabled(config.validation.authn_requests);
664 setting.want_assertions_signed = assertion_signature_required(config.validation.assertions);
665 setting.validate_audience = audience_validation_enabled(config.validation.audience);
666 setting.want_message_signed = response_signature_required(config.validation.responses);
667 setting.want_encrypted_cbc_response_signed =
668 encrypted_cbc_response_signature_required(config.validation.responses);
669 setting.want_logout_request_signed =
670 logout_signature_required(config.validation.logout.requests)?;
671 setting.want_logout_response_signed =
672 logout_signature_required(config.validation.logout.responses)?;
673 Ok(setting)
674 }
675}
676
677impl TryFrom<&IdpConfig> for EntitySetting {
678 type Error = SamlError;
679
680 fn try_from(config: &IdpConfig) -> Result<Self, Self::Error> {
681 config.validate()?;
682 let mut setting = Self::default();
683 apply_common_settings(
684 &config.entity_id,
685 &config.metadata.name_id_format,
686 &config.credentials,
687 &config.algorithms,
688 &config.xml,
689 &config.templates,
690 &mut setting,
691 );
692 setting.want_authn_requests_signed =
693 authn_request_signature_required(config.validation.authn_requests);
694 setting.want_logout_request_signed =
695 logout_signature_required(config.validation.logout.requests)?;
696 setting.want_logout_response_signed =
697 logout_signature_required(config.validation.logout.responses)?;
698 Ok(setting)
699 }
700}