sepafm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +8 -0
  6. data/README.md +236 -0
  7. data/Rakefile +10 -0
  8. data/lib/danske_get_bank_certificate_test.rb +15 -0
  9. data/lib/sepa/application_request.rb +182 -0
  10. data/lib/sepa/application_response.rb +123 -0
  11. data/lib/sepa/client.rb +79 -0
  12. data/lib/sepa/danske_testing/keys/danske_encryption.crt +24 -0
  13. data/lib/sepa/filedescriptor.rb +7 -0
  14. data/lib/sepa/filetypeservice.rb +6 -0
  15. data/lib/sepa/nordea_testing/keys/CSR.csr +0 -0
  16. data/lib/sepa/nordea_testing/keys/nordea.crt +27 -0
  17. data/lib/sepa/nordea_testing/keys/nordea.key +19 -0
  18. data/lib/sepa/nordea_testing/response/content_053.xml +998 -0
  19. data/lib/sepa/nordea_testing/response/content_054.xml +1 -0
  20. data/lib/sepa/nordea_testing/response/download_file_response.xml +14 -0
  21. data/lib/sepa/nordea_testing/response/download_filelist_response.xml +14 -0
  22. data/lib/sepa/nordea_testing/response/get_user_info_response.xml +14 -0
  23. data/lib/sepa/nordea_testing/response/upload_file_response.xml +14 -0
  24. data/lib/sepa/response.rb +177 -0
  25. data/lib/sepa/sender_verifier.rb +15 -0
  26. data/lib/sepa/signature.rb +7 -0
  27. data/lib/sepa/soap_builder.rb +395 -0
  28. data/lib/sepa/soap_danske.rb +47 -0
  29. data/lib/sepa/soap_nordea.rb +68 -0
  30. data/lib/sepa/userfiletype.rb +16 -0
  31. data/lib/sepa/version.rb +3 -0
  32. data/lib/sepa/wsdl/wsdl_danske.xml +234 -0
  33. data/lib/sepa/wsdl/wsdl_danske_cert.xml +280 -0
  34. data/lib/sepa/wsdl/wsdl_nordea.xml +234 -0
  35. data/lib/sepa/wsdl/wsdl_nordea_cert.xml +187 -0
  36. data/lib/sepa/xml_parser.rb +291 -0
  37. data/lib/sepa/xml_schemas/application_request.xsd +135 -0
  38. data/lib/sepa/xml_schemas/application_response.xsd +311 -0
  39. data/lib/sepa/xml_schemas/cert_application_request.xsd +107 -0
  40. data/lib/sepa/xml_schemas/danske_pki.xsd +334 -0
  41. data/lib/sepa/xml_schemas/oasis-200401-wss-wssecurity-secext-1.0.xsd +195 -0
  42. data/lib/sepa/xml_schemas/oasis-200401-wss-wssecurity-utility-1.0.xsd +108 -0
  43. data/lib/sepa/xml_schemas/soap.xsd +126 -0
  44. data/lib/sepa/xml_schemas/wsdl.xml +310 -0
  45. data/lib/sepa/xml_schemas/xml.xsd +287 -0
  46. data/lib/sepa/xml_schemas/xmldsig-core-schema.xsd +318 -0
  47. data/lib/sepa/xml_templates/application_request/create_certificate.xml +10 -0
  48. data/lib/sepa/xml_templates/application_request/danske_get_bank_certificate.xml +10 -0
  49. data/lib/sepa/xml_templates/application_request/download_file.xml +32 -0
  50. data/lib/sepa/xml_templates/application_request/download_file_list.xml +29 -0
  51. data/lib/sepa/xml_templates/application_request/get_certificate.xml +10 -0
  52. data/lib/sepa/xml_templates/application_request/get_user_info.xml +26 -0
  53. data/lib/sepa/xml_templates/application_request/upload_file.xml +29 -0
  54. data/lib/sepa/xml_templates/soap/create_certificate.xml +15 -0
  55. data/lib/sepa/xml_templates/soap/danske_get_bank_certificate.xml +14 -0
  56. data/lib/sepa/xml_templates/soap/download_file.xml +16 -0
  57. data/lib/sepa/xml_templates/soap/download_file_list.xml +16 -0
  58. data/lib/sepa/xml_templates/soap/get_certificate.xml +13 -0
  59. data/lib/sepa/xml_templates/soap/get_user_info.xml +16 -0
  60. data/lib/sepa/xml_templates/soap/header.xml +37 -0
  61. data/lib/sepa/xml_templates/soap/upload_file.xml +16 -0
  62. data/lib/sepa.rb +21 -0
  63. data/lib/sepa_client_testing_mika.rb +32 -0
  64. data/lib/sepa_client_testing_tiere.rb +80 -0
  65. data/sepa.gemspec +29 -0
  66. data/test/sepa/application_request_test.rb +423 -0
  67. data/test/sepa/application_response_test.rb +238 -0
  68. data/test/sepa/cert_application_request_test.rb +99 -0
  69. data/test/sepa/client_test.rb +425 -0
  70. data/test/sepa/danske_test_keys/danskeroot.pem +25 -0
  71. data/test/sepa/danske_test_keys/encryption_pkcs.csr +0 -0
  72. data/test/sepa/danske_test_keys/signing_key.pem +27 -0
  73. data/test/sepa/danske_test_keys/signing_pkcs.csr +0 -0
  74. data/test/sepa/nordea_cert_request_soap_builder_test.rb +112 -0
  75. data/test/sepa/nordea_generic_soap_builder_test.rb +427 -0
  76. data/test/sepa/nordea_test_keys/nordea.crt +27 -0
  77. data/test/sepa/nordea_test_keys/nordea.key +19 -0
  78. data/test/sepa/nordea_test_keys/root_cert.cer +0 -0
  79. data/test/sepa/nordea_test_keys/testcert.csr +0 -0
  80. data/test/sepa/response_test.rb +269 -0
  81. data/test/sepa/sepa_test.rb +20 -0
  82. data/test/sepa/test_files/invalid.wsdl +1 -0
  83. data/test/sepa/test_files/test_responses/df.xml +20 -0
  84. data/test/sepa/test_files/test_responses/dfl.xml +20 -0
  85. data/test/sepa/test_files/test_responses/gui.xml +20 -0
  86. data/test/sepa/test_files/test_responses/uf.xml +20 -0
  87. data/test/sepa/user_file_type_test.rb +21 -0
  88. data/test/sepa/xml_parser_test.rb +73 -0
  89. data/test/test_helper.rb +9 -0
  90. metadata +256 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8d907ad1e367406e6f1a98af49893e5f6211b04
4
+ data.tar.gz: cc91cebf5cfc771b33e98c4136f948fde2626631
5
+ SHA512:
6
+ metadata.gz: 812949f6537bfd6e1df79f7a58b74033593e0788aa99103016eba8f9e578e2dd073337a11f58ff4fca586812d4b4842690e807f0c692c1f4bdaec9900481b02f
7
+ data.tar.gz: e8a469ea127b5003583a99a1c2cfc593fdb53ecfc43b95046bab97c67f1e83d1d9d3844e8442e46b0d78f376fdab2aeb4687a1c5dedb3f9ab931b5af1a2b877e
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+ vendor
16
+ lib/debug.rb
17
+ lib/CSR.csr
18
+ lib/signing_key.pem
19
+ lib/encrypting_key.pem
20
+ lib/req_key.pem
21
+ lib/danske
22
+
23
+ # YARD artifacts
24
+ .yardoc
25
+ _yardoc
26
+ doc/
27
+
28
+ # ms-crap
29
+ *~
30
+
31
+ # osx-crap
32
+ .DS_Store
33
+
34
+ #Coverage
35
+ coverage
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sepa.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2013 Devlab Oy, http://www.devlab.fi
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # Devlab / SEPA
2
+
3
+ This project aims to create an open source implementation of SEPA Financial Messages using Web Services. Project implementation will be done in Ruby. We will also create a REST API for this module.
4
+
5
+ ## First milestone
6
+
7
+ * Support for SEPA Web Services
8
+ * Customer-to-Bank Statement. ISO standard "CustomerCreditTransferInitiationV03", XML schema "pain.001.001.03"
9
+ * Bank-to-Customer Statement. ISO standard "BankToCustomerStatementV02", XML schema "camt.053.001.02"
10
+ * Bank-to-Customer Debit/Credit Notification. ISO standard "BankToCustomerDebitCreditNotificationV02", XML schma "camt.054.001.02"
11
+ * Update README
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'sepa'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install sepa
26
+
27
+ ## Usage
28
+
29
+ ### Communicating with the bank
30
+
31
+ 1. Require the gem:
32
+
33
+ require 'sepa'
34
+
35
+ 2. Define the hash that will be passed to the gem when initializing it:
36
+
37
+ params = {
38
+ bank: :nordea,
39
+ private_key_path: "path/to/key", (OR private_key_plain : "Your private key in plain text form ")
40
+ cert_path: "path/to/key", (OR cert_plain : "Your certificate in plain text form ")
41
+ command: :command_as_symbol,
42
+ customer_id: '11111111',
43
+ environment: 'PRODUCTION',
44
+ status: 'NEW',
45
+ target_id: '11111111A1',
46
+ language: 'FI',
47
+ file_type: 'TITO',
48
+ content: payload,
49
+ file_reference: "11111111A12006030329501800000014"
50
+ }
51
+
52
+ 3. Initialize a new instance of the client and pass the params hash
53
+
54
+ sepa_client = Sepa::Client.new(params)
55
+
56
+ 4. There is only one method that can be called after initializing the client:
57
+
58
+ * Returns the whole soap response as a savon response object:
59
+
60
+ response = client.send
61
+
62
+ ### Verifying the response
63
+
64
+ * Check that the hashes match in the response
65
+
66
+ response.hashes_match?
67
+
68
+ # You can also provide an optional parameter verbose:true
69
+ # if you want to see which hashes failed to verify.
70
+
71
+ response.hashes_match?(verbose: true)
72
+
73
+ * Check that the signature of the response is valid
74
+
75
+ response.signature_is_valid?
76
+
77
+ * Extract the certificate from the response
78
+
79
+ # Will return an OpenSSL::X509::Certificate object
80
+ response.certificate
81
+
82
+ * Check that the certificate is trusted against a root cert
83
+
84
+ # The root cert has to be of type OpenSSL::X509::Certificate
85
+ response.cert_is_trusted?(root_cert)
86
+
87
+ ### Verifying the application response
88
+
89
+ 1. Extract the application request from the request
90
+
91
+ ar = response.application_request
92
+
93
+ * Check that the hashes match in the application response
94
+
95
+ ar.hashes_match?
96
+
97
+ * Check that the signature of the application response is valid
98
+
99
+ ar.signature_is_valid?
100
+
101
+ * Extract the certificate from the application response
102
+
103
+ # Will return an OpenSSL::X509::Certificate object
104
+ ar.certificate
105
+
106
+ * Check that the certificate is trusted against a root cert
107
+
108
+ # The root cert has to be of type OpenSSL::X509::Certificate
109
+ ar.cert_is_trusted?(root_cert)
110
+
111
+ ### For downloading Nordea certificate
112
+
113
+ 1. Require the gem:
114
+
115
+ require 'sepa'
116
+
117
+ 2. Define the hash that will be passed to the gem when initializing it:
118
+
119
+ params = {
120
+ bank: :nordea,
121
+ command: :get_certificate,
122
+ customer_id: '11111111',
123
+ environment: 'TEST',
124
+ csr_path: "path_to_your_local_csr_file", (OR csr_plain: "your csr in plain text format")
125
+ service: 'service'
126
+ }
127
+
128
+ 3. Initialize a new instance of the client and pass the params hash
129
+
130
+ sepa_client = Sepa::Client.new(params)
131
+ sepa_client.call
132
+
133
+ 4. Save the certificate from the response into a local file
134
+
135
+ ### For downloading Danske bank certificates
136
+
137
+ 1. Require the gem:
138
+
139
+ require 'sepa'
140
+
141
+ 2. Define the hash that will be passed to the gem when initializing it:
142
+
143
+ params = {
144
+ bank: :danske,
145
+ target_id: 'Danske FI',
146
+ language: 'EN',
147
+ command: :get_bank_certificate,
148
+ bank_root_cert_serial: '1111110002',
149
+ customer_id: '360817',
150
+ environment: 'TEST',
151
+ }
152
+
153
+ 3. Initialize a new instance of the client and pass the params hash
154
+
155
+ sepa_client = Sepa::Client.new(params)
156
+ sepa_client.call
157
+
158
+ 4. Save the certificates from the response into a local file
159
+
160
+ ***
161
+
162
+ ### Parameter breakdown
163
+
164
+ * bank : The bank you want to send the request to as a symbol. Either :nordea or :danske
165
+
166
+ * private_key_plain: Your private key in plain text format
167
+
168
+ * private_key_path: Path to your local private key file
169
+
170
+ * cert_plain: Your certificate in plain text format.
171
+
172
+ * cert_path: Path to your local certificate file
173
+
174
+ * csr_plain: Your certificate signing request in plain text format
175
+
176
+ * csr_path: Path to your local certificate signing request file
177
+
178
+ * command: Either :download_file_list, :upload_file, :download_file, :get_user_info, :get_certificate or :get_bank_certificate, depending on what you want to do.
179
+
180
+ * customer_id: Your personal id with the bank.
181
+
182
+ * environment: Must be either PRODUCTION or TEST
183
+
184
+ * status: For filtering stuff. Must be either NEW, DOWNLOADED or ALL
185
+
186
+ * target_id: Some specification of the folder which to access in the bank.
187
+
188
+ * language: Language must be either FI, EN or SV
189
+
190
+ * file_type: File types to upload or download:
191
+
192
+ * LMP300 = Laskujen maksupalvelu (lähtevä)
193
+
194
+ * LUM2 = Valuuttamaksut (lähtevä)
195
+
196
+ * KTL = Saapuvat viitemaksut (saapuva)
197
+
198
+ * TITO = Konekielinen tiliote (saapuva)
199
+
200
+ * NDCORPAYS = Yrityksen maksut XML (lähtevä)
201
+
202
+ * NDCAMT53L = Konekielinen XML-tiliote (saapuva)
203
+
204
+ * NDCAMT54L = Saapuvat XML viitemaksu (saapuva)
205
+
206
+ * content: The actual payload to send. The creation of this file may be supported by the client at some point.
207
+
208
+ * file_reference: File reference for :download_file command
209
+
210
+ * pin: Your personal pin-code provided by the bank
211
+
212
+ * service: For testing value is service, otherwise ISSUER
213
+
214
+ * bank_root_cert_serial: Serial number for Danske bank certificate download (1111110002)
215
+
216
+ ***
217
+
218
+ ### Parsing data from bank response xml
219
+ Parsing based on specifications by Federation of Finnish Financial Services provided xml examples account statement [XML account statement](http://www.fkl.fi/teemasivut/sepa/tekninen_dokumentaatio/Dokumentit/FI_camt_053_sample.xml.xml) and debit credit notification [XML debit credit notification](http://www.fkl.fi/teemasivut/sepa/tekninen_dokumentaatio/Dokumentit/FI_camt_054_sample.xml.xml) and ISO20022 transaction reporting guide [ISO20022 Transaction reporting guide](http://www.fkl.fi/en/themes/sepa/sepa_documents/Dokumentit/ISO20022_Payment_Guide.pdf)
220
+ * Hardcode wanted specs into app_response.rb methods get_account_statement_content/get_debit_credit_notification_content
221
+ * Create new instance of ApplicationResponse
222
+ * method get_account_statement_content takes a bank statement file (xml) as a parameter and returns selected info in a hash
223
+ * method get_debit_credit_notification_content takes a debit credit notification file (xml) as a parameter and returns selected info in a hash
224
+ * method animate_response takes a full application response xml as a parameter and parses data into objects, can be used to take out different formats of Content-field, without predefined parameter specs
225
+
226
+ ## Contributing
227
+
228
+ 1. Fork it
229
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
230
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
231
+ 4. Push to the branch (`git push origin my-new-feature`)
232
+ 5. Create new Pull Request
233
+
234
+ ## License
235
+
236
+ Released under the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'lib/sepa'
6
+ t.test_files = FileList['test/sepa/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,15 @@
1
+ require 'sepa'
2
+
3
+ params = {
4
+ bank: :danske,
5
+ target_id: 'Danske FI',
6
+ language: 'EN',
7
+ command: :get_bank_certificate,
8
+ bank_root_cert_serial: '1111110002',
9
+ customer_id: '360817',
10
+ environment: 'TEST',
11
+ }
12
+
13
+ sepa_client = Sepa::Client.new(params)
14
+
15
+ sepa_client.send
@@ -0,0 +1,182 @@
1
+ module Sepa
2
+ class ApplicationRequest
3
+ def initialize(params)
4
+ # Used by most, both Nordea and Danske
5
+ @command = check_command(params.fetch(:command))
6
+ @customer_id = params.fetch(:customer_id)
7
+ @environment = params.fetch(:environment)
8
+ @target_id = params[:target_id]
9
+ @status = params[:status]
10
+ @file_type = params[:file_type]
11
+ @content = params[:content]
12
+ @file_reference = params[:file_reference]
13
+
14
+ @private_key, @cert, @pin, @service, @csr, @hmac,
15
+ @bank_root_cert_serial,@request_id = ''
16
+
17
+ # Set values for the previously defined attributes
18
+ initialize_required_fields_per_request(params)
19
+ end
20
+
21
+ def get_as_base64
22
+ load_template(@command)
23
+ set_nodes_contents
24
+ # No signature for Certificate Requests
25
+ if @command != :get_certificate && @command != :get_bank_certificate
26
+ process_signature
27
+ end
28
+
29
+ Base64.encode64(@ar.to_xml)
30
+ end
31
+
32
+ private
33
+
34
+ def initialize_required_fields_per_request(params)
35
+ generic_commands = [:get_user_info, :upload_file, :download_file,
36
+ :download_file_list]
37
+
38
+ case @command
39
+ when *generic_commands
40
+ @private_key = params.fetch(:private_key)
41
+ @cert = params.fetch(:cert)
42
+ when :get_certificate
43
+ @service = params[:service]
44
+ @pin = params[:pin]
45
+ @csr = params[:csr]
46
+ @hmac = create_hmac_seal(@pin,@csr)
47
+ when :get_bank_certificate
48
+ @pin = params[:pin]
49
+ @request_id = params[:request_id]
50
+ @bank_root_cert_serial = params[:bank_root_cert_serial]
51
+ end
52
+ end
53
+
54
+ def check_command(command)
55
+ valid_commands = [:get_certificate, :download_file_list, :download_file,
56
+ :get_user_info, :upload_file, :download_file,
57
+ :get_bank_certificate]
58
+ unless valid_commands.include?(command)
59
+ fail ArgumentError, "You didn't provide a proper command. " \
60
+ "Acceptable values are #{valid_commands.inspect}"
61
+ else
62
+ command
63
+ end
64
+ end
65
+ # Loads the application request template according to the command
66
+ def load_template(command)
67
+ template_dir = File.expand_path('../xml_templates/application_request',
68
+ __FILE__)
69
+
70
+ case command
71
+
72
+ when :get_certificate
73
+ path = "#{template_dir}/get_certificate.xml"
74
+ when :download_file_list
75
+ path = "#{template_dir}/download_file_list.xml"
76
+ when :get_user_info
77
+ path = "#{template_dir}/get_user_info.xml"
78
+ when :upload_file
79
+ path = "#{template_dir}/upload_file.xml"
80
+ when :download_file
81
+ path = "#{template_dir}/download_file.xml"
82
+ when :get_bank_certificate
83
+ path = "#{template_dir}/danske_get_bank_certificate.xml"
84
+ end
85
+
86
+ @ar = Nokogiri::XML(File.open(path))
87
+ end
88
+
89
+
90
+ def set_node(node, value)
91
+ @ar.at_css(node).content = value
92
+ end
93
+
94
+ # Set the nodes' contents according to the command
95
+ def set_nodes_contents
96
+ if @command != :get_bank_certificate
97
+ set_node("CustomerId", @customer_id)
98
+ set_node("Timestamp", Time.now.iso8601)
99
+ set_node("Environment", @environment)
100
+ set_node("SoftwareId", "Sepa Transfer Library version #{VERSION}")
101
+ set_node("Command",
102
+ @command.to_s.split(/[\W_]/).map {|c| c.capitalize}.join)
103
+ end
104
+
105
+ case @command
106
+
107
+ when :get_certificate
108
+ set_node("Service", @service)
109
+ set_node("Content", Base64.encode64(@csr.to_der))
110
+ set_node("HMAC", Base64.encode64(@hmac).chop)
111
+ when :download_file_list
112
+ set_node("Status", @status)
113
+ set_node("TargetId", @target_id)
114
+ set_node("FileType", @file_type)
115
+ when :download_file
116
+ set_node("Status", @status)
117
+ set_node("TargetId", @target_id)
118
+ set_node("FileType", @file_type)
119
+ set_node("FileReference", @file_reference)
120
+ when :upload_file
121
+ set_node("Content", Base64.encode64(@content))
122
+ set_node("FileType", @file_type)
123
+ set_node("TargetId", @target_id)
124
+ when :get_bank_certificate
125
+ set_node("elem|BankRootCertificateSerialNo", @bank_root_cert_serial)
126
+ set_node("elem|Timestamp", Time.now.iso8601)
127
+ set_node("elem|RequestId", @request_id)
128
+ end
129
+ end
130
+
131
+ def create_hmac_seal(pin, csr)
132
+ hmacseal = OpenSSL::HMAC.digest('sha1',pin,csr.to_der)
133
+ hmacseal
134
+ end
135
+
136
+ def remove_node(doc, node, xmlns)
137
+ doc.at_css("xmlns|#{node}", 'xmlns' => xmlns).remove
138
+ end
139
+
140
+ def add_node_to_root(doc, node)
141
+ doc.root.add_child(node)
142
+ end
143
+
144
+ def calculate_digest(doc)
145
+ sha1 = OpenSSL::Digest::SHA1.new
146
+ Base64.encode64(sha1.digest(doc.canonicalize))
147
+ end
148
+
149
+ def add_value_to_signature(node, value)
150
+ node = @ar.at_css("dsig|#{node}",
151
+ 'dsig' => 'http://www.w3.org/2000/09/xmldsig#')
152
+ node.content = value
153
+ end
154
+
155
+ def calculate_signature(private_key)
156
+ sha1 = OpenSSL::Digest::SHA1.new
157
+ node = @ar.at_css("dsig|SignedInfo",
158
+ 'dsig' => 'http://www.w3.org/2000/09/xmldsig#')
159
+ signature = private_key.sign(sha1, node.canonicalize)
160
+ Base64.encode64(signature)
161
+ end
162
+
163
+ def format_cert(cert)
164
+ cert = cert.to_s
165
+ cert = cert.split('-----BEGIN CERTIFICATE-----')[1]
166
+ cert = cert.split('-----END CERTIFICATE-----')[0]
167
+ cert.gsub!(/\s+/, "")
168
+ end
169
+
170
+ def process_signature
171
+ signature_node = remove_node(@ar,
172
+ 'Signature',
173
+ 'http://www.w3.org/2000/09/xmldsig#')
174
+ digest = calculate_digest(@ar)
175
+ add_node_to_root(@ar, signature_node)
176
+ add_value_to_signature('DigestValue', digest)
177
+ signature = calculate_signature(@private_key)
178
+ add_value_to_signature('SignatureValue', signature)
179
+ add_value_to_signature('X509Certificate',format_cert(@cert))
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,123 @@
1
+ module Sepa
2
+ class ApplicationResponse
3
+ def initialize(ar)
4
+ @ar = ar
5
+
6
+ if !@ar.respond_to?(:canonicalize)
7
+ fail ArgumentError,
8
+ "The application response you provided is not a valid Nokogiri::XML" \
9
+ " file."
10
+ elsif !valid_against_ar_schema?(@ar)
11
+ fail ArgumentError,
12
+ "The application response you provided doesn't validate against" \
13
+ " application response schema."
14
+ end
15
+ end
16
+
17
+ # Checks that the hash value reported in the signature matches the actual
18
+ # one.
19
+ def hashes_match?
20
+ ar = @ar.clone
21
+
22
+ digest_value = ar.at_css(
23
+ 'xmlns|DigestValue',
24
+ 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
25
+ ).content.strip
26
+
27
+ ar.at_css(
28
+ "xmlns|Signature",
29
+ 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
30
+ ).remove
31
+
32
+ actual_digest = calculate_digest(ar)
33
+
34
+ if digest_value == actual_digest
35
+ true
36
+ else
37
+ false
38
+ end
39
+ end
40
+
41
+ # Extracts the X509 certificate from the application response.
42
+ def certificate
43
+ cert_value = @ar.at_css(
44
+ 'xmlns|X509Certificate',
45
+ 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
46
+ ).content.gsub(/\s+/, "")
47
+
48
+ cert = process_cert_value(cert_value)
49
+
50
+ begin
51
+ OpenSSL::X509::Certificate.new(cert)
52
+ rescue => e
53
+ fail OpenSSL::X509::CertificateError,
54
+ "The certificate embedded in the application response could not be " \
55
+ "processed. It's most likely corrupted. " \
56
+ "OpenSSL had this to say: #{e}."
57
+ end
58
+ end
59
+
60
+ # Checks that the signature is signed with the private key of the
61
+ # certificate's public key.
62
+ def signature_is_valid?
63
+ node = @ar.at_css('xmlns|SignedInfo',
64
+ 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#')
65
+
66
+ node = node.canonicalize
67
+
68
+ signature = @ar.at_css(
69
+ 'xmlns|SignatureValue',
70
+ 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
71
+ ).content
72
+
73
+ signature = Base64.decode64(signature)
74
+
75
+ certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, node)
76
+ end
77
+
78
+ # Checks that the certificate in the application response is signed with the
79
+ # private key of the public key of the certificate as parameter.
80
+ def cert_is_trusted?(root_cert)
81
+ if root_cert.subject == certificate.issuer
82
+ certificate.verify(root_cert.public_key)
83
+ else
84
+ fail SecurityError,
85
+ "The issuer of the certificate doesn't match the subject of the " \
86
+ "root certificate."
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def calculate_digest(node)
93
+ sha1 = OpenSSL::Digest::SHA1.new
94
+
95
+ canon_node = node.canonicalize(
96
+ mode=Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
97
+ inclusive_namespaces=nil,with_comments=false
98
+ )
99
+
100
+ Base64.encode64(sha1.digest(canon_node)).gsub(/\s+/, "")
101
+ end
102
+
103
+ def valid_against_ar_schema?(doc)
104
+ schemas_path = File.expand_path('../../../lib/sepa/xml_schemas',
105
+ __FILE__)
106
+
107
+ Dir.chdir(schemas_path) do
108
+ xsd = Nokogiri::XML::Schema(IO.read('application_response.xsd'))
109
+ xsd.valid?(doc)
110
+ end
111
+ end
112
+
113
+ # Takes the certificate from the application response, adds begin and end
114
+ # certificate texts and splits it into multiple lines so that OpenSSL
115
+ # can read it.
116
+ def process_cert_value(cert_value)
117
+ cert = "-----BEGIN CERTIFICATE-----\n"
118
+ cert += cert_value.to_s.gsub(/\s+/, "").scan(/.{1,64}/).join("\n")
119
+ cert += "\n"
120
+ cert += "-----END CERTIFICATE-----"
121
+ end
122
+ end
123
+ end