Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow raw (unsigned) assertions #10

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1d859c1
Identify incoming metadata
oTsogbadrakhChinzorig Jan 17, 2019
2f5be4d
Signed message
oTsogbadrakhChinzorig Jan 18, 2019
43161e2
Identify incoming metadata
oTsogbadrakhChinzorig Jan 18, 2019
41e119f
Revert "Identify incoming metadata"
oTsogbadrakhChinzorig Jan 17, 2019
5b2b924
Signed message (document)
oTsogbadrakhChinzorig Jan 21, 2019
9d933ec
Merge branch 'entity_id_for_incoming_metadata' into updates_for_saml_idp
oTsogbadrakhChinzorig Jan 22, 2019
799513d
Merge branch 'entity_id_for_incoming_metadata' into updates_for_saml_idp
oTsogbadrakhChinzorig Jan 22, 2019
cfafa21
Signed response
oTsogbadrakhChinzorig Feb 14, 2019
cba7e02
Merge branch 'updates_for_saml_idp' into signed_response_message
oTsogbadrakhChinzorig Feb 14, 2019
8332984
Revert "Merge branch 'updates_for_saml_idp' into signed_response_mess…
oTsogbadrakhChinzorig Feb 14, 2019
0492dbd
Signed response
oTsogbadrakhChinzorig Feb 14, 2019
ebcc9bc
Test for Signature part
oTsogbadrakhChinzorig Feb 14, 2019
015bfd5
Merge branch 'signed_response_message' into updates_for_saml_idp
oTsogbadrakhChinzorig Feb 14, 2019
9b988e7
Merge branch 'signed_response_message' into updates_for_saml_idp
oTsogbadrakhChinzorig Feb 15, 2019
317f0bb
Set theme jekyll-theme-architect
Zogoo Feb 15, 2019
438bc93
Merge branch 'entity_id_for_incoming_metadata' into updates_for_saml_idp
oTsogbadrakhChinzorig Feb 15, 2019
ba8c606
Travis build update
oTsogbadrakhChinzorig Feb 15, 2019
c2e77ea
Merge pull request #1 from Zogoo/updates_for_saml_idp
Zogoo Feb 15, 2019
ed07afc
Identify incoming metadata
oTsogbadrakhChinzorig Jan 22, 2019
26d83c4
Merge branch 'updates_for_saml_idp'
oTsogbadrakhChinzorig Feb 17, 2019
19761d8
Adding required gem for response encryption
Zogoo Jun 3, 2019
274fbfa
Set theme jekyll-theme-midnight
Zogoo Jun 3, 2019
4acb010
Merge branch 'master' into require_xmlenc_gem
Zogoo Jun 3, 2019
397c84c
Merge branch 'require_xmlenc_gem'
Zogoo Jun 3, 2019
4853058
Merge branch 'master' into signed_authn_request
Zogoo Jun 12, 2019
bb8ac29
Merge branch 'saml_idp/signed_authn_request' into signed_authn_request
Zogoo Jun 13, 2019
27320ca
Merge branch 'saml_idp/redirect_sso_url' into signed_authn_request
Zogoo Jun 13, 2019
012b708
Ignore VS code config
Zogoo Jun 13, 2019
78be522
Merge branch 'master' into signed_authn_request
Zogoo Jun 13, 2019
d5c4edf
Merge branch 'signed_authn_request'
Zogoo Jun 20, 2019
f7573d4
Merge remote-tracking branch 'saml-idp/master'
Zogoo Jan 20, 2020
f529e45
Merge remote-tracking branch 'saml-idp/master'
Zogoo Jan 22, 2020
cffcc1a
Sign the assertion based on the new initialize parameter signed_asser…
rordeix Sep 11, 2020
b260b0e
Update readme
rordeix Sep 11, 2020
8f6395d
Merge branch 'master' into feature/allow-unsigned-assertions
rordeix Sep 11, 2020
8bfe55e
Rename signed assertion options. Add a spec to controller with raw as…
rordeix Sep 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
/Gemfile.lock
/gemfiles/*.gemfile.lock
/.byebug_history

_config\.yml
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ end

#### Singed assertions and Signed Response

By default SAML Assertion will be signed with an algorithm which defined to `config.algorithm`. Because SAML assertions contain secure information used for authentication such as NameID.
By default SAML Assertion will be signed with an algorithm which defined to `config.algorithm`, because SAML assertions contain secure information used for authentication such as NameID.
Besides that, signing assertions could be optional and can be defined with `config.signed_assertion` option. Setting this configuration flag to `false` will add raw assertions on the response instead of signed ones. If the response is encrypted the `config.signed_assertion` will be ignored and all assertions will be signed.

Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
For that, you can enable it with `config.signed_message` option. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
Expand Down Expand Up @@ -118,6 +119,7 @@ CERT
# config.single_service_post_location = "#{base}/saml/auth"
# config.session_expiry = 86400 # Default: 0 which means never
# config.signed_message = true # Default: false which means unsigned SAML Response
# config.signed_assertion = false # Default: true which means signed assertions on the SAML Response

# Principal (e.g. User) is passed in when you `encode_response`
#
Expand Down
1 change: 1 addition & 0 deletions lib/saml_idp/configurator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Configurator
attr_accessor :reference_id_generator
attr_accessor :attribute_service_location
attr_accessor :single_service_post_location
attr_accessor :single_service_redirect_location
attr_accessor :single_logout_service_post_location
attr_accessor :single_logout_service_redirect_location
attr_accessor :attributes
Expand Down
4 changes: 3 additions & 1 deletion lib/saml_idp/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def encode_authn_response(principal, opts = {})
session_expiry = opts[:session_expiry]
encryption_opts = opts[:encryption] || nil
signed_message_opts = opts[:signed_message] || false
signed_assertion_opts = opts[:signed_assertion] || true

SamlResponse.new(
reference_id,
Expand All @@ -79,7 +80,8 @@ def encode_authn_response(principal, opts = {})
expiry,
encryption_opts,
session_expiry,
signed_message_opts
signed_message_opts,
signed_assertion_opts
).build
end

Expand Down
13 changes: 13 additions & 0 deletions lib/saml_idp/incoming_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ def sign_assertions
end
hashable :sign_assertions

def sign_authn_request
doc = xpath(
"//md:SPSSODescriptor",
ds: signature_namespace,
md: metadata_namespace
).first
if (doc && !doc['AuthnRequestsSigned'].nil?)
return doc['AuthnRequestsSigned'].strip.downcase == 'true'
end
return false
end
hashable :sign_authn_request

def display_name
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
end
Expand Down
5 changes: 4 additions & 1 deletion lib/saml_idp/metadata_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ def fresh
descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: single_logout_service_redirect_location
build_name_id_formats descriptor
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Location: single_service_post_location
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: single_service_redirect_location
build_attribute descriptor
end

Expand Down Expand Up @@ -151,6 +153,7 @@ def x509_certificate
organization_url
attribute_service_location
single_service_post_location
single_service_redirect_location
single_logout_service_post_location
single_logout_service_redirect_location
technical_contact
Expand Down
4 changes: 4 additions & 0 deletions lib/saml_idp/persisted_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ class PersistedMetadata
def sign_assertions?
!!attributes[:sign_assertions]
end

def sign_authn_request?
!!attributes[:sign_authn_request]
end
end
end
11 changes: 8 additions & 3 deletions lib/saml_idp/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,14 @@ def valid?
end

def valid_signature?
# Force signatures for logout requests because there is no other
# protection against a cross-site DoS.
service_provider.valid_signature?(document, logout_request?)
# Force signatures for logout requests because there is no other protection against a cross-site DoS.
# Validate signature when metadata specify AuthnRequest should be signed
metadata = service_provider.current_metadata
if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
document.valid_signature?(service_provider.fingerprint)
else
true
end
end

def service_provider?
Expand Down
10 changes: 7 additions & 3 deletions lib/saml_idp/saml_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
require 'saml_idp/response_builder'
module SamlIdp
class SamlResponse
attr_accessor :assertion_with_signature
attr_accessor :reference_id
attr_accessor :response_id
attr_accessor :issuer_uri
Expand All @@ -18,6 +17,7 @@ class SamlResponse
attr_accessor :encryption_opts
attr_accessor :session_expiry
attr_accessor :signed_message_opts
attr_accessor :signed_assertion_opts

def initialize(reference_id,
response_id,
Expand All @@ -31,7 +31,8 @@ def initialize(reference_id,
expiry=60*60,
encryption_opts=nil,
session_expiry=0,
signed_message_opts
signed_message_opts=false,
signed_assertion_opts=true
)
self.reference_id = reference_id
self.response_id = response_id
Expand All @@ -48,6 +49,7 @@ def initialize(reference_id,
self.encryption_opts = encryption_opts
self.session_expiry = session_expiry
self.signed_message_opts = signed_message_opts
self.signed_assertion_opts = signed_assertion_opts
end

def build
Expand All @@ -57,8 +59,10 @@ def build
def signed_assertion
if encryption_opts
assertion_builder.encrypt(sign: true)
else
elsif signed_assertion_opts
assertion_builder.signed
else
assertion_builder.raw
end
end
private :signed_assertion
Expand Down
7 changes: 1 addition & 6 deletions lib/saml_idp/service_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,13 @@ def valid?
end

def valid_signature?(doc, require_signature = false)
if require_signature || should_validate_signature?
if require_signature || attributes[:validate_signature]
doc.valid_signature?(fingerprint)
else
true
end
end

def should_validate_signature?
attributes[:validate_signature] ||
current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?
end

def refresh_metadata
fresh = fresh_incoming_metadata
if valid_signature?(fresh.document)
Expand Down
1 change: 1 addition & 0 deletions saml_idp.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ section of the README.
s.add_dependency('uuid', '>= 2.3')
s.add_dependency('builder', '>= 3.0')
s.add_dependency('nokogiri', '>= 1.6.2')
s.add_dependency('xmlenc', '>= 0.7.1')

s.add_development_dependency('rake')
s.add_development_dependency('simplecov')
Expand Down
1 change: 1 addition & 0 deletions spec/lib/saml_idp/configurator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module SamlIdp
it { should respond_to :base_saml_location }
it { should respond_to :reference_id_generator }
it { should respond_to :attribute_service_location }
it { should respond_to :single_service_redirect_location }
it { should respond_to :single_service_post_location }
it { should respond_to :single_logout_service_post_location }
it { should respond_to :single_logout_service_redirect_location }
Expand Down
15 changes: 14 additions & 1 deletion spec/lib/saml_idp/incoming_metadata_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module SamlIdp

metadata_1 = <<-eos
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true" WantAssertionsSigned="false">
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="false" WantAssertionsSigned="false">
</md:SPSSODescriptor>
</md:EntityDescriptor>
eos
Expand All @@ -22,6 +22,13 @@ module SamlIdp
</md:EntityDescriptor>
eos

metadata_4 = <<-eos
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
</md:SPSSODescriptor>
</md:EntityDescriptor>
eos

describe IncomingMetadata do
it 'should properly set sign_assertions to false' do
metadata = SamlIdp::IncomingMetadata.new(metadata_1)
Expand All @@ -36,11 +43,17 @@ module SamlIdp
it 'should properly set sign_assertions to true' do
metadata = SamlIdp::IncomingMetadata.new(metadata_2)
expect(metadata.sign_assertions).to eq(true)
expect(metadata.sign_authn_request).to eq(true)
end

it 'should properly set sign_assertions to false when WantAssertionsSigned is not included' do
metadata = SamlIdp::IncomingMetadata.new(metadata_3)
expect(metadata.sign_assertions).to eq(false)
end

it 'should properly set sign_authn_request to false when AuthnRequestsSigned is not included' do
metadata = SamlIdp::IncomingMetadata.new(metadata_4)
expect(metadata.sign_authn_request).to eq(false)
end
end
end
112 changes: 84 additions & 28 deletions spec/lib/saml_idp/saml_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module SamlIdp
end
let(:signed_response_opts) { true }
let(:unsigned_response_opts) { false }
let(:signed_assertion_opts) { true }
let(:subject_encrypted) { described_class.new(reference_id,
response_id,
issuer_uri,
Expand All @@ -38,7 +39,8 @@ module SamlIdp
expiry,
encryption_opts,
session_expiry,
unsigned_response_opts
unsigned_response_opts,
signed_assertion_opts
)
}

Expand All @@ -54,7 +56,8 @@ module SamlIdp
expiry,
nil,
session_expiry,
signed_response_opts
signed_response_opts,
signed_assertion_opts
)
}

Expand All @@ -70,34 +73,87 @@ module SamlIdp
expect(subject.build).to be_present
end

it "builds encrypted" do
expect(subject_encrypted.build).to_not match(audience_uri)
encoded_xml = subject_encrypted.build
resp_settings = saml_settings(saml_acs_url)
resp_settings.private_key = Default::SECRET_KEY
resp_settings.issuer = audience_uri
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
saml_resp.soft = false
expect(saml_resp.is_valid?).to eq(true)
context "encrypted" do
it "builds encrypted" do
expect(subject_encrypted.build).to_not match(audience_uri)
encoded_xml = subject_encrypted.build
resp_settings = saml_settings(saml_acs_url)
resp_settings.private_key = Default::SECRET_KEY
resp_settings.issuer = audience_uri
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
saml_resp.soft = false
expect(saml_resp.is_valid?).to eq(true)
end
end

it "will build signed valid response" do
expect { subject.build }.not_to raise_error
signed_encoded_xml = subject.build
resp_settings = saml_settings(saml_acs_url)
resp_settings.private_key = Default::SECRET_KEY
resp_settings.issuer = audience_uri
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
expect(
Nokogiri::XML(saml_resp.response).at_xpath(
"//p:Response//ds:Signature",
{
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
"ds" => "http://www.w3.org/2000/09/xmldsig#"
}
)).to be_present
expect(saml_resp.send(:validate_signature)).to eq(true)
expect(saml_resp.is_valid?).to eq(true)
context "signed response" do
let(:resp_settings) do
resp_settings = saml_settings(saml_acs_url)
resp_settings.private_key = Default::SECRET_KEY
resp_settings.issuer = audience_uri
resp_settings
end

it "will build signed valid response" do
expect { subject.build }.not_to raise_error
signed_encoded_xml = subject.build
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
expect(
Nokogiri::XML(saml_resp.response).at_xpath(
"//p:Response//ds:Signature",
{
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
"ds" => "http://www.w3.org/2000/09/xmldsig#"
}
)).to be_present
expect(saml_resp.send(:validate_signature)).to eq(true)
expect(saml_resp.is_valid?).to eq(true)
end

context "when signed_assertion_opts is true" do
it "builds a signed assertion" do
expect { subject.build }.not_to raise_error
signed_encoded_xml = subject.build
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
expect(
Nokogiri::XML(saml_resp.response).at_xpath(
"//p:Response//a:Assertion//ds:Signature",
{
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
"a" => "urn:oasis:names:tc:SAML:2.0:assertion",
"ds" => "http://www.w3.org/2000/09/xmldsig#"
}
)).to be_present
end
end

context "when signed_assertion_opts is false" do
let(:signed_assertion_opts) { false }

it "builds a raw assertion" do
expect { subject.build }.not_to raise_error
signed_encoded_xml = subject.build
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
expect(
Nokogiri::XML(saml_resp.response).at_xpath(
"//p:Response//a:Assertion",
{
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
"a" => "urn:oasis:names:tc:SAML:2.0:assertion"
}
)).to be_present

expect(
Nokogiri::XML(saml_resp.response).at_xpath(
"//p:Response//Assertion//ds:Signature",
{
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
"a" => "urn:oasis:names:tc:SAML:2.0:assertion",
"ds" => "http://www.w3.org/2000/09/xmldsig#"
}
)).to_not be_present
end
end
end

it "sets session expiration" do
Expand Down