Commit 03c28fe

mo <mo@mokhan.ca>
2017-12-21 16:23:39
provide api to choose which keypair to use for signing a document.
1 parent dbf01ce
lib/saml/kit/builders/templates/xml_signature.builder
@@ -14,7 +14,7 @@ xml.Signature "xmlns" => Saml::Kit::Namespaces::XMLDSIG do
   xml.SignatureValue ""
   xml.KeyInfo do
     xml.X509Data do
-      xml.X509Certificate x509_certificate
+      xml.X509Certificate certificate.stripped
     end
   end
 end
lib/saml/kit/builders/xml_signature.rb
@@ -19,12 +19,12 @@ module Saml
 
         attr_reader :embed_signature, :configuration
         attr_reader :reference_id
-        attr_reader :x509_certificate
+        attr_reader :certificate
 
-        def initialize(reference_id, configuration:)
+        def initialize(reference_id, configuration:, certificate: )
           @configuration = configuration
           @reference_id = reference_id
-          @x509_certificate = configuration.certificates(use: :signing).last.stripped
+          @certificate = certificate
         end
 
         def signature_method
lib/saml/kit/authentication_request.rb
@@ -41,9 +41,10 @@ module Saml
       # Generate a Response for a specific user.
       # @param user [Object] this is a custom user object that can be used for generating a nameid and assertion attributes.
       # @param binding [Symbol] the SAML binding to use `:http_post` or `:http_redirect`.
-      def response_for(user, binding:, relay_state: nil)
+      # @param configuration [Saml::Kit::Configuration] the configuration to use to build the response.
+      def response_for(user, binding:, relay_state: nil, configuration: Saml::Kit.configuration)
         response_binding = provider.assertion_consumer_service_for(binding: binding)
-        builder = Saml::Kit::Response.builder(user, self) do |x|
+        builder = Saml::Kit::Response.builder(user, self, configuration: configuration) do |x|
           x.embed_signature = provider.want_assertions_signed
           yield x if block_given?
         end
lib/saml/kit/certificate.rb
@@ -35,7 +35,7 @@ module Saml
       end
 
       def ==(other)
-        self.to_s == other.to_s
+        self.fingerprint == other.fingerprint
       end
 
       def eql?(other)
@@ -51,7 +51,7 @@ module Saml
       end
 
       def to_h
-        { use: @use, x509: @value }
+        { use: @use, fingerprint: fingerprint.to_s }
       end
 
       def inspect
lib/saml/kit/key_pair.rb
@@ -3,15 +3,20 @@ module Saml
     class KeyPair # :nodoc:
       attr_reader :certificate, :private_key, :use
 
-      def initialize(certificate, private_key, password, use)
+      def initialize(certificate, private_key, passphrase, use)
         @use = use
         @certificate = Saml::Kit::Certificate.new(certificate, use: use)
-        @private_key = OpenSSL::PKey::RSA.new(private_key, password)
+        @private_key = OpenSSL::PKey::RSA.new(private_key, passphrase)
       end
 
       def for?(use)
         @use == use
       end
+
+      def self.generate(use:, passphrase: SecureRandom.uuid)
+        certificate, private_key = SelfSignedCertificate.new(passphrase).create
+        new(certificate, private_key, passphrase, use)
+      end
     end
   end
 end
lib/saml/kit/self_signed_certificate.rb
@@ -3,8 +3,8 @@ module Saml
     class SelfSignedCertificate
       SUBJECT="/C=CA/ST=Alberta/L=Calgary/O=SamlKit/OU=SamlKit/CN=SamlKit"
 
-      def initialize(password)
-        @password = password
+      def initialize(passphrase)
+        @passphrase = passphrase
       end
 
       def create
@@ -20,7 +20,7 @@ module Saml
         certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
         [
           certificate.to_pem,
-          rsa_key.to_pem(OpenSSL::Cipher.new('AES-256-CBC'), @password)
+          rsa_key.to_pem(OpenSSL::Cipher.new('AES-256-CBC'), @passphrase)
         ]
       end
     end
lib/saml/kit/signatures.rb
@@ -9,16 +9,22 @@ module Saml
         @configuration = configuration
       end
 
+      def sign_with(key_pair)
+        @certificate = key_pair.certificate
+        @private_key = key_pair.private_key
+      end
+
       # @!visibility private
       def build(reference_id)
         return nil unless configuration.sign?
-        Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
+        certificate = @certificate || configuration.certificates(use: :signing).last
+        Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, certificate: certificate)
       end
 
       # @!visibility private
       def complete(raw_xml)
         return raw_xml unless configuration.sign?
-        private_key = configuration.private_keys(use: :signing).last
+        private_key = @private_key || configuration.private_keys(use: :signing).last
         Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
       end
 
lib/saml/kit/templatable.rb
@@ -17,6 +17,10 @@ module Saml
         render(signatures.build(reference_id), xml: xml)
       end
 
+      def sign_with(key_pair)
+        signatures.sign_with(key_pair)
+      end
+
       def sign?
         embed_signature.nil? ? configuration.sign? : embed_signature && configuration.sign?
       end
spec/saml/builders/response_spec.rb
@@ -145,4 +145,26 @@ RSpec.describe Saml::Kit::Builders::Response do
       expect(result.assertion).to be_encrypted
     end
   end
+
+  describe ".build" do
+    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: :signing)
+        config.generate_key_pair_for(use: :signing)
+      end
+    end
+
+    it 'signs the response with a specific certificate' do
+      key_pair = configuration.key_pairs(use: :signing)[1]
+      subject.embed_signature = true
+      subject.sign_with(key_pair)
+
+      result = subject.build
+
+      expect(result).to be_signed
+      expect(result.signature.certificate).to eql(key_pair.certificate)
+    end
+  end
 end
spec/saml/authentication_request_spec.rb
@@ -189,5 +189,19 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
       response = provider.assertion_consumer_service_for(binding: :http_post).deserialize(saml_params)
       expect(response).to be_instance_of(Saml::Kit::Response)
     end
+
+    it 'serializes a response with the specified signing certificate' do
+      allow(subject).to receive(:provider).and_return(provider)
+      configuration = Saml::Kit::Configuration.new do |config|
+        config.generate_key_pair_for(use: :signing)
+      end
+      key_pair = configuration.key_pairs(use: :signing).first
+      url, saml_params = subject.response_for(user, binding: :http_post, configuration: configuration) do |builder|
+        builder.sign_with(key_pair)
+      end
+
+      response = provider.assertion_consumer_service_for(binding: :http_post).deserialize(saml_params)
+      expect(response).to be_instance_of(Saml::Kit::Response)
+    end
   end
 end
spec/saml/composite_metadata_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe Saml::Kit::CompositeMetadata do
   let(:sp_logout_service) { FFaker::Internet.uri("https") }
   let(:idp_logout_service) { FFaker::Internet.uri("https") }
   let(:entity_id) { FFaker::Internet.uri("https") }
+  let(:sp_signing_certificate) { Saml::Kit::KeyPair.generate(use: :signing).certificate }
+  let(:sp_encryption_certificate) { Saml::Kit::KeyPair.generate(use: :encryption).certificate }
+  let(:idp_signing_certificate) { Saml::Kit::KeyPair.generate(use: :signing).certificate }
+  let(:idp_encryption_certificate) { Saml::Kit::KeyPair.generate(use: :encryption).certificate }
   let(:xml) do
     <<-XML
 <EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
@@ -16,14 +20,14 @@ RSpec.describe Saml::Kit::CompositeMetadata do
     <KeyDescriptor use="signing">
       <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
         <X509Data>
-          <X509Certificate>SP-Signing-Certificate</X509Certificate>
+          <X509Certificate>#{sp_signing_certificate.stripped}</X509Certificate>
         </X509Data>
       </KeyInfo>
     </KeyDescriptor>
     <KeyDescriptor use="encryption">
       <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
         <X509Data>
-          <X509Certificate>SP-Encryption-Certificate</X509Certificate>
+          <X509Certificate>#{sp_encryption_certificate.stripped}</X509Certificate>
         </X509Data>
       </KeyInfo>
     </KeyDescriptor>
@@ -35,14 +39,14 @@ RSpec.describe Saml::Kit::CompositeMetadata do
     <KeyDescriptor use="signing">
       <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
         <X509Data>
-          <X509Certificate>IDP-Signing-Certificate</X509Certificate>
+          <X509Certificate>#{idp_signing_certificate.stripped}</X509Certificate>
         </X509Data>
       </KeyInfo>
     </KeyDescriptor>
     <KeyDescriptor use="encryption">
       <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
         <X509Data>
-          <X509Certificate>IDP-Encryption-Certificate</X509Certificate>
+          <X509Certificate>#{idp_encryption_certificate.stripped}</X509Certificate>
         </X509Data>
       </KeyInfo>
     </KeyDescriptor>
@@ -99,23 +103,23 @@ RSpec.describe Saml::Kit::CompositeMetadata do
   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),
+      sp_signing_certificate,
+      sp_encryption_certificate,
+      idp_signing_certificate,
+      idp_encryption_certificate,
     ])
   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),
+      sp_encryption_certificate,
+      idp_encryption_certificate,
     ])
   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),
+      sp_signing_certificate,
+      idp_signing_certificate,
     ])
   end
   it do