main
 1# frozen_string_literal: true
 2
 3RSpec.describe ::Xml::Kit::Signatures do
 4  let(:reference_id) { Xml::Kit::Id.generate }
 5  let(:options) { { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', 'xmlns:saml' => 'urn:oasis:names:tc:SAML:2.0:assertion', ID: reference_id } }
 6  let(:key_pair) { ::Xml::Kit::KeyPair.generate(use: :signing) }
 7
 8  context 'when a key pair is specified' do
 9    let(:signed_xml) do
10      described_class.sign(key_pair: key_pair) do |xml, signature|
11        xml.tag!('samlp:AuthnRequest', options) do
12          signature.template(reference_id)
13          xml.tag!('saml:Issuer', 'MyEntityID')
14        end
15      end
16    end
17    let(:result) { Hash.from_xml(signed_xml) }
18    let(:signature) { result['AuthnRequest']['Signature'] }
19    let(:expected_certificate) { key_pair.certificate.stripped }
20
21    specify { expect(signature['xmlns']).to eql('http://www.w3.org/2000/09/xmldsig#') }
22    specify { expect(signature['SignedInfo']['CanonicalizationMethod']['Algorithm']).to eql('http://www.w3.org/2001/10/xml-exc-c14n#') }
23    specify { expect(signature['SignedInfo']['SignatureMethod']['Algorithm']).to eql('http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') }
24    specify { expect(signature['SignedInfo']['Reference']['URI']).to eql("##{reference_id}") }
25    specify { expect(signature['SignedInfo']['Reference']['DigestMethod']['Algorithm']).to eql('http://www.w3.org/2001/04/xmlenc#sha256') }
26    specify { expect(signature['KeyInfo']['X509Data']['X509Certificate']).to eql(expected_certificate) }
27    specify { expect(signature['SignedInfo']['Reference']['DigestValue']).to be_present }
28    specify { expect(signature['SignatureValue']).to be_present }
29    specify { expect(OpenSSL::X509::Certificate.new(Base64.decode64(signature['KeyInfo']['X509Data']['X509Certificate']))).to be_present }
30
31    specify do
32      expect(signature['SignedInfo']['Reference']['Transforms']['Transform']).to match_array([
33        { 'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' },
34        { 'Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#' }
35      ])
36    end
37  end
38
39  context 'when a key pair is not specified' do
40    let(:signed_xml) do
41      described_class.sign(key_pair: nil) do |xml, signature|
42        xml.AuthnRequest do
43          signature.template(reference_id)
44          xml.Issuer 'MyEntityID'
45        end
46      end
47    end
48    let(:result) { Hash.from_xml(signed_xml) }
49
50    specify { expect(result['AuthnRequest']).to be_present }
51    specify { expect(result['AuthnRequest']['Signature']).to be_nil }
52  end
53
54  context 'when the signature is embedded' do
55    let(:result) do
56      described_class.sign(key_pair: key_pair) do |xml, signature|
57        xml.tag!('saml:Assertion', options) do
58          signature.template(reference_id)
59          xml.tag! 'saml:Subject' do
60            xml.NameID Format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
61          end
62        end
63      end
64    end
65
66    it 'produces a valid signature' do
67      node = Nokogiri::XML(result).at_xpath('//ds:Signature', ds: ::Xml::Kit::Namespaces::XMLDSIG)
68      dsignature = Xmldsig::Signature.new(node, 'ID=$uri or @Id')
69      expect(dsignature).to be_valid(key_pair.certificate.x509)
70      expect(dsignature.errors).to be_empty
71    end
72  end
73end