sepafm 0.1.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/sepa/response.rb CHANGED
@@ -1,10 +1,27 @@
1
1
  module Sepa
2
+
3
+ # Handles soap responses got back from the bank. Bank specific functionality is defined in
4
+ # subclasses. Handles i.e. logic to make sure the response's integrity has not been compromised
5
+ # and has methods to extract content from the response.
2
6
  class Response
3
7
  include ActiveModel::Validations
4
8
  include Utilities
5
9
  include ErrorMessages
6
10
 
7
- attr_reader :soap, :error, :command
11
+ # The raw soap response in xml
12
+ #
13
+ # @return [String]
14
+ attr_reader :soap
15
+
16
+ # Possible Savon::Error with which the {Response} was initialized
17
+ #
18
+ # @return [String]
19
+ attr_reader :error
20
+
21
+ # The command with which the response was initialized
22
+ #
23
+ # @return [Symbol]
24
+ attr_reader :command
8
25
 
9
26
  validate :document_must_validate_against_schema
10
27
  validate :client_errors
@@ -13,6 +30,16 @@ module Sepa
13
30
  validate :verify_signature
14
31
  validate :verify_certificate
15
32
 
33
+ # Initializes the response with a options hash
34
+ #
35
+ # @param hash [Hash] Hash of options
36
+ # @example Possible keys in options hash
37
+ # {
38
+ # response: "something",
39
+ # command: :get_user_info,
40
+ # error: "I'm error",
41
+ # encryption_private_key: OpenSSL::PKey::RSA
42
+ # }
16
43
  def initialize(hash = {})
17
44
  @soap = hash[:response]
18
45
  @command = hash[:command]
@@ -20,13 +47,27 @@ module Sepa
20
47
  @encryption_private_key = hash[:encryption_private_key]
21
48
  end
22
49
 
50
+ # Returns the soap of the response as a Nokogiri document
51
+ #
52
+ # @return [Nokogiri::XML] The soap as Nokogiri document
23
53
  def doc
24
54
  @doc ||= xml_doc @soap
25
55
  end
26
56
 
27
- # Verifies that all digest values in the response match the actual ones.
28
- # Takes an optional verbose parameter to show which digests didn't match
29
- # i.e. verbose: true
57
+ # Verifies that all digest values in the response match the actual ones. Takes an optional
58
+ # verbose parameter to show which digests didn't match. The digest embedded in the document are
59
+ # first retrieved with {#find_digest_values} method and if none are found, false is returned.
60
+ # After this, nodes to calculate hashes from are retrieved and hashes using
61
+ # {#find_nodes_to_verify} method and after this the calculated digests are compared with the
62
+ # embedded ones. If the all match, true is returned. If some digests failed to verify and
63
+ # verbose parameter was passed, digests that failed to verify are printed to screen and
64
+ # false is returned. Otherwise just false is returned.
65
+ #
66
+ # @param options [Hash]
67
+ # @return [false] if hashes don't match or aren't found
68
+ # @return [true] if hashes match
69
+ # @example Options hash
70
+ # { verbose: true }
30
71
  def hashes_match?(options = {})
31
72
  digests = find_digest_values
32
73
 
@@ -53,17 +94,28 @@ module Sepa
53
94
  false
54
95
  end
55
96
 
56
- # Verifies the signature by extracting the public key from the certificate
57
- # embedded in the soap header and verifying the signature value with that.
97
+ # Verifies the signature by extracting the public key from the certificate embedded in the
98
+ # response and verifying the signature value with that. Makes a call to {#validate_signature}
99
+ # to do the actual verification. Passes `:exclusive` to {#validate_signature} so that exclusive
100
+ # mode of xml canonicalization is used.
101
+ #
102
+ # @return [true] if signature is valid
103
+ # @return [false] if signature fails to verify
58
104
  def signature_is_valid?
59
105
  validate_signature(doc, certificate, :exclusive)
60
106
  end
61
107
 
62
- # Gets the application response from the response as an xml document
108
+ # Gets the application response from the response as an xml document. Makes a call to
109
+ # {#extract_application_response} to do the extraction.
110
+ #
111
+ # @return [String] The application response as a raw xml document
63
112
  def application_response
64
113
  @application_response ||= extract_application_response(BXD)
65
114
  end
66
115
 
116
+ # Returns the file references in a download file list response
117
+ #
118
+ # @return [Array] File references
67
119
  def file_references
68
120
  return unless @command == :download_file_list
69
121
 
@@ -74,12 +126,23 @@ module Sepa
74
126
  end
75
127
  end
76
128
 
129
+ # Returns the certificate embedded in the response
130
+ #
131
+ # @return [OpenSSL::X509::Certificate] if the certificate is found
132
+ # @return [nil] if the certificate can't be found
133
+ # @raise [OpenSSL::X509::CertificateError] if the certificate cannot be processed
77
134
  def certificate
78
135
  @certificate ||= begin
79
136
  extract_cert(doc, 'BinarySecurityToken', OASIS_SECEXT)
80
137
  end
81
138
  end
82
139
 
140
+ # Returns the content of the response according to {#command}. When command is `:download_file`,
141
+ # content is returned as a base64 encoded string, when {#command} is `:download_file_list`, the
142
+ # content is returned as xml, when {#command} is `:get_user_info`, the content is returned as xml
143
+ # and when {#command} is `:upload_file`, content is returned as xml
144
+ #
145
+ # @return [String] the content as xml or base64 encoded string
83
146
  def content
84
147
  @content ||= begin
85
148
  xml = xml_doc(application_response)
@@ -103,22 +166,35 @@ module Sepa
103
166
  end
104
167
  end
105
168
 
169
+ # Returns the raw soap as xml
170
+ #
171
+ # @return [String]
106
172
  def to_s
107
173
  @soap
108
174
  end
109
175
 
176
+ # @abstract
110
177
  def bank_encryption_certificate; end
111
178
 
179
+ # @abstract
112
180
  def bank_signing_certificate; end
113
181
 
182
+ # @abstract
114
183
  def bank_root_certificate; end
115
184
 
185
+ # @abstract
116
186
  def own_encryption_certificate; end
117
187
 
188
+ # @abstract
118
189
  def own_signing_certificate; end
119
190
 
191
+ # @abstract
120
192
  def ca_certificate; end
121
193
 
194
+ # Returns the response code of the response
195
+ #
196
+ # @return [String] if the response code can be found
197
+ # @return [nil] if the response code cannot be found
122
198
  def response_code
123
199
  node = doc.at('xmlns|ResponseCode', xmlns: BXD)
124
200
  node.content if node
@@ -126,8 +202,10 @@ module Sepa
126
202
 
127
203
  private
128
204
 
129
- # Finds all reference nodes with digest values in the document and returns
130
- # a hash with uri as the key and digest as the value.
205
+ # Finds all reference nodes with digest values in the document and returns a hash with uri as
206
+ # the key and digest as the value.
207
+ #
208
+ # @return [Hash] hash of digests with reference uri as the key
131
209
  def find_digest_values
132
210
  references = {}
133
211
  reference_nodes = doc.css('xmlns|Reference', xmlns: DSIG)
@@ -142,8 +220,11 @@ module Sepa
142
220
  references
143
221
  end
144
222
 
145
- # Finds nodes to verify by comparing their id's to the uris' in the
146
- # references hash.
223
+ # Finds nodes to verify by comparing their id's to the uris' in the references hash. Then
224
+ # calculates the hashes of those nodes and returns them in a hash
225
+ #
226
+ # @param references [Hash]
227
+ # @return [Hash] hash of calculated digests with reference uri as the key
147
228
  def find_nodes_to_verify(references)
148
229
  nodes = {}
149
230
 
@@ -157,12 +238,18 @@ module Sepa
157
238
  nodes
158
239
  end
159
240
 
241
+ # Validates the document against soap schema unless {#error} is present or command is
242
+ # `:get_bank_certificate`
160
243
  def document_must_validate_against_schema
161
244
  return if @error || command.to_sym == :get_bank_certificate
162
245
 
163
246
  check_validity_against_schema(doc, 'soap.xsd')
164
247
  end
165
248
 
249
+ # Extracts and returns application response from the response
250
+ #
251
+ # @return [String] application response as raw xml if it can be found
252
+ # @return [nil] if application response cannot be found
166
253
  def extract_application_response(namespace)
167
254
  ar_node = doc.at('xmlns|ApplicationResponse', xmlns: namespace)
168
255
  if ar_node
@@ -170,15 +257,22 @@ module Sepa
170
257
  end
171
258
  end
172
259
 
260
+ # Handles errors that have been passed from client
173
261
  def client_errors
174
262
  client_error = error.to_s
175
263
  errors.add(:base, client_error) unless client_error.empty?
176
264
  end
177
265
 
266
+ # Find node by it's reference URI in soap header
267
+ #
268
+ # @param uri [String] the node's URI
269
+ # @return [Nokogiri::XML::Node]
178
270
  def find_node_by_uri(uri)
179
271
  doc.at("[xmlns|Id='#{uri}']", xmlns: OASIS_UTILITY)
180
272
  end
181
273
 
274
+ # Validates response code in response. "00" and "24" are currently considered valid.
275
+ # Validation is not run if {#error} is present
182
276
  def validate_response_code
183
277
  return if @error
184
278
 
@@ -187,14 +281,19 @@ module Sepa
187
281
  end
188
282
  end
189
283
 
284
+ # Validates hashes in the response. {#hashes_match?} must return true for validation to pass.
285
+ # Is not run if {#error} is present or response code is not ok.
190
286
  def validate_hashes
191
287
  return if @error
192
288
  return unless response_code_is_ok?
289
+
193
290
  unless hashes_match?
194
291
  errors.add(:base, HASH_ERROR_MESSAGE)
195
292
  end
196
293
  end
197
294
 
295
+ # Validate signature in the response. Validation is not run if {#error} is present or response
296
+ # is not ok.
198
297
  def verify_signature
199
298
  return if @error
200
299
  return unless response_code_is_ok?
@@ -204,6 +303,9 @@ module Sepa
204
303
  end
205
304
  end
206
305
 
306
+ # Validates certificate in the soap. The certificate must be present and signed by the bank's
307
+ # root certificate for the validation to pass. Is not run if {#error} is present or response
308
+ # code is not ok.
207
309
  def verify_certificate
208
310
  return if @error
209
311
  return unless response_code_is_ok?
@@ -213,6 +315,11 @@ module Sepa
213
315
  end
214
316
  end
215
317
 
318
+ # Checks whether response code in the response is ok. Response code is considered ok if it is
319
+ # "00" or "24".
320
+ #
321
+ # @return [true] if response code is ok
322
+ # @return [false] if response code is not ok
216
323
  def response_code_is_ok?
217
324
  return true if %w(00 24).include? response_code
218
325
 
@@ -1,10 +1,20 @@
1
1
  module Sepa
2
+
3
+ # Builds a soap message with given parameters. This class is extended with proper bank module
4
+ # depending on bank.
2
5
  class SoapBuilder
3
6
  include Utilities
4
7
 
8
+ # Application request built with the same parameters as the soap
9
+ #
10
+ # @return [ApplicationRequest]
5
11
  attr_reader :application_request
6
12
 
7
- # SoapBuilder creates the SOAP structure.
13
+ # Initializes the {SoapBuilder} with the params hash and then extends the {SoapBuilder} with the
14
+ # correct bank module. The {SoapBuilder} class is usually created by the client which handles
15
+ # parameter validation.
16
+ #
17
+ # @param params [Hash] options hash
8
18
  def initialize(params)
9
19
  @bank = params[:bank]
10
20
  @own_signing_certificate = params[:own_signing_certificate]
@@ -27,13 +37,16 @@ module Sepa
27
37
  find_correct_bank_extension
28
38
  end
29
39
 
40
+ # Returns the soap as raw xml
41
+ #
42
+ # @return [String] the soap as xml
30
43
  def to_xml
31
- # Returns a complete SOAP message in xml format
32
44
  find_correct_build.to_xml
33
45
  end
34
46
 
35
47
  private
36
48
 
49
+ # Extends the class with proper module depending on bank
37
50
  def find_correct_bank_extension
38
51
  case @bank
39
52
  when :danske
@@ -43,6 +56,13 @@ module Sepa
43
56
  end
44
57
  end
45
58
 
59
+ # Calculates digest hash for the given node in the given document. The node is canonicalized
60
+ # exclusively before digest calculation.
61
+ #
62
+ # @param doc [Nokogiri::XML] Document that contains the node
63
+ # @param node [String] The name of the node
64
+ # @return [String] the base64 encoded string
65
+ # @todo remove this method and use {Utilities#calculate_digest}
46
66
  def calculate_digest(doc, node)
47
67
  sha1 = OpenSSL::Digest::SHA1.new
48
68
  node = doc.at_css(node)
@@ -55,6 +75,14 @@ module Sepa
55
75
  encode(sha1.digest(canon_node)).gsub(/\s+/, "")
56
76
  end
57
77
 
78
+ # Calculates signature for the given node in the given document. Uses the signing private key
79
+ # given to SoapBuilder for the signing. The node is canonicalized exclusively before signature
80
+ # calculation.
81
+ #
82
+ # @param doc [Nokogiri::XML] Document that contains the node
83
+ # @param node [String] Name of the node to calculate signature from
84
+ # @return [String] the base64 encoded signature
85
+ # @todo refactor to use canonicalization from utilities
58
86
  def calculate_signature(doc, node)
59
87
  sha1 = OpenSSL::Digest::SHA1.new
60
88
  node = doc.at_css(node)
@@ -68,21 +96,43 @@ module Sepa
68
96
  encode(signature).gsub(/\s+/, "")
69
97
  end
70
98
 
99
+ # Loads soap header template to be later populated
100
+ #
101
+ # @return [Nokogiri::XML] the header as Nokogiri document
71
102
  def load_header_template
72
103
  path = File.open("#{SOAP_TEMPLATE_PATH}/header.xml")
73
104
  Nokogiri::XML(path)
74
105
  end
75
106
 
107
+ # Sets value to a node's content in the given document
108
+ # @param doc [Nokogiri::XML] The document that contains the node
109
+ # @param node [String] The name of the node which value is about to be set
110
+ # @param value [#to_s] The value which will be set to the node
76
111
  def set_node(doc, node, value)
77
112
  doc.at_css(node).content = value
78
113
  end
79
114
 
115
+ # Adds soap body to header template
116
+ #
117
+ # @return [Nokogiri::XML] the soap with added body as a nokogiri document
80
118
  def add_body_to_header
81
119
  body = @template.at_css('env|Body')
82
120
  @header_template.root.add_child(body)
83
121
  @header_template
84
122
  end
85
123
 
124
+ # Add needed information to soap header. Mainly security related stuff. The process is as
125
+ # follows:
126
+ # 1. The reference id of the security token is set using {#set_token_id} method
127
+ # 2. Created and expires timestamps are set. Expires is set to be 5 minutes after creation.
128
+ # 3. Timestamp reference id is set with {#set_node_id} method
129
+ # 4. The digest of timestamp node is calculated and set to correct node
130
+ # 5. The reference id of body is set with {#set_node_id}
131
+ # 6. The digest of body is calculated and set to correct node
132
+ # 7. The signature of SignedInfo node is calculated and added to correct node
133
+ # 8. Own signing certificate is formatted (Begin and end certificate removed and linebreaks
134
+ # removed) and embedded in the soap
135
+ # @todo split into smaller methods
86
136
  def process_header
87
137
  set_token_id
88
138
 
@@ -108,6 +158,7 @@ module Sepa
108
158
  set_node(@header_template, 'wsse|BinarySecurityToken', formatted_cert)
109
159
  end
110
160
 
161
+ # Generates a random token id and sets it to correct node
111
162
  def set_token_id
112
163
  security_token_id = "token-#{SecureRandom.uuid}"
113
164
 
@@ -1,6 +1,13 @@
1
1
  module Sepa
2
+
3
+ # Contains utility methods that are used in this gem.
2
4
  module Utilities
3
5
 
6
+ # Calculates a SHA1 digest for a given node. Before the calculation, the node is canonicalized
7
+ # exclusively.
8
+ #
9
+ # @param node [Nokogiri::Node] the node which the digest is calculated from
10
+ # @return [String] the calculated digest
4
11
  def calculate_digest(node)
5
12
  sha1 = OpenSSL::Digest::SHA1.new
6
13
 
@@ -9,9 +16,12 @@ module Sepa
9
16
  encode(sha1.digest(canon_node)).gsub(/\s+/, "")
10
17
  end
11
18
 
12
- # Takes a certificate, adds begin and end
13
- # certificate texts and splits it into multiple lines so that OpenSSL
14
- # can read it.
19
+ # Takes a certificate, adds begin and end certificate texts and splits it into multiple lines so
20
+ # that OpenSSL can read it.
21
+ #
22
+ # @param cert_value [#to_s] the certificate to be processed
23
+ # @return [String] the processed certificate
24
+ # @todo rename maybe because this seems more formatting than {#format_cert}
15
25
  def process_cert_value(cert_value)
16
26
  cert = "-----BEGIN CERTIFICATE-----\n"
17
27
  cert << cert_value.to_s.gsub(/\s+/, "").scan(/.{1,64}/).join("\n")
@@ -19,6 +29,13 @@ module Sepa
19
29
  cert << "-----END CERTIFICATE-----"
20
30
  end
21
31
 
32
+ # Removes begin and end certificate texts from a certificate and removes whitespaces to make the
33
+ # certificate read to be embedded in xml.
34
+ #
35
+ # @param cert [#to_s] The certificate to be formatted
36
+ # @return [String] the formatted certificate
37
+ # @todo rename maybe
38
+ # @see #process_cert_value
22
39
  def format_cert(cert)
23
40
  cert = cert.to_s
24
41
  cert = cert.split('-----BEGIN CERTIFICATE-----')[1]
@@ -26,12 +43,23 @@ module Sepa
26
43
  cert.gsub!(/\s+/, "")
27
44
  end
28
45
 
46
+ # Removes begin and end certificate request texts from a certificate signing request and removes
47
+ # whitespaces
48
+ #
49
+ # @param cert_request [String] the certificate request to be formatted
50
+ # @return [String] the formatted certificate request
51
+ # @todo rename
29
52
  def format_cert_request(cert_request)
30
53
  cert_request = cert_request.split('-----BEGIN CERTIFICATE REQUEST-----')[1]
31
54
  cert_request = cert_request.split('-----END CERTIFICATE REQUEST-----')[0]
32
55
  cert_request.gsub!(/\s+/, "")
33
56
  end
34
57
 
58
+ # Validates whether a doc is valid against a schema. Adds error using ActiveModel validations if
59
+ # document is not valid against the schema.
60
+ #
61
+ # @param doc [Nokogiri::XML::Document] the document to validate
62
+ # @param schema [String] name of the schema file in {SCHEMA_PATH}
35
63
  def check_validity_against_schema(doc, schema)
36
64
  Dir.chdir(SCHEMA_PATH) do
37
65
  xsd = Nokogiri::XML::Schema(IO.read(schema))
@@ -41,8 +69,16 @@ module Sepa
41
69
  end
42
70
  end
43
71
 
44
- # Extracts a certificate from a document and return it as an OpenSSL X509 certificate
45
- # Return nil is the node cannot be found
72
+ # Extracts a certificate from a document and returns it as an OpenSSL X509 certificate. Returns
73
+ # nil if the node cannot be found
74
+ #
75
+ # @param doc [Nokogiri::XML::Document] the document that contains the certificate node
76
+ # @param node [String] the name of the node that contains the certificate
77
+ # @param namespace [String] the namespace of the certificate node
78
+ # @return [OpenSSL::X509::Certificate] the extracted certificate if it is extracted successfully
79
+ # @return [nil] if the certificate cannot be found
80
+ # @raise [OpenSSL::X509::CertificateError] if there is a problem with the certificate
81
+ # @todo refactor not to fail
46
82
  def extract_cert(doc, node, namespace)
47
83
  cert_raw = doc.at("xmlns|#{node}", 'xmlns' => namespace)
48
84
 
@@ -56,10 +92,17 @@ module Sepa
56
92
  x509_certificate(cert)
57
93
  rescue => e
58
94
  fail OpenSSL::X509::CertificateError,
59
- "The certificate could not be processed. It's most likely corrupted. OpenSSL had this to say: #{e}."
95
+ "The certificate could not be processed. It's most likely corrupted. " \
96
+ "OpenSSL had this to say: #{e}."
60
97
  end
61
98
  end
62
99
 
100
+ # Checks whether a certificate signing request is valid
101
+ #
102
+ # @param cert_request [#to_s] the certificate signing request
103
+ # @return [true] if the certificate signing request is valid
104
+ # @return [false] if the certificate signing request is not valid
105
+ # @todo rename
63
106
  def cert_request_valid?(cert_request)
64
107
  begin
65
108
  OpenSSL::X509::Request.new cert_request
@@ -70,6 +113,12 @@ module Sepa
70
113
  true
71
114
  end
72
115
 
116
+ # Loads a soap or application request xml template according to a parameter and command.
117
+ #
118
+ # @param template [String] path to a template directory. Currently supported values are defined
119
+ # in contants {AR_TEMPLATE_PATH} and {SOAP_TEMPLATE_PATH}.
120
+ # @return [Nokogiri::XML::Document] the loaded template
121
+ # @raise [ArgumentError] if a template cannot be found for a command
73
122
  def load_body_template(template)
74
123
  path = "#{template}/"
75
124
 
@@ -95,49 +144,144 @@ module Sepa
95
144
  xml_doc(File.open(path))
96
145
  end
97
146
 
147
+ # Gets current utc time in iso-format
148
+ #
149
+ # @return [String] current utc time in iso-format
98
150
  def iso_time
99
151
  @iso_time ||= Time.now.utc.iso8601
100
152
  end
101
153
 
154
+ # Calculates an HMAC for a given pin and certificate signing request. Used by Nordea certificate
155
+ # requests.
156
+ #
157
+ # @param pin [#to_s] the one-time pin number got from bank
158
+ # @param csr [#to_s] the certificate signing request
159
+ # @return [String] the generated HMAC for the values
102
160
  def hmac(pin, csr)
103
161
  encode(OpenSSL::HMAC.digest('sha1', pin, csr)).chop
104
162
  end
105
163
 
164
+ # Converts a certificate signing request from base64 encoded string to binary string
165
+ #
166
+ # @param csr [#to_s] certificate signing request in base64 encoded format
167
+ # @return [String] the certificate signing request in binary format
106
168
  def csr_to_binary(csr)
107
169
  OpenSSL::X509::Request.new(csr).to_der
108
170
  end
109
171
 
172
+ # Canonicalizes a node inclusively
173
+ #
174
+ # @param doc [Nokogiri::XML::Document] the document that contains the node
175
+ # @param namespace [String] the namespace of the node
176
+ # @param node [String] name of the node
177
+ # @return [String] the canonicalized node if the node can be found
178
+ # @return [nil] if the node cannot be found
110
179
  def canonicalized_node(doc, namespace, node)
111
180
  content_node = doc.at("xmlns|#{node}", xmlns: namespace)
112
181
  content_node.canonicalize if content_node
113
182
  end
114
183
 
184
+ # Converts an xml string to a nokogiri document
185
+ #
186
+ # @param value [to_s] the xml document
187
+ # @return [Nokogiri::XML::Document] the xml document
115
188
  def xml_doc(value)
116
189
  Nokogiri::XML value
117
190
  end
118
191
 
192
+ # Decodes a base64 encoded string
193
+ #
194
+ # @param value [#to_s] the base64 encoded string
195
+ # @return [String] the decoded string
119
196
  def decode(value)
120
197
  Base64.decode64 value
121
198
  end
122
199
 
200
+ # Canonicalizes an xml node exclusively without comments
201
+ #
202
+ # @param value [Nokogiri::XML::Node, #canonicalize] the node to be canonicalized
203
+ # @return [String] the canonicalized node
123
204
  def canonicalize_exclusively(value)
124
205
  value.canonicalize(mode = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
125
206
  inclusive_namespaces = nil,
126
207
  with_comments = false)
127
208
  end
128
209
 
210
+ # Creates a new OpenSSL X509 certificate from a string
211
+ #
212
+ # @param value [#to_s] the string from which to create the certificate
213
+ # @return [OpenSSL::X509::Certificate] the OpenSSL X509 certificate
214
+ # @example Example certificate to convert
215
+ # "-----BEGIN CERTIFICATE-----
216
+ # MIIDwTCCAqmgAwIBAgIEAX1JuTANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJT
217
+ # RTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMR8wHQYDVQQDExZOb3Jk
218
+ # ZWEgQ29ycG9yYXRlIENBIDAxMRQwEgYDVQQFEws1MTY0MDYtMDEyMDAeFw0xMzA1
219
+ # MDIxMjI2MzRaFw0xNTA1MDIxMjI2MzRaMEQxCzAJBgNVBAYTAkZJMSAwHgYDVQQD
220
+ # DBdOb3JkZWEgRGVtbyBDZXJ0aWZpY2F0ZTETMBEGA1UEBRMKNTc4MDg2MDIzODCB
221
+ # nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwtFEfAtbJuGzQwwRumZkvYh2BjGY
222
+ # VsAMUeiKtOne3bZSeisfCq+TXqL1gI9LofyeAQ9I/sDm6tL80yrD5iaSUqVm6A73
223
+ # 9MsmpW/iyZcVf7ms8xAN51ESUgN6akwZCU9pH62ngJDj2gUsktY0fpsoVsARdrvO
224
+ # Fk0fTSUXKWd6LbcCAwEAAaOCAR0wggEZMAkGA1UdEwQCMAAwEQYDVR0OBAoECEBw
225
+ # 2cj7+XMAMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEALddbbzwun
226
+ # MDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVh
227
+ # LnNlL0NDQTAxMA4GA1UdDwEB/wQEAwIFoDCBhQYDVR0fBH4wfDB6oHigdoZ0bGRh
228
+ # cCUzQS8vbGRhcC5uYi5zZS9jbiUzRE5vcmRlYStDb3Jwb3JhdGUrQ0ErMDElMkNv
229
+ # JTNETm9yZGVhK0JhbmsrQUIrJTI4cHVibCUyOSUyQ2MlM0RTRSUzRmNlcnRpZmlj
230
+ # YXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBACLUPB1Gmq6286/s
231
+ # ROADo7N+w3eViGJ2fuOTLMy4R0UHOznKZNsuk4zAbS2KycbZsE5py4L8o+IYoaS8
232
+ # 8YHtEeckr2oqHnPpz/0Eg7wItj8Ad+AFWJqzbn6Hu/LQhlnl5JEzXzl3eZj9oiiJ
233
+ # 1q/2CGXvFomY7S4tgpWRmYULtCK6jode0NhgNnAgOI9uy76pSS16aDoiQWUJqQgV
234
+ # ydowAnqS9h9aQ6gedwbOdtkWmwKMDVXU6aRz9Gvk+JeYJhtpuP3OPNGbbC5L7NVd
235
+ # no+B6AtwxmG3ozd+mPcMeVuz6kKLAmQyIiBSrRNa5OrTkq/CUzxO9WUgTnm/Sri7
236
+ # zReR6mU=
237
+ # -----END CERTIFICATE-----"
129
238
  def x509_certificate(value)
130
239
  OpenSSL::X509::Certificate.new value
131
240
  end
132
241
 
242
+ # Base64 encodes a given value
243
+ #
244
+ # @param value [#to_s] the value to be encoded
245
+ # @return [String] the base64 encoded string
133
246
  def encode(value)
134
247
  Base64.encode64 value
135
248
  end
136
249
 
250
+ # Creates a new OpenSSL RSA key from a string key
251
+ #
252
+ # @param key_as_string [to_s] the key as a string
253
+ # @return [OpenSSL::PKey::RSA] the OpenSSL RSA key
254
+ # @example Example key to convert
255
+ # "-----BEGIN PRIVATE KEY-----
256
+ # MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMLRRHwLWybhs0MM
257
+ # EbpmZL2IdgYxmFbADFHoirTp3t22UnorHwqvk16i9YCPS6H8ngEPSP7A5urS/NMq
258
+ # w+YmklKlZugO9/TLJqVv4smXFX+5rPMQDedRElIDempMGQlPaR+tp4CQ49oFLJLW
259
+ # NH6bKFbAEXa7zhZNH00lFylnei23AgMBAAECgYEAqt912/7x4jaQTrxlSELLFVp9
260
+ # eo1BesVTiPwXvPpsGbbyvGjZ/ztkXNs9zZbh1aCGzZMkiR2U7F5GlsiprlIif4cF
261
+ # 6Xz7rCjaAs7iDRt9PjhjVuqNGR2I+VIIlbQ9XWFJ3lJFW3v7TIZ8JbLnn0XOFz+Z
262
+ # BBSSGTK1zTNh4TBQtjECQQDe5M3uu9m4RwSw9R6GaDw/IFQZgr0oWSv0WIjRwvwW
263
+ # nFnSX2lbkNAjulP0daGsmn7vxIpqZxPxwcrU4wFqTF5dAkEA38DnbCm3YfogzwLH
264
+ # Nre2hBmGqjWarhtxqtRarrkgnmOd8W0Z1Hb1dSHrliUSVSrINbK5ZdEV15Rpu7VD
265
+ # OePzIwJAPMslS+8alANyyR0iJUC65fDYX1jkZOPldDDNqIDJJxWf/hwd7WaTDpuc
266
+ # mHmZDi3ZX2Y45oqUywSzYNtFoIuR1QJAZYUZuyqmSK77SdGB36K1DfSi9AFEQDC1
267
+ # fwPAbTwTv6mFFPAiYxLiRZXxVPtW+QtjMXH4ymh2V4y/+GnCqbZyLwJBAJQSDAME
268
+ # Sn4Uz7Zjk3UrBIbMYEv0u2mcCypwsb0nGE5/gzDPjGE9cxWW+rXARIs+sNQVClnh
269
+ # 45nhdfYxOjgYff0=
270
+ # -----END PRIVATE KEY-----"
137
271
  def rsa_key(key_as_string)
138
272
  OpenSSL::PKey::RSA.new key_as_string
139
273
  end
140
274
 
275
+ # Generates a random id for a node in soap and sets it to the soap header
276
+ #
277
+ # @param document [Nokogiri::XML::Document] the document that contains the node
278
+ # @param namespace [String] the namespace of the node
279
+ # @param node [String] name of the node
280
+ # @param position [Integer] the soap header might contain many references and this parameter
281
+ # defines which reference is used. Numbering starts from 0.
282
+ # @return [String] the generated id of the node
283
+ # @todo create functionality to automatically add reference nodes to header so than position is
284
+ # not needed
141
285
  def set_node_id(document, namespace, node, position)
142
286
  node_id = "#{node.downcase}-#{SecureRandom.uuid}"
143
287
  document.at("xmlns|#{node}", xmlns: namespace)['wsu:Id'] = node_id
@@ -146,6 +290,15 @@ module Sepa
146
290
  node_id
147
291
  end
148
292
 
293
+ # Verifies that a signature has been created with the private key of a certificate
294
+ #
295
+ # @param doc [Nokogiri::XML::Document] the document that contains the signature
296
+ # @param certificate [OpenSSL::X509::Certificate] the certificate to verify the signature
297
+ # against
298
+ # @param canonicalization_method [Symbol] The canonicalization method that has been used to
299
+ # canonicalize the SignedInfo node. Accepts `:normal` or `:exclusive`.
300
+ # @return [true] if signature verifies
301
+ # @return [false] if signature fails to verify or if it cannot be found
149
302
  def validate_signature(doc, certificate, canonicalization_method)
150
303
  node = doc.at('xmlns|SignedInfo', xmlns: DSIG)
151
304
 
@@ -165,6 +318,14 @@ module Sepa
165
318
  certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, node)
166
319
  end
167
320
 
321
+ # Verifies that a certificate has been signed by the private key of a root certificate
322
+ #
323
+ # @param certificate [OpenSSL::X509::Certificate] the certificate to verify
324
+ # @param root_certificate [OpenSSL::X509::Certificate] the root certificate
325
+ # @return [true] if the certificate has been signed by the private key of the root certificate
326
+ # @return [false] if the certificate has not been signed by the private key of the root
327
+ # certificate, the certificates are nil or the subject of the root certificate is not the
328
+ # issuer of the certificate
168
329
  def verify_certificate_against_root_certificate(certificate, root_certificate)
169
330
  return false unless certificate && root_certificate
170
331
  return false unless root_certificate.subject == certificate.issuer
data/lib/sepa/version.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  module Sepa
2
- VERSION = "0.1.5"
2
+
3
+ # The current version of the gem
4
+ VERSION = "1.0.0"
3
5
  end