xmldsig 0.2.1 → 0.2.2

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.
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ v0.2.2 3-8-2013
4
+ - added default canonicalization
@@ -2,7 +2,7 @@ module Xmldsig
2
2
  class Canonicalizer
3
3
  attr_accessor :node, :method, :inclusive_namespaces
4
4
 
5
- def initialize(node, method, inclusive_namespaces = [])
5
+ def initialize(node, method = nil, inclusive_namespaces = [])
6
6
  @node = node
7
7
  @method = method
8
8
  @inclusive_namespaces = inclusive_namespaces
@@ -22,6 +22,8 @@ module Xmldsig
22
22
  Nokogiri::XML::XML_C14N_1_0
23
23
  when "http://www.w3.org/2006/12/xml-c14n11"
24
24
  Nokogiri::XML::XML_C14N_1_1
25
+ else
26
+ Nokogiri::XML::XML_C14N_1_0
25
27
  end
26
28
  end
27
29
  end
@@ -2,7 +2,8 @@ module Xmldsig
2
2
  class Reference
3
3
  attr_accessor :reference, :errors
4
4
 
5
- class ReferencedNodeNotFound < Exception; end
5
+ class ReferencedNodeNotFound < Exception;
6
+ end
6
7
 
7
8
  def initialize(reference)
8
9
  @reference = reference
@@ -24,8 +25,8 @@ module Xmldsig
24
25
  ref
25
26
  else
26
27
  raise(
27
- ReferencedNodeNotFound,
28
- "Could not find the referenced node #{id}'"
28
+ ReferencedNodeNotFound,
29
+ "Could not find the referenced node #{id}'"
29
30
  )
30
31
  end
31
32
  else
@@ -42,8 +43,13 @@ module Xmldsig
42
43
  end
43
44
 
44
45
  def calculate_digest_value
45
- node = transforms.apply(referenced_node)
46
- digest_method.digest node
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
47
53
  end
48
54
 
49
55
  def digest_method
@@ -58,7 +64,7 @@ module Xmldsig
58
64
 
59
65
  def digest_value=(digest_value)
60
66
  reference.at_xpath("descendant::ds:DigestValue", NAMESPACES).content =
61
- Base64.encode64(digest_value).chomp
67
+ Base64.encode64(digest_value).chomp
62
68
  end
63
69
 
64
70
  def transforms
@@ -1,3 +1,3 @@
1
1
  module Xmldsig
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
@@ -0,0 +1,133 @@
1
+ require 'base64'
2
+
3
+ class SigningService
4
+
5
+ class << self
6
+ def create_redirect_params(xml, relay_state = "")
7
+ relay_state = relay_state ? "&RelayState=#{CGI.escape(relay_state)}" : ""
8
+
9
+ encoded_xml = Saml::Encoding.to_http_redirect_binding_param(xml)
10
+ response_params = "SAMLResponse=#{encoded_xml}#{relay_state}&SigAlg=#{CGI.escape('http://www.w3.org/2000/09/xmldsig#rsa-sha1')}"
11
+ signature = CGI.escape(sign_params(:params => response_params, :private_key => Saml::Config.private_key))
12
+
13
+ "#{response_params}&Signature=#{signature}"
14
+ end
15
+
16
+ def parse_signature_params(query)
17
+ params = {}
18
+ query.split(/[&;]/).each do |pairs|
19
+ key, value = pairs.split('=',2)
20
+ params[key] = value
21
+ end
22
+
23
+ relay_state = params["RelayState"] ? "&RelayState=#{params['RelayState']}" : ""
24
+ "SAMLRequest=#{params['SAMLRequest']}#{relay_state}&SigAlg=#{params['SigAlg']}"
25
+ end
26
+
27
+ def sign_params(options={})
28
+ key = OpenSSL::PKey::RSA.new options[:private_key]
29
+ Base64.encode64(key.sign(OpenSSL::Digest::SHA1.new, options[:params])).gsub("\n", '')
30
+ end
31
+
32
+ def verify_params(options={})
33
+ cert = OpenSSL::X509::Certificate.new(options[:cert_pem])
34
+ key = OpenSSL::PKey::RSA.new cert.public_key
35
+ key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(options[:signature]), parse_signature_params(options[:query_string]))
36
+ end
37
+
38
+ def sign!(xml, options={})
39
+ raise "Missing :id_attr option" if options[:id_attr].nil?
40
+ in_tmp_dir do
41
+ options[:private_key_path] = create_tmp_file(options[:private_key])
42
+ xml_file_path = create_tmp_file xml
43
+ command = sign_command(xml_file_path, options)
44
+ result, exitstatus = run command
45
+ if exitstatus == 0
46
+ result
47
+ else
48
+ run sign_command(xml_file_path, options.merge(:debug => true))
49
+ raise "unable to sign xml: #{command}\ngot error #{exitstatus}:\n#{result}"
50
+ end
51
+ end
52
+ end
53
+
54
+ # You can add --pubkey rsapub.pem or --trusted rootcert.pem to check that signature
55
+ # is actually valid. See http://www.aleksey.com/pipermail/xmlsec/2003/001120.html
56
+ def verify_signature!(xml, options={})
57
+ in_tmp_dir do
58
+ if options[:id_attr].blank?
59
+ raise "Missing :id_attr option"
60
+ end
61
+ if options[:cert_pem].blank?
62
+ raise "Missing :cert_pem option"
63
+ else
64
+ options[:cert_path] = create_tmp_file(options[:cert_pem])
65
+ end
66
+ command = verify_command(create_tmp_file(xml), options)
67
+ result, exitstatus = run command
68
+ if (exitstatus) != 0
69
+ raise "unable to validate xml signature: #{command}\ngot error #{exitstatus}:\n#{result}"
70
+ end
71
+ result
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.logger
77
+ @logger ||= Logger.new('test.log')
78
+ end
79
+
80
+ private #------------------------------------------------------------------------------
81
+
82
+ class << self
83
+ def in_tmp_dir
84
+ Dir.mktmpdir do |dir|
85
+ Dir.chdir(dir) do
86
+ yield
87
+ end
88
+ end
89
+ end
90
+
91
+ def create_tmp_file contents
92
+ file_path = "signing_tmp_#{Time.now.to_f}_#{::SecureRandom.hex}"
93
+ File.open(file_path, 'w+') do |f|
94
+ f.puts contents.to_s.strip
95
+ end
96
+ file_path
97
+ end
98
+
99
+ def run command
100
+ result = `#{command}`
101
+ exitstatus = $?.exitstatus
102
+ if exitstatus != 0
103
+ logger.error "Got exitstatus '#{exitstatus}' when running #{command}:\n#{result}"
104
+ end
105
+ [result, exitstatus]
106
+ end
107
+
108
+ def sign_command(xml_file_path, options)
109
+ command = xml_sec_command
110
+ command << " --sign "
111
+ command << " --print-debug " if options[:debug]
112
+ command << " --id-attr:#{options[:id_attr]} " if options[:id_attr]
113
+ command << " --enabled-reference-uris empty,same-doc,local "
114
+ command << " --privkey-pem #{options[:private_key_path]} " if options[:private_key_path]
115
+ command << " #{xml_file_path}"
116
+ command
117
+ end
118
+
119
+ def verify_command(xml_file_path, options)
120
+ command = xml_sec_command
121
+ command << " --verify "
122
+ command << " --print-debug " if options[:debug]
123
+ command << " --id-attr:#{options[:id_attr]} " if options[:id_attr]
124
+ command << " --enabled-reference-uris empty,same-doc,local "
125
+ command << " --pubkey-cert-pem #{options[:cert_path]}" if options[:cert_path]
126
+ command << " #{xml_file_path} 2>&1"
127
+ end
128
+
129
+ def xml_sec_command
130
+ "xmlsec1"
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,18 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDhzCCAm8CBgE4cUAd7DANBgkqhkiG9w0BAQsFADCBhjELMAkGA1UEBhMCREUxDzANBgNVBAgT
3
+ Bkhlc3NlbjEaMBgGA1UEBxMRRnJhbmtmdXJ0IGFtIE1haW4xDTALBgNVBAoTBFJBQk8xHTAbBgNV
4
+ BAsTFE1lcmNoYW50IFhNTCBTaWduaW5nMRwwGgYDVQQDExNBdG9zIFdvcmxkbGluZSBHbWJIMB4X
5
+ DTEyMDYzMDIyMDAwMFoXDTE3MDcwMTEwMDAwMFowgYYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZI
6
+ ZXNzZW4xGjAYBgNVBAcTEUZyYW5rZnVydCBhbSBNYWluMQ0wCwYDVQQKEwRSQUJPMR0wGwYDVQQL
7
+ ExRNZXJjaGFudCBYTUwgU2lnbmluZzEcMBoGA1UEAxMTQXRvcyBXb3JsZGxpbmUgR21iSDCCASIw
8
+ DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+hIul8xb811QmqLg9mzkoHh+0BdnQJZCqyHyFM
9
+ eY7tkFOW8vwi13OxSMGLFzNnehjitMievsx2s5yQW/3NrjG3xLw/18PJrSIgulngs6Cjw9mmIRSn
10
+ FZp3ViZR5aEmjP3aHGxIT7MTqt/AzU6TVaOYur55WmiOFJSA15AN+Onf3U+H06y/kbZlj9+QwKxe
11
+ 6jEZnaMlfSyhct5elswqEKjencUUU6qdRmsH8nSXmyrFJstXKlZsygDBJQSWxHqNE4r6lnYmpflC
12
+ 76KMNcW1xsp58Qa8axlpZ3UjL+nVtBKw4t+R2ebQcz12N+vv/8TBJd8ckZ+YwW4Cm2fGGcc2CcEC
13
+ AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAaLXoT+EezVl4YFcFTWI58Zg7C7WujRn+pTSFGN8MLFtx
14
+ MKfujLAMRh3YPVzXr2yE1kdMiMbq7IKtsKpb3PPgD3rb6YrP7zcwzDRxkXs802BgVxCPmdYrsa1i
15
+ PdJReq2VVTKoBHXSiKWowwBQFPOOc1XjFHcJ3Nq5WgssGEjk+puRW+i8GLaIv1KdwVlWLyHNArTs
16
+ W5JCcdtBhnMDz/g3/fRMu4EAnVFVmM75KNztVvgqkt+mZVuXfHfTCSv2RVFbrJvm/xrCmGk1VxDE
17
+ t4zSMdFEi98xh8DOC1oIhMf6JDImL1JyHqTljOIjBCo2uE5TFqQ/QZiOk0IC8Rb9y3lb2g==
18
+ -----END CERTIFICATE-----
@@ -0,0 +1,41 @@
1
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48RGlyZWN0
2
+ b3J5UmVzIHhtbG5zPSJodHRwOi8vd3d3LmlkZWFsZGVzay5jb20vaWRlYWwv
3
+ bWVzc2FnZXMvbWVyLWFjcS8zLjMuMSIgeG1sbnM6bnMyPSJodHRwOi8vd3d3
4
+ LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiB2ZXJzaW9uPSIzLjMuMSI+CiAg
5
+ ICA8Y3JlYXRlRGF0ZVRpbWVzdGFtcD4yMDEzLTA3LTExVDEwOjA2OjU5Ljg3
6
+ Nlo8L2NyZWF0ZURhdGVUaW1lc3RhbXA+CiAgICA8QWNxdWlyZXI+CiAgICAg
7
+ ICAgPGFjcXVpcmVySUQ+MDAyMDwvYWNxdWlyZXJJRD4KICAgIDwvQWNxdWly
8
+ ZXI+CiAgICA8RGlyZWN0b3J5PgogICAgICAgIDxkaXJlY3RvcnlEYXRlVGlt
9
+ ZXN0YW1wPjIwMTMtMDctMTFUMTA6MDY6NTkuODc2WjwvZGlyZWN0b3J5RGF0
10
+ ZVRpbWVzdGFtcD4KICAgICAgICA8Q291bnRyeT4KICAgICAgICAgICAgPGNv
11
+ dW50cnlOYW1lcz5EZXV0c2NobGFuZDwvY291bnRyeU5hbWVzPgogICAgICAg
12
+ ICAgICA8SXNzdWVyPgogICAgICAgICAgICAgICAgPGlzc3VlcklEPklOR0JO
13
+ TDJBPC9pc3N1ZXJJRD4KICAgICAgICAgICAgICAgIDxpc3N1ZXJOYW1lPklz
14
+ c3VlciBTaW11bGF0aW9uIFYzIC0gSU5HPC9pc3N1ZXJOYW1lPgogICAgICAg
15
+ ICAgICA8L0lzc3Vlcj4KICAgICAgICAgICAgPElzc3Vlcj4KICAgICAgICAg
16
+ ICAgICAgIDxpc3N1ZXJJRD5SQUJPTkwyVTwvaXNzdWVySUQ+CiAgICAgICAg
17
+ ICAgICAgICA8aXNzdWVyTmFtZT5Jc3N1ZXIgU2ltdWxhdGlvbiBWMyAtIFJB
18
+ Qk88L2lzc3Vlck5hbWU+CiAgICAgICAgICAgIDwvSXNzdWVyPgogICAgICAg
19
+ IDwvQ291bnRyeT4KICAgIDwvRGlyZWN0b3J5Pgo8U2lnbmF0dXJlIHhtbG5z
20
+ PSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48U2lnbmVk
21
+ SW5mbz48Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6
22
+ Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjxTaWduYXR1
23
+ cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0
24
+ L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+PFJlZmVyZW5jZSBVUkk9IiI+
25
+ PFRyYW5zZm9ybXM+PFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cu
26
+ dzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+
27
+ PC9UcmFuc2Zvcm1zPjxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8v
28
+ d3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48RGlnZXN0VmFs
29
+ dWU+a2NnajNnSWppMFk1OEJ4MlhTTUFNZytUNmhqa21HbWhTNWFRc21IUW4r
30
+ MD08L0RpZ2VzdFZhbHVlPjwvUmVmZXJlbmNlPjwvU2lnbmVkSW5mbz48U2ln
31
+ bmF0dXJlVmFsdWU+bHNXOUo2bXVPWWpFMVJpMGgwZHNFYVRqTU96MEs0RnFv
32
+ bnlOamNIbkFONTE2Um9HOTZDcGxvQWJRdHBhUlAva0trajBlRUh4UWVXeQoy
33
+ WFZHcHliYjRTUjNqdU96ZlM3b21rcHhoeXZqZkJVSStjNUZrUTBma1dmOHFB
34
+ YVRxeUxoSXhhVGtGZHpNZ0xvKy9CU3QvNExuenFZClVmcDRLR1hlQmpDZHRU
35
+ Mmg0R2F3eFo4c0Y1cUlXQzg5SUl5UkNwMXhuVmUzQlVlTkc3RmNSN3dlV1dY
36
+ MGhIZDZhaHF6aUxTMnFWYW8KRUZZdmRaK003ajVWZ0hndUt2aWtlK01tKzlW
37
+ Mmo0UlVuMVJobWg5R2ZsR1VzK2c4SWtWRmtibkdQQ3JJVm5HOElUOWYrOSsr
38
+ cFQzegpPMGJ5NzZKWXVRRDdnWFFrMnZ3dFBkVjFMTHBRMHdoMjJ2UUtrUT09
39
+ PC9TaWduYXR1cmVWYWx1ZT48S2V5SW5mbz48S2V5TmFtZT5GQzBBMTdBN0FC
40
+ RDcyMzY5NzI2RUE0RDREQkVGOTgzODEyOEE3Qzc4PC9LZXlOYW1lPjwvS2V5
41
+ SW5mbz48L1NpZ25hdHVyZT48L0RpcmVjdG9yeVJlcz4=
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <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#">
3
+ <foo:Bar>bar</foo:Bar>
4
+ <ds:Signature>
5
+ <ds:SignedInfo>
6
+ <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
7
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
8
+ <ds:Reference URI="#foo">
9
+ <ds:Transforms>
10
+ <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
11
+ </ds:Transforms>
12
+ <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
13
+ <ds:DigestValue></ds:DigestValue>
14
+ </ds:Reference>
15
+ </ds:SignedInfo>
16
+ <ds:SignatureValue></ds:SignatureValue>
17
+ </ds:Signature>
18
+ </foo:Foo>
@@ -9,16 +9,37 @@ describe Xmldsig do
9
9
  describe "#{document}" do
10
10
  let(:unsigned_xml) { File.read(document) }
11
11
  let(:unsigned_document) { Xmldsig::SignedDocument.new(unsigned_xml) }
12
+ let(:signed_document) { unsigned_document.sign(private_key) }
12
13
 
13
14
  it "should be signable an validateable" do
14
- signed_document = unsigned_document.sign(private_key)
15
15
  Xmldsig::SignedDocument.new(signed_document).validate(certificate).should be_true
16
16
  end
17
-
17
+
18
18
  it 'should have a signature element' do
19
- signed_document = unsigned_document.sign(private_key)
20
19
  Xmldsig::SignedDocument.new(signed_document).signatures.count.should == 1
21
20
  end
21
+
22
+ # TODO: remove this verification step when library matures
23
+ #it 'matches the result from xmlsec1' do
24
+ # result = `xmlsec1 --sign --id-attr:ID http://example.com/foo#:Foo --privkey-pem spec/fixtures/key.pem #{document}`
25
+ # result.gsub!("\n", '')
26
+ # signed_document.gsub!("\n", '')
27
+ # result.should == signed_document
28
+ #end
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "Verify signed documents" do
34
+ Dir["spec/fixtures/signed/*.txt"].each do |document|
35
+ describe "#{document}" do
36
+ let(:signed_xml) { Base64.decode64(File.read(document)) }
37
+ let(:signed_document) { Xmldsig::SignedDocument.new(signed_xml) }
38
+ let(:certificate) { OpenSSL::X509::Certificate.new(File.read(document.gsub('.txt', '.cert'))) }
39
+
40
+ it "should be validateable" do
41
+ signed_document.validate(certificate).should be_true
42
+ end
22
43
  end
23
44
  end
24
45
  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.2.1
4
+ version: 0.2.2
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-07-10 00:00:00.000000000 Z
12
+ date: 2013-08-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -37,6 +37,7 @@ files:
37
37
  - .gitignore
38
38
  - .rspec
39
39
  - .travis.yaml
40
+ - CHANGELOG.md
40
41
  - Gemfile
41
42
  - Guardfile
42
43
  - LICENSE
@@ -52,16 +53,20 @@ files:
52
53
  - lib/xmldsig/transforms/enveloped_signature.rb
53
54
  - lib/xmldsig/transforms/transform.rb
54
55
  - lib/xmldsig/version.rb
56
+ - signing_service.rb
55
57
  - spec/fixtures/certificate.cer
56
58
  - spec/fixtures/certificate2.cer
57
59
  - spec/fixtures/key.pem
58
60
  - spec/fixtures/signed.xml
61
+ - spec/fixtures/signed/ideal.cert
62
+ - spec/fixtures/signed/ideal.txt
59
63
  - spec/fixtures/unsigned.xml
60
64
  - spec/fixtures/unsigned/canonicalizer_1_0.xml
61
65
  - spec/fixtures/unsigned/canonicalizer_1_1.xml
62
66
  - spec/fixtures/unsigned/canonicalizer_exc.xml
63
67
  - spec/fixtures/unsigned/digest_sha1.xml
64
68
  - spec/fixtures/unsigned/with_soap_envelope.xml
69
+ - spec/fixtures/unsigned/without_canonicalization.xml
65
70
  - spec/fixtures/unsigned/without_namespace_prefix.xml
66
71
  - spec/fixtures/unsigned/without_reference_uri.xml
67
72
  - spec/fixtures/unsigned_multiple_references.xml
@@ -103,12 +108,15 @@ test_files:
103
108
  - spec/fixtures/certificate2.cer
104
109
  - spec/fixtures/key.pem
105
110
  - spec/fixtures/signed.xml
111
+ - spec/fixtures/signed/ideal.cert
112
+ - spec/fixtures/signed/ideal.txt
106
113
  - spec/fixtures/unsigned.xml
107
114
  - spec/fixtures/unsigned/canonicalizer_1_0.xml
108
115
  - spec/fixtures/unsigned/canonicalizer_1_1.xml
109
116
  - spec/fixtures/unsigned/canonicalizer_exc.xml
110
117
  - spec/fixtures/unsigned/digest_sha1.xml
111
118
  - spec/fixtures/unsigned/with_soap_envelope.xml
119
+ - spec/fixtures/unsigned/without_canonicalization.xml
112
120
  - spec/fixtures/unsigned/without_namespace_prefix.xml
113
121
  - spec/fixtures/unsigned/without_reference_uri.xml
114
122
  - spec/fixtures/unsigned_multiple_references.xml