Skip to content

Commit e4621b6

Browse files
committed
Be able to register future SP x509cert on the settings and publish it on SP metadata
1 parent 5aa5d39 commit e4621b6

File tree

7 files changed

+157
-15
lines changed

7 files changed

+157
-15
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ The Service Provider will decrypt the EncryptedAssertion with its private key.
453453
454454
Notice that this toolkit uses 'settings.certificate' and 'settings.private_key' for the sign and decrypt processes.
455455
456+
457+
## Key rollover
458+
459+
If you plan to update the SP x509cert and privateKey you can define the parameter 'certificate_new' at the settings and that new SP public certificate will be published on the SP metadata so Identity Providers can read them and get ready for rollover.
460+
461+
456462
## Single Log Out
457463
458464
The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout.
@@ -583,6 +589,7 @@ class SamlController < ApplicationController
583589
end
584590
```
585591
592+
586593
## Clock Drift
587594
588595
Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition", this may be due to clock differences between your system and that of the Identity Provider.

lib/onelogin/ruby-saml/metadata.rb

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,26 @@ def generate(settings, pretty_print=false)
3333
}
3434

3535
# Add KeyDescriptor if messages will be signed / encrypted
36+
# with SP certificate, and new SP certificate if any
3637
cert = settings.get_sp_cert
37-
if cert
38-
cert_text = Base64.encode64(cert.to_der).gsub("\n", '')
39-
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
40-
ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
41-
xd = ki.add_element "ds:X509Data"
42-
xc = xd.add_element "ds:X509Certificate"
43-
xc.text = cert_text
38+
cert_new = settings.get_sp_cert_new
4439

45-
if settings.security[:want_assertions_encrypted]
46-
kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
47-
ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
48-
xd2 = ki2.add_element "ds:X509Data"
49-
xc2 = xd2.add_element "ds:X509Certificate"
50-
xc2.text = cert_text
40+
for sp_cert in [cert, cert_new]
41+
if sp_cert
42+
cert_text = Base64.encode64(sp_cert.to_der).gsub("\n", '')
43+
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
44+
ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
45+
xd = ki.add_element "ds:X509Data"
46+
xc = xd.add_element "ds:X509Certificate"
47+
xc.text = cert_text
48+
49+
if settings.security[:want_assertions_encrypted]
50+
kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
51+
ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
52+
xd2 = ki2.add_element "ds:X509Data"
53+
xc2 = xd2.add_element "ds:X509Certificate"
54+
xc2.text = cert_text
55+
end
5156
end
5257
end
5358

lib/onelogin/ruby-saml/settings.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def initialize(overrides = {})
4646
attr_accessor :attributes_index
4747
attr_accessor :force_authn
4848
attr_accessor :certificate
49+
attr_accessor :certificate_new
4950
attr_accessor :private_key
5051
attr_accessor :authn_context
5152
attr_accessor :authn_context_comparison
@@ -133,6 +134,15 @@ def get_sp_cert
133134
OpenSSL::X509::Certificate.new(formatted_cert)
134135
end
135136

137+
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
138+
#
139+
def get_sp_cert_new
140+
return nil if certificate_new.nil? || certificate_new.empty?
141+
142+
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
143+
OpenSSL::X509::Certificate.new(formatted_cert)
144+
end
145+
136146
# @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
137147
#
138148
def get_sp_key

test/certificates/ruby-saml-2.crt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICVDCCAb2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBHMQswCQYDVQQGEwJ1czEQ
3+
MA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIGA1UEAwwLZXhh
4+
bXBsZS5jb20wHhcNMTcwNDA3MDgzMDAzWhcNMjcwNDA1MDgzMDAzWjBHMQswCQYD
5+
VQQGEwJ1czEQMA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIG
6+
A1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKhP
7+
S4/0azxbQekHHewQGKD7Pivr3CDpsrKxY3xlVanxj427OwzOb5KUVzsDEazumt6s
8+
ZFY8HfidsjXY4EYA4ZzyL7ciIAR5vlAsIYN9nJ4AwVDnN/RjVwj+TN6BqWPLpVIp
9+
Hc6Dl005HyE0zJnk1DZDn2tQVrIzbD3FhCp7YeotAgMBAAGjUDBOMB0GA1UdDgQW
10+
BBRYZx4thASfNvR/E7NsCF2IaZ7wIDAfBgNVHSMEGDAWgBRYZx4thASfNvR/E7Ns
11+
CF2IaZ7wIDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBACz4aobx9aG3
12+
kh+rNyrlgM3K6dYfnKG1/YH5sJCAOvg8kDr0fQAQifH8lFVWumKUMoAe0bFTfwWt
13+
p/VJ8MprrEJth6PFeZdczpuv+fpLcNj2VmNVJqvQYvS4m36OnBFh1QFZW8UrbFIf
14+
dtm2nuZ+twSKqfKwjLdqcoX0p39h7Uw/
15+
-----END CERTIFICATE-----

test/metadata_test.rb

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,6 @@ class MetadataTest < Minitest::Test
128128

129129
it "generates Service Provider Metadata with AuthnRequestsSigned" do
130130
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
131-
assert_equal ruby_saml_cert.to_der, cert.to_der
132-
133131
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
134132
end
135133
end
@@ -141,6 +139,7 @@ class MetadataTest < Minitest::Test
141139

142140
it "generates Service Provider Metadata with X509Certificate for encrypt" do
143141
assert_equal 2, key_descriptors.length
142+
144143
assert_equal "encryption", key_descriptors[1].attribute("use").value
145144

146145
assert_equal 2, cert_nodes.length
@@ -150,6 +149,75 @@ class MetadataTest < Minitest::Test
150149
end
151150
end
152151

152+
describe "with a future SP certificate" do
153+
let(:key_descriptors) do
154+
REXML::XPath.match(
155+
xml_doc,
156+
"//md:KeyDescriptor",
157+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata"
158+
)
159+
end
160+
let(:cert_nodes) do
161+
REXML::XPath.match(
162+
xml_doc,
163+
"//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
164+
"md" => "urn:oasis:names:tc:SAML:2.0:metadata",
165+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
166+
)
167+
end
168+
169+
before do
170+
settings.certificate = ruby_saml_cert_text
171+
settings.certificate_new = ruby_saml_cert_text2
172+
end
173+
174+
it "generates Service Provider Metadata with 2 X509Certificate for sign" do
175+
assert_equal 2, key_descriptors.length
176+
assert_equal "signing", key_descriptors[0].attribute("use").value
177+
assert_equal "signing", key_descriptors[1].attribute("use").value
178+
179+
cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text))
180+
cert_new = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[1].text))
181+
182+
assert_equal 2, cert_nodes.length
183+
assert_equal ruby_saml_cert.to_der, cert.to_der
184+
assert_equal ruby_saml_cert2.to_der, cert_new.to_der
185+
186+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
187+
end
188+
189+
describe "and signed authentication requests" do
190+
before do
191+
settings.security[:authn_requests_signed] = true
192+
end
193+
194+
it "generates Service Provider Metadata with AuthnRequestsSigned" do
195+
assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value
196+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
197+
end
198+
end
199+
200+
describe "and encrypted assertions" do
201+
before do
202+
settings.security[:want_assertions_encrypted] = true
203+
end
204+
205+
it "generates Service Provider Metadata with X509Certificate for encrypt" do
206+
assert_equal 4, key_descriptors.length
207+
assert_equal "signing", key_descriptors[0].attribute("use").value
208+
assert_equal "encryption", key_descriptors[1].attribute("use").value
209+
assert_equal "signing", key_descriptors[2].attribute("use").value
210+
assert_equal "encryption", key_descriptors[3].attribute("use").value
211+
212+
assert_equal 4, cert_nodes.length
213+
assert_equal cert_nodes[0].text, cert_nodes[1].text
214+
assert_equal cert_nodes[2].text, cert_nodes[3].text
215+
assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd")
216+
end
217+
end
218+
219+
end
220+
153221
describe "when attribute service is configured with multiple attribute values" do
154222
let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") }
155223
let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") }

test/settings_test.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,35 @@ class SettingsTest < Minitest::Test
152152

153153
end
154154

155+
describe "#get_sp_cert_new" do
156+
it "returns nil when the cert is an empty string" do
157+
@settings = OneLogin::RubySaml::Settings.new
158+
@settings.certificate_new = ""
159+
assert_nil @settings.get_sp_cert_new
160+
end
161+
162+
it "returns nil when the cert is nil" do
163+
@settings = OneLogin::RubySaml::Settings.new
164+
@settings.certificate_new = nil
165+
assert_nil @settings.get_sp_cert_new
166+
end
167+
168+
it "returns the certificate when it is valid" do
169+
@settings = OneLogin::RubySaml::Settings.new
170+
@settings.certificate_new = ruby_saml_cert_text
171+
assert @settings.get_sp_cert_new.kind_of? OpenSSL::X509::Certificate
172+
end
173+
174+
it "raises when the certificate is not valid" do
175+
# formatted but invalid cert
176+
@settings.certificate_new = read_certificate("formatted_certificate")
177+
assert_raises(OpenSSL::X509::CertificateError) {
178+
@settings.get_sp_cert_new
179+
}
180+
end
181+
182+
end
183+
155184
describe "#get_sp_key" do
156185
it "returns nil when the private key is an empty string" do
157186
@settings = OneLogin::RubySaml::Settings.new

test/test_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ def ruby_saml_cert
188188
@ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text)
189189
end
190190

191+
def ruby_saml_cert2
192+
@ruby_saml_cert2 ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text2)
193+
end
194+
191195
def ruby_saml_cert_fingerprint
192196
@ruby_saml_cert_fingerprint ||= Digest::SHA1.hexdigest(ruby_saml_cert.to_der).scan(/../).join(":")
193197
end
@@ -196,6 +200,10 @@ def ruby_saml_cert_text
196200
read_certificate("ruby-saml.crt")
197201
end
198202

203+
def ruby_saml_cert_text2
204+
read_certificate("ruby-saml-2.crt")
205+
end
206+
199207
def ruby_saml_key
200208
@ruby_saml_key ||= OpenSSL::PKey::RSA.new(ruby_saml_key_text)
201209
end

0 commit comments

Comments
 (0)