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 +4 -2
- data/lib/xmldsig/reference.rb +74 -0
- data/lib/xmldsig/signature.rb +13 -49
- data/lib/xmldsig/signed_document.rb +1 -1
- data/lib/xmldsig/version.rb +1 -1
- data/spec/fixtures/unsigned_multiple_references.xml +38 -0
- data/spec/lib/xmldsig/reference_spec.rb +65 -0
- data/spec/lib/xmldsig/signature_spec.rb +19 -33
- metadata +7 -2
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"
|
16
|
-
"ec"
|
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
|
data/lib/xmldsig/signature.rb
CHANGED
@@ -1,34 +1,23 @@
|
|
1
1
|
module Xmldsig
|
2
2
|
class Signature
|
3
|
-
attr_accessor :signature
|
3
|
+
attr_accessor :signature
|
4
4
|
|
5
5
|
def initialize(signature)
|
6
6
|
@signature = signature
|
7
|
-
@errors = []
|
8
7
|
end
|
9
8
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
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
|
27
|
-
|
15
|
+
def errors
|
16
|
+
references.flat_map(&:errors) + @errors
|
28
17
|
end
|
29
18
|
|
30
19
|
def sign(private_key = nil, &block)
|
31
|
-
|
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
|
-
|
34
|
+
references.each { |r| r.errors = [] }
|
35
|
+
validate_digest_values
|
46
36
|
validate_signature_value(certificate, &block)
|
47
|
-
|
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
|
108
|
-
|
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
|
data/lib/xmldsig/version.rb
CHANGED
@@ -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(
|
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.
|
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-
|
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
|