xmldsig 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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