From 732e6e2c6d66a93777a30be9041b0d692991fa55 Mon Sep 17 00:00:00 2001 From: Nick Holden Date: Mon, 11 Oct 2021 18:22:53 -0700 Subject: [PATCH] Add security setting to more strictly enforce audience validation --- README.md | 23 +++++++++++++++++++++++ lib/onelogin/ruby-saml/response.rb | 7 ++++++- lib/onelogin/ruby-saml/settings.rb | 3 ++- test/response_test.rb | 12 ++++++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5a8caa9..bb19ae6e 100644 --- a/README.md +++ b/README.md @@ -664,6 +664,29 @@ validation fails. You may disable such exceptions using the `settings.security[: settings.security[:soft] = true # Do not raise error on failed signature/certificate validations ``` +#### Audience Validation + +A service provider should only consider a SAML response valid if the IdP includes an +element containting an element that uniquely identifies the service provider. Unless you specify +the `skip_audience` option, Ruby SAML will validate that each SAML response includes an element +whose contents matches `settings.sp_entity_id`. + +By default, Ruby SAML considers an element containing only empty elements +to be valid. That means an otherwise valid SAML response with a condition like this would be valid: + +```xml + + + +``` + +You may enforce that an element containing only empty elements +is invalid using the `settings.security[:strict_audience_validation]` parameter. + +```ruby +settings.security[:strict_audience_validation] = true +``` + #### Key Rollover To update the SP X.509 certificate and private key without disruption of service, you may define the parameter diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb index fceb0026..73e7e524 100644 --- a/lib/onelogin/ruby-saml/response.rb +++ b/lib/onelogin/ruby-saml/response.rb @@ -613,7 +613,12 @@ def validate_in_response_to # def validate_audience return true if options[:skip_audience] - return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty? + return true if settings.sp_entity_id.nil? || settings.sp_entity_id.empty? + + if audiences.empty? + return true unless settings.security[:strict_audience_validation] + return append_error("Invalid Audiences. The element contained only empty elements. Expected audience #{settings.sp_entity_id}.") + end unless audiences.include? settings.sp_entity_id s = audiences.count > 1 ? 's' : ''; diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb index 58e1afce..3f4357e3 100644 --- a/lib/onelogin/ruby-saml/settings.rb +++ b/lib/onelogin/ruby-saml/settings.rb @@ -280,7 +280,8 @@ def get_binding(value) :digest_method => XMLSecurity::Document::SHA1, :signature_method => XMLSecurity::Document::RSA_SHA1, :check_idp_cert_expiration => false, - :check_sp_cert_expiration => false + :check_sp_cert_expiration => false, + :strict_audience_validation => false, }.freeze }.freeze end diff --git a/test/response_test.rb b/test/response_test.rb index 28dbc477..a51f3481 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -490,14 +490,22 @@ def generate_audience_error(expected, actual) assert_empty response.errors end - it "return true when the audience is self closing" do + it "return true when the audience is self closing and strict audience validation is not enabled" do response_audience_self_closed.settings = settings response_audience_self_closed.settings.sp_entity_id = '{audience}' assert response_audience_self_closed.send(:validate_audience) assert_empty response_audience_self_closed.errors end - it "return false when the audience is valid" do + it "return false when the audience is self closing and strict audience validation is enabled" do + response_audience_self_closed.settings = settings + response_audience_self_closed.settings.security[:strict_audience_validation] = true + response_audience_self_closed.settings.sp_entity_id = '{audience}' + refute response_audience_self_closed.send(:validate_audience) + assert_includes response_audience_self_closed.errors, "Invalid Audiences. The element contained only empty elements. Expected audience {audience}." + end + + it "return false when the audience is invalid" do response.settings = settings response.settings.sp_entity_id = 'invalid_audience' assert !response.send(:validate_audience)