xmldsig 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/xmldsig.rb CHANGED
@@ -8,11 +8,13 @@ require "xmldsig/transforms/transform"
8
8
  require "xmldsig/transforms/canonicalize"
9
9
  require "xmldsig/transforms/enveloped_signature"
10
10
  require "xmldsig/transforms"
11
+ require "xmldsig/reference"
11
12
  require "xmldsig/signature"
12
13
 
13
14
  module Xmldsig
14
15
  NAMESPACES = {
15
- "ds" => "http://www.w3.org/2000/09/xmldsig#",
16
- "ec" => "http://www.w3.org/2001/10/xml-exc-c14n#"
16
+ "ds" => "http://www.w3.org/2000/09/xmldsig#",
17
+ "ec" => "http://www.w3.org/2001/10/xml-exc-c14n#",
18
+ "wsu" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
17
19
  }
18
20
  end
@@ -0,0 +1,74 @@
1
+ module Xmldsig
2
+ class Reference
3
+ attr_accessor :reference, :errors
4
+
5
+ class ReferencedNodeNotFound < Exception; end
6
+
7
+ def initialize(reference)
8
+ @reference = reference
9
+ @errors = []
10
+ end
11
+
12
+ def document
13
+ reference.document
14
+ end
15
+
16
+ def sign
17
+ self.digest_value = calculate_digest_value
18
+ end
19
+
20
+ def referenced_node
21
+ if reference_uri && reference_uri != ""
22
+ id = reference_uri[1..-1]
23
+ if ref = document.dup.at_xpath("//*[@ID='#{id}' or @wsu:Id='#{id}']", NAMESPACES)
24
+ ref
25
+ else
26
+ raise(
27
+ ReferencedNodeNotFound,
28
+ "Could not find the referenced node #{id}'"
29
+ )
30
+ end
31
+ else
32
+ document.dup.root
33
+ end
34
+ end
35
+
36
+ def reference_uri
37
+ reference.get_attribute("URI")
38
+ end
39
+
40
+ def digest_value
41
+ Base64.decode64 reference.at_xpath("descendant::ds:DigestValue", NAMESPACES).content
42
+ end
43
+
44
+ def calculate_digest_value
45
+ node = transforms.apply(referenced_node)
46
+ digest_method.digest node
47
+ end
48
+
49
+ def digest_method
50
+ algorithm = reference.at_xpath("descendant::ds:DigestMethod", NAMESPACES).get_attribute("Algorithm")
51
+ case algorithm
52
+ when "http://www.w3.org/2001/04/xmlenc#sha256"
53
+ Digest::SHA2
54
+ when "http://www.w3.org/2000/09/xmldsig#sha1"
55
+ Digest::SHA1
56
+ end
57
+ end
58
+
59
+ def digest_value=(digest_value)
60
+ reference.at_xpath("descendant::ds:DigestValue", NAMESPACES).content =
61
+ Base64.encode64(digest_value).chomp
62
+ end
63
+
64
+ def transforms
65
+ Transforms.new(reference.xpath("descendant::ds:Transform", NAMESPACES))
66
+ end
67
+
68
+ def validate_digest_value
69
+ unless digest_value == calculate_digest_value
70
+ @errors << :digest_value
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,34 +1,23 @@
1
1
  module Xmldsig
2
2
  class Signature
3
- attr_accessor :signature, :errors
3
+ attr_accessor :signature
4
4
 
5
5
  def initialize(signature)
6
6
  @signature = signature
7
- @errors = []
8
7
  end
9
8
 
10
- def digest_value
11
- Base64.decode64 signed_info.at_xpath("descendant::ds:DigestValue", NAMESPACES).content
12
- end
13
-
14
- def document
15
- signature.document
16
- end
17
-
18
- def referenced_node
19
- if reference_uri && reference_uri != ""
20
- document.dup.at_xpath("//*[@ID='#{reference_uri[1..-1]}']")
21
- else
22
- document.dup.root
9
+ def references
10
+ @references ||= signature.xpath("descendant::ds:Reference", NAMESPACES).map do |node|
11
+ Reference.new(node)
23
12
  end
24
13
  end
25
14
 
26
- def reference_uri
27
- signature.at_xpath("descendant::ds:Reference", NAMESPACES).get_attribute("URI")
15
+ def errors
16
+ references.flat_map(&:errors) + @errors
28
17
  end
29
18
 
30
19
  def sign(private_key = nil, &block)
31
- self.digest_value = calculate_digest_value
20
+ references.each(&:sign)
32
21
  self.signature_value = calculate_signature_value(private_key, &block)
33
22
  end
34
23
 
@@ -42,18 +31,14 @@ module Xmldsig
42
31
 
43
32
  def valid?(certificate = nil, &block)
44
33
  @errors = []
45
- validate_digest_value
34
+ references.each { |r| r.errors = [] }
35
+ validate_digest_values
46
36
  validate_signature_value(certificate, &block)
47
- @errors.empty?
37
+ errors.empty?
48
38
  end
49
39
 
50
40
  private
51
41
 
52
- def calculate_digest_value
53
- node = transforms.apply(referenced_node)
54
- digest_method.digest node
55
- end
56
-
57
42
  def canonicalization_method
58
43
  signed_info.at_xpath("descendant::ds:CanonicalizationMethod", NAMESPACES).get_attribute("Algorithm")
59
44
  end
@@ -70,21 +55,6 @@ module Xmldsig
70
55
  end
71
56
  end
72
57
 
73
- def digest_method
74
- algorithm = signed_info.at_xpath("descendant::ds:DigestMethod", NAMESPACES).get_attribute("Algorithm")
75
- case algorithm
76
- when "http://www.w3.org/2001/04/xmlenc#sha256"
77
- Digest::SHA2
78
- when "http://www.w3.org/2000/09/xmldsig#sha1"
79
- Digest::SHA1
80
- end
81
- end
82
-
83
- def digest_value=(digest_value)
84
- signed_info.at_xpath("descendant::ds:DigestValue", NAMESPACES).content =
85
- Base64.encode64(digest_value).chomp
86
- end
87
-
88
58
  def signature_algorithm
89
59
  signed_info.at_xpath("descendant::ds:SignatureMethod", NAMESPACES).get_attribute("Algorithm")
90
60
  end
@@ -104,14 +74,8 @@ module Xmldsig
104
74
  Base64.encode64(signature_value).chomp
105
75
  end
106
76
 
107
- def transforms
108
- Transforms.new(signature.xpath("descendant::ds:Transform", NAMESPACES))
109
- end
110
-
111
- def validate_digest_value
112
- unless digest_value == calculate_digest_value
113
- errors << :digest_value
114
- end
77
+ def validate_digest_values
78
+ references.each(&:validate_digest_value)
115
79
  end
116
80
 
117
81
  def validate_signature_value(certificate)
@@ -122,7 +86,7 @@ module Xmldsig
122
86
  end
123
87
 
124
88
  unless signature_valid
125
- errors << :signature
89
+ @errors << :signature
126
90
  end
127
91
  end
128
92
  end
@@ -16,7 +16,7 @@ module Xmldsig
16
16
  end
17
17
 
18
18
  def signed_nodes
19
- signatures.collect(&:referenced_node)
19
+ signatures.flat_map(&:references).map(&:referenced_node)
20
20
  end
21
21
 
22
22
  def signatures
@@ -1,3 +1,3 @@
1
1
  module Xmldsig
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0"?>
2
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
3
+ <soapenv:Header></soapenv:Header>
4
+ <soapenv:Body>
5
+
6
+ <SomeMethod ID="Data-1">
7
+ <Data>some Content</Data>
8
+ </SomeMethod>
9
+
10
+ <Timestamp ID="Timestamp-1">
11
+ <Created>2010-10-25T12:09:44Z</Created>
12
+ <Expires>2010-10-25T12:14:44Z</Expires>
13
+ </Timestamp>
14
+
15
+ <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
16
+ <SignedInfo>
17
+ <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
18
+ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
19
+ <Reference URI="#Timestamp-1">
20
+ <Transforms>
21
+ <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
22
+ </Transforms>
23
+ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
24
+ <DigestValue></DigestValue>
25
+ </Reference>
26
+ <Reference URI="#Data-1">
27
+ <Transforms>
28
+ <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
29
+ </Transforms>
30
+ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
31
+ <DigestValue></DigestValue>
32
+ </Reference>
33
+ </SignedInfo>
34
+ <SignatureValue></SignatureValue>
35
+ </Signature>
36
+
37
+ </soapenv:Body>
38
+ </soapenv:Envelope>
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ describe Xmldsig::Reference do
4
+ let(:document) { Nokogiri::XML::Document.parse File.read("spec/fixtures/signed.xml") }
5
+ let(:reference) { Xmldsig::Reference.new(document.at_xpath('//ds:Reference', Xmldsig::NAMESPACES)) }
6
+
7
+ describe "#digest_value" do
8
+ it "returns the digest value in the xml" do
9
+ reference.digest_value.should == Base64.decode64("ftoSYFdze1AWgGHF5N9i9SFKThXkqH2AdyzA3/epbJw=")
10
+ end
11
+ end
12
+
13
+ describe "#document" do
14
+ it "returns the document" do
15
+ reference.document.should == document
16
+ end
17
+ end
18
+
19
+ describe "#sign" do
20
+ let(:document) { Nokogiri::XML::Document.parse File.read("spec/fixtures/unsigned.xml") }
21
+
22
+ it "sets the correct digest value" do
23
+ reference.sign
24
+ reference.digest_value.should == Base64.decode64("ftoSYFdze1AWgGHF5N9i9SFKThXkqH2AdyzA3/epbJw=")
25
+ end
26
+ end
27
+
28
+ describe "#referenced_node" do
29
+ it "returns the referenced_node by id" do
30
+ reference.referenced_node.to_s.should ==
31
+ document.at_xpath("//*[@ID='foo']").to_s
32
+ end
33
+
34
+ it "returns the referenced node by parent" do
35
+ reference.stub(:reference_uri).and_return("")
36
+ reference.referenced_node.to_s.should ==
37
+ document.root.to_s
38
+ end
39
+
40
+ it "returns the reference node when using WS-Security style id attribute" do
41
+ node = document.at_xpath('//*[@ID]')
42
+ node.add_namespace('wsu', Xmldsig::NAMESPACES['wsu'])
43
+ node['wsu:Id'] = node['ID']
44
+ node.remove_attribute('ID')
45
+
46
+ reference.referenced_node.
47
+ attribute_with_ns('Id', Xmldsig::NAMESPACES['wsu']).value.
48
+ should == 'foo'
49
+ end
50
+
51
+ it "raises ReferencedNodeNotFound when the refenced node is not present" do
52
+ node = document.at_xpath('//*[@ID]')
53
+ node.remove_attribute('ID')
54
+
55
+ expect { reference.referenced_node }.
56
+ to raise_error(Xmldsig::Reference::ReferencedNodeNotFound)
57
+ end
58
+ end
59
+
60
+ describe "#reference_uri" do
61
+ it "returns the reference uri" do
62
+ reference.reference_uri.should == "#foo"
63
+ end
64
+ end
65
+ end
@@ -8,37 +8,6 @@ describe Xmldsig::Signature do
8
8
  let(:signature_node) { document.at_xpath("//ds:Signature", Xmldsig::NAMESPACES) }
9
9
  let(:signature) { Xmldsig::Signature.new(signature_node) }
10
10
 
11
- describe "#digest_value" do
12
- it "returns the digest value in the xml" do
13
- signature.digest_value.should == Base64.decode64("ftoSYFdze1AWgGHF5N9i9SFKThXkqH2AdyzA3/epbJw=")
14
- end
15
- end
16
-
17
- describe "#document" do
18
- it "returns the document" do
19
- signature.document.should == document
20
- end
21
- end
22
-
23
- describe "#referenced_node" do
24
- it "returns the referenced_node by id" do
25
- signature.referenced_node.to_s.should ==
26
- document.at_xpath("//*[@ID='foo']").to_s
27
- end
28
-
29
- it "returns the referenced node by parent" do
30
- signature.stub(:reference_uri).and_return("")
31
- signature.referenced_node.to_s.should ==
32
- document.root.to_s
33
- end
34
- end
35
-
36
- describe "#reference_uri" do
37
- it "returns the reference uri" do
38
- signature.reference_uri.should == "#foo"
39
- end
40
- end
41
-
42
11
  describe "#sign" do
43
12
  let(:document) { Nokogiri::XML::Document.parse File.read("spec/fixtures/unsigned.xml") }
44
13
  let(:signature_node) { document.at_xpath("//ds:Signature", Xmldsig::NAMESPACES) }
@@ -49,7 +18,7 @@ describe Xmldsig::Signature do
49
18
  end
50
19
 
51
20
  it "sets the digest value" do
52
- signature.digest_value.should == Base64.decode64("ftoSYFdze1AWgGHF5N9i9SFKThXkqH2AdyzA3/epbJw=")
21
+ signature.references.first.digest_value.should == Base64.decode64("ftoSYFdze1AWgGHF5N9i9SFKThXkqH2AdyzA3/epbJw=")
53
22
  end
54
23
 
55
24
  it "sets the signature value" do
@@ -72,6 +41,21 @@ describe Xmldsig::Signature do
72
41
  ")
73
42
  end
74
43
 
44
+ describe "multiple references" do
45
+ let(:document) { Nokogiri::XML::Document.parse File.read("spec/fixtures/unsigned_multiple_references.xml") }
46
+
47
+ it "can sign the document" do
48
+ signature.sign(private_key)
49
+ signature.should be_valid(certificate)
50
+ end
51
+
52
+ it "gets a digest per reference" do
53
+ signature.references.count.should be == 2
54
+ signature.sign(private_key)
55
+ signature.references[0].digest_value.should be == Base64.decode64("P1nUq8Y/LPmd+EON/mcNMNRjT78=")
56
+ signature.references[1].digest_value.should be == Base64.decode64("RoGAaQeuNJuDMWcgsD7RuGbFACo=")
57
+ end
58
+ end
75
59
  end
76
60
 
77
61
  describe "#signed_info" do
@@ -94,7 +78,9 @@ describe Xmldsig::Signature do
94
78
  end
95
79
 
96
80
  it "returns false if the xml changed" do
97
- signature.stub(:document).and_return(Nokogiri::XML::Document.parse(File.read("spec/fixtures/signed.xml").gsub("\s\s", "\s")))
81
+ signature.references.first.stub(:document).and_return(
82
+ Nokogiri::XML::Document.parse(File.read("spec/fixtures/signed.xml").gsub("\s\s", "\s"))
83
+ )
98
84
  signature.valid?(certificate)
99
85
  signature.errors.should include(:digest_value)
100
86
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmldsig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-02 00:00:00.000000000 Z
12
+ date: 2013-06-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -44,6 +44,7 @@ files:
44
44
  - Rakefile
45
45
  - lib/xmldsig.rb
46
46
  - lib/xmldsig/canonicalizer.rb
47
+ - lib/xmldsig/reference.rb
47
48
  - lib/xmldsig/signature.rb
48
49
  - lib/xmldsig/signed_document.rb
49
50
  - lib/xmldsig/transforms.rb
@@ -63,7 +64,9 @@ files:
63
64
  - spec/fixtures/unsigned/with_soap_envelope.xml
64
65
  - spec/fixtures/unsigned/without_namespace_prefix.xml
65
66
  - spec/fixtures/unsigned/without_reference_uri.xml
67
+ - spec/fixtures/unsigned_multiple_references.xml
66
68
  - spec/fixtures/unsigned_nested_signature.xml
69
+ - spec/lib/xmldsig/reference_spec.rb
67
70
  - spec/lib/xmldsig/signature_spec.rb
68
71
  - spec/lib/xmldsig/signed_document_spec.rb
69
72
  - spec/lib/xmldsig/transforms/transform_spec.rb
@@ -107,7 +110,9 @@ test_files:
107
110
  - spec/fixtures/unsigned/with_soap_envelope.xml
108
111
  - spec/fixtures/unsigned/without_namespace_prefix.xml
109
112
  - spec/fixtures/unsigned/without_reference_uri.xml
113
+ - spec/fixtures/unsigned_multiple_references.xml
110
114
  - spec/fixtures/unsigned_nested_signature.xml
115
+ - spec/lib/xmldsig/reference_spec.rb
111
116
  - spec/lib/xmldsig/signature_spec.rb
112
117
  - spec/lib/xmldsig/signed_document_spec.rb
113
118
  - spec/lib/xmldsig/transforms/transform_spec.rb