require 'spec_helper' describe Xmldsig::SignedDocument do let(:signed_xml) { File.read("spec/fixtures/signed.xml") } let(:signed_document) { Xmldsig::SignedDocument.new(signed_xml) } let(:unsigned_xml) { File.read("spec/fixtures/unsigned.xml") } let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml) } let(:private_key) { OpenSSL::PKey::RSA.new(File.read("spec/fixtures/key.pem")) } let(:certificate) { OpenSSL::X509::Certificate.new(File.read("spec/fixtures/certificate.cer")) } let(:other_certificate) { OpenSSL::X509::Certificate.new(File.read("spec/fixtures/certificate2.cer")) } describe "#initialize" do it "sets the document to a nokogiri document" do document = described_class.new(signed_xml) expect(document.document).to be_a(Nokogiri::XML::Document) end it "raises on badly formed XML" do badly_formed = <<-EOXML foo bar EOXML expect { described_class.new(badly_formed) }.to raise_error(Nokogiri::XML::SyntaxError) end it "accepts a nokogiri document" do doc = Nokogiri::XML(unsigned_xml) signed_document = described_class.new(doc) expect(signed_document.document).to be_a(Nokogiri::XML::Document) end end describe "#signatures" do let(:unsigned_xml) { File.read("spec/fixtures/unsigned_nested_signature.xml") } let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml) } it "returns only the signed nodes" do expect(signed_document.signatures).to be_all { |signature| signature.is_a?(Xmldsig::Signature) } end it "returns the outer signatures first" do expect(unsigned_document.signatures.first.references.first.reference_uri).to eq('#foo') end end describe "#signed_nodes" do it "returns only the signed nodes" do expect(signed_document.signed_nodes.collect(&:name)).to eq(%w(Foo)) end end describe "#validate" do it "returns true if the signature and digest value are correct" do expect(signed_document.validate(certificate)).to eq(true) end it "returns false if the certificate is not valid" do expect(signed_document.validate(other_certificate)).to eq(false) end it "returns false if there are no signatures and validation is strict" do xml_without_signature = Xmldsig::SignedDocument.new('') expect(xml_without_signature.validate(certificate)).to eq(false) end it "accepts a custom schema" do expect(signed_document.validate(certificate, Xmldsig::XSD_X509_SERIAL_FIX_FILE)).to eql true end it "accepts a block" do expect(signed_document.validate do |signature_value, data| certificate.public_key.verify(OpenSSL::Digest::SHA256.new, signature_value, data) end).to eq(true) end it "validates a document with a http://www.w3.org/2001/10/xml-exc-c14n#WithComments transform" do unsigned_xml_with_comments = File.read("spec/fixtures/signed_xml-exc-c14n#with_comments.xml") unsigned_documents_with_comments = Xmldsig::SignedDocument.new(unsigned_xml_with_comments) signed_xml_with_comments = unsigned_documents_with_comments.sign(private_key) expect(Xmldsig::SignedDocument.new(signed_xml_with_comments).validate(certificate)).to eq(true) end end describe "#sign" do it "returns a signed document" do signed_document = unsigned_document.sign(private_key) expect(Xmldsig::SignedDocument.new(signed_document).validate(certificate)).to eq(true) end it "accepts a block" do signed_document = unsigned_document.sign do |data| private_key.sign(OpenSSL::Digest::SHA256.new, data) end expect(Xmldsig::SignedDocument.new(signed_document).validate(certificate)).to eq(true) end context 'with the force false' do let(:unsigned_xml) { File.read("spec/fixtures/unsigned_nested_signed_signature.xml") } let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml) } let(:signed_xml) { unsigned_document.sign(private_key) } let(:signed_document) { Xmldsig::SignedDocument.new(signed_xml) } it 'only signs the root signature and leaves the nested signature intact' do expect(signed_document.signatures.first.valid?(certificate)).to eq(true) expect(signed_document.signatures.last.valid?(certificate)).to eq(false) expect(signed_document.signatures.last.signature_value).to eq(unsigned_document.signatures.last.signature_value) end end context 'with the force true' do let(:unsigned_xml) { File.read("spec/fixtures/unsigned_nested_signed_signature.xml") } let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml, force: true) } let(:signed_xml) { unsigned_document.sign(private_key) } let(:signed_document) { Xmldsig::SignedDocument.new(signed_xml) } it 'only signs the root signature and leaves the nested signature intact' do expect(signed_document.signatures.first.valid?(certificate)).to eq(true) expect(signed_document.signatures.last.valid?(certificate)).to eq(true) expect(signed_document.signatures.last.signature_value).to_not be(unsigned_document.signatures.last.signature_value) end end context 'with inclusive namespaces for the signature' do let(:unsigned_xml) { File.read("spec/fixtures/unsigned_signature_namespace.xml") } let(:signed_xml) { File.read("spec/fixtures/signed_signature_namespace.xml") } it 'canonicalizes and signs correctly' do expect(unsigned_document.sign(private_key)).to eq(signed_xml) end end end describe "Nested Signatures" do let(:unsigned_xml) { File.read("spec/fixtures/unsigned_nested_signature.xml") } let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml) } let(:signed_document) { unsigned_document.sign(private_key) } it "when signed should be valid" do expect(Xmldsig::SignedDocument.new(signed_document).validate(certificate)).to eq(true) end it "should sign 2 elements" do expect(unsigned_document.signed_nodes.count).to eq(2) end it "allows individual signs" do unsigned_document.signatures.last.sign(private_key) expect(unsigned_document.validate(certificate)).to eq(false) expect(unsigned_document.signatures.last.valid?(certificate)).to eq(true) end end end