Skip to content

Commit 0a9649c

Browse files
committed
Merge branch 'sign-metadata' of https://github.com/Umofomia/ruby-saml into Umofomia-sign-metadata
2 parents c850f4c + 60dd414 commit 0a9649c

File tree

9 files changed

+96
-70
lines changed

9 files changed

+96
-70
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,17 @@ In order to be able to sign we need first to define the private key and the publ
336336
The settings related to sign are stored in the `security` attribute of the settings:
337337
338338
```ruby
339-
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
340-
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
339+
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
340+
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
341341
settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
342+
settings.security[:metadata_signed] = true # Enable or not signature on Metadata
342343
343344
settings.security[:digest_method] = XMLSecurity::Document::SHA1
344345
settings.security[:signature_method] = XMLSecurity::Document::SHA1
345346
346-
settings.security[:embed_sign] = false # Embeded signature or HTTP GET parameter Signature
347+
# Embeded signature or HTTP GET parameter signature
348+
# Note that metadata signature is always embedded regardless of this value.
349+
settings.security[:embed_sign] = false
347350
```
348351
349352

lib/onelogin/ruby-saml/authrequest.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def create_authentication_xml_doc(settings)
113113
end
114114
end
115115

116-
# embebed sign
116+
# embed signature
117117
if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
118118
private_key = settings.get_sp_key()
119119
cert = settings.get_sp_cert()

lib/onelogin/ruby-saml/logoutrequest.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def create_logout_request_xml_doc(settings)
8989
sessionindex.text = settings.sessionindex
9090
end
9191

92-
# embebed sign
92+
# embed signature
9393
if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
9494
private_key = settings.get_sp_key()
9595
cert = settings.get_sp_cert()

lib/onelogin/ruby-saml/metadata.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require "rexml/document"
2-
require "rexml/xpath"
31
require "uri"
42

53
require "onelogin/ruby-saml/logging"
@@ -10,10 +8,9 @@
108
# will be updated automatically
119
module OneLogin
1210
module RubySaml
13-
include REXML
1411
class Metadata
1512
def generate(settings)
16-
meta_doc = REXML::Document.new
13+
meta_doc = XMLSecurity::Document.new
1714
root = meta_doc.add_element "md:EntityDescriptor", {
1815
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
1916
}
@@ -85,6 +82,13 @@ def generate(settings)
8582
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
8683

8784
meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
85+
86+
# embed signature
87+
if settings.security[:metadata_signed] && settings.private_key && settings.certificate
88+
private_key = settings.get_sp_key()
89+
meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
90+
end
91+
8892
ret = ""
8993
# pretty print the XML so IdP administrators can easily see what the SP supports
9094
meta_doc.write(ret, 1)

lib/onelogin/ruby-saml/slo_logoutresponse.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def create_logout_response_xml_doc(settings, request_id = nil, logout_message =
8787
issuer.text = settings.issuer
8888
end
8989

90-
# embebed sign
90+
# embed signature
9191
if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
9292
private_key = settings.get_sp_key()
9393
cert = settings.get_sp_cert()

test/logoutrequest_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class RequestTest < Minitest::Test
9090
end
9191
end
9292

93-
describe "when the settings indicate to sign (embebed) the logout request" do
93+
describe "when the settings indicate to sign (embedded) the logout request" do
9494
it "created a signed logout request" do
9595
settings = OneLogin::RubySaml::Settings.new
9696
settings.idp_slo_target_url = "http://example.com?field=value"

test/metadata_test.rb

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,86 +5,105 @@
55
class MetadataTest < Minitest::Test
66

77
describe 'Metadata' do
8-
def setup
9-
@settings = OneLogin::RubySaml::Settings.new
10-
@settings.issuer = "https://example.com"
11-
@settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
12-
@settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
13-
@settings.security[:authn_requests_signed] = false
14-
end
15-
16-
it "generates Service Provider Metadata with X509Certificate" do
17-
@settings.security[:authn_requests_signed] = true
18-
@settings.certificate = ruby_saml_cert_text
19-
20-
xml_text = OneLogin::RubySaml::Metadata.new.generate(@settings)
21-
22-
# assert xml_text can be parsed into an xml doc
23-
xml_doc = REXML::Document.new(xml_text)
8+
let(:settings) { OneLogin::RubySaml::Settings.new }
9+
let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings) }
10+
let(:xml_doc) { REXML::Document.new(xml_text) }
11+
let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") }
12+
let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") }
2413

25-
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
26-
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
27-
28-
cert_node = REXML::XPath.first(xml_doc, "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", {
29-
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
30-
"ds" => "http://www.w3.org/2000/09/xmldsig#"
31-
})
32-
cert_text = cert_node.text
33-
cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_text))
34-
assert_equal ruby_saml_cert.to_der, cert.to_der
35-
end
36-
37-
it "generates Service Provider Metadata" do
38-
settings = OneLogin::RubySaml::Settings.new
14+
before do
3915
settings.issuer = "https://example.com"
4016
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
4117
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
42-
settings.security[:authn_requests_signed] = false
43-
44-
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
18+
end
4519

20+
it "generates Service Provider Metadata" do
4621
# assert correct xml declaration
4722
start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor"
4823
assert xml_text[0..start.length-1] == start
4924

50-
# assert xml_text can be parsed into an xml doc
51-
xml_doc = REXML::Document.new(xml_text)
52-
5325
assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value
5426

55-
spsso_descriptor = REXML::XPath.first(xml_doc, "//md:SPSSODescriptor")
5627
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value
5728
assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value
5829
assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value
5930

6031
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip
6132

62-
acs = REXML::XPath.first(xml_doc, "//md:AssertionConsumerService")
6333
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value
6434
assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value
6535
end
6636

67-
it "generates attribute service if configured" do
68-
settings = OneLogin::RubySaml::Settings.new
69-
settings.issuer = "https://example.com"
70-
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
71-
settings.assertion_consumer_service_url = "https://foo.example/saml/consume"
72-
settings.attribute_consuming_service.configure do
73-
service_name "Test Service"
74-
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
37+
describe "when auth requests are signed" do
38+
let(:cert_node) do
39+
REXML::XPath.first(
40+
xml_doc,
41+
"//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
42+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
43+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
44+
)
45+
end
46+
let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_node.text)) }
47+
48+
before do
49+
settings.security[:authn_requests_signed] = true
50+
settings.certificate = ruby_saml_cert_text
51+
end
52+
53+
it "generates Service Provider Metadata with X509Certificate" do
54+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
55+
assert_equal ruby_saml_cert.to_der, cert.to_der
56+
end
57+
end
58+
59+
describe "when attribute service is configured" do
60+
let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
61+
let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }
62+
63+
before do
64+
settings.attribute_consuming_service.configure do
65+
service_name "Test Service"
66+
add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value")
67+
end
68+
end
69+
70+
it "generates attribute service" do
71+
assert_equal "true", attr_svc.attribute("isDefault").value
72+
assert_equal "1", attr_svc.attribute("index").value
73+
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
74+
75+
assert_equal "Name", req_attr.attribute("Name").value
76+
assert_equal "Name Format", req_attr.attribute("NameFormat").value
77+
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
78+
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
79+
end
80+
end
81+
82+
describe "when the settings indicate to sign (embedded) the metadata" do
83+
before do
84+
settings.security[:metadata_signed] = true
85+
settings.certificate = ruby_saml_cert_text
86+
settings.private_key = ruby_saml_key_text
87+
end
88+
89+
it "creates a signed metadata" do
90+
assert_match %r[<ds:SignatureValue>\s*([a-zA-Z0-9/+=]+)\s*</ds:SignatureValue>]m, xml_text
91+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
92+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], xml_text
7593
end
7694

77-
xml_text = OneLogin::RubySaml::Metadata.new.generate(settings)
78-
xml_doc = REXML::Document.new(xml_text)
79-
acs = REXML::XPath.first(xml_doc, "//md:AttributeConsumingService")
80-
assert_equal "true", acs.attribute("isDefault").value
81-
assert_equal "1", acs.attribute("index").value
82-
assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service"
83-
req_attr = REXML::XPath.first(xml_doc, "//md:RequestedAttribute")
84-
assert_equal "Name", req_attr.attribute("Name").value
85-
assert_equal "Name Format", req_attr.attribute("NameFormat").value
86-
assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value
87-
assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip
95+
describe "when digest and signature methods are specified" do
96+
before do
97+
settings.security[:signature_method] = XMLSecurity::Document::SHA256
98+
settings.security[:digest_method] = XMLSecurity::Document::SHA512
99+
end
100+
101+
it "creates a signed metadata with specified digest and signature methods" do
102+
assert_match %r[<ds:SignatureValue>\s*([a-zA-Z0-9/+=]+)\s*</ds:SignatureValue>]m, xml_text
103+
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], xml_text
104+
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'/>], xml_text
105+
end
106+
end
88107
end
89108
end
90109
end

test/request_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class RequestTest < Minitest::Test
145145
end
146146
end
147147

148-
describe "when the settings indicate to sign (embebed) the request" do
148+
describe "when the settings indicate to sign (embedded) the request" do
149149
it "create a signed request" do
150150
settings = OneLogin::RubySaml::Settings.new
151151
settings.compress_request = false

test/slo_logoutresponse_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class SloLogoutresponseTest < Minitest::Test
6767
assert_match /<samlp:StatusMessage>Custom Logout Message<\/samlp:StatusMessage>/, inflated
6868
end
6969

70-
describe "when the settings indicate to sign (embebed) the logout response" do
70+
describe "when the settings indicate to sign (embedded) the logout response" do
7171
it "create a signed logout response" do
7272
settings = OneLogin::RubySaml::Settings.new
7373
settings.compress_response = false

0 commit comments

Comments
 (0)