Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Create namespace alias `OneLogin = Object` for backward compatibility, to be removed in version `2.1.0`.
* [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Change directly structure from `lib/onelogin/ruby-saml` to `lib/ruby_saml`.
* [#685](https://github.com/SAML-Toolkits/ruby-saml/pull/685) Move schema files from `lib/onelogin/schemas` to `lib/ruby_saml/schemas`.
* [#686](https://github.com/SAML-Toolkits/ruby-saml/pull/686) Use SHA-256 as the default hashing algorithm everywhere instead of SHA-1, including signatures, fingerprints, and digests.

### 1.17.0
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Add `Settings#sp_cert_multi` paramter to facilitate SP certificate and key rotation.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def saml_settings
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha256"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"

# Optional for most SAML IdPs
Expand Down
22 changes: 20 additions & 2 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## Updating from 1.17.x to 2.0.0

### Before Upgrading
### Before upgrading

Before attempting to upgrade to `2.0.0`:
- Upgrade your project to minimum Ruby 3.0, JRuby 9.4, or TruffleRuby 22.
- Upgrade RubySaml to `1.17.x`. Note that RubySaml `1.17.x` is compatible with up to Ruby 3.3.

### Root Namespace Changed to RubySaml
### Root namespace changed to RubySaml

RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. This will require you
to search your codebase for the string `OneLogin::` and remove it as appropriate. Aside from this namespace change,
Expand All @@ -17,6 +17,24 @@ the class names themselves have intentionally been kept the same.
For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work.
This alias will be removed in RubySaml version `2.1.0`.

### Security: Change default hashing algorithm to SHA-256 (was SHA-1)

For security reasons, RubySaml version `2.0.0` uses SHA-256 as its default hashing algorithm everywhere
instead of the now-obsolete SHA-1. This affects:
- The default signature and digest algorithms used when generating SP metadata.
- The default signature algorithm used when generating SP messages such as AuthnRequests.
- The default fingerprint of IdP metadata (`:idp_cert_fingerprint` as generated by `RubySaml::IdpMetadataParser`)

To preserve the old insecure SHA-1 behavior *(not recommended)*, you may set `RubySaml::Settings` as follows:

```ruby
# Preserve RubySaml 1.x insecure SHA-1 behavior
settings = RubySaml::Settings.new
settings.idp_cert_fingerprint_algorithm = XMLSecurity::Document::SHA1
settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
```

## Updating from 1.12.x to 1.13.0

Version `1.13.0` adds `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding`, and
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_saml/idp_metadata_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def certificates

# @return [String|nil] the fingerpint of the X509Certificate if it exists
#
def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA256)
@fingerprint ||= begin
return unless certificate

Expand Down
6 changes: 3 additions & 3 deletions lib/ruby_saml/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def get_binding(value)
DEFAULTS = {
assertion_consumer_service_binding: Utils::BINDINGS[:post],
single_logout_service_binding: Utils::BINDINGS[:redirect],
idp_cert_fingerprint_algorithm: XMLSecurity::Document::SHA1,
idp_cert_fingerprint_algorithm: XMLSecurity::Document::SHA256,
compress_request: true,
compress_response: true,
message_max_bytesize: 250_000,
Expand All @@ -292,8 +292,8 @@ def get_binding(value)
want_name_id: false,
metadata_signed: false,
embed_sign: false, # Deprecated
digest_method: XMLSecurity::Document::SHA1,
signature_method: XMLSecurity::Document::RSA_SHA1,
digest_method: XMLSecurity::Document::SHA256,
signature_method: XMLSecurity::Document::RSA_SHA256,
check_idp_cert_expiration: false,
check_sp_cert_expiration: false,
strict_audience_validation: false,
Expand Down
8 changes: 4 additions & 4 deletions lib/xml_security.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ def algorithm(element)
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && ::Regexp.last_match(2).to_i

case algorithm
when 256 then OpenSSL::Digest::SHA256
when 1 then OpenSSL::Digest::SHA1
when 384 then OpenSSL::Digest::SHA384
when 512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA1
OpenSSL::Digest::SHA256
end
end

Expand Down Expand Up @@ -114,7 +114,7 @@ def uuid
# <KeyInfo />
# <Object />
# </Signature>
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
def sign_document(private_key, certificate, signature_method = RSA_SHA256, digest_method = SHA256)
noko = Nokogiri::XML(to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
Expand Down Expand Up @@ -216,7 +216,7 @@ def validate_document(idp_cert_fingerprint, soft = true, options = {})
if options[:fingerprint_alg]
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
else
fingerprint_alg = OpenSSL::Digest.new('SHA1')
fingerprint_alg = OpenSSL::Digest.new('SHA256')
end
fingerprint = fingerprint_alg.hexdigest(cert.to_der)

Expand Down
38 changes: 19 additions & 19 deletions test/idp_metadata_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def initialize; end
assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id
assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
Expand All @@ -38,15 +38,15 @@ def initialize; end
idp_metadata_parser = RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
settings = idp_metadata_parser.parse(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
end

it "extract certificate from md:KeyDescriptor[@use='encryption']" do
idp_metadata_parser = RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
settings = idp_metadata_parser.parse(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
end

it "extract certificate from md:KeyDescriptor" do
Expand All @@ -55,7 +55,7 @@ def initialize; end
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
idp_metadata = idp_metadata.sub('<md:KeyDescriptor use="encryption">', '<md:KeyDescriptor>')
settings = idp_metadata_parser.parse(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
end

it "extract SSO endpoint with no specific binding, it takes the first" do
Expand Down Expand Up @@ -162,7 +162,7 @@ def initialize; end
}
}
})
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method]
assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
end
Expand All @@ -175,7 +175,7 @@ def initialize; end

RubySaml::IdpMetadataParser.new.parse(idp_metadata_descriptor, :settings => settings)

assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method]
assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
end
Expand All @@ -190,7 +190,7 @@ def initialize; end
assert_equal "https://hello.example.com/access/saml/idp.xml", metadata[:idp_entity_id]
assert_equal "https://hello.example.com/access/saml/login", metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_sso_service_binding]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", metadata[:idp_cert_fingerprint]
assert_equal "https://hello.example.com/access/saml/logout", metadata[:idp_slo_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_slo_service_binding]
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", metadata[:name_identifier_format]
Expand All @@ -202,15 +202,15 @@ def initialize; end
idp_metadata_parser = RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", metadata[:idp_cert_fingerprint]
end

it "extract certificate from md:KeyDescriptor[@use='encryption']" do
idp_metadata_parser = RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", parsed_metadata[:idp_cert_fingerprint]
end

it "extract certificate from md:KeyDescriptor" do
Expand All @@ -219,7 +219,7 @@ def initialize; end
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
idp_metadata = idp_metadata.sub('<md:KeyDescriptor use="encryption">', '<md:KeyDescriptor>')
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", parsed_metadata[:idp_cert_fingerprint]
end

it "extract SSO endpoint with no specific binding, it takes the first" do
Expand Down Expand Up @@ -261,7 +261,7 @@ def initialize; end
}
}
})
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", parsed_metadata[:idp_cert_fingerprint]
assert_nil parsed_metadata[:security]
end

Expand All @@ -272,8 +272,8 @@ def initialize; end
metadata1 = idp_metadata_parser.parse_to_hash(idp_metadata1)
metadata2 = idp_metadata_parser.parse_to_hash(idp_metadata2)

assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata1[:idp_cert_fingerprint]
assert_equal "CD:2B:2B:DA:FF:F5:DB:64:10:7C:AC:FD:FE:0F:CB:5D:73:5F:16:07", metadata2[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", metadata1[:idp_cert_fingerprint]
assert_equal "E5:52:D9:2C:3C:DC:3D:09:5C:90:76:82:AB:B6:75:B4:92:92:2C:42:87:7E:18:EB:17:F3:1F:39:FE:9F:7C:6A", metadata2[:idp_cert_fingerprint]
end
end

Expand Down Expand Up @@ -320,7 +320,7 @@ def initialize; end
assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id
assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.idp_cert_fingerprint
assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
Expand Down Expand Up @@ -356,7 +356,7 @@ def initialize; end
assert_equal "https://hello.example.com/access/saml/idp.xml", parsed_metadata[:idp_entity_id]
assert_equal "https://hello.example.com/access/saml/login", parsed_metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", parsed_metadata[:idp_cert_fingerprint]
assert_equal "https://hello.example.com/access/saml/logout", parsed_metadata[:idp_slo_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_slo_service_binding]
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", parsed_metadata[:name_identifier_format]
Expand Down Expand Up @@ -467,7 +467,7 @@ def initialize; end
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", @settings.name_identifier_format
assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", @settings.idp_cert_fingerprint
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", @settings.idp_cert_fingerprint
assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding
assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names
Expand All @@ -477,10 +477,10 @@ def initialize; end
it "should handle multiple descriptors at once" do
settings = @idp_metadata_parser.parse_to_array(@idp_metadata)
assert_equal "https://foo.example.com/access/saml/idp.xml", settings.first[:idp_entity_id]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.first[:idp_cert_fingerprint]
assert_equal "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21", settings.first[:idp_cert_fingerprint]
assert_equal '2014-04-17T18:02:33.910Z', settings.first[:valid_until]
assert_equal "https://bar.example.com/access/saml/idp.xml", settings.last[:idp_entity_id]
assert_equal "08:EB:6E:60:A2:14:4E:89:EC:FA:05:74:9D:72:BF:5D:BE:54:F0:1A", settings.last[:idp_cert_fingerprint]
assert_equal "74:E4:FA:29:20:26:36:8A:72:5E:9D:CF:4F:8E:1F:DC:D4:CE:E2:3C:9D:6F:93:35:A1:A7:8A:4D:79:83:21:D0", settings.last[:idp_cert_fingerprint]
assert_equal '2014-04-17T18:02:33.910Z', settings.last[:valid_until]
end
end
Expand Down Expand Up @@ -649,7 +649,7 @@ def initialize; end

it "should return idp_cert and idp_cert_fingerprint and no idp_cert_multi" do
assert_equal(expected_cert, @settings.idp_cert)
assert_equal("2D:A9:40:88:28:EE:67:BB:4A:5B:E0:58:A7:CC:71:95:2D:1B:C9:D3", @settings.idp_cert_fingerprint)
assert_equal("46:E3:68:F4:ED:61:43:2B:EC:36:E3:99:E9:03:4B:99:E5:B3:58:EF:A9:A9:00:FC:2D:C8:7C:14:C6:60:E3:8F", @settings.idp_cert_fingerprint)
assert_equal({ :signing => [expected_cert], :encryption => [expected_cert] }, @settings.idp_cert_multi)
assert_equal("https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id)
assert_equal("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format)
Expand Down
Loading