Commit dfdb79d
Changed files (6)
lib
saml
kit
spec
lib/saml/kit/locales/en.yml
@@ -6,3 +6,4 @@ en:
metadata:
invalid_idp: "must contain identity provider metadata."
blank: "metadata is required."
+ invalid_signature: "invalid signature."
lib/saml/kit/identity_provider_metadata.rb
@@ -13,6 +13,7 @@ module Saml
validates_presence_of :metadata
validate :must_contain_idp_descriptor
validate :must_match_xsd
+ validate :must_have_valid_signature
def initialize(xml)
@xml = xml
@@ -89,6 +90,15 @@ module Saml
end
end
+ def must_have_valid_signature
+ return if to_xml.blank?
+ errors[:metadata] << error_message('metadata.invalid_signature') unless valid_signature?
+ end
+
+ def valid_signature?
+ Saml::Kit::Xml.new(to_xml).valid?
+ end
+
def fingerprint_for(value)
x509 = OpenSSL::X509::Certificate.new(value)
OpenSSL::Digest::SHA256.new.hexdigest(x509.to_der).upcase.scan(/../).join(":")
lib/saml/kit/xml.rb
@@ -0,0 +1,57 @@
+module Saml
+ module Kit
+ class Xml
+ include ActiveModel::Validations
+
+ attr_reader :raw_xml, :document
+
+ validate :validate_signature
+ validate :validate_certificate, if: :signature_element
+
+ def initialize(raw_xml)
+ @raw_xml = raw_xml
+ @document = Nokogiri::XML(raw_xml, nil, nil, Nokogiri::XML::ParseOptions::STRICT) do |config|
+ config.noblanks
+ 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))
+ end
+
+ private
+
+ def validate_signature
+ invalid_signatures.flat_map(&:errors).uniq.each do |error|
+ errors.add(error, "is invalid") if error != :signature
+ end
+ end
+
+ def signed_document
+ Xmldsig::SignedDocument.new(document, id_attr: 'ID=$uri or @Id')
+ end
+
+ def invalid_signatures
+ signed_document.signatures.find_all do |signature|
+ !signature.valid?(certificate)
+ end
+ end
+
+ def validate_certificate(now = Time.current)
+ 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}")
+ end
+ end
+ end
+ end
+end
lib/saml/kit.rb
@@ -1,12 +1,14 @@
require "saml/kit/version"
+require "active_model"
+require "active_support/core_ext/hash/conversions"
+require "active_support/core_ext/numeric/time"
+require "active_support/duration"
require "builder"
require "nokogiri"
require "securerandom"
-require "active_model"
-require "active_support/duration"
-require "active_support/core_ext/numeric/time"
-require "active_support/core_ext/hash/conversions"
+require "xmldsig"
+
require "saml/kit/authentication_request"
require "saml/kit/configuration"
require "saml/kit/namespaces"
@@ -15,6 +17,7 @@ require "saml/kit/response"
require "saml/kit/service_provider_registry"
require "saml/kit/identity_provider_metadata"
require "saml/kit/service_provider_metadata"
+require "saml/kit/xml"
I18n.load_path += Dir[File.expand_path("kit/locales/*.yml", File.dirname(__FILE__))]
spec/saml/identity_provider_metadata_spec.rb
@@ -222,21 +222,15 @@ EOS
metadata_xml = IO.read("spec/fixtures/metadata/ad_2012.xml").gsub(old_url, new_url)
subject = described_class.new(metadata_xml)
- subject.validate do |error|
- errors << error
- end
- expect(errors).to be_present
- expect(errors[0].message).to eql(I18n.translate("activerecord.errors.models.sso_configuration.attributes.metadata.invalid_signature"))
+ expect(subject).to be_invalid
+ expect(subject.errors[:metadata]).to include("invalid signature.")
end
it 'is valid, when the content has not been tampered with' do
metadata_xml = IO.read("spec/fixtures/metadata/ad_2012.xml")
subject = described_class.new(metadata_xml)
- subject.validate do |error|
- errors << error
- end
- expect(errors).to be_empty
+ expect(subject).to be_valid
end
end
end
saml-kit.gemspec
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "activesupport", "~> 5.1"
spec.add_dependency "builder", "~> 3.2"
spec.add_dependency "nokogiri", "~> 1.8"
+ spec.add_dependency "xmldsig", "~> 0.6"
spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"