xmldsig-fiscalizer 0.2.4

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