signer 1.2.1 → 1.3.0

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.
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