xmldsig 0.2.1 → 0.2.2

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