Comparing changes

v0.2.5 v0.2.6
8 commits 10 files changed

Commits

dfaa712 bump to 0.2.6. mo 2017-12-16 17:04:09
9f44e30 extract keypair class. mo 2017-12-16 16:44:56
3091bbf extract key pair class. mo 2017-12-16 16:31:09
59218ca fix missing translation. mo 2017-12-15 23:04:42
73be9f0 bubble up decryption errors. mo 2017-12-15 22:48:11
5141c29 use unix timestamp for version. mo 2017-12-15 21:17:14
lib/saml/kit/builders/templates/assertion.builder
@@ -1,7 +1,7 @@
 encryption_for(xml: xml) do |xml|
   xml.Assertion(assertion_options) do
     xml.Issuer issuer
-    signature_for(reference_id: reference_id, xml: xml) unless encrypt
+    signature_for(reference_id: reference_id, xml: xml)
     xml.Subject do
       xml.NameID name_id, Format: name_id_format
       xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
lib/saml/kit/locales/en.yml
@@ -4,6 +4,7 @@ en:
     errors:
       Assertion:
         expired: "must not be expired."
+        must_match_issuer: "must match entityId."
       AuthnRequest:
         invalid: "must contain AuthnRequest."
         invalid_fingerprint: "does not match."
@@ -23,7 +24,6 @@ en:
         invalid_fingerprint: "does not match."
         invalid_response_to: "must match request id."
         invalid_version: "must be 2.0."
-        must_match_issuer: "must match entityId."
         unregistered: "must originate from registered identity provider."
       SPSSODescriptor:
         invalid: "must contain SPSSODescriptor."
lib/saml/kit/assertion.rb
@@ -63,12 +63,12 @@ module Saml
         []
       end
 
-      private
-
       def encrypted?
         @xml_hash.fetch('Response', {}).fetch('EncryptedAssertion', nil).present?
       end
 
+      private
+
       def assertion
         if encrypted?
           decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
lib/saml/kit/configuration.rb
@@ -12,14 +12,12 @@ module Saml
         @registry = DefaultRegistry.new
         @session_timeout = 3.hours
         @logger = Logger.new(STDOUT)
+        @key_pairs = []
         yield self if block_given?
       end
 
       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)
-        })
+        @key_pairs.push(KeyPair.new(certificate, private_key, password, use))
       end
 
       def generate_key_pair_for(use:, password: SecureRandom.uuid)
@@ -27,13 +25,16 @@ module Saml
         add_key_pair(certificate, private_key, password: password, use: use)
       end
 
+      def key_pairs(use: nil)
+        use.present? ? @key_pairs.find_all { |x| x.for?(use) } : @key_pairs
+      end
+
       def certificates(use: nil)
-        certificates = key_pairs.map { |x| x[:certificate] }
-        use.present? ? certificates.find_all { |x| x.for?(use) } : certificates
+        key_pairs(use: use).flat_map(&:certificate)
       end
 
       def private_keys(use: :signing)
-        key_pairs.find_all { |x| x[:certificate].for?(use) }.map { |x| x[:private_key] }
+        key_pairs(use: use).flat_map(&:private_key)
       end
 
       def encryption_certificate
@@ -54,12 +55,6 @@ module Saml
       def sign?
         certificates(use: :signing).any?
       end
-
-      private
-
-      def key_pairs
-        @key_pairs ||= []
-      end
     end
   end
 end
lib/saml/kit/key_pair.rb
@@ -0,0 +1,17 @@
+module Saml
+  module Kit
+    class KeyPair
+      attr_reader :certificate, :private_key
+
+      def initialize(certificate, private_key, password, use)
+        @use = use
+        @certificate = Saml::Kit::Certificate.new(certificate, use: use)
+        @private_key = OpenSSL::PKey::RSA.new(private_key, password)
+      end
+
+      def for?(use)
+        @use == use
+      end
+    end
+  end
+end
lib/saml/kit/templatable.rb
@@ -24,7 +24,8 @@ module Saml
         if encrypt?
           temp = ::Builder::XmlMarkup.new
           yield temp
-          xml_encryption = Saml::Kit::Builders::XmlEncryption.new(temp.target!, encryption_certificate.public_key)
+          signed_xml = signatures.complete(temp.target!)
+          xml_encryption = Saml::Kit::Builders::XmlEncryption.new(signed_xml, encryption_certificate.public_key)
           render(xml_encryption, xml: xml)
         else
           yield xml
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
 module Saml
   module Kit
-    VERSION = "0.2.5"
+    VERSION = "0.2.6"
   end
 end
lib/saml/kit.rb
@@ -35,6 +35,7 @@ require "saml/kit/configuration"
 require "saml/kit/crypto"
 require "saml/kit/default_registry"
 require "saml/kit/fingerprint"
+require "saml/kit/key_pair"
 require "saml/kit/logout_response"
 require "saml/kit/logout_request"
 require "saml/kit/metadata"
spec/saml/builders/response_spec.rb
@@ -124,5 +124,25 @@ RSpec.describe Saml::Kit::Builders::Response do
       expect(result['Response']['Signature']).to be_present
       expect(result['Response']['Assertion']['Signature']).to be_present
     end
+
+    it 'generates a signed response and signed and encrypted assertion' do
+      subject.encrypt = true
+      subject.sign = true
+
+      result = Saml::Kit::Response.new(subject.to_xml, configuration: configuration)
+      expect(result).to be_signed
+      expect(result.assertion).to be_signed
+      expect(result.assertion).to be_encrypted
+    end
+
+    it 'generates an encrypted assertion' do
+      subject.encrypt = true
+      subject.sign = false
+
+      result = Saml::Kit::Response.new(subject.to_xml, configuration: configuration)
+      expect(result).to_not be_signed
+      expect(result.assertion).to_not be_signed
+      expect(result.assertion).to be_encrypted
+    end
   end
 end
spec/saml/xml_decryption_spec.rb
@@ -21,13 +21,13 @@ RSpec.describe Saml::Kit::XmlDecryption do
           "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
           "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
           "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
-          "EncryptionMethod"=> { 
+          "EncryptionMethod"=> {
             "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
           },
           "KeyInfo"=> {
             "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
             "EncryptedKey"=> {
-              "EncryptionMethod"=>{ 
+              "EncryptionMethod"=>{
                 "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
               },
               "CipherData"=>{
@@ -44,5 +44,49 @@ RSpec.describe Saml::Kit::XmlDecryption do
       decrypted = subject.decrypt(data)
       expect(decrypted.strip).to eql(secret)
     end
+
+    it 'raise an error when it cannot decrypt the data' do
+      certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
+      public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
+      private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
+
+      cipher = OpenSSL::Cipher.new('AES-128-CBC')
+      cipher.encrypt
+      key = cipher.random_key
+      iv = cipher.random_iv
+      encrypted = cipher.update(secret) + cipher.final
+
+      data = {
+        "EncryptedData"=> {
+          "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
+          "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+          "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
+          "EncryptionMethod"=> {
+            "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
+          },
+          "KeyInfo"=> {
+            "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+            "EncryptedKey"=> {
+              "EncryptionMethod"=>{
+                "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+              },
+              "CipherData"=>{
+                "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
+              }
+            }
+          },
+          "CipherData"=>{
+            "CipherValue"=> Base64.encode64(iv + encrypted)
+          }
+        }
+      }
+
+      new_private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create[1]
+      new_private_key = OpenSSL::PKey::RSA.new(new_private_key_pem, password)
+      subject = described_class.new(configuration: double(private_keys: [new_private_key]))
+      expect do
+        subject.decrypt(data)
+      end.to raise_error(OpenSSL::PKey::RSAError)
+    end
   end
 end