Comparing changes

v0.2.3 v0.2.4
25 commits 54 files changed

Commits

2c12390 bump version. mo 2017-12-15 03:42:28
91bf8b4 change rspec format. mo 2017-12-14 23:13:11
4a2d37e remove circular reference. mo 2017-12-14 22:47:02
257793c remove merge file. mo 2017-12-14 22:32:18
0f7abe9 delegate to builder. mo 2017-12-14 22:28:25
a5505c9 fix deprecation warnings. mo 2017-12-14 20:29:37
cf05d42 decouple from deprecated method. mo 2017-12-14 18:55:26
db0e70f decouple from deprecated method. mo 2017-12-14 18:47:39
31b59d8 fix broken spec. mo 2017-12-14 18:40:54
81da4f7 fix broken spec. mo 2017-12-14 18:38:54
fc73fcc fix more broken specs. mo 2017-12-14 18:37:40
2ae1e99 fix a few broken specs. mo 2017-12-14 17:54:13
bdfc930 pass in parent hash. mo 2017-12-13 20:08:08
3b8bbde extract Assertion class. mo 2017-12-13 16:51:21
exe/saml-kit-create-self-signed-certificate
@@ -1,6 +1,7 @@
 #!/usr/bin/env ruby
 require 'saml/kit'
 
+puts "Enter Password:"
 password = STDIN.read.strip
 certificate, private_key = Saml::Kit::SelfSignedCertificate.new(password).create
 
lib/saml/kit/bindings/http_redirect.rb
@@ -12,7 +12,7 @@ module Saml
           builder.sign = false
           builder.destination = location
           document = builder.build
-          [UrlBuilder.new.build(document, relay_state: relay_state), {}]
+          [UrlBuilder.new(configuration: builder.configuration).build(document, relay_state: relay_state), {}]
         end
 
         def deserialize(params)
lib/saml/kit/bindings/url_builder.rb
@@ -3,21 +3,25 @@ module Saml
     module Bindings
       class UrlBuilder
         include Serializable
+        attr_reader :configuration
 
-        def initialize(private_key: Saml::Kit.configuration.signing_private_key)
-          @private_key = private_key
+        def initialize(configuration: Saml::Kit.configuration)
+          @configuration = configuration
         end
 
         def build(saml_document, relay_state: nil)
           payload = canonicalize(saml_document, relay_state)
-          "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
+          if configuration.sign?
+            "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
+          else
+            "#{saml_document.destination}?#{payload}"
+          end
         end
 
         private
 
-        attr_reader :private_key
-
         def signature_for(payload)
+          private_key = configuration.private_keys(use: :signing).sample
           encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
         end
 
lib/saml/kit/builders/templates/assertion.builder
@@ -1,27 +1,29 @@
-xml.Assertion(assertion_options) do
-  xml.Issuer issuer
-  signature_for(reference_id: reference_id, xml: xml) unless encrypt
-  xml.Subject do
-    xml.NameID name_id, Format: name_id_format
-    xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
-      xml.SubjectConfirmationData "", subject_confirmation_data_options
+encryption_for(xml: xml) do |xml|
+  xml.Assertion(assertion_options) do
+    xml.Issuer issuer
+    signature_for(reference_id: reference_id, xml: xml) unless encrypt
+    xml.Subject do
+      xml.NameID name_id, Format: name_id_format
+      xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
+        xml.SubjectConfirmationData "", subject_confirmation_data_options
+      end
     end
-  end
-  xml.Conditions conditions_options do
-    xml.AudienceRestriction do
-      xml.Audience request.issuer
+    xml.Conditions conditions_options do
+      xml.AudienceRestriction do
+        xml.Audience request.issuer
+      end
     end
-  end
-  xml.AuthnStatement authn_statement_options do
-    xml.AuthnContext do
-      xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+    xml.AuthnStatement authn_statement_options do
+      xml.AuthnContext do
+        xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+      end
     end
-  end
-  if assertion_attributes.any?
-    xml.AttributeStatement do
-      assertion_attributes.each do |key, value|
-        xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
-          xml.AttributeValue value.to_s
+    if assertion_attributes.any?
+      xml.AttributeStatement do
+        assertion_attributes.each do |key, value|
+          xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
+            xml.AttributeValue value.to_s
+          end
         end
       end
     end
lib/saml/kit/builders/templates/identity_provider_metadata.builder
@@ -2,11 +2,11 @@ xml.instruct!
 xml.EntityDescriptor entity_descriptor_options do
   signature_for(reference_id: id, xml: xml)
   xml.IDPSSODescriptor idp_sso_descriptor_options do
-    if configuration.signing_certificate_pem.present?
-      render configuration.signing_certificate, xml: xml
+    configuration.certificates(use: :signing).each do |certificate|
+      render certificate, xml: xml
     end
-    if configuration.encryption_certificate_pem.present?
-      render configuration.encryption_certificate, xml: xml
+    configuration.certificates(use: :encryption).each do |certificate|
+      render certificate, xml: xml
     end
     logout_urls.each do |item|
       xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
lib/saml/kit/builders/templates/nil_class.builder
lib/saml/kit/builders/templates/response.builder
@@ -5,7 +5,5 @@ xml.Response response_options do
   xml.Status do
     xml.StatusCode Value: status_code
   end
-  encryption_for(xml: xml) do |xml|
-    render assertion, xml: xml
-  end
+  render assertion, xml: xml
 end
lib/saml/kit/builders/templates/service_provider_metadata.builder
@@ -2,11 +2,11 @@ xml.instruct!
 xml.EntityDescriptor entity_descriptor_options do
   signature_for(reference_id: id, xml: xml)
   xml.SPSSODescriptor descriptor_options do
-    if configuration.signing_certificate_pem.present?
-      render configuration.signing_certificate, xml: xml
+    configuration.certificates(use: :signing).each do |certificate|
+      render certificate, xml: xml
     end
-    if configuration.encryption_certificate_pem.present?
-      render configuration.encryption_certificate, xml: xml
+    configuration.certificates(use: :encryption).each do |certificate|
+      render certificate, xml: xml
     end
     logout_urls.each do |item|
       xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
lib/saml/kit/builders/assertion.rb
@@ -5,7 +5,7 @@ module Saml
         include Templatable
         extend Forwardable
 
-        def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version
+        def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination, :encryption_certificate
 
         def initialize(response_builder)
           @response_builder = response_builder
@@ -38,7 +38,7 @@ module Saml
           {
             InResponseTo: request.id,
             NotOnOrAfter: 3.hours.since(now).utc.iso8601,
-            Recipient: request.assertion_consumer_service_url,
+            Recipient: destination,
           }
         end
 
lib/saml/kit/builders/authentication_request.rb
@@ -3,17 +3,16 @@ module Saml
     module Builders
       class AuthenticationRequest
         include Saml::Kit::Templatable
-        attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :sign, :destination
+        attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
         attr_accessor :version
         attr_reader :configuration
 
-        def initialize(configuration: Saml::Kit.configuration, sign: true)
+        def initialize(configuration: Saml::Kit.configuration)
           @configuration = configuration
           @id = Id.generate
           @issuer = configuration.issuer
           @name_id_format = Namespaces::PERSISTENT
           @now = Time.now.utc
-          @sign = sign
           @version = "2.0"
         end
 
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -4,18 +4,17 @@ module Saml
       class IdentityProviderMetadata
         include Saml::Kit::Templatable
         attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
-        attr_accessor :want_authn_requests_signed, :sign
+        attr_accessor :want_authn_requests_signed
         attr_reader :logout_urls, :single_sign_on_urls
         attr_reader :configuration
 
-        def initialize(configuration = Saml::Kit.configuration)
+        def initialize(configuration: Saml::Kit.configuration)
           @attributes = []
           @configuration = configuration
           @entity_id = configuration.issuer
           @id = Id.generate
           @logout_urls = []
           @name_id_formats = [Namespaces::PERSISTENT]
-          @sign = true
           @single_sign_on_urls = []
           @want_authn_requests_signed = true
         end
lib/saml/kit/builders/logout_request.rb
@@ -4,10 +4,10 @@ module Saml
       class LogoutRequest
         include Saml::Kit::Templatable
         attr_accessor :id, :destination, :issuer, :name_id_format, :now
-        attr_accessor :sign, :version
+        attr_accessor :version
         attr_reader :user, :configuration
 
-        def initialize(user, configuration: Saml::Kit.configuration, sign: true)
+        def initialize(user, configuration: Saml::Kit.configuration)
           @configuration = configuration
           @user = user
           @id = "_#{SecureRandom.uuid}"
@@ -15,11 +15,10 @@ module Saml
           @name_id_format = Saml::Kit::Namespaces::PERSISTENT
           @now = Time.now.utc
           @version = "2.0"
-          @sign = sign
         end
 
         def build
-          Saml::Kit::LogoutRequest.new(to_xml)
+          Saml::Kit::LogoutRequest.new(to_xml, configuration: configuration)
         end
 
         private
lib/saml/kit/builders/logout_response.rb
@@ -3,17 +3,16 @@ module Saml
     module Builders
       class LogoutResponse
         include Saml::Kit::Templatable
-        attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
+        attr_accessor :id, :issuer, :version, :status_code, :now, :destination
         attr_reader :request
         attr_reader :configuration
 
-        def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
+        def initialize(user, request, configuration: Saml::Kit.configuration)
           @configuration = configuration
           @id = Id.generate
           @issuer = configuration.issuer
           @now = Time.now.utc
           @request = request
-          @sign = sign
           @status_code = Namespaces::SUCCESS
           @user = user
           @version = "2.0"
lib/saml/kit/builders/response.rb
@@ -6,7 +6,7 @@ module Saml
         attr_reader :user, :request
         attr_accessor :id, :reference_id, :now
         attr_accessor :version, :status_code
-        attr_accessor :issuer, :sign, :destination, :encrypt
+        attr_accessor :issuer, :destination, :encrypt
         attr_reader :configuration
 
         def initialize(user, request, configuration: Saml::Kit.configuration)
@@ -18,9 +18,8 @@ module Saml
           @version = "2.0"
           @status_code = Namespaces::SUCCESS
           @issuer = configuration.issuer
-          @destination = destination_for(request)
           @sign = want_assertions_signed
-          @encrypt = false
+          @encrypt = encryption_certificate.present?
           @configuration = configuration
         end
 
@@ -28,29 +27,24 @@ module Saml
           request.provider.want_assertions_signed
         rescue => error
           Saml::Kit.logger.error(error)
-          true
+          nil
         end
 
         def build
-          Saml::Kit::Response.new(to_xml, request_id: request.id)
-        end
-
-        private
-
-        def assertion
-          @assertion ||= Saml::Kit::Builders::Assertion.new(self)
+          Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
         end
 
         def encryption_certificate
           request.provider.encryption_certificates.first
+        rescue => error
+          Saml::Kit.logger.error(error)
+          nil
         end
 
-        def destination_for(request)
-          if request.signed? && request.trusted?
-            request.assertion_consumer_service_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
-          else
-            request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
-          end
+        private
+
+        def assertion
+          @assertion ||= Saml::Kit::Builders::Assertion.new(self)
         end
 
         def response_options
lib/saml/kit/builders/service_provider_metadata.rb
@@ -3,19 +3,18 @@ module Saml
     module Builders
       class ServiceProviderMetadata
         include Saml::Kit::Templatable
-        attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
+        attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats
         attr_accessor :organization_name, :organization_url, :contact_email
         attr_accessor :want_assertions_signed
         attr_reader :configuration
 
-        def initialize(configuration = Saml::Kit.configuration)
+        def initialize(configuration: Saml::Kit.configuration)
           @acs_urls = []
           @configuration = configuration
           @entity_id = configuration.issuer
           @id = Id.generate
           @logout_urls = []
           @name_id_formats = [Namespaces::PERSISTENT]
-          @sign = true
           @want_assertions_signed = true
         end
 
@@ -43,7 +42,7 @@ module Saml
 
         def descriptor_options
           {
-            AuthnRequestsSigned: sign,
+            AuthnRequestsSigned: sign?,
             WantAssertionsSigned: want_assertions_signed,
             protocolSupportEnumeration: Namespaces::PROTOCOL,
           }
lib/saml/kit/builders/xml_signature.rb
@@ -21,11 +21,10 @@ module Saml
         attr_reader :reference_id
         attr_reader :x509_certificate
 
-        def initialize(reference_id, configuration:, sign: true)
+        def initialize(reference_id, configuration:)
           @configuration = configuration
           @reference_id = reference_id
-          @sign = sign
-          @x509_certificate = configuration.signing_certificate.stripped
+          @x509_certificate = configuration.certificates(use: :signing).sample.stripped
         end
 
         def signature_method
lib/saml/kit/locales/en.yml
@@ -11,6 +11,9 @@ en:
         invalid_signature: "invalid signature."
       InvalidDocument:
         invalid: "must contain valid SAMLRequest"
+      LogoutRequest:
+        invalid_fingerprint: "does not match."
+        unregistered: "is unregistered."
       LogoutResponse:
         unregistered: "is unregistered."
       Response:
lib/saml/kit/assertion.rb
@@ -0,0 +1,73 @@
+module Saml
+  module Kit
+    class Assertion
+      def initialize(xml_hash, configuration:)
+        @xml_hash = xml_hash
+        @configuration = configuration
+      end
+
+      def name_id
+        assertion.fetch('Subject', {}).fetch('NameID', nil)
+      end
+
+      def signed?
+        assertion.fetch('Signature', nil).present?
+      end
+
+      def attributes
+        @attributes ||=
+          begin
+            attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
+            items = if attrs.is_a? Hash
+                      [[attrs["Name"], attrs["AttributeValue"]]]
+                    else
+                      attrs.map { |item| [item['Name'], item['AttributeValue']] }
+                    end
+            Hash[items].with_indifferent_access
+          end
+      end
+
+      def started_at
+        parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
+      end
+
+      def expired_at
+        parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
+      end
+
+      def certificate
+        assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
+      end
+
+      def audiences
+        Array(assertion['Conditions']['AudienceRestriction']['Audience'])
+      rescue => error
+        Saml::Kit.logger.error(error)
+        []
+      end
+
+      private
+
+      def encrypted?
+        @xml_hash.fetch('Response', {}).fetch('EncryptedAssertion', nil).present?
+      end
+
+      def assertion
+        if encrypted?
+          decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
+          Saml::Kit.logger.debug(decrypted)
+          Hash.from_xml(decrypted)['Assertion']
+        else
+          @xml_hash.fetch('Response', {}).fetch('Assertion', {})
+        end
+      end
+
+      def parse_date(value)
+        DateTime.parse(value)
+      rescue => error
+        Saml::Kit.logger.error(error)
+        Time.at(0).to_datetime
+      end
+    end
+  end
+end
lib/saml/kit/authentication_request.rb
@@ -3,8 +3,8 @@ module Saml
     class AuthenticationRequest < Document
       include Requestable
 
-      def initialize(xml)
-        super(xml, name: "AuthnRequest")
+      def initialize(xml, configuration: Saml::Kit.configuration)
+        super(xml, name: "AuthnRequest", configuration: configuration)
       end
 
       def assertion_consumer_service_url
lib/saml/kit/buildable.rb
@@ -4,10 +4,8 @@ module Saml
       extend ActiveSupport::Concern
 
       class_methods do
-        def build(*args)
-          builder(*args).tap do |x|
-            yield x if block_given?
-          end.build
+        def build(*args, &block)
+          builder(*args, &block).build
         end
 
         def builder(*args)
lib/saml/kit/composite_metadata.rb
@@ -1,16 +1,19 @@
 module Saml
   module Kit
     class CompositeMetadata < Metadata
+      include Enumerable
       attr_reader :service_provider, :identity_provider
 
       def initialize(xml)
         super("IDPSSODescriptor", xml)
-        @service_provider = Saml::Kit::ServiceProviderMetadata.new(xml)
-        @identity_provider = Saml::Kit::IdentityProviderMetadata.new(xml)
+        @metadatum = [
+          Saml::Kit::ServiceProviderMetadata.new(xml),
+          Saml::Kit::IdentityProviderMetadata.new(xml),
+        ]
       end
 
       def services(type)
-        xpath = "//md:EntityDescriptor/md:SPSSODescriptor/md:#{type}|//md:EntityDescriptor/md:IDPSSODescriptor/md:#{type}"
+        xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join("|")
         document.find_all(xpath).map do |item|
           binding = item.attribute("Binding").value
           location = item.attribute("Location").value
@@ -19,14 +22,16 @@ module Saml
       end
 
       def certificates
-        identity_provider.certificates + service_provider.certificates
+        flat_map(&:certificates)
+      end
+
+      def each(&block)
+        @metadatum.each(&block)
       end
 
       def method_missing(name, *args)
-        if identity_provider.respond_to?(name)
-          identity_provider.public_send(name, *args)
-        elsif service_provider.respond_to?(name)
-          service_provider.public_send(name, *args)
+        if target = find { |x| x.respond_to?(name) }
+          target.public_send(name, *args)
         else
           super
         end
lib/saml/kit/configuration.rb
@@ -3,37 +3,62 @@ module Saml
     class Configuration
       attr_accessor :issuer
       attr_accessor :signature_method, :digest_method
-      attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
-      attr_accessor :encryption_certificate_pem, :encryption_private_key_pem, :encryption_private_key_password
       attr_accessor :registry, :session_timeout
       attr_accessor :logger
 
       def initialize
         @signature_method = :SHA256
         @digest_method = :SHA256
-        @signing_private_key_password = SecureRandom.uuid
-        @encryption_private_key_password = SecureRandom.uuid
-        @signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
-        @encryption_certificate_pem, @encryption_private_key_pem = SelfSignedCertificate.new(@encryption_private_key_password).create
         @registry = DefaultRegistry.new
         @session_timeout = 3.hours
         @logger = Logger.new(STDOUT)
+        yield self if block_given?
       end
 
-      def signing_certificate
-        Saml::Kit::Certificate.new(signing_certificate_pem, use: :signing)
+      def add_key_pair(certificate, private_key, password:, use: :signing)
+        key_pairs.push({
+          certificate: Saml::Kit::Certificate.new(certificate, use: use),
+          private_key: OpenSSL::PKey::RSA.new(private_key, password)
+        })
+      end
+
+      def generate_key_pair_for(use:, password: SecureRandom.uuid)
+        certificate, private_key = SelfSignedCertificate.new(password).create
+        add_key_pair(certificate, private_key, password: password, use: use)
+      end
+
+      def certificates(use: nil)
+        certificates = key_pairs.map { |x| x[:certificate] }
+        use.present? ? certificates.find_all { |x| x.for?(use) } : certificates
+      end
+
+      def private_keys(use: :signing)
+        key_pairs.find_all { |x| x[:certificate].for?(use) }.map { |x| x[:private_key] }
       end
 
       def encryption_certificate
-        Saml::Kit::Certificate.new(encryption_certificate_pem, use: :encryption)
+        Saml::Kit.deprecate("encryption_certificate is deprecated. Use certificates(use: :encryption) instead")
+        certificates(use: :encryption).last
       end
 
       def signing_private_key
-        OpenSSL::PKey::RSA.new(signing_private_key_pem, signing_private_key_password)
+        Saml::Kit.deprecate("signing_private_key is deprecated. Use private_keys(use: :signing) instead")
+        private_keys(use: :signing).last
       end
 
       def encryption_private_key
-        OpenSSL::PKey::RSA.new(encryption_private_key_pem, encryption_private_key_password)
+        Saml::Kit.deprecate("encryption_private_key is deprecated. Use private_keys(use: :encryption) instead")
+        private_keys(use: :encryption).last
+      end
+
+      def sign?
+        certificates(use: :signing).any?
+      end
+
+      private
+
+      def key_pairs
+        @key_pairs ||= []
       end
     end
   end
lib/saml/kit/document.rb
@@ -12,9 +12,10 @@ module Saml
       validate :must_be_expected_type
       validate :must_be_valid_version
 
-      attr_reader :content, :name
+      attr_reader :content, :name, :configuration
 
-      def initialize(xml, name:)
+      def initialize(xml, name:, configuration: Saml::Kit.configuration)
+        @configuration = configuration
         @content = xml
         @name = name
         @xml_hash = Hash.from_xml(xml) || {}
lib/saml/kit/logout_request.rb
@@ -4,8 +4,8 @@ module Saml
       include Requestable
       validates_presence_of :single_logout_service, if: :expected_type?
 
-      def initialize(xml)
-        super(xml, name: "LogoutRequest")
+      def initialize(xml, configuration: Saml::Kit.configuration)
+        super(xml, name: "LogoutRequest", configuration: configuration)
       end
 
       def name_id
lib/saml/kit/metadata.rb
@@ -91,8 +91,8 @@ module Saml
       end
 
       def verify(algorithm, signature, data)
-        signing_certificates.find do |cert|
-          cert.public_key.verify(algorithm, signature, data)
+        signing_certificates.find do |certificate|
+          certificate.public_key.verify(algorithm, signature, data)
         end
       end
 
lib/saml/kit/response.rb
@@ -2,42 +2,16 @@ module Saml
   module Kit
     class Response < Document
       include Respondable
+      extend Forwardable
+
+      def_delegators :assertion, :name_id, :[], :attributes, :started_at, :expired_at, :audiences
 
       validate :must_be_active_session
       validate :must_match_issuer
 
-      def initialize(xml, request_id: nil)
+      def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
         @request_id = request_id
-        super(xml, name: "Response")
-      end
-
-      def name_id
-        assertion.fetch('Subject', {}).fetch('NameID', nil)
-      end
-
-      def [](key)
-        attributes[key]
-      end
-
-      def attributes
-        @attributes ||=
-          begin
-            attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
-            items = if attrs.is_a? Hash
-              [[attrs["Name"], attrs["AttributeValue"]]]
-            else
-              attrs.map { |item| [item['Name'], item['AttributeValue']] }
-            end
-            Hash[items].with_indifferent_access
-          end
-      end
-
-      def started_at
-        parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
-      end
-
-      def expired_at
-        parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
+        super(xml, name: "Response", configuration: configuration)
       end
 
       def expired?
@@ -48,29 +22,16 @@ module Saml
         Time.current > started_at && !expired?
       end
 
-      def encrypted?
-        to_h[name]['EncryptedAssertion'].present?
-      end
-
       def assertion
-        @assertion =
-          begin
-            if encrypted?
-              decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
-              Saml::Kit.logger.debug(decrypted)
-              Hash.from_xml(decrypted)['Assertion']
-            else
-              to_h.fetch(name, {}).fetch('Assertion', {})
-            end
-          end
+        @assertion = Saml::Kit::Assertion.new(to_h, configuration: @configuration)
       end
 
       def signed?
-        super || assertion.fetch('Signature', nil).present?
+        super || assertion.signed?
       end
 
       def certificate
-        super || assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
+        super || assertion.certificate
       end
 
       private
@@ -85,25 +46,11 @@ module Saml
         return unless expected_type?
         return unless success?
 
-        unless audiences.include?(Saml::Kit.configuration.issuer)
+        unless audiences.include?(configuration.issuer)
           errors[:audience] << error_message(:must_match_issuer)
         end
       end
 
-      def audiences
-        Array(assertion['Conditions']['AudienceRestriction']['Audience'])
-      rescue => error
-        Saml::Kit.logger.error(error)
-        []
-      end
-
-      def parse_date(value)
-        DateTime.parse(value)
-      rescue => error
-        Saml::Kit.logger.error(error)
-        Time.at(0).to_datetime
-      end
-
       Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
     end
   end
lib/saml/kit/self_signed_certificate.rb
@@ -17,15 +17,6 @@ module Saml
         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,
lib/saml/kit/signature.rb
@@ -1,36 +1,23 @@
 module Saml
   module Kit
     class Signature
-      attr_reader :sign, :xml
-      attr_reader :configuration
+      attr_reader :signatures
+      attr_reader :xml
 
-      def initialize(xml, configuration:, sign: true)
-        @configuration = configuration
-        @sign = sign
+      def initialize(xml, signatures)
+        @signatures = signatures
         @xml = xml
       end
 
       def template(reference_id)
-        return unless sign
-        signature = signatures.build(reference_id)
-        Template.new(signature).to_xml(xml: xml)
+        Template.new(signatures.build(reference_id)).to_xml(xml: xml)
       end
 
-      def finalize
+      def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
+        signatures = Saml::Kit::Signatures.new(configuration: configuration)
+        yield xml, new(xml, signatures)
         signatures.complete(xml.target!)
       end
-
-      def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
-        signature = new(xml, sign: sign, configuration: configuration)
-        yield xml, signature
-        signature.finalize
-      end
-
-      private
-
-      def signatures
-        @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
-      end
     end
   end
 end
lib/saml/kit/signatures.rb
@@ -1,28 +1,22 @@
 module Saml
   module Kit
     class Signatures
-      attr_reader :sign, :configuration
+      attr_reader :configuration
 
-      def initialize(configuration:, sign: true)
+      def initialize(configuration:)
         @configuration = configuration
-        @sign = sign
       end
 
       def build(reference_id)
-        Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, sign: sign)
+        return nil unless configuration.sign?
+        Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
       end
 
       def complete(raw_xml)
-        return raw_xml unless sign
-
+        return raw_xml unless configuration.sign?
+        private_key = configuration.private_keys(use: :signing).sample
         Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
       end
-
-      private
-
-      def private_key
-        configuration.signing_private_key
-      end
     end
   end
 end
lib/saml/kit/templatable.rb
@@ -1,17 +1,23 @@
 module Saml
   module Kit
     module Templatable
+      attr_accessor :sign
+
       def to_xml(xml: ::Builder::XmlMarkup.new)
         signatures.complete(render(self, xml: xml))
       end
 
       def signature_for(reference_id:, xml:)
-        return unless sign
+        return unless sign?
         render(signatures.build(reference_id), xml: xml)
       end
 
+      def sign?
+        sign.nil? ? configuration.sign? : sign && configuration.sign?
+      end
+
       def signatures
-        @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
+        @signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
       end
 
       def encryption_for(xml:)
lib/saml/kit/template.rb
@@ -18,7 +18,8 @@ module Saml
       end
 
       def template_path
-        File.join(File.expand_path(File.dirname(__FILE__)), "builders/templates/#{template_name}")
+        root_path = File.expand_path(File.dirname(__FILE__))
+        File.join(root_path, "builders/templates/", template_name)
       end
 
       def template
lib/saml/kit/trustable.rb
@@ -30,7 +30,7 @@ module Saml
       end
 
       def provider
-        Saml::Kit.registry.metadata_for(issuer)
+        configuration.registry.metadata_for(issuer)
       end
 
       def signature_verified!
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
 module Saml
   module Kit
-    VERSION = "0.2.3"
+    VERSION = "0.2.4"
   end
 end
lib/saml/kit/xml.rb
@@ -7,6 +7,7 @@ module Saml
         "ds": Namespaces::XMLDSIG,
         "md": Namespaces::METADATA,
         "saml": Namespaces::ASSERTION,
+        "pro": Namespaces::PROTOCOL,
       }.freeze
 
       attr_reader :raw_xml, :document
lib/saml/kit/xml_decryption.rb
@@ -3,8 +3,8 @@ module Saml
     class XmlDecryption
       attr_reader :private_key
 
-      def initialize(private_key = Saml::Kit.configuration.encryption_private_key)
-        @private_key = private_key
+      def initialize(configuration: Saml::Kit.configuration)
+        @private_key = configuration.private_keys(use: :encryption).sample
       end
 
       def decrypt(data)
lib/saml/kit.rb
@@ -26,6 +26,7 @@ require "saml/kit/requestable"
 require "saml/kit/trustable"
 require "saml/kit/document"
 
+require "saml/kit/assertion"
 require "saml/kit/authentication_request"
 require "saml/kit/bindings"
 require "saml/kit/certificate"
spec/saml/bindings/http_post_spec.rb
@@ -1,14 +1,37 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Bindings::HttpPost do
-  let(:location) { FFaker::Internet.http_url }
+  let(:location) { FFaker::Internet.uri("https") }
   subject { described_class.new(location: location) }
 
+  describe "equality" do
+    it 'is referentially equal' do
+      expect(subject).to eql(subject)
+    end
+
+    it 'is equal by value' do
+      expect(subject).to eql(
+        Saml::Kit::Bindings::HttpPost.new(location: location)
+      )
+    end
+
+    it 'is not equal' do
+      expect(subject).to_not eql(
+        described_class.new(location: FFaker::Internet.uri("https"))
+      )
+    end
+  end
+
   describe "#serialize" do
     let(:relay_state) { "ECHO" }
+    let(:configuration) do
+      Saml::Kit::Configuration.new do |config|
+        config.generate_key_pair_for(use: :signing)
+      end
+    end
 
     it 'encodes the request using the HTTP-POST encoding for a AuthenticationRequest' do
-      builder = Saml::Kit::AuthenticationRequest.builder_class.new
+      builder = Saml::Kit::AuthenticationRequest.builder_class.new(configuration: configuration)
       url, saml_params = subject.serialize(builder, relay_state: relay_state)
 
       expect(url).to eql(location)
@@ -22,7 +45,7 @@ RSpec.describe Saml::Kit::Bindings::HttpPost do
 
     it 'returns a SAMLRequest for a LogoutRequest' do
       user = double(:user, name_id_for: SecureRandom.uuid)
-      builder = Saml::Kit::LogoutRequest.builder_class.new(user)
+      builder = Saml::Kit::LogoutRequest.builder_class.new(user, configuration: configuration)
       url, saml_params = subject.serialize(builder, relay_state: relay_state)
 
       expect(url).to eql(location)
@@ -37,7 +60,7 @@ RSpec.describe Saml::Kit::Bindings::HttpPost do
     it 'returns a SAMLResponse for a LogoutResponse' do
       user = double(:user, name_id_for: SecureRandom.uuid)
       request = instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid)
-      builder = Saml::Kit::LogoutResponse.builder_class.new(user, request)
+      builder = Saml::Kit::LogoutResponse.builder_class.new(user, request, configuration: configuration)
       url, saml_params = subject.serialize(builder, relay_state: relay_state)
 
       expect(url).to eql(location)
spec/saml/bindings/http_redirect_spec.rb
@@ -6,9 +6,14 @@ RSpec.describe Saml::Kit::Bindings::HttpRedirect do
 
   describe "#serialize" do
     let(:relay_state) { "ECHO" }
+    let(:configuration) do
+      Saml::Kit::Configuration.new do |config|
+        config.generate_key_pair_for(use: :signing)
+      end
+    end
 
     it 'encodes the request using the HTTP-Redirect encoding' do
-      builder = Saml::Kit::AuthenticationRequest.builder_class.new
+      builder = Saml::Kit::AuthenticationRequest.builder_class.new(configuration: configuration)
       url, _ = subject.serialize(builder, relay_state: relay_state)
       expect(url).to start_with(location)
       expect(url).to have_query_param('SAMLRequest')
spec/saml/bindings/url_builder_spec.rb
@@ -13,6 +13,13 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
       [Saml::Kit::LogoutResponse, 'SAMLResponse'],
     ].each do |(response_type, query_string_parameter)|
       describe response_type.to_s do
+        subject { described_class.new(configuration: configuration) }
+        let(:configuration) do
+          Saml::Kit::Configuration.new do |config|
+            config.generate_key_pair_for(use: :signing)
+          end
+        end
+
         let(:response) { instance_double(response_type, destination: destination, to_xml: xml, query_string_parameter: query_string_parameter) }
 
         def to_query_params(url)
@@ -54,7 +61,8 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
           payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
           payload << "&RelayState=#{query_params['RelayState']}"
           payload << "&SigAlg=#{query_params['SigAlg']}"
-          expected_signature = Base64.strict_encode64(Saml::Kit.configuration.signing_private_key.sign(OpenSSL::Digest::SHA256.new, payload))
+          private_key = configuration.private_keys(use: :signing).sample
+          expected_signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
           expect(query_params['Signature']).to eql(expected_signature)
         end
 
@@ -65,7 +73,8 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
 
           payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
           payload << "&SigAlg=#{query_params['SigAlg']}"
-          expected_signature = Base64.strict_encode64(Saml::Kit.configuration.signing_private_key.sign(OpenSSL::Digest::SHA256.new, payload))
+          private_key = configuration.private_keys(use: :signing).sample
+          expected_signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
           expect(query_params['Signature']).to eql(expected_signature)
         end
       end
spec/saml/builders/identity_provider_metadata_spec.rb
@@ -1,7 +1,13 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::IdentityProviderMetadata do
-  subject { described_class.new }
+  subject { described_class.new(configuration: configuration) }
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.generate_key_pair_for(use: :signing)
+      config.generate_key_pair_for(use: :encryption)
+    end
+  end
   let(:email) { FFaker::Internet.email }
   let(:org_name) { FFaker::Movie.title }
   let(:url) { FFaker::Internet.uri("https") }
@@ -38,10 +44,8 @@ RSpec.describe Saml::Kit::Builders::IdentityProviderMetadata do
     expect(result['EntityDescriptor']['IDPSSODescriptor']['SingleLogoutService']['Location']).to eql("https://www.example.com/logout")
     expect(result['EntityDescriptor']['IDPSSODescriptor']['Attribute']['Name']).to eql("id")
     certificates = result['EntityDescriptor']['IDPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }
-    expect(certificates).to match_array([
-      Saml::Kit.configuration.signing_certificate.stripped,
-      Saml::Kit.configuration.encryption_certificate.stripped,
-    ])
+    expected_certificates = configuration.certificates.map(&:stripped)
+    expect(certificates).to match_array(expected_certificates)
     expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
     expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
     expect(result['EntityDescriptor']['Organization']['OrganizationURL']).to eql(url)
spec/saml/builders/logout_request_spec.rb
@@ -1,9 +1,14 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::LogoutRequest do
-  subject { described_class.new(user) }
+  subject { described_class.new(user, configuration: configuration) }
   let(:user) { double(:user, name_id_for: name_id) }
   let(:name_id) { SecureRandom.uuid }
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.generate_key_pair_for(use: :signing)
+    end
+  end
 
   it 'produces the expected xml' do
     travel_to 1.second.from_now
spec/saml/builders/response_spec.rb
@@ -1,26 +1,28 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::Response do
-  subject { described_class.new(user, request) }
+  subject { described_class.new(user, request, configuration: configuration) }
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.issuer = issuer
+      config.generate_key_pair_for(use: :signing)
+      config.generate_key_pair_for(use: :encryption)
+    end
+  end
   let(:email) { FFaker::Internet.email }
   let(:assertion_consumer_service_url) { FFaker::Internet.uri("https") }
   let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { email: email, created_at: Time.now.utc.iso8601 }) }
   let(:request) { double(:request, id: Saml::Kit::Id.generate, assertion_consumer_service_url: assertion_consumer_service_url, issuer: issuer, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, trusted?: true, signed?: true) }
-  let(:provider) { double(want_assertions_signed: false, encryption_certificates: [Saml::Kit::Certificate.new(encryption_pem, use: :encryption)]) }
-  let(:encryption_pem) { Saml::Kit.configuration.encryption_certificate.stripped }
+  let(:provider) { double(:provider, want_assertions_signed: false, encryption_certificates: [configuration.certificates(use: :encryption).last] ) }
   let(:issuer) { FFaker::Internet.uri("https") }
 
-  before :each do
-    allow(Saml::Kit.configuration).to receive(:issuer).and_return(issuer)
-  end
-
   describe "#build" do
     it 'builds a response with the request_id' do
       expect(subject.build.request_id).to eql(request.id)
     end
 
     it 'builds a valid encrypted assertion' do
-      allow(Saml::Kit.configuration.registry).to receive(:metadata_for).with(issuer).and_return(provider)
+      allow(configuration.registry).to receive(:metadata_for).with(issuer).and_return(provider)
       allow(provider).to receive(:matches?).and_return(true)
 
       subject.sign = true
@@ -34,6 +36,8 @@ RSpec.describe Saml::Kit::Builders::Response do
     it 'returns a proper response for the user' do
       travel_to 1.second.from_now
       allow(Saml::Kit.configuration).to receive(:issuer).and_return(issuer)
+      subject.destination = assertion_consumer_service_url
+      subject.encrypt = false
       hash = Hash.from_xml(subject.to_xml)
 
       expect(hash['Response']['ID']).to be_present
@@ -81,6 +85,7 @@ RSpec.describe Saml::Kit::Builders::Response do
       metadata = builder.build
       allow(request).to receive(:provider).and_return(metadata)
 
+      subject = described_class.new(user, request)
       hash = Hash.from_xml(subject.to_xml)
       expect(hash['Response']['Signature']).to be_nil
     end
@@ -90,7 +95,7 @@ RSpec.describe Saml::Kit::Builders::Response do
       result = Hash.from_xml(subject.to_xml)
       expect(result['Response']['EncryptedAssertion']).to be_present
       encrypted_assertion = result['Response']['EncryptedAssertion']
-      decrypted_assertion = Saml::Kit::XmlDecryption.new.decrypt(encrypted_assertion)
+      decrypted_assertion = Saml::Kit::XmlDecryption.new(configuration: configuration).decrypt(encrypted_assertion)
       decrypted_hash = Hash.from_xml(decrypted_assertion)
       expect(decrypted_hash['Assertion']).to be_present
       expect(decrypted_hash['Assertion']['Issuer']).to be_present
@@ -120,29 +125,4 @@ RSpec.describe Saml::Kit::Builders::Response do
       expect(result['Response']['Assertion']['Signature']).to be_present
     end
   end
-
-  describe "#destination" do
-    let(:assertion_consumer_service_url) { "https://#{FFaker::Internet.domain_name}/acs" }
-    let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: []) }
-    subject { described_class.new(user, request).build }
-
-    describe "when the request is signed and trusted" do
-      let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid, assertion_consumer_service_url: assertion_consumer_service_url, issuer: FFaker::Movie.title, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: nil, signed?: true, trusted?: true) }
-
-      it 'returns the ACS embedded in the request' do
-        expect(subject.destination).to eql(assertion_consumer_service_url)
-      end
-    end
-
-    describe "when the request is not trusted" do
-      let(:registered_acs_url) { FFaker::Internet.uri("https") }
-      let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid, assertion_consumer_service_url: assertion_consumer_service_url, issuer: FFaker::Movie.title, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, signed?: true, trusted?: false) }
-      let(:provider) { instance_double(Saml::Kit::ServiceProviderMetadata, want_assertions_signed: false) }
-
-      it 'returns the registered ACS embedded in the metadata' do
-        allow(provider).to receive(:assertion_consumer_service_for).and_return(double(location: registered_acs_url))
-        expect(subject.destination).to eql(registered_acs_url)
-      end
-    end
-  end
 end
spec/saml/builders/service_provider_metadata_spec.rb
@@ -1,6 +1,13 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
+  subject { described_class.new(configuration: configuration) }
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.generate_key_pair_for(use: :signing)
+      config.generate_key_pair_for(use: :encryption)
+    end
+  end
   let(:assertion_consumer_service_url) { FFaker::Internet.http_url }
   let(:email) { FFaker::Internet.email }
   let(:org_name) { FFaker::Movie.title }
@@ -37,10 +44,8 @@ RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
     expect(result['EntityDescriptor']['SPSSODescriptor']['AssertionConsumerService']['index']).to eql('0')
     expect(result['EntityDescriptor']['Signature']).to be_present
     expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['use'] }).to match_array(['signing', 'encryption'])
-    expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }).to match_array([
-      Saml::Kit.configuration.signing_certificate.stripped,
-      Saml::Kit.configuration.encryption_certificate.stripped,
-    ])
+    expected_certificates = configuration.certificates.map(&:stripped)
+    expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }).to match_array(expected_certificates)
     expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
     expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
     expect(result['EntityDescriptor']['Organization']['OrganizationURL']).to eql(url)
spec/saml/authentication_request_spec.rb
@@ -1,14 +1,14 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::AuthenticationRequest do
-  subject { described_class.new(raw_xml) }
+  subject { described_class.new(raw_xml, configuration: configuration) }
   let(:id) { Saml::Kit::Id.generate }
   let(:assertion_consumer_service_url) { "https://#{FFaker::Internet.domain_name}/acs" }
   let(:issuer) { FFaker::Movie.title }
   let(:destination) { FFaker::Internet.http_url }
   let(:name_id_format) { Saml::Kit::Namespaces::EMAIL_ADDRESS }
   let(:raw_xml) do
-    described_class.build do |builder|
+    described_class.build(configuration: configuration) do |builder|
       builder.id = id
       builder.now = Time.now.utc
       builder.issuer = issuer
@@ -17,6 +17,11 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
       builder.destination = destination
     end.to_xml
   end
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.generate_key_pair_for(use: :signing)
+    end
+  end
 
   it { expect(subject.issuer).to eql(issuer) }
   it { expect(subject.id).to eql(id) }
@@ -26,16 +31,16 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
 
   describe "#valid?" do
     let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
-    let(:metadata) { instance_double(Saml::Kit::ServiceProviderMetadata) }
+    let(:metadata) { Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) }
 
     before :each do
-      allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
+      allow(configuration).to receive(:registry).and_return(registry)
       allow(registry).to receive(:metadata_for).and_return(metadata)
-      allow(metadata).to receive(:matches?).and_return(true)
+      #allow(metadata).to receive(:matches?).and_return(true)
     end
 
     it 'is valid when left untampered' do
-      subject = described_class.new(raw_xml)
+      subject = described_class.new(raw_xml, configuration: configuration)
       expect(subject).to be_valid
     end
 
@@ -78,7 +83,9 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
 
     it 'validates the schema of the request' do
       id = Saml::Kit::Id.generate
-      signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+      configuration = Saml::Kit::Configuration.new
+      configuration.generate_key_pair_for(use: :signing)
+      signed_xml = Saml::Kit::Signature.sign(configuration: configuration) do |xml, signature|
         xml.tag!('samlp:AuthnRequest', "xmlns:samlp" => Saml::Kit::Namespaces::PROTOCOL, AssertionConsumerServiceURL: assertion_consumer_service_url, ID: id) do
           signature.template(id)
           xml.Fake do
@@ -98,7 +105,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
 </samlp:AuthnRequest>
       XML
 
-      subject = described_class.new(raw_xml)
+      subject = described_class.new(raw_xml, configuration: configuration)
       subject.signature_verified!
       expect(subject).to be_valid
     end
spec/saml/certificate_spec.rb
@@ -1,7 +1,12 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::Certificate do
-  subject { Saml::Kit.configuration.signing_certificate }
+  #subject { Saml::Kit.configuration.certificates(use: :signing).last }
+  subject { described_class.new(certificate, use: :signing) }
+  let(:certificate) do
+    cert, _ = Saml::Kit::SelfSignedCertificate.new('password').create
+    cert
+  end
 
   describe "#fingerprint" do
     it 'returns a fingerprint' do
spec/saml/composite_metadata_spec.rb.orig
@@ -1,187 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe Saml::Kit::CompositeMetadata do
-  subject { described_class.new(xml) }
-  let(:post_binding) { Saml::Kit::Bindings::HTTP_POST  }
-  let(:redirect_binding) { Saml::Kit::Bindings::HTTP_REDIRECT }
-  let(:sign_on_service) { FFaker::Internet.uri("https") }
-  let(:assertion_consumer_service) { FFaker::Internet.uri("https") }
-  let(:sp_logout_service) { FFaker::Internet.uri("https") }
-  let(:idp_logout_service) { FFaker::Internet.uri("https") }
-  let(:entity_id) { FFaker::Internet.uri("https") }
-  let(:xml) do
-    <<-XML
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
-  <SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
-    <KeyDescriptor use="signing">
-      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
-        <X509Data>
-          <X509Certificate>SP-Signing-Certificate</X509Certificate>
-        </X509Data>
-      </KeyInfo>
-    </KeyDescriptor>
-    <KeyDescriptor use="encryption">
-      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
-        <X509Data>
-          <X509Certificate>SP-Encryption-Certificate</X509Certificate>
-        </X509Data>
-      </KeyInfo>
-    </KeyDescriptor>
-    <SingleLogoutService Binding="#{post_binding}" Location="#{sp_logout_service}"/>
-    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
-    <AssertionConsumerService Binding="#{post_binding}" Location="#{assertion_consumer_service}" index="0" isDefault="true"/>
-  </SPSSODescriptor>
-  <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
-    <KeyDescriptor use="signing">
-      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
-        <X509Data>
-          <X509Certificate>IDP-Signing-Certificate</X509Certificate>
-        </X509Data>
-      </KeyInfo>
-    </KeyDescriptor>
-    <KeyDescriptor use="encryption">
-      <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
-        <X509Data>
-          <X509Certificate>IDP-Encryption-Certificate</X509Certificate>
-        </X509Data>
-      </KeyInfo>
-    </KeyDescriptor>
-    <SingleLogoutService Binding="#{post_binding}" Location="#{idp_logout_service}"/>
-    <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
-    <SingleSignOnService Binding="#{post_binding}" Location="#{sign_on_service}"/>
-    <SingleSignOnService Binding="#{redirect_binding}" Location="#{sign_on_service}"/>
-    <Attribute xmlns="#{Saml::Kit::Namespaces::ASSERTION}" Name="id" ></Attribute>
-  </IDPSSODescriptor>
-  <Organization>
-    <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
-    <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
-    <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
-  </Organization>
-  <ContactPerson contactType="technical">
-    <Company>mailto:hi@example.com</Company>
-  </ContactPerson>
-</EntityDescriptor>
-    XML
-  end
-
-  describe "#single_sign_on_services" do
-    it 'returns the single sign on services from the idp' do
-      expect(subject.single_sign_on_services).to match_array([
-        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
-        Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
-      ])
-    end
-  end
-
-  describe "#single_sign_on_service_for" do
-<<<<<<< HEAD
-    it 'returns the http post binding' do
-=======
-    it 'returns the post binding' do
->>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
-      expect(subject.single_sign_on_service_for(binding: :http_post)).to eql(
-        Saml::Kit::Bindings::HttpPost.new(location: sign_on_service)
-      )
-    end
-<<<<<<< HEAD
-
-    it 'returns the http redirect binding' do
-      expect(subject.single_sign_on_service_for(binding: :http_redirect)).to eql(
-        Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service)
-      )
-    end
-  end
-
-  describe "#assertion_consumer_services" do
-=======
-  end
-
-  it { expect(subject.want_authn_requests_signed).to be_truthy }
-  it { expect(subject.attributes).to match_array([name: 'id', format: nil]) }
-  it { expect(subject.login_request_for(binding: :http_post)).to be_present }
-  it do
-    expect(subject.assertion_consumer_services).to match_array([
-      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
-    ])
-  end
-  it do
-    expect(subject.assertion_consumer_service_for(binding: :http_post)).to eql(
-      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
-    )
-  end
-  it { expect(subject.want_assertions_signed).to be_truthy }
-  it { expect(subject.entity_id).to eql(entity_id) }
-  it { expect(subject.name_id_formats).to match_array([Saml::Kit::Namespaces::PERSISTENT]) }
-  it do
-    expect(subject.certificates).to match_array([
-      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
-      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
-      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
-      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
-    ])
-  end
-
-  it do
-    expect(subject.encryption_certificates).to match_array([
-      Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
-      Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
-    ])
-  end
-  it do
-    expect(subject.signing_certificates).to match_array([
-      Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
-      Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
-    ])
-  end
-  it do
-    expect(subject.services('SingleLogoutService')).to match_array([
-      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
-      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
-    ])
-  end
-  it do
-    expect(subject.service_for(type: 'SingleLogoutService', binding: :http_post)).to eql(
-      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
-    )
-  end
-  it do
-    expect(subject.services('AssertionConsumerService')).to match_array([
-      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
-    ])
-  end
-  it do
-    expect(subject.service_for(type: 'AssertionConsumerService', binding: :http_post)).to eql(
-      Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
-    )
-  end
-  it do
-    expect(subject.services('SingleSignOnService')).to match_array([
-      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
-      Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
-    ])
-  end
-  it do
-    expect(subject.service_for(type: 'SingleSignOnService', binding: :http_post)).to eql(
-      Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
-    )
-  end
-
-  it do
-    expect(subject.single_logout_services).to match_array([
-      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
-      Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
-    ])
-  end
-
-  it do
-    expect(subject.single_logout_service_for(binding: :http_post)).to eql(
-      Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
-    )
-  end
-
-  it do
-    user = double(:user, name_id_for: SecureRandom.uuid)
-    expect(subject.logout_request_for(user)).to be_present
->>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
-  end
-end
spec/saml/http_post_spec.rb
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe Saml::Kit::Bindings::HttpPost do
-  describe "equality" do
-    let(:location) { FFaker::Internet.uri("https") }
-    subject { Saml::Kit::Bindings::HttpPost.new(location: location) }
-
-    it 'is referentially equal' do
-      expect(subject).to eql(subject)
-    end
-
-    it 'is equal by value' do
-      expect(subject).to eql(
-        Saml::Kit::Bindings::HttpPost.new(location: location)
-      )
-    end
-
-    it 'is not equal' do
-      expect(subject).to_not eql(
-        Saml::Kit::Bindings::HttpPost.new(
-          location: FFaker::Internet.uri("https")
-        )
-      )
-    end
-  end
-end
spec/saml/logout_request_spec.rb
@@ -1,13 +1,21 @@
 require 'spec_helper'
 
 RSpec.describe Saml::Kit::LogoutRequest do
-  subject { described_class.build(user) }
+  subject { described_class.build(user, configuration: configuration) }
   let(:user) { double(:user, name_id_for: name_id) }
   let(:name_id) { SecureRandom.uuid }
+  let(:issuer) { FFaker::Internet.uri("https") }
+  let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
+  let(:configuration) do
+    Saml::Kit::Configuration.new do |config|
+      config.issuer = issuer
+      config.registry = registry
+      config.generate_key_pair_for(use: :signing)
+    end
+  end
 
   it 'parses the issuer' do
-    issuer = FFaker::Internet.uri("https")
-    subject = described_class.build(user) do |builder|
+    subject = described_class.build(user, configuration: configuration) do |builder|
       builder.issuer = issuer
     end
     expect(subject.issuer).to eql(issuer)
@@ -24,7 +32,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
 
   it 'parses the destination' do
     destination = FFaker::Internet.uri("https")
-    subject = described_class.build(user) do |builder|
+    subject = described_class.build(user, configuration: configuration) do |builder|
       builder.destination = destination
     end
     expect(subject.destination).to eql(destination)
@@ -35,16 +43,15 @@ RSpec.describe Saml::Kit::LogoutRequest do
   end
 
   describe "#valid?" do
-    let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
-    let(:metadata) { instance_double(Saml::Kit::ServiceProviderMetadata) }
+    let(:metadata) do
+      Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) do |builder|
+        builder.entity_id = issuer
+        builder.add_single_logout_service(FFaker::Internet.uri("https"), binding: :http_post)
+      end
+    end
 
     before :each do
-      allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
       allow(registry).to receive(:metadata_for).and_return(metadata)
-      allow(metadata).to receive(:matches?).and_return(true)
-      allow(metadata).to receive(:single_logout_services).and_return([
-        Saml::Kit::Bindings::HttpPost.new(location: FFaker::Internet.http_url)
-      ])
     end
 
     it 'is valid when left untampered' do
@@ -53,7 +60,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
 
     it 'is invalid if the document has been tampered with' do
       issuer = FFaker::Internet.uri("https")
-      raw_xml = described_class.build(user) do |builder|
+      raw_xml = described_class.build(user, configuration: configuration) do |builder|
         builder.issuer = issuer
       end.to_xml.gsub(issuer, 'corrupt')
 
@@ -92,7 +99,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
       expect(subject.errors[:single_logout_service]).to be_present
     end
 
-    it 'is valid when a single lgout service url is available via the registry' do
+    it 'is valid when a single logout service url is available via the registry' do
       issuer = FFaker::Internet.uri("https")
       allow(registry).to receive(:metadata_for).with(issuer).and_return(metadata)
       allow(metadata).to receive(:matches?).and_return(true)
@@ -100,7 +107,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
         Saml::Kit::Bindings::HttpPost.new(location: FFaker::Internet.uri("https"))
       ])
 
-      subject = described_class.build(user) do |builder|
+      subject = described_class.build(user, configuration: configuration) do |builder|
         builder.issuer = issuer
       end
       expect(subject).to be_valid
@@ -108,7 +115,9 @@ RSpec.describe Saml::Kit::LogoutRequest do
 
     it 'validates the schema of the request' do
       id = Saml::Kit::Id.generate
-      signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+      configuration = Saml::Kit::Configuration.new
+      configuration.generate_key_pair_for(use: :signing)
+      signed_xml = Saml::Kit::Signature.sign(configuration: configuration) do |xml, signature|
         xml.LogoutRequest ID: id do
           signature.template(id)
           xml.Fake do
spec/saml/response_spec.rb
@@ -6,11 +6,13 @@ RSpec.describe Saml::Kit::Response do
     let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { id: SecureRandom.uuid }) }
     let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
     let(:metadata) { instance_double(Saml::Kit::IdentityProviderMetadata) }
-    subject { described_class.build(user, request) }
-
-    before :each do
-      allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
-      allow(Saml::Kit.configuration).to receive(:issuer).and_return(request.issuer)
+    subject { described_class.build(user, request, configuration: configuration) }
+    let(:configuration) do
+      Saml::Kit::Configuration.new do |config|
+        config.issuer = request.issuer
+        config.registry = registry
+        config.generate_key_pair_for(use: :signing)
+      end
     end
 
     it 'is valid' do
@@ -55,7 +57,9 @@ RSpec.describe Saml::Kit::Response do
       allow(registry).to receive(:metadata_for).and_return(metadata)
       allow(metadata).to receive(:matches?).and_return(true)
       id = Saml::Kit::Id.generate
-      signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+      configuration = Saml::Kit::Configuration.new
+      configuration.generate_key_pair_for(use: :signing)
+      signed_xml = Saml::Kit::Signature.sign(configuration: configuration) do |xml, signature|
         xml.tag! "samlp:Response", "xmlns:samlp" => Saml::Kit::Namespaces::PROTOCOL, ID: id do
           signature.template(id)
           xml.Fake do
@@ -132,7 +136,7 @@ RSpec.describe Saml::Kit::Response do
       allow(registry).to receive(:metadata_for).and_return(metadata)
       allow(metadata).to receive(:matches?).and_return(true)
 
-      allow(Saml::Kit.configuration).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
+      allow(configuration).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
       allow(request).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
 
       expect(subject).to be_invalid
@@ -362,7 +366,7 @@ XML
       public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
       private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
 
-      allow(Saml::Kit.configuration).to receive(:encryption_private_key).and_return(private_key)
+      allow(Saml::Kit.configuration).to receive(:private_keys).with(use: :encryption).and_return([private_key])
 
       cipher = OpenSSL::Cipher.new('AES-128-CBC')
       cipher.encrypt
@@ -396,8 +400,10 @@ XML
 XML
 
       subject = described_class.new(xml)
-      expect(subject).to be_encrypted
-      expect(subject.attributes).to be_present
+      expect(subject.attributes).to match_array([
+        ["created_at", "2017-11-23T04:33:58Z"],
+        ["email", "sidney_bayer@nienowemmerich.com"]
+      ])
     end
   end
 
spec/saml/service_provider_metadata_spec.rb
@@ -19,10 +19,10 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
     end
 
     it 'returns each of the certificates' do
-      expect(subject.certificates).to match_array([
-        Saml::Kit::Certificate.new(Saml::Kit.configuration.signing_certificate.stripped, use: :signing),
-        Saml::Kit::Certificate.new(Saml::Kit.configuration.encryption_certificate.stripped, use: :encryption),
-      ])
+      expected_certificates = Saml::Kit.configuration.certificates.map do |x|
+        Saml::Kit::Certificate.new(x.stripped, use: x.use)
+      end
+      expect(subject.certificates).to match_array(expected_certificates)
     end
 
     it 'returns each acs url and binding' do
@@ -52,7 +52,7 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
 
   describe "#validate" do
     let(:service_provider_metadata) do
-      described_class.build do |builder|
+      described_class.build(configuration: configuration) do |builder|
         builder.entity_id = entity_id
         builder.add_assertion_consumer_service(acs_post_url, binding: :http_post)
         builder.add_assertion_consumer_service(acs_redirect_url, binding: :http_redirect)
@@ -60,6 +60,11 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
         builder.add_single_logout_service(logout_redirect_url, binding: :http_redirect)
       end.to_xml
     end
+    let(:configuration) do
+      Saml::Kit::Configuration.new do |config|
+        config.generate_key_pair_for(use: :signing)
+      end
+    end
 
     it 'valid when given valid service provider metadata' do
       expect(described_class.new(service_provider_metadata)).to be_valid
@@ -113,7 +118,12 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
   end
 
   describe "#matches?" do
-    subject { Saml::Kit::ServiceProviderMetadata.build }
+    let(:configuration) do
+      config = Saml::Kit::Configuration.new
+      config.generate_key_pair_for(use: :signing)
+      config
+    end
+    subject { Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) }
 
     it 'returns true when the fingerprint matches one of the signing certificates' do
       certificate = Hash.from_xml(subject.to_xml)['EntityDescriptor']['Signature']['KeyInfo']['X509Data']['X509Certificate']
spec/saml/signature_spec.rb
@@ -3,9 +3,7 @@ require "spec_helper"
 RSpec.describe Saml::Kit::Signature do
   let(:configuration) do
     config = Saml::Kit::Configuration.new
-    config.signing_certificate_pem = certificate
-    config.signing_private_key_pem = private_key
-    config.signing_private_key_password = password
+    config.add_key_pair(certificate, private_key, password: password, use: :signing)
     config
   end
 
@@ -20,14 +18,10 @@ RSpec.describe Saml::Kit::Signature do
     x.public_key = public_key
     x.serial = 0x0
     x.version = 2
-    factory = OpenSSL::X509::ExtensionFactory.new
-    factory.subject_certificate = factory.issuer_certificate = x
-    x.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
-    x.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
     x.sign(rsa_key, OpenSSL::Digest::SHA256.new)
     x.to_pem
   end
-  let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), password) }
+  let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher.new('des3'), password) }
   let(:password) { "password" }
 
   it 'generates a signature' do
@@ -36,7 +30,7 @@ RSpec.describe Saml::Kit::Signature do
       "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
       ID: reference_id,
     }
-    signed_xml = described_class.sign(sign: true, configuration: configuration) do |xml, signature|
+    signed_xml = described_class.sign(configuration: configuration) do |xml, signature|
       xml.tag!('samlp:AuthnRequest', options) do
         signature.template(reference_id)
         xml.tag!('saml:Issuer', "MyEntityID")
@@ -63,7 +57,7 @@ RSpec.describe Saml::Kit::Signature do
   end
 
   it 'does not add a signature' do
-    signed_xml = described_class.sign(sign: false, configuration: configuration) do |xml, signature|
+    signed_xml = described_class.sign(configuration: Saml::Kit::Configuration.new) do |xml, signature|
       xml.AuthnRequest do
         signature.template(reference_id)
         xml.Issuer "MyEntityID"
spec/saml/xml_decryption_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Saml::Kit::XmlDecryption do
           }
         }
       }
-      subject = described_class.new(private_key)
+      subject = described_class.new(configuration: double(private_keys: [private_key]))
       decrypted = subject.decrypt(data)
       expect(decrypted.strip).to eql(secret)
     end
spec/saml/xml_spec.rb
@@ -4,9 +4,14 @@ 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(:configuration) do
+      configuration = Saml::Kit::Configuration.new
+      configuration.generate_key_pair_for(use: :signing)
+      configuration
+    end
 
     let(:signed_xml) do
-      Saml::Kit::ServiceProviderMetadata.build do |builder|
+      Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) do |builder|
         builder.entity_id = FFaker::Movie.title
         builder.add_assertion_consumer_service(login_url, binding: :http_post)
         builder.add_assertion_consumer_service(login_url, binding: :http_redirect)
.rspec
@@ -1,2 +1,2 @@
---format documentation
 --color
+--require spec_helper