Commit c42c5fa
Changed files (2)
lib
saml
kit
spec
saml
lib/saml/kit/xml.rb
@@ -6,7 +6,7 @@ module Saml
attr_reader :raw_xml, :document
validate :validate_signature
- validate :validate_certificate, if: :signature_element
+ validate :validate_certificate
def initialize(raw_xml)
@raw_xml = raw_xml
@@ -15,41 +15,41 @@ module Saml
end
end
- def signature_element
- document.at_xpath('//ds:Signature', Xmldsig::NAMESPACES)
- end
-
- def certificate
- xpath = '//ds:KeyInfo/ds:X509Data/ds:X509Certificate'
- raw_signature = signature_element.xpath(xpath, Xmldsig::NAMESPACES).text
- OpenSSL::X509::Certificate.new(Base64.decode64(raw_signature))
+ def x509_certificates
+ xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
+ document.search(xpath, Xmldsig::NAMESPACES).map do |item|
+ OpenSSL::X509::Certificate.new(Base64.decode64(item.text))
+ end
end
private
def validate_signature
invalid_signatures.flat_map(&:errors).uniq.each do |error|
- errors.add(error, "is invalid") if error != :signature
+ errors.add(error, "is invalid")
end
end
- def signed_document
- Xmldsig::SignedDocument.new(document, id_attr: 'ID=$uri or @Id')
- end
-
def invalid_signatures
+ signed_document = Xmldsig::SignedDocument.new(document, id_attr: 'ID=$uri or @Id')
signed_document.signatures.find_all do |signature|
- !signature.valid?(certificate)
+ x509_certificates.all? do |certificate|
+ !signature.valid?(certificate)
+ end
end
end
def validate_certificate(now = Time.current)
- if now < certificate.not_before
- errors.add(:certificate, "Not valid before #{certificate.not_before}")
- end
+ return unless document.at_xpath('//ds:Signature', Xmldsig::NAMESPACES).present?
+
+ x509_certificates.each do |certificate|
+ if now < certificate.not_before
+ errors.add(:certificate, "Not valid before #{certificate.not_before}")
+ end
- if now > certificate.not_after
- errors.add(:certificate, "Not valid after #{certificate.not_after}")
+ if now > certificate.not_after
+ errors.add(:certificate, "Not valid after #{certificate.not_after}")
+ end
end
end
end
spec/saml/xml_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::Xml do
+ describe "#valid_signature?" do
+ let(:login_url) { "https://#{FFaker::Internet.domain_name}/login" }
+ let(:logout_url) { "https://#{FFaker::Internet.domain_name}/logout" }
+
+ let(:signed_xml) do
+ builder = Saml::Kit::ServiceProviderMetadata::Builder.new
+ builder.entity_id = FFaker::Movie.title
+ builder.add_assertion_consumer_service(login_url, binding: :post)
+ builder.add_assertion_consumer_service(login_url, binding: :http_redirect)
+ builder.add_single_logout_service(logout_url, binding: :post)
+ builder.add_single_logout_service(logout_url, binding: :http_redirect)
+ builder.to_xml
+ end
+
+ it 'returns true, when the digest and signature is valid' do
+ subject = described_class.new(signed_xml)
+ expect(subject).to be_valid
+ end
+
+ it 'returns false, when the SHA1 digest is not valid' do
+ subject = described_class.new(signed_xml.gsub("EntityDescriptor", "uhoh"))
+ expect(subject).to_not be_valid
+ expect(subject.errors[:digest_value]).to be_present
+ end
+
+ it 'it is invalid when digest is incorrect' do
+ old_digest = Hash.from_xml(signed_xml)['EntityDescriptor']['Signature']['SignedInfo']['Reference']['DigestValue']
+ subject = described_class.new(signed_xml.gsub(old_digest, 'sabotage'))
+ expect(subject).to_not be_valid
+ expect(subject.errors[:digest_value]).to be_present
+ end
+
+ it 'returns false, when the signature is invalid' do
+ old_signature = Hash.from_xml(signed_xml)['EntityDescriptor']['Signature']['SignatureValue']
+ signed_xml.gsub!(old_signature, 'sabotage')
+ subject = described_class.new(signed_xml)
+ expect(subject).to_not be_valid
+ expect(subject.errors[:signature]).to be_present
+ end
+
+ [
+ 'ad_2012.xml',
+ 'ad_with_logout.xml',
+ 'okta.xml',
+ ].each do |xml_file|
+ it "validates #{xml_file}" do
+ travel_to DateTime.new(2017, 10, 6)
+ xml = IO.read("spec/fixtures/metadata/#{xml_file}")
+ expect(described_class.new(xml)).to be_valid
+ end
+ end
+ end
+end