SAML-Toolkits / ruby-saml

SAML SSO for Ruby
https://developers.onelogin.com/v1.0/page/saml-toolkit-for-ruby-on-rails
MIT License
898 stars 561 forks source link

Validate signature of published federation metadata #639

Closed pre closed 1 year ago

pre commented 1 year ago

Given a signed federation metadata and a certificate such as the following:

How one can validate the signature of the metadata?

The README tells us that the following is able to retrieve the metadata, however, it seems there is no metadata signature validation available for #parse_remote ?

idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new

validate_cert = true
settings = idp_metadata_parser.parse_remote(
  "https://haka.funet.fi/metadata/haka-metadata.xml",
  validate_cert,
  entity_id: "https://login.helsinki.fi/shibboleth"
)

PS. If someone is reading this after 24.8.2022 the certificate will have been updated from v6 to v7. If someone wants to attempt the signature validation, the up-to-date metadata signature with a certificate are linked here: https://wiki.eduuni.fi/display/CSCHAKA/Metadata

pitbulk commented 1 year ago

@pre no idea if you already were able to achieve this, otherwise, take a look on my comments.

Right now the Signature verification is not supported by the parse_remote method.

The validate_cert param enables/disable the certificate validation in case of HTTPs

That said, instead the parse_remote you could:

require "xml_security"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/idp_metadata_parser"

url = "https://haka.funet.fi/metadata/haka-metadata.xml"
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new

uri = URI.parse(url)
raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https"
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
end

get = Net::HTTP::Get.new(uri.request_uri)
get.basic_auth uri.user, uri.password if uri.user
response = http.request(get)
xml = response.body
errors = []
doc = XMLSecurity::SignedDocument.new(xml, errors)
cert = OneLogin::RubySaml::Utils.format_cert("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")
metadata_sign_cert = OpenSSL::X509::Certificate.new(cert)
valid = doc.validate_document_with_cert(metadata_sign_cert, true)
if valid
  settings = idp_metadata_parser.parse(
    xml,
    entity_id: "https://login.helsinki.fi/shibboleth"
  )
else
  print "Metadata certificate invalid"
end
pre commented 1 year ago

Thanks! This seems to do the trick. Hopefully it’ll find its way behind a method in ruby-saml someday :)

pitbulk commented 1 year ago

For now I gonna add this code to the README.