1use crate::browser::{AcsEndpoint, SloEndpoint, SsoEndpoint};
2use crate::entity::EntitySetting;
3use crate::error::SamlError;
4
5use super::algorithms::{name_id_format_uris, transform_algorithm_uris, NameIdFormat};
6use super::credentials::Credentials;
7use super::descriptors::{validate_entity_id, EntityId, IdpMetadataConfig, SpMetadataConfig};
8use super::policies::{
9 assertion_signature_required, audience_validation_enabled, authn_request_signature_required,
10 authn_request_signing_enabled, logout_signature_required, message_signature_required,
11 name_id_creation_allowed, AlgorithmPolicy, AssertionEncryptionPolicy,
12 AuthnRequestSigningPolicy, IdpValidationPolicy, SpValidationPolicy, TemplatePolicy, XmlPolicy,
13};
14#[cfg(not(feature = "crypto-bergshamra"))]
15use super::policies::{
16 AssertionSignaturePolicy, AuthnRequestValidationPolicy, LogoutPolicy, LogoutSignaturePolicy,
17 MessageSignaturePolicy,
18};
19
20#[derive(Debug, Clone)]
37pub struct SpConfig {
38 pub entity_id: EntityId,
40 pub metadata: SpMetadataConfig,
42 pub credentials: Credentials,
44 pub validation: SpValidationPolicy,
46 pub algorithms: AlgorithmPolicy,
48 pub xml: XmlPolicy,
50 pub templates: TemplatePolicy,
52}
53
54impl SpConfig {
55 pub fn new(entity_id: EntityId, metadata: SpMetadataConfig) -> Self {
61 Self {
62 entity_id,
63 metadata,
64 credentials: Credentials::default(),
65 validation: SpValidationPolicy::default(),
66 algorithms: AlgorithmPolicy::default(),
67 xml: XmlPolicy::default(),
68 templates: TemplatePolicy::default(),
69 }
70 }
71
72 pub fn try_new(entity_id: EntityId, metadata: SpMetadataConfig) -> Result<Self, SamlError> {
79 let config = Self::new(entity_id, metadata);
80 config.validate()?;
81 Ok(config)
82 }
83
84 pub fn builder(entity_id: EntityId) -> SpConfigBuilder {
86 SpConfigBuilder::new(entity_id)
87 }
88
89 pub fn validate(&self) -> Result<(), SamlError> {
96 validate_entity_id(&self.entity_id)?;
97 self.metadata.validate()?;
98 validate_sp_policy(self)
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct SpConfigBuilder {
105 entity_id: EntityId,
106 metadata: SpMetadataConfig,
107 credentials: Credentials,
108 validation: SpValidationPolicy,
109 algorithms: AlgorithmPolicy,
110 xml: XmlPolicy,
111 templates: TemplatePolicy,
112}
113
114impl SpConfigBuilder {
115 fn new(entity_id: EntityId) -> Self {
116 Self {
117 entity_id,
118 metadata: SpMetadataConfig::new(Vec::new()),
119 credentials: Credentials::default(),
120 validation: SpValidationPolicy::strict(),
121 algorithms: AlgorithmPolicy::default(),
122 xml: XmlPolicy::default(),
123 templates: TemplatePolicy::default(),
124 }
125 }
126
127 pub fn acs_endpoint(mut self, endpoint: AcsEndpoint) -> Self {
129 self.metadata.assertion_consumer_service.push(endpoint);
130 self
131 }
132
133 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
135 self.metadata.single_logout_service.push(endpoint);
136 self
137 }
138
139 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
141 self.metadata.name_id_format.push(format);
142 self
143 }
144
145 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
147 self.metadata.elements_order = Some(elements_order);
148 self
149 }
150
151 pub fn credentials(mut self, credentials: Credentials) -> Self {
153 self.credentials = credentials;
154 self
155 }
156
157 pub fn validation(mut self, validation: SpValidationPolicy) -> Self {
159 self.validation = validation;
160 self
161 }
162
163 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
165 self.algorithms = algorithms;
166 self
167 }
168
169 pub fn xml(mut self, xml: XmlPolicy) -> Self {
171 self.xml = xml;
172 self
173 }
174
175 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
177 self.templates = templates;
178 self
179 }
180
181 pub fn build(self) -> Result<SpConfig, SamlError> {
189 let config = SpConfig {
190 entity_id: self.entity_id,
191 metadata: self.metadata,
192 credentials: self.credentials,
193 validation: self.validation,
194 algorithms: self.algorithms,
195 xml: self.xml,
196 templates: self.templates,
197 };
198 config.validate()?;
199 Ok(config)
200 }
201}
202
203#[derive(Debug, Clone)]
223pub struct IdpConfig {
224 pub entity_id: EntityId,
226 pub metadata: IdpMetadataConfig,
228 pub credentials: Credentials,
230 pub validation: IdpValidationPolicy,
232 pub algorithms: AlgorithmPolicy,
234 pub xml: XmlPolicy,
236 pub templates: TemplatePolicy,
238}
239
240impl IdpConfig {
241 pub fn new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Self {
247 Self {
248 entity_id,
249 metadata,
250 credentials: Credentials::default(),
251 validation: IdpValidationPolicy::default(),
252 algorithms: AlgorithmPolicy::default(),
253 xml: XmlPolicy::default(),
254 templates: TemplatePolicy::default(),
255 }
256 }
257
258 pub fn try_new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Result<Self, SamlError> {
265 let config = Self::new(entity_id, metadata);
266 config.validate()?;
267 Ok(config)
268 }
269
270 pub fn builder(entity_id: EntityId) -> IdpConfigBuilder {
272 IdpConfigBuilder::new(entity_id)
273 }
274
275 pub fn validate(&self) -> Result<(), SamlError> {
282 validate_entity_id(&self.entity_id)?;
283 self.metadata.validate()?;
284 validate_idp_policy(self)
285 }
286}
287
288#[derive(Debug, Clone)]
290pub struct IdpConfigBuilder {
291 entity_id: EntityId,
292 metadata: IdpMetadataConfig,
293 credentials: Credentials,
294 validation: IdpValidationPolicy,
295 algorithms: AlgorithmPolicy,
296 xml: XmlPolicy,
297 templates: TemplatePolicy,
298}
299
300impl IdpConfigBuilder {
301 fn new(entity_id: EntityId) -> Self {
302 Self {
303 entity_id,
304 metadata: IdpMetadataConfig::new(Vec::new()),
305 credentials: Credentials::default(),
306 validation: IdpValidationPolicy::strict(),
307 algorithms: AlgorithmPolicy::default(),
308 xml: XmlPolicy::default(),
309 templates: TemplatePolicy::default(),
310 }
311 }
312
313 pub fn sso_endpoint(mut self, endpoint: SsoEndpoint) -> Self {
315 self.metadata.single_sign_on_service.push(endpoint);
316 self
317 }
318
319 pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
321 self.metadata.single_logout_service.push(endpoint);
322 self
323 }
324
325 pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
327 self.metadata.name_id_format.push(format);
328 self
329 }
330
331 pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
333 self.metadata.elements_order = Some(elements_order);
334 self
335 }
336
337 pub fn credentials(mut self, credentials: Credentials) -> Self {
339 self.credentials = credentials;
340 self
341 }
342
343 pub fn validation(mut self, validation: IdpValidationPolicy) -> Self {
345 self.validation = validation;
346 self
347 }
348
349 pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
351 self.algorithms = algorithms;
352 self
353 }
354
355 pub fn xml(mut self, xml: XmlPolicy) -> Self {
357 self.xml = xml;
358 self
359 }
360
361 pub fn templates(mut self, templates: TemplatePolicy) -> Self {
363 self.templates = templates;
364 self
365 }
366
367 pub fn build(self) -> Result<IdpConfig, SamlError> {
374 let config = IdpConfig {
375 entity_id: self.entity_id,
376 metadata: self.metadata,
377 credentials: self.credentials,
378 validation: self.validation,
379 algorithms: self.algorithms,
380 xml: self.xml,
381 templates: self.templates,
382 };
383 config.validate()?;
384 Ok(config)
385 }
386}
387
388fn validate_common_credentials(credentials: &Credentials) -> Result<(), SamlError> {
389 if credentials.signing_key_passphrase.is_some() && credentials.signing_key.is_none() {
390 return Err(SamlError::MissingKey("signing_key".into()));
391 }
392 if credentials.decryption_key_passphrase.is_some() && credentials.decryption_key.is_none() {
393 return Err(SamlError::MissingKey("decryption_key".into()));
394 }
395 Ok(())
396}
397
398fn validate_sp_policy(config: &SpConfig) -> Result<(), SamlError> {
399 validate_sp_crypto_support(config)?;
400 validate_common_credentials(&config.credentials)?;
401 if matches!(
402 config.validation.authn_requests,
403 AuthnRequestSigningPolicy::Sign
404 ) {
405 validate_signing_credentials(&config.credentials)?;
406 }
407 if matches!(
408 config.xml.encryption.assertions,
409 AssertionEncryptionPolicy::EncryptAssertions
410 ) && config.credentials.decryption_key.is_none()
411 {
412 return Err(SamlError::MissingKey("decryption_key".into()));
413 }
414 Ok(())
415}
416
417fn validate_idp_policy(config: &IdpConfig) -> Result<(), SamlError> {
418 validate_idp_crypto_support(config)?;
419 validate_common_credentials(&config.credentials)
420}
421
422fn validate_signing_credentials(credentials: &Credentials) -> Result<(), SamlError> {
423 if credentials.signing_key.is_none() {
424 return Err(SamlError::MissingKey("signing_key".into()));
425 }
426 if credentials.signing_certificate.is_none() {
427 return Err(SamlError::MissingKey("signing_certificate".into()));
428 }
429 Ok(())
430}
431
432#[cfg(feature = "crypto-bergshamra")]
433fn validate_sp_crypto_support(_config: &SpConfig) -> Result<(), SamlError> {
434 Ok(())
435}
436
437#[cfg(not(feature = "crypto-bergshamra"))]
438fn validate_sp_crypto_support(config: &SpConfig) -> Result<(), SamlError> {
439 if sp_config_requires_crypto(config) {
440 return Err(SamlError::Unsupported(
441 "selected SP config policy requires the crypto-bergshamra feature".into(),
442 ));
443 }
444 Ok(())
445}
446
447#[cfg(feature = "crypto-bergshamra")]
448fn validate_idp_crypto_support(_config: &IdpConfig) -> Result<(), SamlError> {
449 Ok(())
450}
451
452#[cfg(not(feature = "crypto-bergshamra"))]
453fn validate_idp_crypto_support(config: &IdpConfig) -> Result<(), SamlError> {
454 if idp_config_requires_crypto(config) {
455 return Err(SamlError::Unsupported(
456 "selected IdP config policy requires the crypto-bergshamra feature".into(),
457 ));
458 }
459 Ok(())
460}
461
462#[cfg(not(feature = "crypto-bergshamra"))]
463fn sp_config_requires_crypto(config: &SpConfig) -> bool {
464 matches!(
465 config.validation.assertions,
466 AssertionSignaturePolicy::RequireSigned
467 ) || matches!(
468 config.validation.messages,
469 MessageSignaturePolicy::RequireSigned
470 ) || matches!(
471 config.validation.authn_requests,
472 AuthnRequestSigningPolicy::Sign
473 ) || logout_policy_requires_crypto(config.validation.logout)
474 || matches!(
475 config.xml.encryption.assertions,
476 AssertionEncryptionPolicy::EncryptAssertions
477 )
478}
479
480#[cfg(not(feature = "crypto-bergshamra"))]
481fn idp_config_requires_crypto(config: &IdpConfig) -> bool {
482 matches!(
483 config.validation.authn_requests,
484 AuthnRequestValidationPolicy::RequireSigned
485 ) || logout_policy_requires_crypto(config.validation.logout)
486 || matches!(
487 config.xml.encryption.assertions,
488 AssertionEncryptionPolicy::EncryptAssertions
489 )
490}
491
492#[cfg(not(feature = "crypto-bergshamra"))]
493fn logout_policy_requires_crypto(policy: LogoutPolicy) -> bool {
494 logout_signature_policy_requires_crypto(policy.requests)
495 || logout_signature_policy_requires_crypto(policy.responses)
496}
497
498#[cfg(not(feature = "crypto-bergshamra"))]
499fn logout_signature_policy_requires_crypto(policy: LogoutSignaturePolicy) -> bool {
500 matches!(policy, LogoutSignaturePolicy::RequireSigned)
501}
502
503fn apply_common_settings(
504 entity_id: &EntityId,
505 name_id_format: &[NameIdFormat],
506 credentials: &Credentials,
507 algorithms: &AlgorithmPolicy,
508 xml: &XmlPolicy,
509 templates: &TemplatePolicy,
510 setting: &mut EntitySetting,
511) {
512 setting.entity_id = Some(entity_id.as_str().to_string());
513 setting.request_signature_algorithm = algorithms.signature.as_uri().to_string();
514 setting.data_encryption_algorithm = algorithms.data_encryption.as_uri().to_string();
515 setting.key_encryption_algorithm = algorithms.key_encryption.as_uri().to_string();
516 setting.message_signing_order = algorithms.message_signing_order;
517 setting.is_assertion_encrypted = matches!(
518 xml.encryption.assertions,
519 AssertionEncryptionPolicy::EncryptAssertions
520 );
521 setting.allow_insecure_software_rsa_key_transport_decryption = xml
522 .encryption
523 .allows_insecure_software_rsa_key_transport_decryption();
524 setting.relay_state = templates.relay_state.clone();
525 setting.name_id_format = name_id_format_uris(name_id_format);
526 setting.private_key = credentials
527 .signing_key
528 .as_ref()
529 .map(|key| key.as_str().to_string());
530 setting.private_key_pass = credentials
531 .signing_key_passphrase
532 .as_ref()
533 .map(|passphrase| passphrase.as_str().to_string());
534 setting.signing_cert = credentials
535 .signing_certificate
536 .as_ref()
537 .map(|certificate| certificate.as_str().to_string());
538 setting.encrypt_cert = credentials
539 .encryption_certificate
540 .as_ref()
541 .map(|certificate| certificate.as_str().to_string());
542 setting.enc_private_key = credentials
543 .decryption_key
544 .as_ref()
545 .map(|key| key.as_str().to_string());
546 setting.enc_private_key_pass = credentials
547 .decryption_key_passphrase
548 .as_ref()
549 .map(|passphrase| passphrase.as_str().to_string());
550 setting.clock_drifts = xml.clock_drifts;
551 setting.redirect_inflate_max_bytes = xml.redirect_inflate_max_bytes;
552 setting.xml_limits = xml.limits;
553 setting.tag_prefix_protocol = templates.tag_prefix_protocol.clone();
554 setting.tag_prefix_assertion = templates.tag_prefix_assertion.clone();
555 setting.tag_prefix_encrypted_assertion = templates.tag_prefix_encrypted_assertion.clone();
556 setting.login_response_template = templates.login_response_template.clone();
557 setting.login_request_template = templates.login_request_template.clone();
558 setting.logout_request_template = templates.logout_request_template.clone();
559 setting.logout_response_template = templates.logout_response_template.clone();
560 setting.signature_config = templates.signature_config.clone();
561 setting.transformation_algorithms =
562 transform_algorithm_uris(&algorithms.signed_reference_transforms);
563}
564
565impl TryFrom<&SpConfig> for EntitySetting {
566 type Error = SamlError;
567
568 fn try_from(config: &SpConfig) -> Result<Self, Self::Error> {
569 config.validate()?;
570 let mut setting = Self::default();
571 apply_common_settings(
572 &config.entity_id,
573 &config.metadata.name_id_format,
574 &config.credentials,
575 &config.algorithms,
576 &config.xml,
577 &config.templates,
578 &mut setting,
579 );
580 setting.allow_create = name_id_creation_allowed(config.validation.name_id_creation);
581 setting.authn_requests_signed =
582 authn_request_signing_enabled(config.validation.authn_requests);
583 setting.want_assertions_signed = assertion_signature_required(config.validation.assertions);
584 setting.validate_audience = audience_validation_enabled(config.validation.audience);
585 setting.want_message_signed = message_signature_required(config.validation.messages);
586 setting.want_logout_request_signed =
587 logout_signature_required(config.validation.logout.requests)?;
588 setting.want_logout_response_signed =
589 logout_signature_required(config.validation.logout.responses)?;
590 Ok(setting)
591 }
592}
593
594impl TryFrom<&IdpConfig> for EntitySetting {
595 type Error = SamlError;
596
597 fn try_from(config: &IdpConfig) -> Result<Self, Self::Error> {
598 config.validate()?;
599 let mut setting = Self::default();
600 apply_common_settings(
601 &config.entity_id,
602 &config.metadata.name_id_format,
603 &config.credentials,
604 &config.algorithms,
605 &config.xml,
606 &config.templates,
607 &mut setting,
608 );
609 setting.want_authn_requests_signed =
610 authn_request_signature_required(config.validation.authn_requests);
611 setting.want_logout_request_signed =
612 logout_signature_required(config.validation.logout.requests)?;
613 setting.want_logout_response_signed =
614 logout_signature_required(config.validation.logout.responses)?;
615 Ok(setting)
616 }
617}