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'
VERSION =
"1.3.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/xmldsig-more#rsa-sha256',
      digester: lambda { OpenSSL::Digest::SHA256.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::Engine.load
      gost_engine = OpenSSL::Engine.by_id('gost')
      gost_engine.set_default(0xFFFF)
      gost_engine.digest('md_gost94')
    },
  },
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document) ⇒ Signer

Returns a new instance of Signer.



16
17
18
19
20
# File 'lib/signer.rb', line 16

def initialize(document)
  self.document = Nokogiri::XML(document.to_s, &:noblanks)
  self.digest_algorithm = :sha1
  self.set_default_signature_method!
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

#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



70
71
72
# File 'lib/signer.rb', line 70

def security_node
  @security_node ||= document.xpath("//o:Security", "o" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd").first
end

#security_token_idObject



66
67
68
# File 'lib/signer.rb', line 66

def security_token_id
  @security_token_id ||= "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

Instance Method Details

#binary_security_token_nodeObject

<o:BinarySecurityToken u:Id=“” ValueType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3” EncodingType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary”>

...

</o:BinarySecurityToken> <SignedInfo>

...

</SignedInfo> <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>



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/signer.rb', line 120

def binary_security_token_node
  node = document.xpath("//o:BinarySecurityToken", "o" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd").first
  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)
    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('o:SecurityTokenReference', document)
    key_info_node.add_child(security_token_reference_node)
    reference_node = Nokogiri::XML::Node.new('o: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) ⇒ Object



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

def canonicalize(node = document)
  node.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, nil, nil) # The last argument should be exactly +nil+ to remove comments from result
end

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

<Reference URI=“#_0”>

<Transforms>
  <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>aeqXriJuUCk4tPNPAGDXGqHj6ao=</DigestValue>

</Reference>



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/signer.rb', line 184

def digest!(target_node, options = {})
  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)}"
  if id.to_s.size > 0
    wsu_ns ||= namespace_prefix(target_node, WSU_NAMESPACE, 'wsu')
    target_node["#{wsu_ns}:Id"] = id.to_s
  end
  target_canon = canonicalize(target_node)
  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}" : ""
  signed_info_node.add_child(reference_node)

  transforms_node = Nokogiri::XML::Node.new('Transforms', document)
  reference_node.add_child(transforms_node)

  transform_node = Nokogiri::XML::Node.new('Transform', document)
  if options[:enveloped]
    transform_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
  else
    transform_node['Algorithm'] = 'http://www.w3.org/2001/10/xml-exc-c14n#'
  end
  transforms_node.add_child(transform_node)

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

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

#digest_algorithmObject

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



27
28
29
# File 'lib/signer.rb', line 27

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.



36
37
38
# File 'lib/signer.rb', line 36

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

#sign!(options = {}) ⇒ Object

<SignatureValue>…</SignatureValue>



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/signer.rb', line 221

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

  if options[:issuer_serial]
    x509_data_node
  end

  signed_info_canon = canonicalize(signed_info_node)

  signature = private_key.sign(@sign_digester.digester, signed_info_canon)
  signature_value_digest = Base64.encode64(signature).gsub("\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)
  self
end

#signature_digest_algorithmObject

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



41
42
43
# File 'lib/signer.rb', line 41

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=



46
47
48
# File 'lib/signer.rb', line 46

def signature_digest_algorithm=(algorithm)
  @sign_digester = Signer::Digester.new(algorithm)
end

#signature_nodeObject

<Signature xmlns=“www.w3.org/2000/09/xmldsig#”>



79
80
81
82
83
84
85
86
87
# File 'lib/signer.rb', line 79

def signature_node
  node = document.xpath("//ds:Signature", "ds" => "http://www.w3.org/2000/09/xmldsig#").first
  unless node
    node = Nokogiri::XML::Node.new('Signature', document)
    node.default_namespace = 'http://www.w3.org/2000/09/xmldsig#'
    security_node.add_child(node)
  end
  node
end

#signed_info_nodeObject

<SignedInfo>

<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
...

</SignedInfo>



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/signer.rb', line 94

def signed_info_node
  node = signature_node.xpath("//ds:SignedInfo", "ds" => 'http://www.w3.org/2000/09/xmldsig#').first
  unless node
    node = Nokogiri::XML::Node.new('SignedInfo', document)
    signature_node.add_child(node)
    canonicalization_method_node = Nokogiri::XML::Node.new('CanonicalizationMethod', document)
    canonicalization_method_node['Algorithm'] = 'http://www.w3.org/2001/10/xml-exc-c14n#'
    node.add_child(canonicalization_method_node)
    signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document)
    signature_method_node['Algorithm'] = self.signature_algorithm_id
    node.add_child(signature_method_node)
  end
  node
end

#to_xmlObject



22
23
24
# File 'lib/signer.rb', line 22

def to_xml
  document.to_xml(:save_with => 0)
end

#x509_data_nodeObject

<KeyInfo>

<X509Data>
  <X509IssuerSerial>
    <X509IssuerName>System.Security.Cryptography.X509Certificates.X500DistinguishedName</X509IssuerName>
    <X509SerialNumber>13070789</X509SerialNumber>
  </X509IssuerSerial>
  <X509Certificate>MIID+jCCAuKgAwIBAgIEAMdxxTANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJTRTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMScwJQYDVQQDEx5Ob3JkZWEgcm9sZS1jZXJ0aWZpY2F0ZXMgQ0EgMDExFDASBgNVBAUTCzUxNjQwNi0wMTIwMB4XDTA5MDYxMTEyNTAxOVoXDTExMDYxMTEyNTAxOVowcjELMAkGA1UEBhMCU0UxIDAeBgNVBAMMF05vcmRlYSBEZW1vIENlcnRpZmljYXRlMRQwEgYDVQQEDAtDZXJ0aWZpY2F0ZTEUMBIGA1UEKgwLTm9yZGVhIERlbW8xFTATBgNVBAUTDDAwOTU1NzI0Mzc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcgz5AzbxTbsCE51No7fPnSqmQBIMW9OiPkiHotwYQTl+H9qwDvQRyBqHN26tnw7hNvEShd1ZRGUg4drMEXDV5CmKqsAevs9lauWDaHnGKPNHZJ1hNNYXHwymksEz5zMnG8eqRdhb4vOV2FzreJeYpsgx31Bv0aTofHcHVz4uGcCAwEAAaOCASAwggEcMAkGA1UdEwQCMAAwEQYDVR0OBAoECEj6Y9/vU03WMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEIFjfLBeTpRMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVhLnNlL1JDQTAxMA4GA1UdDwEB/wQEAwIGQDCBiAYDVR0fBIGAMH4wfKB6oHiGdmxkYXA6Ly9sZGFwLm5iLnNlL2NuPU5vcmRlYSUyMHJvbGUtY2VydGlmaWNhdGVzJTIwQ0ElMjAwMSxvPU5vcmRlYSUyMEJhbmslMjBBQiUyMChwdWJsKSxjPVNFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBAEXUv87VpHk51y3TqkMb1MYDqeKvQRE1cNcvhEJhIzdDpXMA9fG0KqvSTT1e0ZI2r78mXDvtTZnpic44jX2XMSmKO6n+1taAXq940tJUhF4arYMUxwDKOso0Doanogug496gipqMlpLgvIhGt06sWjNrvHzp2eGydUFdCsLr2ULqbDcut7g6eMcmrsnrOntjEU/J3hO8gyCeldJ+fI81qarrK/I0MZLR5LWCyVG/SKduoxHLX7JohsbIGyK1qAh9fi8l6X1Rcu80v5inpu71E/DnjbkAZBo7vsj78zzdk7KNliBIqBcIszdJ3dEHRWSI7FspRxyiR0NDm4lpyLwFtfw=</X509Certificate>
</X509Data>

</KeyInfo>



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/signer.rb', line 151

def x509_data_node
  issuer_name_node   = Nokogiri::XML::Node.new('X509IssuerName', document)
  issuer_name_node.content = "System.Security.Cryptography.X509Certificates.X500DistinguishedName"

  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).gsub("\n", '')

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

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

  signed_info_node.add_next_sibling(key_info_node)

  data_node
end