Class: Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/signer.rb,
lib/signer/version.rb,
lib/signer/digester.rb

Defined Under Namespace

Classes: Digester

Constant Summary collapse

WSU_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'.freeze
WSSE_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'.freeze
DS_NAMESPACE =
'http://www.w3.org/2000/09/xmldsig#'.freeze
SIGNATURE_ALGORITHM =
{
  # SHA 1
  sha1: {
    id: 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
    name: 'SHA1'
  },
  # SHA 256
  sha256: {
    id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    name: 'SHA256'
  },
  # SHA512
  sha512: {
    id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512',
    name: 'SHA512'
  },
  # GOST R 34-11 94
  gostr3411: {
    id: 'http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411',
    name: 'GOST R 34.11-94'
  },
  # GOST R 34-11 2012 256 bit
  gostr34112012_256: {
    id: 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256',
    name: 'GOST R 34.11-2012 256',
  },
}.freeze
CANONICALIZE_ALGORITHM =
{
  c14n_exec_1_0: {
    name: 'c14n execlusive 1.0',
    value: Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
    id: 'http://www.w3.org/2001/10/xml-exc-c14n#'
  },
  c14n_1_0: {
    name: 'c14n 1.0',
    value: Nokogiri::XML::XML_C14N_1_0,
    id: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
  },
  c14n_1_1: {
    name: 'c14n 1.1',
    value: Nokogiri::XML::XML_C14N_1_1,
    id: 'https://www.w3.org/TR/2008/REC-xml-c14n11-20080502/'
  }
}.freeze
VERSION =
'1.10.0'
DIGEST_ALGORITHMS =

Digest algorithms supported "out of the box"

{
  # SHA 1
  sha1: {
    name: 'SHA1',
    id: 'http://www.w3.org/2000/09/xmldsig#sha1',
    digester: lambda { OpenSSL::Digest::SHA1.new },
  },
  # SHA 256
  sha256: {
    name: 'SHA256',
    id: 'http://www.w3.org/2001/04/xmlenc#sha256',
    digester: lambda { OpenSSL::Digest::SHA256.new },
  },
  # SHA512
  sha512: {
    name: 'SHA512',
    id: 'http://www.w3.org/2001/04/xmlenc#sha512',
    digester: lambda { OpenSSL::Digest::SHA512.new },
  },
  # GOST R 34-11 94
  gostr3411: {
    name: 'GOST R 34.11-94',
    id: 'http://www.w3.org/2001/04/xmldsig-more#gostr3411',
    digester: lambda { OpenSSL::Digest.new('md_gost94') },
  },
  # GOST R 34-11 2012 256 bit
  gostr34112012_256: {
    name: 'GOST R 34.11-2012 256',
    id: 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256',
    digester: lambda { begin OpenSSL::Digest.new('streebog256') rescue OpenSSL::Digest.new('md_gost12_256') end },
  },
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document, noblanks: true, wss: true, canonicalize_algorithm: :c14n_exec_1_0) ⇒ Signer

Returns a new instance of Signer.



64
65
66
67
68
69
70
71
72
73
# File 'lib/signer.rb', line 64

def initialize(document, noblanks: true, wss: true, canonicalize_algorithm: :c14n_exec_1_0)
  self.document = Nokogiri::XML(document.to_s) do |config|
    config.noblanks if noblanks
  end
  self.document.namespace_inheritance = true if self.document.respond_to?(:namespace_inheritance)
  self.digest_algorithm = :sha1
  self.wss = wss
  self.canonicalize_algorithm = canonicalize_algorithm
  self.signature_digest_algorithm = :sha1
end

Instance Attribute Details

#certObject

Returns the value of attribute cert.



11
12
13
# File 'lib/signer.rb', line 11

def cert
  @cert
end

#documentObject

Returns the value of attribute document.



10
11
12
# File 'lib/signer.rb', line 10

def document
  @document
end

#ds_namespace_prefixObject

Returns the value of attribute ds_namespace_prefix.



10
11
12
# File 'lib/signer.rb', line 10

def ds_namespace_prefix
  @ds_namespace_prefix
end

#private_keyObject

Returns the value of attribute private_key.



10
11
12
# File 'lib/signer.rb', line 10

def private_key
  @private_key
end

#security_nodeObject



136
137
138
# File 'lib/signer.rb', line 136

def security_node
  @security_node ||= wss? ? document.xpath('//wsse:Security', wsse: WSSE_NAMESPACE).first : ''
end

#security_token_idObject



132
133
134
# File 'lib/signer.rb', line 132

def security_token_id
  @security_token_id ||= wss? ? "uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1" : ""
end

#signature_algorithm_idObject

Returns the value of attribute signature_algorithm_id.



10
11
12
# File 'lib/signer.rb', line 10

def signature_algorithm_id
  @signature_algorithm_id
end

#signature_nodeObject



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/signer.rb', line 145

def signature_node
  @signature_node ||= begin
    @signature_node = security_node.at_xpath('ds:Signature', ds: DS_NAMESPACE)
    unless @signature_node
      @signature_node = Nokogiri::XML::Node.new('Signature', document)
      set_namespace_for_node(@signature_node, DS_NAMESPACE, ds_namespace_prefix)
      security_node.add_child(@signature_node)
    end
    @signature_node
  end
end

#wssObject

Returns the value of attribute wss.



10
11
12
# File 'lib/signer.rb', line 10

def wss
  @wss
end

Instance Method Details

#binary_security_token_nodeObject

<o:BinarySecurityToken u:Id="" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3&quot; EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary&quot;&gt; ... </o:BinarySecurityToken> ... <o:SecurityTokenReference> <o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3&quot; URI="#uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"/> </o:SecurityTokenReference>



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/signer.rb', line 191

def binary_security_token_node
  return unless wss?
  node = document.at_xpath('wsse:BinarySecurityToken', wsse: WSSE_NAMESPACE)
  unless node
    node = Nokogiri::XML::Node.new('BinarySecurityToken', document)
    node['ValueType']    = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'
    node['EncodingType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'
    node.content = Base64.encode64(cert.to_der).gsub("\n", '')
    signature_node.add_previous_sibling(node)
    wsse_ns = namespace_prefix(node, WSSE_NAMESPACE, 'wsse')
    wsu_ns = namespace_prefix(node, WSU_NAMESPACE, 'wsu')
    node["#{wsu_ns}:Id"] = security_token_id
    key_info_node = Nokogiri::XML::Node.new('KeyInfo', document)
    security_token_reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:SecurityTokenReference", document)
    key_info_node.add_child(security_token_reference_node)
    set_namespace_for_node(key_info_node, DS_NAMESPACE, ds_namespace_prefix)
    reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:Reference", document)
    reference_node['ValueType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'
    reference_node['URI'] = "##{security_token_id}"
    security_token_reference_node.add_child(reference_node)
    signed_info_node.add_next_sibling(key_info_node)
  end
  node
end

#canonicalize(node = document, inclusive_namespaces = nil, algorithm: canonicalize_algorithm) ⇒ Object



140
141
142
# File 'lib/signer.rb', line 140

def canonicalize(node = document, inclusive_namespaces=nil, algorithm: canonicalize_algorithm)
  node.canonicalize(algorithm, inclusive_namespaces, nil)
end

#canonicalize_algorithmObject



87
88
89
# File 'lib/signer.rb', line 87

def canonicalize_algorithm
  @canonicalize_algorithm[:value]
end

#canonicalize_algorithm=(algorithm) ⇒ Object



91
92
93
# File 'lib/signer.rb', line 91

def canonicalize_algorithm=(algorithm)
  @canonicalize_algorithm = CANONICALIZE_ALGORITHM[algorithm]
end

#canonicalize_idObject



83
84
85
# File 'lib/signer.rb', line 83

def canonicalize_id
  @canonicalize_algorithm[:id]
end

#canonicalize_nameObject



79
80
81
# File 'lib/signer.rb', line 79

def canonicalize_name
  @canonicalize_algorithm[:name]
end

#digest!(target_node, options = {}) ⇒ Object

Digests some target_node, which integrity you wish to track. Any changes in digested node will invalidate signed message. All digest should be calculated before signing.

Available options:

  • [+:id+] Id for the node, if you don't want to use automatically calculated one
  • [+:inclusive_namespaces+] Array of namespace prefixes which definitions should be added to node during canonicalization
  • [+:enveloped+]
  • [+:enveloped_legacy+] add solely enveloped-signature in Transforms with :enveloped:.
  • [+:ref_type+] add Type attribute to Reference node, if ref_type is not nil

Example of XML that will be inserted in message for call like digest!(node, inclusive_namespaces: ['soap']):

aeqXriJuUCk4tPNPAGDXGqHj6ao=


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/signer.rb', line 289

def digest!(target_node, options = {})
  if wss?
    wsu_ns = namespace_prefix(target_node, WSU_NAMESPACE)
    current_id = target_node["#{wsu_ns}:Id"] if wsu_ns
    id = options[:id] || current_id || "_#{Digest::SHA1.hexdigest(target_node.to_s)}"
    unless id.to_s.empty?
      wsu_ns ||= namespace_prefix(target_node, WSU_NAMESPACE, 'wsu')
      target_node["#{wsu_ns}:Id"] = id.to_s
    end
  elsif target_node['Id'].nil?
    id = options[:id] || "_#{Digest::SHA1.hexdigest(target_node.to_s)}"
    target_node['Id'] = id.to_s unless id.empty?
  else
    id = options[:id] || target_node['Id']
  end

  target_canon = canonicalize(target_node, options[:inclusive_namespaces])
  target_digest = Base64.encode64(@digester.digest(target_canon)).strip

  reference_node = Nokogiri::XML::Node.new('Reference', document)
  reference_node['URI'] = id.to_s.size > 0 ? "##{id}" : ""
  reference_node['Type'] = options[:ref_type] if options[:ref_type]

  signed_info_node.add_child(reference_node)
  set_namespace_for_node(reference_node, DS_NAMESPACE, ds_namespace_prefix)

  transforms_node = Nokogiri::XML::Node.new('Transforms', document)
  reference_node.add_child(transforms_node) unless options[:no_transform]
  set_namespace_for_node(transforms_node, DS_NAMESPACE, ds_namespace_prefix)

  # create reference + transforms node
  transform!(transforms_node, options)

  digest_method_node = Nokogiri::XML::Node.new('DigestMethod', document)
  digest_method_node['Algorithm'] = @digester.digest_id

  reference_node.add_child(digest_method_node)
  set_namespace_for_node(digest_method_node, DS_NAMESPACE, ds_namespace_prefix)

  digest_value_node = Nokogiri::XML::Node.new('DigestValue', document)
  digest_value_node.content = target_digest
  reference_node.add_child(digest_value_node)
  set_namespace_for_node(digest_value_node, DS_NAMESPACE, ds_namespace_prefix)
  self
end

#digest_algorithmObject

Return symbol name for supported digest algorithms and string name for custom ones.



96
97
98
# File 'lib/signer.rb', line 96

def digest_algorithm
  @digester.symbol || @digester.digest_name
end

#digest_algorithm=(algorithm) ⇒ Object

Allows to change algorithm for node digesting (default is SHA1).

You may pass either a one of :sha1, :sha256 or :gostr3411 symbols or Hash with keys :id with a string, which will denote algorithm in XML Reference tag and :digester with instance of class with interface compatible with OpenSSL::Digest class.



105
106
107
# File 'lib/signer.rb', line 105

def digest_algorithm=(algorithm)
  @digester = Signer::Digester.new(algorithm)
end

#sign!(options = {}) ⇒ Object

Sign document with provided certificate, private key and other options

This should be very last action before calling to_xml, all the required nodes should be digested with digest! before signing.

Available options:

  • [+:security_token+] Serializes certificate in DER format, encodes it with Base64 and inserts it within <BinarySecurityToken> tag
  • [+:issuer_serial+]
  • [+:issuer_in_security_token+]
  • [+:inclusive_namespaces+] Array of namespace prefixes which definitions should be added to signed info node during canonicalization


346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/signer.rb', line 346

def sign!(options = {})
  if options[:security_token]
    binary_security_token_node
  end

  if options[:issuer_serial]
    x509_data_node(options[:issuer_in_security_token])
  end

  if options[:inclusive_namespaces]
    c14n_method_node = signed_info_node.at_xpath('ds:CanonicalizationMethod', ds: DS_NAMESPACE)
    inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document)
    inclusive_namespaces_node.add_namespace_definition('ec', c14n_method_node['Algorithm'])
    inclusive_namespaces_node['PrefixList'] = options[:inclusive_namespaces].join(' ')
    c14n_method_node.add_child(inclusive_namespaces_node)
  end

  signed_info_canon = canonicalize(signed_info_node, options[:inclusive_namespaces])

  signature = private_key.sign(@sign_digester.digester, signed_info_canon)
  signature_value_digest = Base64.encode64(signature).delete("\n")

  signature_value_node = Nokogiri::XML::Node.new('SignatureValue', document)
  signature_value_node.content = signature_value_digest
  signed_info_node.add_next_sibling(signature_value_node)
  set_namespace_for_node(signature_value_node, DS_NAMESPACE, ds_namespace_prefix)
  self
end

#signature_digest_algorithmObject

Return symbol name for supported digest algorithms and string name for custom ones.



110
111
112
# File 'lib/signer.rb', line 110

def signature_digest_algorithm
  @sign_digester.symbol || @sign_digester.digest_name
end

#signature_digest_algorithm=(algorithm) ⇒ Object

Allows to change digesting algorithm for signature creation. Same as digest_algorithm=



115
116
117
118
# File 'lib/signer.rb', line 115

def signature_digest_algorithm=(algorithm)
  @sign_digester = Signer::Digester.new(algorithm)
  self.signature_algorithm_id = SIGNATURE_ALGORITHM[algorithm][:id]
end

#signed_info_nodeObject

...


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/signer.rb', line 162

def signed_info_node
  node = signature_node.at_xpath('ds:SignedInfo', ds: DS_NAMESPACE)
  unless node
    node = Nokogiri::XML::Node.new('SignedInfo', document)
    signature_node.add_child(node)
    set_namespace_for_node(node, DS_NAMESPACE, ds_namespace_prefix)
    canonicalization_method_node = Nokogiri::XML::Node.new('CanonicalizationMethod', document)
    canonicalization_method_node['Algorithm'] = canonicalize_id
    node.add_child(canonicalization_method_node)
    set_namespace_for_node(canonicalization_method_node, DS_NAMESPACE, ds_namespace_prefix)
    signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document)
    signature_method_node['Algorithm'] = self.signature_algorithm_id
    node.add_child(signature_method_node)
    set_namespace_for_node(signature_method_node, DS_NAMESPACE, ds_namespace_prefix)
  end
  node
end

#to_xmlObject



75
76
77
# File 'lib/signer.rb', line 75

def to_xml
  document.to_xml(save_with: 0)
end

#x509_data_node(issuer_in_security_token = false) ⇒ Object

(optional) System.Security.Cryptography.X509Certificates.X500DistinguishedName 13070789 MIID+jCCAuKgAwIBAgIEAMdxxTANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJTRTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMScwJQYDVQQDEx5Ob3JkZWEgcm9sZS1jZXJ0aWZpY2F0ZXMgQ0EgMDExFDASBgNVBAUTCzUxNjQwNi0wMTIwMB4XDTA5MDYxMTEyNTAxOVoXDTExMDYxMTEyNTAxOVowcjELMAkGA1UEBhMCU0UxIDAeBgNVBAMMF05vcmRlYSBEZW1vIENlcnRpZmljYXRlMRQwEgYDVQQEDAtDZXJ0aWZpY2F0ZTEUMBIGA1UEKgwLTm9yZGVhIERlbW8xFTATBgNVBAUTDDAwOTU1NzI0Mzc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcgz5AzbxTbsCE51No7fPnSqmQBIMW9OiPkiHotwYQTl+H9qwDvQRyBqHN26tnw7hNvEShd1ZRGUg4drMEXDV5CmKqsAevs9lauWDaHnGKPNHZJ1hNNYXHwymksEz5zMnG8eqRdhb4vOV2FzreJeYpsgx31Bv0aTofHcHVz4uGcCAwEAAaOCASAwggEcMAkGA1UdEwQCMAAwEQYDVR0OBAoECEj6Y9/vU03WMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEIFjfLBeTpRMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVhLnNlL1JDQTAxMA4GA1UdDwEB/wQEAwIGQDCBiAYDVR0fBIGAMH4wfKB6oHiGdmxkYXA6Ly9sZGFwLm5iLnNlL2NuPU5vcmRlYSUyMHJvbGUtY2VydGlmaWNhdGVzJTIwQ0ElMjAwMSxvPU5vcmRlYSUyMEJhbmslMjBBQiUyMChwdWJsKSxjPVNFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBAEXUv87VpHk51y3TqkMb1MYDqeKvQRE1cNcvhEJhIzdDpXMA9fG0KqvSTT1e0ZI2r78mXDvtTZnpic44jX2XMSmKO6n+1taAXq940tJUhF4arYMUxwDKOso0Doanogug496gipqMlpLgvIhGt06sWjNrvHzp2eGydUFdCsLr2ULqbDcut7g6eMcmrsnrOntjEU/J3hO8gyCeldJ+fI81qarrK/I0MZLR5LWCyVG/SKduoxHLX7JohsbIGyK1qAh9fi8l6X1Rcu80v5inpu71E/DnjbkAZBo7vsj78zzdk7KNliBIqBcIszdJ3dEHRWSI7FspRxyiR0NDm4lpyLwFtfw= (optional)


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/signer.rb', line 227

def x509_data_node(issuer_in_security_token = false)
  issuer_name_node   = Nokogiri::XML::Node.new('X509IssuerName', document)
  issuer_name_node.content = cert.issuer.to_s(OpenSSL::X509::Name::RFC2253)

  issuer_number_node = Nokogiri::XML::Node.new('X509SerialNumber', document)
  issuer_number_node.content = cert.serial

  issuer_serial_node = Nokogiri::XML::Node.new('X509IssuerSerial', document)
  issuer_serial_node.add_child(issuer_name_node)
  issuer_serial_node.add_child(issuer_number_node)

  cetificate_node    = Nokogiri::XML::Node.new('X509Certificate', document)
  cetificate_node.content = Base64.encode64(cert.to_der).delete("\n")

  data_node          = Nokogiri::XML::Node.new('X509Data', document)
  data_node.add_child(issuer_serial_node)
  data_node.add_child(cetificate_node)

  if issuer_in_security_token
    security_token_reference_node = Nokogiri::XML::Node.new("wsse:SecurityTokenReference", document)
    security_token_reference_node.add_child(data_node)
  end

  key_info_node      = Nokogiri::XML::Node.new('KeyInfo', document)
  key_info_node.add_child(issuer_in_security_token ? security_token_reference_node : data_node)

  signed_info_node.add_next_sibling(key_info_node)

  set_namespace_for_node(key_info_node, DS_NAMESPACE, ds_namespace_prefix)
  set_namespace_for_node(security_token_reference_node, WSSE_NAMESPACE, ds_namespace_prefix) if issuer_in_security_token
  set_namespace_for_node(data_node, DS_NAMESPACE, ds_namespace_prefix)
  set_namespace_for_node(issuer_serial_node, DS_NAMESPACE, ds_namespace_prefix)
  set_namespace_for_node(cetificate_node, DS_NAMESPACE, ds_namespace_prefix)
  set_namespace_for_node(issuer_name_node, DS_NAMESPACE, ds_namespace_prefix)
  set_namespace_for_node(issuer_number_node, DS_NAMESPACE, ds_namespace_prefix)

  data_node
end