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 +8 -8
- data/CHANGELOG.md +4 -0
- data/README.md +17 -0
- data/lib/signer.rb +55 -5
- data/lib/signer/digester.rb +74 -0
- data/lib/signer/version.rb +1 -1
- data/spec/fixtures/output_1_sha256.xml +2 -0
- data/spec/signer_spec.rb +21 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YjcwMWM0ZWJiMGRmZWVlZmYzMWJlZmNiZjk3MDViODMwMDk5ZGRiMg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YjgwNGI1ZDFmYTc4NWMyZWM1NTdhNTk1ZjFiY2MxMmY5MWYzYjMzZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MTRmNjU2NDM4MjRhM2ZlZTA2MmFjMWQyNDZmMDVkNTEwOTAwMGE1MDBmMjc5
|
10
|
+
ZTE4Mzg5MjBjMWViYmU0NTMwYzgzY2E4YmVkMzc2NWNlNjhmZWIzM2JiMzI3
|
11
|
+
MjVhNDU3YTQwMmQ1ZjYwOTNmMWMxOGQwOWY1NDNlNzM3ZjA0NTI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YmM4ZDhlYTU4YmQ3NDllYzYyZDg0MTU5Y2Q2OGY1ODM3YmI2Yzg4ODc4Yjkz
|
14
|
+
OTlhNDQ1NmM5OTliNmRkZmVlMzI5MzZlOWQxZjE3YTUyZjQ2MWQ2ZWU0Y2U1
|
15
|
+
NzRlODhjMjBjOWZhZDBmNGI0NjdmYWUyOGU5NjMxNDc3OWViMWE=
|
data/CHANGELOG.md
CHANGED
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.
|
data/lib/signer.rb
CHANGED
@@ -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, :
|
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'] =
|
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(
|
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'] =
|
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(
|
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
|
data/lib/signer/version.rb
CHANGED
@@ -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>
|
data/spec/signer_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|