Class: Kiji::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/kiji/signer.rb

Constant Summary collapse

WSU_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
WSSE_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document) {|_self| ... } ⇒ Signer

Returns a new instance of Signer.

Yields:

  • (_self)

Yield Parameters:

  • _self (Kiji::Signer)

    the object that the method was called on



20
21
22
23
24
25
26
27
# File 'lib/kiji/signer.rb', line 20

def initialize(document)
  # self.document = Nokogiri::XML(document.to_s, &:noblanks)
  self.document = Nokogiri::XML(document.to_s)
  self.digest_algorithm = :sha1
  self.set_default_signature_method!

  yield(self) if block_given?
end

Instance Attribute Details

#certObject

Returns the value of attribute cert.



14
15
16
# File 'lib/kiji/signer.rb', line 14

def cert
  @cert
end

#documentObject

Returns the value of attribute document.



13
14
15
# File 'lib/kiji/signer.rb', line 13

def document
  @document
end

#private_keyObject

Returns the value of attribute private_key.



13
14
15
# File 'lib/kiji/signer.rb', line 13

def private_key
  @private_key
end

#security_nodeObject



77
78
79
# File 'lib/kiji/signer.rb', line 77

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

#security_token_idObject



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

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.



13
14
15
# File 'lib/kiji/signer.rb', line 13

def signature_algorithm_id
  @signature_algorithm_id
end

#signature_nodeObject

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



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/kiji/signer.rb', line 87

def signature_node
  @signature_node ||= begin
    @signature_node = security_node.at_xpath('ds:Signature', ds: 'http://www.w3.org/2000/09/xmldsig#')
    unless @signature_node
      @signature_node = Nokogiri::XML::Node.new('Signature', document)
      @signature_node['Id'] = DateTime.now.strftime('%Y%m%d%H%M%S')
      @signature_node.default_namespace = 'http://www.w3.org/2000/09/xmldsig#'
      security_node.add_child(@signature_node)
    end
    @signature_node
  end
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>



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/kiji/signer.rb', line 131

def binary_security_token_node
  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)
    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) ⇒ Object



81
82
83
84
# File 'lib/kiji/signer.rb', line 81

def canonicalize(node = document, inclusive_namespaces = nil)
  # node.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, inclusive_namespaces, nil) # The last argument should be exactly +nil+ to remove comments from result
  node.canonicalize(Nokogiri::XML::XML_C14N_1_1, inclusive_namespaces, nil) # The last argument should be exactly +nil+ to remove comments from result
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

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

<Reference URI="#_0">
  <Transforms>
    <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
      <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap" />
    </Transform>
  </Transforms>
  <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
  <DigestValue>aeqXriJuUCk4tPNPAGDXGqHj6ao=</DigestValue>
</Reference>


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/kiji/signer.rb', line 206

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, options[:inclusive_namespaces])
  # target_digest = Base64.encode64(@digester.digest(target_canon)).strip
  target_digest = @digester.base64(target_canon)

  reference_node = Nokogiri::XML::Node.new('Reference', document)
  reference_node['URI'] = id.to_s.size > 0 ? encode_ja(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#'
    transform_node['Algorithm'] = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
  end
  if options[:inclusive_namespaces]
    inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document)
    inclusive_namespaces_node.add_namespace_definition('ec', transform_node['Algorithm'])
    inclusive_namespaces_node['PrefixList'] = options[:inclusive_namespaces].join(' ')
    transform_node.add_child(inclusive_namespaces_node)
  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.



34
35
36
# File 'lib/kiji/signer.rb', line 34

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.



43
44
45
# File 'lib/kiji/signer.rb', line 43

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

#digest_file!(file_content, options = {}) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/kiji/signer.rb', line 250

def digest_file!(file_content, options = {})
  # target_digest = Base64.encode64(@digester.digest(file_content)).strip
  target_digest = @digester.base64(file_content)

  reference_node = Nokogiri::XML::Node.new('Reference', document)
  id = options[:id]
  reference_node['URI'] = id.to_s.size > 0 ? encode_ja(id) : ''
  signed_info_node.add_child(reference_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

#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
  • :inclusive_namespaces

    Array of namespace prefixes which definitions should be added to signed info node during canonicalization



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/kiji/signer.rb', line 279

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

  if options[:inclusive_namespaces]
    c14n_method_node = signed_info_node.at_xpath('ds:CanonicalizationMethod', ds: 'http://www.w3.org/2000/09/xmldsig#')
    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).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.



48
49
50
# File 'lib/kiji/signer.rb', line 48

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=



53
54
55
# File 'lib/kiji/signer.rb', line 53

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

#signed_info_nodeObject

<SignedInfo>

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

</SignedInfo>



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/kiji/signer.rb', line 105

def signed_info_node
  node = signature_node.at_xpath('ds:SignedInfo', ds: 'http://www.w3.org/2000/09/xmldsig#')
  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/TR/2001/REC-xml-c14n-20010315'
    node.add_child(canonicalization_method_node)
    signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document)
    signature_method_node['Algorithm'] = signature_algorithm_id
    node.add_child(signature_method_node)
  end
  node
end

#to_xmlObject



29
30
31
# File 'lib/kiji/signer.rb', line 29

def to_xml
  document.to_xml(save_with: 0, encoding: 'UTF-8')
end

#x509_data_nodeObject

<KeyInfo>

<X509Data>
  <X509Certificate>MIID+jCCAuKgAwIBA...</X509Certificate>
</X509Data>

</KeyInfo>



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/kiji/signer.rb', line 159

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