sepafm 0.1.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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