Module: Sepa::Utilities

Included in:
ApplicationRequest, ApplicationResponse, Client, NordeaResponse, OpResponse, Response, SoapBuilder
Defined in:
lib/sepa/utilities.rb

Overview

Contains utility methods that are used in this gem.

Instance Method Summary collapse

Instance Method Details

#calculate_digest(node) ⇒ String

Calculates a SHA1 digest for a given node. Before the calculation, the node is canonicalized exclusively.

Parameters:

  • node (Nokogiri::Node)

    the node which the digest is calculated from

Returns:

  • (String)

    the calculated digest



9
10
11
12
13
14
15
# File 'lib/sepa/utilities.rb', line 9

def calculate_digest(node)
  sha1 = OpenSSL::Digest::SHA1.new

  canon_node = canonicalize_exclusively(node)

  encode(sha1.digest(canon_node)).gsub(/\s+/, "")
end

#canonicalize_exclusively(value) ⇒ String

Canonicalizes an xml node exclusively without comments

Parameters:

  • value (Nokogiri::XML::Node, #canonicalize)

    the node to be canonicalized

Returns:

  • (String)

    the canonicalized node



189
190
191
# File 'lib/sepa/utilities.rb', line 189

def canonicalize_exclusively(value)
  value.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
end

#canonicalized_node(doc, namespace, node) ⇒ String?

Canonicalizes a node inclusively

Parameters:

  • doc (Nokogiri::XML::Document)

    the document that contains the node

  • namespace (String)

    the namespace of the node

  • node (String)

    name of the node

Returns:

  • (String)

    the canonicalized node if the node can be found

  • (nil)

    if the node cannot be found



164
165
166
167
# File 'lib/sepa/utilities.rb', line 164

def canonicalized_node(doc, namespace, node)
  content_node = doc.at("xmlns|#{node}", xmlns: namespace)
  content_node.canonicalize if content_node
end

#cert_request_valid?(cert_request) ⇒ true, false

TODO:

rename

Checks whether a certificate signing request is valid

Parameters:

  • cert_request (#to_s)

    the certificate signing request

Returns:

  • (true)

    if the certificate signing request is valid

  • (false)

    if the certificate signing request is not valid



104
105
106
107
108
109
110
111
112
# File 'lib/sepa/utilities.rb', line 104

def cert_request_valid?(cert_request)
  begin
    OpenSSL::X509::Request.new cert_request
  rescue
    return false
  end

  true
end

#check_validity_against_schema(doc, schema) ⇒ Object

Validates whether a doc is valid against a schema. Adds error using ActiveModel validations if document is not valid against the schema.

Parameters:

  • doc (Nokogiri::XML::Document)

    the document to validate

  • schema (String)

    name of the schema file in SCHEMA_PATH



61
62
63
64
65
66
67
68
# File 'lib/sepa/utilities.rb', line 61

def check_validity_against_schema(doc, schema)
  Dir.chdir(SCHEMA_PATH) do
    xsd = Nokogiri::XML::Schema(IO.read(schema))
    unless doc.respond_to?(:canonicalize) && xsd.valid?(doc)
      errors.add(:base, 'The document did not validate against the schema file')
    end
  end
end

#csr_to_binary(csr) ⇒ String

Converts a certificate signing request from base64 encoded string to binary string

Parameters:

  • csr (#to_s)

    certificate signing request in base64 encoded format

Returns:

  • (String)

    the certificate signing request in binary format



153
154
155
# File 'lib/sepa/utilities.rb', line 153

def csr_to_binary(csr)
  OpenSSL::X509::Request.new(csr).to_der
end

#decode(value) ⇒ String

Decodes a base64 encoded string

Parameters:

  • value (#to_s)

    the base64 encoded string

Returns:

  • (String)

    the decoded string



181
182
183
# File 'lib/sepa/utilities.rb', line 181

def decode(value)
  Base64.decode64 value
end

#encode(value) ⇒ String

Base64 encodes a given value

Parameters:

  • value (#to_s)

    the value to be encoded

Returns:

  • (String)

    the base64 encoded string



229
230
231
# File 'lib/sepa/utilities.rb', line 229

def encode(value)
  Base64.encode64 value
end

#extract_cert(doc, node, namespace) ⇒ OpenSSL::X509::Certificate?

TODO:

refactor not to fail

Extracts a certificate from a document and returns it as an OpenSSL X509 certificate. Returns nil if the node cannot be found

Parameters:

  • doc (Nokogiri::XML::Document)

    the document that contains the certificate node

  • node (String)

    the name of the node that contains the certificate

  • namespace (String)

    the namespace of the certificate node

Returns:

  • (OpenSSL::X509::Certificate)

    the extracted certificate if it is extracted successfully

  • (nil)

    if the certificate cannot be found

Raises:

  • (OpenSSL::X509::CertificateError)

    if there is a problem with the certificate



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/sepa/utilities.rb', line 80

def extract_cert(doc, node, namespace)
  cert_raw = doc.at("xmlns|#{node}", 'xmlns' => namespace)

  return nil unless cert_raw

  cert_raw = cert_raw.content.gsub(/\s+/, "")

  cert = process_cert_value(cert_raw)

  begin
    x509_certificate(cert)
  rescue => e
    fail OpenSSL::X509::CertificateError,
         "The certificate could not be processed. It's most likely corrupted. " \
         "OpenSSL had this to say: #{e}."
  end
end

#format_cert(cert) ⇒ String

TODO:

rename maybe

Removes begin and end certificate texts from a certificate and removes whitespaces to make the certificate read to be embedded in xml.

Parameters:

  • cert (#to_s)

    The certificate to be formatted

Returns:

  • (String)

    the formatted certificate

See Also:



37
38
39
40
41
42
# File 'lib/sepa/utilities.rb', line 37

def format_cert(cert)
  cert = cert.to_s
  cert = cert.split('-----BEGIN CERTIFICATE-----')[1]
  cert = cert.split('-----END CERTIFICATE-----')[0]
  cert.gsub!(/\s+/, "")
end

#format_cert_request(cert_request) ⇒ String

TODO:

rename

Removes begin and end certificate request texts from a certificate signing request and removes whitespaces

Parameters:

  • cert_request (String)

    the certificate request to be formatted

Returns:

  • (String)

    the formatted certificate request



50
51
52
53
54
# File 'lib/sepa/utilities.rb', line 50

def format_cert_request(cert_request)
  cert_request = cert_request.split('-----BEGIN CERTIFICATE REQUEST-----')[1]
  cert_request = cert_request.split('-----END CERTIFICATE REQUEST-----')[0]
  cert_request.gsub!(/\s+/, "")
end

#hmac(pin, csr) ⇒ String

Calculates an HMAC for a given pin and certificate signing request. Used by Nordea certificate requests.

Parameters:

  • pin (#to_s)

    the one-time pin number got from bank

  • csr (#to_s)

    the certificate signing request

Returns:

  • (String)

    the generated HMAC for the values



145
146
147
# File 'lib/sepa/utilities.rb', line 145

def hmac(pin, csr)
  encode(OpenSSL::HMAC.digest('sha1', pin, csr)).chop
end

#iso_timeString

Gets current utc time in iso-format

Returns:

  • (String)

    current utc time in iso-format



135
136
137
# File 'lib/sepa/utilities.rb', line 135

def iso_time
  @iso_time ||= Time.now.utc.iso8601
end

#load_body_template(template) ⇒ Nokogiri::XML::Document

Loads a soap or application request xml template according to a parameter and command.

Parameters:

Returns:

  • (Nokogiri::XML::Document)

    the loaded template

Raises:

  • (ArgumentError)

    if a template cannot be found for a command



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/sepa/utilities.rb', line 120

def load_body_template(template)
  fail ArgumentError, 'Unsupported command' unless SUPPORTED_COMMANDS.include?(@command)

  file = if STANDARD_COMMANDS.include?(@command)
           "#{template}/#{@command}.xml"
         else
           "#{template}/#{@bank}/#{@command}.xml"
         end

  xml_doc(File.open(file))
end

#process_cert_value(cert_value) ⇒ String

TODO:

rename maybe because this seems more formatting than #format_cert

Takes a certificate, adds begin and end certificate texts and splits it into multiple lines so that OpenSSL can read it.

Parameters:

  • cert_value (#to_s)

    the certificate to be processed

Returns:

  • (String)

    the processed certificate



23
24
25
26
27
28
# File 'lib/sepa/utilities.rb', line 23

def process_cert_value(cert_value)
  cert = "-----BEGIN CERTIFICATE-----\n"
  cert << cert_value.to_s.gsub(/\s+/, "").scan(/.{1,64}/).join("\n")
  cert << "\n"
  cert << "-----END CERTIFICATE-----"
end

#rsa_key(key_as_string) ⇒ OpenSSL::PKey::RSA

Creates a new OpenSSL RSA key from a string key

Examples:

Example key to convert

"-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMLRRHwLWybhs0MM
EbpmZL2IdgYxmFbADFHoirTp3t22UnorHwqvk16i9YCPS6H8ngEPSP7A5urS/NMq
w+YmklKlZugO9/TLJqVv4smXFX+5rPMQDedRElIDempMGQlPaR+tp4CQ49oFLJLW
NH6bKFbAEXa7zhZNH00lFylnei23AgMBAAECgYEAqt912/7x4jaQTrxlSELLFVp9
eo1BesVTiPwXvPpsGbbyvGjZ/ztkXNs9zZbh1aCGzZMkiR2U7F5GlsiprlIif4cF
6Xz7rCjaAs7iDRt9PjhjVuqNGR2I+VIIlbQ9XWFJ3lJFW3v7TIZ8JbLnn0XOFz+Z
BBSSGTK1zTNh4TBQtjECQQDe5M3uu9m4RwSw9R6GaDw/IFQZgr0oWSv0WIjRwvwW
nFnSX2lbkNAjulP0daGsmn7vxIpqZxPxwcrU4wFqTF5dAkEA38DnbCm3YfogzwLH
Nre2hBmGqjWarhtxqtRarrkgnmOd8W0Z1Hb1dSHrliUSVSrINbK5ZdEV15Rpu7VD
OePzIwJAPMslS+8alANyyR0iJUC65fDYX1jkZOPldDDNqIDJJxWf/hwd7WaTDpuc
mHmZDi3ZX2Y45oqUywSzYNtFoIuR1QJAZYUZuyqmSK77SdGB36K1DfSi9AFEQDC1
fwPAbTwTv6mFFPAiYxLiRZXxVPtW+QtjMXH4ymh2V4y/+GnCqbZyLwJBAJQSDAME
Sn4Uz7Zjk3UrBIbMYEv0u2mcCypwsb0nGE5/gzDPjGE9cxWW+rXARIs+sNQVClnh
45nhdfYxOjgYff0=
-----END PRIVATE KEY-----"

Parameters:

  • key_as_string (to_s)

    the key as a string

Returns:

  • (OpenSSL::PKey::RSA)

    the OpenSSL RSA key



254
255
256
# File 'lib/sepa/utilities.rb', line 254

def rsa_key(key_as_string)
  OpenSSL::PKey::RSA.new key_as_string
end

#set_node_id(document, namespace, node, position) ⇒ String

TODO:

create functionality to automatically add reference nodes to header so than position is not needed

Generates a random id for a node in soap and sets it to the soap header

defines which reference is used. Numbering starts from 0.

Parameters:

  • document (Nokogiri::XML::Document)

    the document that contains the node

  • namespace (String)

    the namespace of the node

  • node (String)

    name of the node

  • position (Integer)

    the soap header might contain many references and this parameter

Returns:

  • (String)

    the generated id of the node



268
269
270
271
272
273
274
# File 'lib/sepa/utilities.rb', line 268

def set_node_id(document, namespace, node, position)
  node_id = "#{node.downcase}-#{SecureRandom.uuid}"
  document.at("xmlns|#{node}", xmlns: namespace)['wsu:Id'] = node_id
  @header_template.css('dsig|Reference')[position]['URI'] = "##{node_id}"

  node_id
end

#validate_signature(doc, certificate, canonicalization_method) ⇒ true, false

Verifies that a signature has been created with the private key of a certificate

Parameters:

  • doc (Nokogiri::XML::Document)

    the document that contains the signature

  • certificate (OpenSSL::X509::Certificate)

    the certificate to verify the signature against

  • canonicalization_method (Symbol)

    The canonicalization method that has been used to canonicalize the SignedInfo node. Accepts :normal or :exclusive.

Returns:

  • (true)

    if signature verifies

  • (false)

    if signature fails to verify or if it cannot be found



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/sepa/utilities.rb', line 285

def validate_signature(doc, certificate, canonicalization_method)
  node = doc.at('xmlns|SignedInfo', xmlns: DSIG)

  return false unless node

  node = case canonicalization_method
         when :normal
           node.canonicalize
         when :exclusive
           canonicalize_exclusively node
         end

  signature = doc.at('xmlns|SignatureValue', xmlns: DSIG).content
  signature = decode(signature)

  # Return true or false
  certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, node)
end

#verify_certificate_against_root_certificate(certificate, root_certificate) ⇒ true, false

Verifies that a certificate has been signed by the private key of a root certificate

Parameters:

  • certificate (OpenSSL::X509::Certificate)

    the certificate to verify

  • root_certificate (OpenSSL::X509::Certificate)

    the root certificate

Returns:

  • (true)

    if the certificate has been signed by the private key of the root certificate

  • (false)

    if the certificate has not been signed by the private key of the root certificate, the certificates are nil or the subject of the root certificate is not the issuer of the certificate



312
313
314
315
316
317
# File 'lib/sepa/utilities.rb', line 312

def verify_certificate_against_root_certificate(certificate, root_certificate)
  return false unless certificate && root_certificate
  return false unless root_certificate.subject == certificate.issuer

  certificate.verify(root_certificate.public_key)
end

#x509_certificate(value) ⇒ OpenSSL::X509::Certificate

Creates a new OpenSSL X509 certificate from a string

Examples:

Example certificate to convert

"-----BEGIN CERTIFICATE-----
MIIDwTCCAqmgAwIBAgIEAX1JuTANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJT
RTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMR8wHQYDVQQDExZOb3Jk
ZWEgQ29ycG9yYXRlIENBIDAxMRQwEgYDVQQFEws1MTY0MDYtMDEyMDAeFw0xMzA1
MDIxMjI2MzRaFw0xNTA1MDIxMjI2MzRaMEQxCzAJBgNVBAYTAkZJMSAwHgYDVQQD
DBdOb3JkZWEgRGVtbyBDZXJ0aWZpY2F0ZTETMBEGA1UEBRMKNTc4MDg2MDIzODCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwtFEfAtbJuGzQwwRumZkvYh2BjGY
VsAMUeiKtOne3bZSeisfCq+TXqL1gI9LofyeAQ9I/sDm6tL80yrD5iaSUqVm6A73
9MsmpW/iyZcVf7ms8xAN51ESUgN6akwZCU9pH62ngJDj2gUsktY0fpsoVsARdrvO
Fk0fTSUXKWd6LbcCAwEAAaOCAR0wggEZMAkGA1UdEwQCMAAwEQYDVR0OBAoECEBw
2cj7+XMAMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEALddbbzwun
MDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVh
LnNlL0NDQTAxMA4GA1UdDwEB/wQEAwIFoDCBhQYDVR0fBH4wfDB6oHigdoZ0bGRh
cCUzQS8vbGRhcC5uYi5zZS9jbiUzRE5vcmRlYStDb3Jwb3JhdGUrQ0ErMDElMkNv
JTNETm9yZGVhK0JhbmsrQUIrJTI4cHVibCUyOSUyQ2MlM0RTRSUzRmNlcnRpZmlj
YXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBACLUPB1Gmq6286/s
ROADo7N+w3eViGJ2fuOTLMy4R0UHOznKZNsuk4zAbS2KycbZsE5py4L8o+IYoaS8
8YHtEeckr2oqHnPpz/0Eg7wItj8Ad+AFWJqzbn6Hu/LQhlnl5JEzXzl3eZj9oiiJ
1q/2CGXvFomY7S4tgpWRmYULtCK6jode0NhgNnAgOI9uy76pSS16aDoiQWUJqQgV
ydowAnqS9h9aQ6gedwbOdtkWmwKMDVXU6aRz9Gvk+JeYJhtpuP3OPNGbbC5L7NVd
no+B6AtwxmG3ozd+mPcMeVuz6kKLAmQyIiBSrRNa5OrTkq/CUzxO9WUgTnm/Sri7
zReR6mU=
-----END CERTIFICATE-----"

Parameters:

  • value (#to_s)

    the string from which to create the certificate

Returns:

  • (OpenSSL::X509::Certificate)

    the OpenSSL X509 certificate



221
222
223
# File 'lib/sepa/utilities.rb', line 221

def x509_certificate(value)
  OpenSSL::X509::Certificate.new value
end

#xml_doc(value) ⇒ Nokogiri::XML::Document

Converts an xml string to a nokogiri document

Parameters:

  • value (to_s)

    the xml document

Returns:

  • (Nokogiri::XML::Document)

    the xml document



173
174
175
# File 'lib/sepa/utilities.rb', line 173

def xml_doc(value)
  Nokogiri::XML value
end