xmldsig-fiscalizer 0.2.4

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +12 -0
  7. data/Guardfile +24 -0
  8. data/LICENSE +22 -0
  9. data/README.md +80 -0
  10. data/Rakefile +10 -0
  11. data/lib/xmldsig.rb +20 -0
  12. data/lib/xmldsig/canonicalizer.rb +30 -0
  13. data/lib/xmldsig/reference.rb +80 -0
  14. data/lib/xmldsig/signature.rb +93 -0
  15. data/lib/xmldsig/signed_document.rb +26 -0
  16. data/lib/xmldsig/transforms.rb +26 -0
  17. data/lib/xmldsig/transforms/canonicalize.rb +25 -0
  18. data/lib/xmldsig/transforms/enveloped_signature.rb +10 -0
  19. data/lib/xmldsig/transforms/transform.rb +18 -0
  20. data/lib/xmldsig/version.rb +3 -0
  21. data/signing_service.rb +133 -0
  22. data/spec/fixtures/certificate.cer +16 -0
  23. data/spec/fixtures/certificate2.cer +16 -0
  24. data/spec/fixtures/key.pem +15 -0
  25. data/spec/fixtures/signed.xml +23 -0
  26. data/spec/fixtures/signed/ideal.cert +18 -0
  27. data/spec/fixtures/signed/ideal.txt +41 -0
  28. data/spec/fixtures/unsigned.xml +21 -0
  29. data/spec/fixtures/unsigned/canonicalizer_1_0.xml +19 -0
  30. data/spec/fixtures/unsigned/canonicalizer_1_1.xml +19 -0
  31. data/spec/fixtures/unsigned/canonicalizer_exc.xml +21 -0
  32. data/spec/fixtures/unsigned/digest_sha1.xml +21 -0
  33. data/spec/fixtures/unsigned/with_soap_envelope.xml +33 -0
  34. data/spec/fixtures/unsigned/without_canonicalization.xml +18 -0
  35. data/spec/fixtures/unsigned/without_namespace_prefix.xml +19 -0
  36. data/spec/fixtures/unsigned/without_reference_uri.xml +21 -0
  37. data/spec/fixtures/unsigned_multiple_references.xml +38 -0
  38. data/spec/fixtures/unsigned_nested_signature.xml +40 -0
  39. data/spec/lib/xmldsig/reference_spec.rb +65 -0
  40. data/spec/lib/xmldsig/signature_spec.rb +100 -0
  41. data/spec/lib/xmldsig/signed_document_spec.rb +94 -0
  42. data/spec/lib/xmldsig/transforms/enveloped_signature_spec.rb +18 -0
  43. data/spec/lib/xmldsig/transforms/transform_spec.rb +10 -0
  44. data/spec/lib/xmldsig_spec.rb +47 -0
  45. data/spec/spec_helper.rb +22 -0
  46. data/xmldsig.gemspec +20 -0
  47. metadata +127 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2af9d1508d566e056ff120a185ca6a714b1da10f
4
+ data.tar.gz: eaeedd065b180313b84e6919ef386ecd98b7169c
5
+ SHA512:
6
+ metadata.gz: fb6470d68f64f84802c1fa60c5cd5c6edcf8059d077a6c292bb1dcee729a7114e0a656007b061e9872d0755348fd8a35350109cdf1e064de58f4a6308277838d
7
+ data.tar.gz: 909f60a9657222993191e72e7d69de72ad07bdac7aa2afcf9eaf1e72c72eec4349b74dea5bb206e777ecc63e58b179b11116d32d84cdac16829c06cd313a4b5c
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ v0.2.2 3-8-2013
4
+ - added default canonicalization
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xmldsig.gemspec
4
+ gemspec
5
+
6
+ group :test, :development do
7
+ gem 'simplecov'
8
+ gem 'rspec'
9
+ gem 'guard-rspec'
10
+ gem 'rb-fsevent', '~> 0.9.1'
11
+ gem 'rake'
12
+ end
@@ -0,0 +1,24 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+
17
+ # Capybara features specs
18
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
+
20
+ # Turnip features and steps
21
+ watch(%r{^spec/acceptance/(.+)\.feature$})
22
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
+ end
24
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 benoist
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ [![Build Status](https://secure.travis-ci.org/benoist/xmldsig.png?branch=master)](http://travis-ci.org/benoist/xmldsig)
2
+ # Xmldsig
3
+
4
+ This gem is a (partial) implementation of the XMLDsig specification (http://www.w3.org/TR/xmldsig-core)
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'xmldsig'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install xmldsig
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ unsigned_xml = <<-XML
24
+ <?xml version="1.0" encoding="UTF-8"?>
25
+ <foo:Foo ID="foo" xmlns:foo="http://example.com/foo#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#">
26
+ <foo:Bar>bar</foo:Bar>
27
+ <ds:Signature>
28
+ <ds:SignedInfo>
29
+ <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
30
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
31
+ <ds:Reference URI="#foo">
32
+ <ds:Transforms>
33
+ <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
34
+ <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
35
+ <ec:InclusiveNamespaces PrefixList="foo"/>
36
+ </ds:Transform>
37
+ </ds:Transforms>
38
+ <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
39
+ <ds:DigestValue></ds:DigestValue>
40
+ </ds:Reference>
41
+ </ds:SignedInfo>
42
+ <ds:SignatureValue></ds:SignatureValue>
43
+ </ds:Signature>
44
+ </foo:Foo>
45
+ XML
46
+
47
+ private_key = OpenSSL::PKey::RSA.new(File.read("key.pem"))
48
+ certificate = OpenSSL::X509::Certificate.new(File.read("certificate.cer"))
49
+
50
+ unsigned_document = Xmldsig::SignedDocument.new(unsigned_xml)
51
+ signed_xml = unsigned_document.sign(private_key)
52
+
53
+ # With block
54
+ signed_xml = unsigned_document.sign do |data|
55
+ private_key.sign(OpenSSL::Digest::SHA256.new, data)
56
+ end
57
+
58
+ # Validation
59
+
60
+ signed_document = Xmldsig::SignedDocument.new(signed_xml)
61
+ signed_document.validate(certificate)
62
+
63
+ # With block
64
+ signed_document = Xmldsig::SignedDocument.new(signed_xml)
65
+ signed_document.validate do |signature_value, data|
66
+ certificate.public_key.verify(OpenSSL::Digest::SHA256.new, signature_value, data)
67
+ end
68
+ ```
69
+
70
+ ## Known issues
71
+
72
+ 1. Windows in app purchase verification requires extra whitespace removal: https://github.com/benoist/xmldsig/issues/13
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create new Pull Request
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:core) do |spec|
7
+ spec.rspec_opts = ['--backtrace']
8
+ end
9
+
10
+ task :default => [:core]
@@ -0,0 +1,20 @@
1
+ require "nokogiri"
2
+ require "openssl"
3
+ require "base64"
4
+ require "xmldsig/version"
5
+ require "xmldsig/canonicalizer"
6
+ require "xmldsig/signed_document"
7
+ require "xmldsig/transforms/transform"
8
+ require "xmldsig/transforms/canonicalize"
9
+ require "xmldsig/transforms/enveloped_signature"
10
+ require "xmldsig/transforms"
11
+ require "xmldsig/reference"
12
+ require "xmldsig/signature"
13
+
14
+ module Xmldsig
15
+ NAMESPACES = {
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"
19
+ }
20
+ end
@@ -0,0 +1,30 @@
1
+ module Xmldsig
2
+ class Canonicalizer
3
+ attr_accessor :node, :method, :inclusive_namespaces
4
+
5
+ def initialize(node, method = nil, inclusive_namespaces = [])
6
+ @node = node
7
+ @method = method
8
+ @inclusive_namespaces = inclusive_namespaces
9
+ end
10
+
11
+ def canonicalize
12
+ node.canonicalize(mode(method), inclusive_namespaces)
13
+ end
14
+
15
+ private
16
+
17
+ def mode(method)
18
+ case method
19
+ when "http://www.w3.org/2001/10/xml-exc-c14n#"
20
+ Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
21
+ when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
22
+ Nokogiri::XML::XML_C14N_1_0
23
+ when "http://www.w3.org/2006/12/xml-c14n11"
24
+ Nokogiri::XML::XML_C14N_1_1
25
+ else
26
+ Nokogiri::XML::XML_C14N_1_0
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,80 @@
1
+ module Xmldsig
2
+ class Reference
3
+ attr_accessor :reference, :errors
4
+
5
+ class ReferencedNodeNotFound < Exception;
6
+ end
7
+
8
+ def initialize(reference)
9
+ @reference = reference
10
+ @errors = []
11
+ end
12
+
13
+ def document
14
+ reference.document
15
+ end
16
+
17
+ def sign
18
+ self.digest_value = calculate_digest_value
19
+ end
20
+
21
+ def referenced_node
22
+ if reference_uri && reference_uri != ""
23
+ id = reference_uri[1..-1]
24
+ if ref = document.dup.at_xpath("//*[@ID='#{id}' or @Id='#{id}' or @id='#{id}' or @wsu:Id='#{id}']", NAMESPACES)
25
+ ref
26
+ else
27
+ raise(
28
+ ReferencedNodeNotFound,
29
+ "Could not find the referenced node #{id}'"
30
+ )
31
+ end
32
+ else
33
+ document.dup.root
34
+ end
35
+ end
36
+
37
+ def reference_uri
38
+ reference.get_attribute("URI")
39
+ end
40
+
41
+ def digest_value
42
+ Base64.decode64 reference.at_xpath("descendant::ds:DigestValue", NAMESPACES).content
43
+ end
44
+
45
+ def calculate_digest_value
46
+ transformed = transforms.apply(referenced_node)
47
+ case transformed
48
+ when String
49
+ digest_method.digest transformed
50
+ when Nokogiri::XML::Node
51
+ digest_method.digest Canonicalizer.new(transformed).canonicalize
52
+ end
53
+ end
54
+
55
+ def digest_method
56
+ algorithm = reference.at_xpath("descendant::ds:DigestMethod", NAMESPACES).get_attribute("Algorithm")
57
+ case algorithm
58
+ when "http://www.w3.org/2001/04/xmlenc#sha256"
59
+ Digest::SHA2
60
+ when "http://www.w3.org/2000/09/xmldsig#sha1"
61
+ Digest::SHA1
62
+ end
63
+ end
64
+
65
+ def digest_value=(digest_value)
66
+ reference.at_xpath("descendant::ds:DigestValue", NAMESPACES).content =
67
+ Base64.encode64(digest_value).chomp
68
+ end
69
+
70
+ def transforms
71
+ Transforms.new(reference.xpath("descendant::ds:Transform", NAMESPACES))
72
+ end
73
+
74
+ def validate_digest_value
75
+ unless digest_value == calculate_digest_value
76
+ @errors << :digest_value
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,93 @@
1
+ module Xmldsig
2
+ class Signature
3
+ attr_accessor :signature
4
+
5
+ def initialize(signature)
6
+ @signature = signature
7
+ end
8
+
9
+ def references
10
+ @references ||= signature.xpath("descendant::ds:Reference", NAMESPACES).map do |node|
11
+ Reference.new(node)
12
+ end
13
+ end
14
+
15
+ def errors
16
+ references.flat_map(&:errors) + @errors
17
+ end
18
+
19
+ def sign(private_key = nil, &block)
20
+ references.each(&:sign)
21
+ self.signature_value = calculate_signature_value(private_key, &block)
22
+ end
23
+
24
+ def signed_info
25
+ signature.at_xpath("descendant::ds:SignedInfo", NAMESPACES)
26
+ end
27
+
28
+ def signature_value
29
+ Base64.decode64 signature.at_xpath("descendant::ds:SignatureValue", NAMESPACES).content
30
+ end
31
+
32
+ def valid?(certificate = nil, &block)
33
+ @errors = []
34
+ references.each { |r| r.errors = [] }
35
+ validate_digest_values
36
+ validate_signature_value(certificate, &block)
37
+ errors.empty?
38
+ end
39
+
40
+ private
41
+
42
+ def canonicalization_method
43
+ signed_info.at_xpath("descendant::ds:CanonicalizationMethod", NAMESPACES).get_attribute("Algorithm")
44
+ end
45
+
46
+ def canonicalized_signed_info
47
+ Canonicalizer.new(signed_info, canonicalization_method).canonicalize
48
+ end
49
+
50
+ def calculate_signature_value(private_key, &block)
51
+ if private_key
52
+ private_key.sign(signature_method.new, canonicalized_signed_info)
53
+ else
54
+ yield(canonicalized_signed_info, signature_algorithm)
55
+ end
56
+ end
57
+
58
+ def signature_algorithm
59
+ signed_info.at_xpath("descendant::ds:SignatureMethod", NAMESPACES).get_attribute("Algorithm")
60
+ end
61
+
62
+ def signature_method
63
+ algorithm = signature_algorithm && signature_algorithm =~ /sha(.*?)$/i && $1.to_i
64
+ case algorithm
65
+ when 256 then
66
+ OpenSSL::Digest::SHA256
67
+ else
68
+ OpenSSL::Digest::SHA1
69
+ end
70
+ end
71
+
72
+ def signature_value=(signature_value)
73
+ signature.at_xpath("descendant::ds:SignatureValue", NAMESPACES).content =
74
+ Base64.encode64(signature_value).chomp
75
+ end
76
+
77
+ def validate_digest_values
78
+ references.each(&:validate_digest_value)
79
+ end
80
+
81
+ def validate_signature_value(certificate)
82
+ signature_valid = if certificate
83
+ certificate.public_key.verify(signature_method.new, signature_value, canonicalized_signed_info)
84
+ else
85
+ yield(signature_value, canonicalized_signed_info, signature_algorithm)
86
+ end
87
+
88
+ unless signature_valid
89
+ @errors << :signature
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,26 @@
1
+ module Xmldsig
2
+ class SignedDocument
3
+ attr_accessor :document
4
+
5
+ def initialize(document, options = {})
6
+ @document = Nokogiri::XML::Document.parse(document)
7
+ end
8
+
9
+ def validate(certificate = nil, &block)
10
+ signatures.any? && signatures.all? { |signature| signature.valid?(certificate, &block) }
11
+ end
12
+
13
+ def sign(private_key = nil, instruct = true, &block)
14
+ signatures.each { |signature| signature.sign(private_key, &block) }
15
+ instruct ? @document.to_s : @document.root.to_s
16
+ end
17
+
18
+ def signed_nodes
19
+ signatures.flat_map(&:references).map(&:referenced_node)
20
+ end
21
+
22
+ def signatures
23
+ document.xpath("//ds:Signature", NAMESPACES).reverse.collect { |node| Signature.new(node) } || []
24
+ end
25
+ end
26
+ end