Commit af87b45
Changed files (9)
lib/saml/kit/authentication_request.rb
@@ -5,6 +5,7 @@ module Saml
validates_presence_of :content
validate :must_be_request
validate :must_have_valid_signature
+ validate :must_be_registered_service_provider
attr_reader :content, :name
@@ -26,6 +27,14 @@ module Saml
@hash[name]['Issuer']
end
+ def certificate
+ @hash[name]['Signature']['KeyInfo']['X509Data']['X509Certificate']
+ end
+
+ def fingerprint
+ Fingerprint.new(certificate)
+ end
+
def to_xml
@content
end
@@ -36,6 +45,13 @@ module Saml
private
+ def must_be_registered_service_provider
+ return unless login_request?
+ return if Saml::Kit.configuration.service_provider_registry.registered?(issuer, fingerprint)
+
+ errors[:base] << error_message(:invalid)
+ end
+
def must_have_valid_signature
return if to_xml.blank?
@@ -49,9 +65,12 @@ module Saml
def must_be_request
return if @hash.nil?
- if @hash[name].blank?
- errors[:base] << error_message(:invalid)
- end
+ errors[:base] << error_message(:invalid) unless login_request?
+ end
+
+ def login_request?
+ return false if to_xml.blank?
+ @hash[name].present?
end
def error_message(key)
lib/saml/kit/configuration.rb
@@ -7,11 +7,15 @@ module Saml
attr_accessor :issuer
attr_accessor :signature_method, :digest_method
attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
+ attr_accessor :service_provider_registry
def initialize
+ #@issuer = SecureRandom.uuid
@signature_method = :SHA256
@digest_method = :SHA256
- @signing_certificate_pem, @signing_private_key_pem, @signing_private_key_password = create_self_signed_certificate
+ @signing_private_key_password = SecureRandom.uuid
+ @signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
+ @service_provider_registry = DefaultServiceProviderRegistry.new
end
def stripped_signing_certificate
@@ -25,32 +29,6 @@ module Saml
def signing_private_key
OpenSSL::PKey::RSA.new(signing_private_key_pem, signing_private_key_password)
end
-
- private
-
- def create_self_signed_certificate
- rsa_key = OpenSSL::PKey::RSA.new(2048)
- public_key = rsa_key.public_key
- certificate = OpenSSL::X509::Certificate.new
- certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/C=CA/ST=Alberta/L=Calgary/O=Xsig/OU=Xsig/CN=Xsig")
- certificate.not_before = DateTime.now.beginning_of_day
- certificate.not_after = 1.year.from_now.end_of_day
- certificate.public_key = public_key
- certificate.serial = 0x0
- certificate.version = 2
- factory = OpenSSL::X509::ExtensionFactory.new
- factory.subject_certificate = factory.issuer_certificate = certificate
- certificate.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
- certificate.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
- certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
-
- password = SecureRandom.uuid
- [
- certificate.to_pem,
- rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), password),
- password
- ]
- end
end
end
end
lib/saml/kit/default_service_provider_registry.rb
@@ -0,0 +1,9 @@
+module Saml
+ module Kit
+ class DefaultServiceProviderRegistry
+ def registered?(issuer, fingerprint)
+ issuer.present? && fingerprint.algorithm(OpenSSL::Digest::SHA256).present?
+ end
+ end
+ end
+end
lib/saml/kit/fingerprint.rb
@@ -0,0 +1,31 @@
+module Saml
+ module Kit
+ class Fingerprint
+ attr_reader :x509
+
+ def initialize(raw_certificate)
+ @x509 = OpenSSL::X509::Certificate.new(raw_certificate)
+ rescue OpenSSL::X509::CertificateError
+ @x509 = OpenSSL::X509::Certificate.new(Base64.decode64(raw_certificate))
+ end
+
+ def algorithm(algorithm)
+ pretty_fingerprint(algorithm.new.hexdigest(x509.to_der))
+ end
+
+ def ==(other)
+ self.to_s == other.to_s
+ end
+
+ def to_s
+ algorithm(OpenSSL::Digest::SHA256)
+ end
+
+ private
+
+ def pretty_fingerprint(fingerprint)
+ fingerprint.upcase.scan(/../).join(":")
+ end
+ end
+ end
+end
lib/saml/kit/metadata.rb
@@ -37,7 +37,7 @@ module Saml
cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", NAMESPACES).text
{
text: cert,
- fingerprint: fingerprint_for(cert, OpenSSL::Digest::SHA256),
+ fingerprint: Fingerprint.new(cert).algorithm(OpenSSL::Digest::SHA256),
use: item.attribute('use').value,
}
end
@@ -79,15 +79,6 @@ module Saml
document.search(xpath, NAMESPACES)
end
- def fingerprint_for(value, algorithm)
- x509 = OpenSSL::X509::Certificate.new(Base64.decode64(value))
- pretty_fingerprint(algorithm.new.hexdigest(x509.to_der))
- end
-
- def pretty_fingerprint(fingerprint)
- fingerprint.upcase.scan(/../).join(":")
- end
-
def metadata
find_by("/md:EntityDescriptor/md:#{descriptor_name}").present?
end
lib/saml/kit/self_signed_certificate.rb
@@ -0,0 +1,30 @@
+module Saml
+ module Kit
+ class SelfSignedCertificate
+ def initialize(password)
+ @password = password
+ end
+
+ def create
+ rsa_key = OpenSSL::PKey::RSA.new(2048)
+ public_key = rsa_key.public_key
+ certificate = OpenSSL::X509::Certificate.new
+ certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/C=CA/ST=Alberta/L=Calgary/O=Xsig/OU=Xsig/CN=Xsig")
+ certificate.not_before = DateTime.now.beginning_of_day
+ certificate.not_after = 1.year.from_now.end_of_day
+ certificate.public_key = public_key
+ certificate.serial = 0x0
+ certificate.version = 2
+ factory = OpenSSL::X509::ExtensionFactory.new
+ factory.subject_certificate = factory.issuer_certificate = certificate
+ certificate.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
+ certificate.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
+ certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
+ [
+ certificate.to_pem,
+ rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), @password)
+ ]
+ end
+ end
+ end
+end
lib/saml/kit.rb
@@ -12,11 +12,14 @@ require "xmldsig"
require "saml/kit/authentication_request"
require "saml/kit/configuration"
+require "saml/kit/default_service_provider_registry"
+require "saml/kit/fingerprint"
require "saml/kit/namespaces"
require "saml/kit/metadata"
require "saml/kit/request"
require "saml/kit/response"
require "saml/kit/identity_provider_metadata"
+require "saml/kit/self_signed_certificate"
require "saml/kit/service_provider_metadata"
require "saml/kit/signature"
require "saml/kit/xml"
spec/saml/authentication_request_spec.rb
@@ -43,6 +43,8 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
end
describe "#valid?" do
+ let(:registry) { double }
+
it 'is valid when left untampered' do
expect(described_class.new(raw_xml)).to be_valid
end
@@ -61,5 +63,16 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
xml = Saml::Kit::IdentityProviderMetadata::Builder.new.to_xml
expect(described_class.new(xml)).to be_invalid
end
+
+ it 'is invalid when the fingerprint of the certificate does not match the registered fingerprint' do
+ builder = described_class::Builder.new
+ builder.issuer = issuer
+ xml = builder.to_xml
+
+ allow(Saml::Kit.configuration).to receive(:service_provider_registry).and_return(registry)
+ fingerprint = Saml::Kit::Fingerprint.new(Hash.from_xml(xml)['AuthnRequest']['Signature']['KeyInfo']['X509Data']['X509Certificate'])
+ allow(registry).to receive(:registered?).with(issuer, fingerprint).and_return(false)
+ expect(described_class.new(xml)).to be_invalid
+ end
end
end
spec/saml/fingerprint_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+RSpec.describe Saml::Kit::Fingerprint do
+ describe "#sha" do
+ it 'returns the SHA256' do
+ certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
+ x509 = OpenSSL::X509::Certificate.new(certificate)
+ sha256 = OpenSSL::Digest::SHA256.new.hexdigest(x509.to_der).upcase.scan(/../).join(":")
+
+ expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA256)).to eql(sha256)
+ end
+
+ it 'returns the SHA1' do
+ certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
+ x509 = OpenSSL::X509::Certificate.new(certificate)
+ sha1 = OpenSSL::Digest::SHA1.new.hexdigest(x509.to_der).upcase.scan(/../).join(":")
+
+ expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA1)).to eql(sha1)
+ end
+ end
+end