signer 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGVmNjVlMGNlMjM5ZmIyMzczYWIzNzRmNWYzOWRlZTA2NzFmMWZiOA==
4
+ YjcwMWM0ZWJiMGRmZWVlZmYzMWJlZmNiZjk3MDViODMwMDk5ZGRiMg==
5
5
  data.tar.gz: !binary |-
6
- ZDZjYWFiNmE2ODU5MGM4YjUxM2NjMzYyOTBmODBhZWQxYWJkYTFlNw==
6
+ YjgwNGI1ZDFmYTc4NWMyZWM1NTdhNTk1ZjFiY2MxMmY5MWYzYjMzZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzQ3NjE2NTgxZGFjNGI5NmE2NjAxZWQ0MjNhMDY1YjAzZWNlOGY2YmM1YzNl
10
- ZDNhYjEwZmQwYzYzYjc3NDA5OGRmM2MzYjgwZWFiYTQzYjllODNmNTllNjBk
11
- ZWI4MmIwNWE0ODg1OGRlNzk5NmUxYjNmODJlMjIxN2Y3ZDAzNTM=
9
+ MTRmNjU2NDM4MjRhM2ZlZTA2MmFjMWQyNDZmMDVkNTEwOTAwMGE1MDBmMjc5
10
+ ZTE4Mzg5MjBjMWViYmU0NTMwYzgzY2E4YmVkMzc2NWNlNjhmZWIzM2JiMzI3
11
+ MjVhNDU3YTQwMmQ1ZjYwOTNmMWMxOGQwOWY1NDNlNzM3ZjA0NTI=
12
12
  data.tar.gz: !binary |-
13
- OTkzZWFiN2I4YmNiN2QyOWQ2N2E1MTZkMmQ0ODgwMGZhMjEzZTg4YzgzMzk4
14
- NTJmNGY3ZWMzZTUzYzNjZWUzZmI2NTA0YWExM2EzZjY5ODEwY2ZhNGQ3NDRm
15
- ZmI3ZWZmNTFlYWMyZjM3YjNlY2UwOTJiNDE5YzQ4N2Y0MWRiODI=
13
+ YmM4ZDhlYTU4YmQ3NDllYzYyZDg0MTU5Y2Q2OGY1ODM3YmI2Yzg4ODc4Yjkz
14
+ OTlhNDQ1NmM5OTliNmRkZmVlMzI5MzZlOWQxZjE3YTUyZjQ2MWQ2ZWU0Y2U1
15
+ NzRlODhjMjBjOWZhZDBmNGI0NjdmYWUyOGU5NjMxNDc3OWViMWE=
@@ -1,3 +1,7 @@
1
+ ## 1.3.0 (2014-06-16)
2
+
3
+ - Allow to sign with other digest algorithms - SHA1, SHA256, and GOST R 34.11-94 (#3, @Envek)
4
+
1
5
  ## 1.2.1 (2014-05-14)
2
6
 
3
7
  - Fix canonicalization: should be without comments (#2, @Envek)
data/README.md CHANGED
@@ -200,6 +200,23 @@ Output:
200
200
  </s:Envelope>
201
201
  ```
202
202
 
203
+ ## Different signature and digest algorithms support
204
+
205
+ You can change digest algorithms used for both node digesting and signing. Default for both is SHA1. Currently __SHA1__ `:sha1`, __SHA256__ `:sha256`, and __GOST R 34.11-94__ `:gostr3411` are supported out of the box.
206
+
207
+ ```ruby
208
+ signer.digest_algorithm = :sha256 # Set algorithm for node digesting
209
+ signer.signature_digest_algorithm = :sha256 # Set algorithm for message digesting for signing
210
+ ```
211
+
212
+ You can provide you own digest support by passing in these methods a `Hash` with `:id` and `:digester` keys. In `:id` should be a string for XML `//Reference/DigestMethod[Algorithm]`, in `:digester` should be a Ruby object, compatible by interface with `OpenSSL::Digest` class, at least it should respond to `digest` and `reset` methods.
213
+
214
+ Signature algorithm is dependent from keypair used for signing and can't be changed. Usually it's __RSA__. Currently gem recognizes __GOST R 34.10-2001__ certificates and sets up a XML identifier for it. If used signature algorithm and signature digest doesn't corresponds with XML identifier, you can change identifier with `signature_algorithm_id` method.
215
+
216
+ Please note, that these settings will be changed or reset on certificate assignment, please change them after setting certificate!
217
+
218
+ __NOTE__: To sign XMLs with __GOST R 34.10-2001__, you need to have Ruby compiled with patches from https://bugs.ruby-lang.org/issues/9830 and correctly configured OpenSSL (see https://github.com/openssl/openssl/blob/master/engines/ccgost/README.gost)
219
+
203
220
  ## Miscellaneous
204
221
 
205
222
  If you need to digest a `BinarySecurityToken` tag, you need to construct it yourself **before** signing.
@@ -3,22 +3,66 @@ require "base64"
3
3
  require "digest/sha1"
4
4
  require "openssl"
5
5
 
6
+ require "signer/digester"
6
7
  require "signer/version"
7
8
 
8
9
  class Signer
9
- attr_accessor :document, :cert, :private_key
10
+ attr_accessor :document, :private_key, :signature_algorithm_id
11
+ attr_reader :cert
10
12
  attr_writer :security_node, :security_token_id
11
13
 
12
14
  WSU_NAMESPACE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
13
15
 
14
16
  def initialize(document)
15
17
  self.document = Nokogiri::XML(document.to_s, &:noblanks)
18
+ self.digest_algorithm = :sha1
19
+ self.set_default_signature_method!
16
20
  end
17
21
 
18
22
  def to_xml
19
23
  document.to_xml(:save_with => 0)
20
24
  end
21
25
 
26
+ # Return symbol name for supported digest algorithms and string name for custom ones.
27
+ def digest_algorithm
28
+ @digester.symbol || @digester.digest_name
29
+ end
30
+
31
+ # Allows to change algorithm for node digesting (default is SHA1).
32
+ #
33
+ # You may pass either a one of +:sha1+, +:sha256+ or +:gostr3411+ symbols
34
+ # or +Hash+ with keys +:id+ with a string, which will denote algorithm in XML Reference tag
35
+ # and +:digester+ with instance of class with interface compatible with +OpenSSL::Digest+ class.
36
+ def digest_algorithm=(algorithm)
37
+ @digester = Signer::Digester.new(algorithm)
38
+ end
39
+
40
+ # Return symbol name for supported digest algorithms and string name for custom ones.
41
+ def signature_digest_algorithm
42
+ @sign_digester.symbol || @sign_digester.digest_name
43
+ end
44
+
45
+ # Allows to change digesting algorithm for signature creation. Same as +digest_algorithm=+
46
+ def signature_digest_algorithm=(algorithm)
47
+ @sign_digester = Signer::Digester.new(algorithm)
48
+ end
49
+
50
+ # Receives certificate for signing and tries to guess a digest algorithm for signature creation.
51
+ #
52
+ # Will change +signature_digest_algorithm+ and +signature_algorithm_id+ for known certificate types and reset to defaults for others.
53
+ def cert=(certificate)
54
+ @cert = certificate
55
+ # Try to guess a digest algorithm for signature creation
56
+ case @cert.signature_algorithm
57
+ when 'GOST R 34.11-94 with GOST R 34.10-2001'
58
+ self.signature_digest_algorithm = :gostr3411
59
+ self.signature_algorithm_id = 'http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411'
60
+ # Add clauses for other types of keys that require other digest algorithms and identifiers
61
+ else # most common 'sha1WithRSAEncryption' type included here
62
+ self.set_default_signature_method! # Reset any changes as they can become malformed
63
+ end
64
+ end
65
+
22
66
  def security_token_id
23
67
  @security_token_id ||= "uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"
24
68
  end
@@ -56,7 +100,7 @@ class Signer
56
100
  canonicalization_method_node['Algorithm'] = 'http://www.w3.org/2001/10/xml-exc-c14n#'
57
101
  node.add_child(canonicalization_method_node)
58
102
  signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document)
59
- signature_method_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
103
+ signature_method_node['Algorithm'] = self.signature_algorithm_id
60
104
  node.add_child(signature_method_node)
61
105
  end
62
106
  node
@@ -146,7 +190,7 @@ class Signer
146
190
  target_node["#{wsu_ns}:Id"] = id.to_s
147
191
  end
148
192
  target_canon = canonicalize(target_node)
149
- target_digest = Base64.encode64(OpenSSL::Digest::SHA1.digest(target_canon)).strip
193
+ target_digest = Base64.encode64(@digester.digest(target_canon)).strip
150
194
 
151
195
  reference_node = Nokogiri::XML::Node.new('Reference', document)
152
196
  reference_node['URI'] = id.to_s.size > 0 ? "##{id}" : ""
@@ -164,7 +208,7 @@ class Signer
164
208
  transforms_node.add_child(transform_node)
165
209
 
166
210
  digest_method_node = Nokogiri::XML::Node.new('DigestMethod', document)
167
- digest_method_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#sha1'
211
+ digest_method_node['Algorithm'] = @digester.digest_id
168
212
  reference_node.add_child(digest_method_node)
169
213
 
170
214
  digest_value_node = Nokogiri::XML::Node.new('DigestValue', document)
@@ -185,7 +229,7 @@ class Signer
185
229
 
186
230
  signed_info_canon = canonicalize(signed_info_node)
187
231
 
188
- signature = private_key.sign(OpenSSL::Digest::SHA1.new, signed_info_canon)
232
+ signature = private_key.sign(@sign_digester.digester, signed_info_canon)
189
233
  signature_value_digest = Base64.encode64(signature).gsub("\n", '')
190
234
 
191
235
  signature_value_node = Nokogiri::XML::Node.new('SignatureValue', document)
@@ -196,6 +240,12 @@ class Signer
196
240
 
197
241
  protected
198
242
 
243
+ # Reset digest algorithm for signature creation and signature algorithm identifier
244
+ def set_default_signature_method!
245
+ self.signature_digest_algorithm = :sha1
246
+ self.signature_algorithm_id = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
247
+ end
248
+
199
249
  ##
200
250
  # Searches in namespaces, defined on +target_node+ or its ancestors,
201
251
  # for the +namespace+ with given URI and returns its prefix.
@@ -0,0 +1,74 @@
1
+ require 'openssl'
2
+
3
+ class Signer
4
+
5
+ # Digest algorithms supported "out of the box"
6
+ DIGEST_ALGORITHMS = {
7
+ # SHA 1
8
+ sha1: {
9
+ name: 'SHA1',
10
+ id: 'http://www.w3.org/2000/09/xmldsig#sha1',
11
+ digester: lambda { OpenSSL::Digest::SHA1.new },
12
+ },
13
+ # SHA 256
14
+ sha256: {
15
+ name: 'SHA256',
16
+ id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
17
+ digester: lambda { OpenSSL::Digest::SHA256.new },
18
+ },
19
+ # GOST R 34-11 94
20
+ gostr3411: {
21
+ name: 'GOST R 34.11-94',
22
+ id: 'http://www.w3.org/2001/04/xmldsig-more#gostr3411',
23
+ digester: lambda {
24
+ OpenSSL::Engine.load
25
+ gost_engine = OpenSSL::Engine.by_id('gost')
26
+ gost_engine.set_default(0xFFFF)
27
+ gost_engine.digest('md_gost94')
28
+ },
29
+ },
30
+ }
31
+
32
+ # Class that holds +OpenSSL::Digest+ instance with some meta information for digesting in XML.
33
+ class Digester
34
+
35
+ # You may pass either a one of +:sha1+, +:sha256+ or +:gostr3411+ symbols
36
+ # or +Hash+ with keys +:id+ with a string, which will denote algorithm in XML Reference tag
37
+ # and +:digester+ with instance of class with interface compatible with +OpenSSL::Digest+ class.
38
+ def initialize(algorithm)
39
+ if algorithm.kind_of? Symbol
40
+ @digest_info = DIGEST_ALGORITHMS[algorithm].dup
41
+ @digest_info[:digester] = @digest_info[:digester].call
42
+ @symbol = algorithm
43
+ else
44
+ @digest_info = algorithm
45
+ end
46
+ end
47
+
48
+ attr_reader :symbol
49
+
50
+ # Digest
51
+ def digest(message)
52
+ self.digester.digest(message)
53
+ end
54
+
55
+ alias call digest
56
+
57
+ # Returns +OpenSSL::Digest+ (or derived class) instance
58
+ def digester
59
+ @digest_info[:digester].reset
60
+ end
61
+
62
+ # Human-friendly name
63
+ def digest_name
64
+ @digest_info[:name]
65
+ end
66
+
67
+ # XML-friendly name (for specifying in XML +DigestMethod+ node +Algorithm+ attribute)
68
+ def digest_id
69
+ @digest_info[:id]
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -1,3 +1,3 @@
1
1
  class Signer
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0"?>
2
+ <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:wsurandom="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://tempuri.org/IDocumentService/SearchDocuments</a:Action><a:MessageID>urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><To xmlns="http://www.w3.org/2005/08/addressing" xmlns:a="http://www.w3.org/2003/05/soap-envelope" a:mustUnderstand="1">http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc</To><o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1"><wsurandom:Timestamp><wsurandom:Created>2012-05-02T18:17:14.467Z</wsurandom:Created><wsurandom:Expires>2012-05-02T18:22:14.467Z</wsurandom:Expires></wsurandom:Timestamp><o:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" wsurandom:Id="uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1">MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=</o:BinarySecurityToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><DigestValue>frx76VWYEsTF+bL6gMKAs3NSkziiB2pRLVb2zZNOWZ0=</DigestValue></Reference></SignedInfo><SignatureValue>FqbJM5mu4LGECjGLNsGoV37qvMBbnukAbAUjHU9BeOkN8reoLfLdsoGvnl+T6+nv7Oaw5LrCDWVdA31RmbqVMn0qPm39CtIAPncUJD0Zr1XKvL2oIA8ySgR4pqQR/GM8fdOkQPRIXXAjTxg6UuVYmXn0vfpLeH2uqoQR1L98Rj8=</SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"/></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body><SearchDocuments xmlns="http://tempuri.org/"><searchCriteria xmlns:b="http://schemas.datacontract.org/2004/07/BusinessLogic.Data.Documents.Integration" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><b:RegistrationNo>1</b:RegistrationNo></searchCriteria></SearchDocuments></s:Body></s:Envelope>
@@ -47,6 +47,27 @@ describe Signer do
47
47
  signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(:save_with => 0)
48
48
  end
49
49
 
50
+ it "should digest and sign SOAP XML with SHA256" do
51
+ input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_1.xml')
52
+ cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
53
+ private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
54
+
55
+ signer = Signer.new(File.read(input_xml_file))
56
+ signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
57
+ signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
58
+ signer.digest_algorithm = :sha256
59
+ signer.signature_digest_algorithm = :sha256
60
+ signer.signature_algorithm_id = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
61
+
62
+ signer.digest!(signer.binary_security_token_node)
63
+
64
+ signer.sign!
65
+
66
+ output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_1_sha256.xml')
67
+
68
+ signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(:save_with => 0)
69
+ end
70
+
50
71
  it "should sign simple XML" do
51
72
  input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_2.xml')
52
73
  cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: signer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edgars Beigarts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-14 00:00:00.000000000 Z
11
+ date: 2014-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  version_requirements: !ruby/object:Gem::Requirement
@@ -59,6 +59,7 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - lib/signer/digester.rb
62
63
  - lib/signer/version.rb
63
64
  - lib/signer.rb
64
65
  - README.md
@@ -70,6 +71,7 @@ files:
70
71
  - spec/fixtures/input_3_c14n_comments.xml
71
72
  - spec/fixtures/key.pem
72
73
  - spec/fixtures/output_1.xml
74
+ - spec/fixtures/output_1_sha256.xml
73
75
  - spec/fixtures/output_2.xml
74
76
  - spec/fixtures/output_3_c14n_comments.xml
75
77
  - spec/signer_spec.rb
@@ -104,6 +106,7 @@ test_files:
104
106
  - spec/fixtures/input_3_c14n_comments.xml
105
107
  - spec/fixtures/key.pem
106
108
  - spec/fixtures/output_1.xml
109
+ - spec/fixtures/output_1_sha256.xml
107
110
  - spec/fixtures/output_2.xml
108
111
  - spec/fixtures/output_3_c14n_comments.xml
109
112
  - spec/signer_spec.rb