Commit 56d026d

mo <mo@mokhan.ca>
2017-11-22 03:02:43
parse the signature and certificate from a Response.
1 parent 69ac9d8
lib/saml/kit/default_registry.rb
@@ -6,6 +6,7 @@ module Saml
       end
 
       def register(metadata)
+        Saml::Kit.logger.debug(metadata.to_xml(pretty: true))
         @items[metadata.entity_id] = metadata
       end
 
lib/saml/kit/document.rb
@@ -48,8 +48,8 @@ module Saml
         @xml_hash
       end
 
-      def to_xml
-        content
+      def to_xml(pretty: false)
+        pretty ? Nokogiri::XML(content).to_xml(indent: 2) : content
       end
 
       def to_s
lib/saml/kit/metadata.rb
@@ -78,6 +78,7 @@ module Saml
         if :signing == use.to_sym
           hash_value = fingerprint.algorithm(hash_algorithm)
           signing_certificates.find do |signing_certificate|
+            Saml::Kit.logger.debug [hash_value, signing_certificate[:fingerprint]].inspect
             hash_value == signing_certificate[:fingerprint]
           end
         end
@@ -87,8 +88,8 @@ module Saml
         @xml_hash ||= Hash.from_xml(to_xml)
       end
 
-      def to_xml
-        @xml
+      def to_xml(pretty: false)
+        pretty ? Nokogiri::XML(@xml).to_xml(indent: 2) : @xml
       end
 
       def to_s
lib/saml/kit/response.rb
@@ -42,6 +42,14 @@ module Saml
         Time.current > started_at && !expired?
       end
 
+      def signed?
+        super || to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Signature', nil).present?
+      end
+
+      def certificate
+        super || to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
+      end
+
       private
 
       def must_be_active_session
@@ -77,7 +85,7 @@ module Saml
         attr_reader :user, :request
         attr_accessor :id, :reference_id, :now
         attr_accessor :version, :status_code
-        attr_accessor :issuer, :sign, :destination
+        attr_accessor :issuer, :sign, :destination, :encrypt
 
         def initialize(user, request)
           @user = user
@@ -90,6 +98,7 @@ module Saml
           @issuer = configuration.issuer
           @destination = destination_for(request)
           @sign = want_assertions_signed
+          @encrypt = false
         end
 
         def want_assertions_signed
spec/saml/response_spec.rb
@@ -242,6 +242,167 @@ RSpec.describe Saml::Kit::Response do
     end
   end
 
+  describe "#signed?" do
+    let(:now) { Time.now.utc }
+    let(:id) { SecureRandom.uuid }
+    let(:url) { FFaker::Internet.uri("https") }
+
+    it 'returns true when the Assertion is signed' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0">
+    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+      <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+        <ds:Reference URI="#_#{id}">
+          <ds:Transforms>
+            <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </ds:Transforms>
+          <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+          <ds:DigestValue></ds:DigestValue>
+        </ds:Reference>
+      </ds:SignedInfo>
+      <ds:SignatureValue></ds:SignatureValue>
+      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+        <ds:X509Data>
+          <ds:X509Certificate></ds:X509Certificate>
+        </ds:X509Data>
+      </KeyInfo>
+    </ds:Signature>
+  </Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject).to be_signed
+    end
+
+    it 'returns true when the Response is signed' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+    <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+      <ds:Reference URI="#_#{id}">
+        <ds:Transforms>
+          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        </ds:Transforms>
+        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+        <ds:DigestValue></ds:DigestValue>
+      </ds:Reference>
+    </ds:SignedInfo>
+    <ds:SignatureValue></ds:SignatureValue>
+    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <ds:X509Data>
+        <ds:X509Certificate></ds:X509Certificate>
+      </ds:X509Data>
+    </KeyInfo>
+  </ds:Signature>
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0"></Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject).to be_signed
+    end
+
+    it 'returns false when there is no signature' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0"></Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject).to_not be_signed
+    end
+  end
+
+  describe "#certificate" do
+    let(:now) { Time.now.utc }
+    let(:id) { SecureRandom.uuid }
+    let(:url) { FFaker::Internet.uri("https") }
+    let(:certificate) { FFaker::Movie.title }
+
+    it 'returns the certificate when the Assertion is signed' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0">
+    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+      <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+        <ds:Reference URI="#_#{id}">
+          <ds:Transforms>
+            <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+          </ds:Transforms>
+          <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+          <ds:DigestValue></ds:DigestValue>
+        </ds:Reference>
+      </ds:SignedInfo>
+      <ds:SignatureValue></ds:SignatureValue>
+      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+        <ds:X509Data>
+          <ds:X509Certificate>#{certificate}</ds:X509Certificate>
+        </ds:X509Data>
+      </KeyInfo>
+    </ds:Signature>
+  </Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject.certificate).to eql(certificate)
+    end
+
+    it 'returns the certificate when the Response is signed' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+    <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+      <ds:Reference URI="#_#{id}">
+        <ds:Transforms>
+          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+        </ds:Transforms>
+        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+        <ds:DigestValue></ds:DigestValue>
+      </ds:Reference>
+    </ds:SignedInfo>
+    <ds:SignatureValue></ds:SignatureValue>
+    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
+      <ds:X509Data>
+        <ds:X509Certificate>#{certificate}</ds:X509Certificate>
+      </ds:X509Data>
+    </KeyInfo>
+  </ds:Signature>
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0"></Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject.certificate).to eql(certificate)
+    end
+
+    it 'returns nil when there is no signature' do
+      xml = <<-XML
+<?xml version="1.0"?>
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="_#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{url}" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="_#{SecureRandom.uuid}">
+  <Assertion xmlns="#{Saml::Kit::Namespaces::ASSERTION}" ID="_#{id}" IssueInstant="#{now.iso8601}" Version="2.0"></Assertion>
+</samlp:Response>
+      XML
+      subject = described_class.new(xml)
+      expect(subject.certificate).to be_nil
+    end
+  end
+
   describe described_class::Builder do
     subject { described_class.new(user, request) }
     let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: []) }
@@ -252,5 +413,13 @@ RSpec.describe Saml::Kit::Response do
         expect(subject.build.request_id).to eql(request.id)
       end
     end
+
+    describe "#to_xml" do
+      xit 'generates an EncryptedAssertion' do
+        subject.encrypt = true
+        result = Hash.from_xml(subject.to_xml)
+        expect(result['Response']['EncryptedAssertion']).to be_present
+      end
+    end
   end
 end