sepafm 0.1.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +6 -0
- data/lib/sepa/application_request.rb +87 -0
- data/lib/sepa/application_response.rb +37 -2
- data/lib/sepa/attribute_checks.rb +30 -0
- data/lib/sepa/banks/danske/danske_response.rb +86 -0
- data/lib/sepa/banks/danske/soap_danske.rb +81 -3
- data/lib/sepa/banks/nordea/nordea_response.rb +18 -0
- data/lib/sepa/banks/nordea/soap_nordea.rb +25 -2
- data/lib/sepa/client.rb +203 -18
- data/lib/sepa/error_messages.rb +54 -13
- data/lib/sepa/response.rb +118 -11
- data/lib/sepa/soap_builder.rb +53 -2
- data/lib/sepa/utilities.rb +167 -6
- data/lib/sepa/version.rb +3 -1
- data/lib/sepafm.rb +57 -4
- data/readme.md +74 -60
- data/test/sepa/sepa_test.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3071e7c2f8f6ab6f0fcff615a38f2d11517952da
|
4
|
+
data.tar.gz: fc8beef75e2f56ece35cccbd5101d1d2cc9af062
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 734914613e29893e3c2188a0a3e390bbaf0ae0c170ef58a045f40261e3691c49fa9fc560753c2f5db83a0bd0ff013aab936d889746bc462d0b99571538ea318d
|
7
|
+
data.tar.gz: f0d4fac6bbb809d2750f2f6f501e5467398dd85ecef9ab8abbd6a2f6788734284ca12b0ebf066b0e87fc36637c1911c6ad2d1ec53a992abefcd4c110241c83a0
|
data/.yardopts
ADDED
@@ -1,7 +1,21 @@
|
|
1
1
|
module Sepa
|
2
|
+
|
3
|
+
# Contains functionality to build the application request
|
4
|
+
#
|
5
|
+
# @todo Add return values for content modifying methods to signal whether they succeeded or not
|
2
6
|
class ApplicationRequest
|
3
7
|
include Utilities
|
4
8
|
|
9
|
+
# Initializes the {ApplicationRequest} with a params hash. The application request is usually
|
10
|
+
# initialized by the {SoapBuilder}. The xml template of the application request is also loaded
|
11
|
+
# here.
|
12
|
+
#
|
13
|
+
# @param params [Hash] the hash containing attributes needed by the {ApplicationRequest}. All
|
14
|
+
# the key => value pairs in the hash are initialized as instance variables. The hash in the
|
15
|
+
# initialization is usually the same as with {SoapBuilder} so the values have already been
|
16
|
+
# validated by the client.
|
17
|
+
# @todo Consider not using instance_variable_set so that all the available instance variables
|
18
|
+
# can easily be seen.
|
5
19
|
def initialize(params = {})
|
6
20
|
# Set all params as instance variables
|
7
21
|
params.each do |key, value|
|
@@ -11,6 +25,11 @@ module Sepa
|
|
11
25
|
@application_request = load_body_template AR_TEMPLATE_PATH
|
12
26
|
end
|
13
27
|
|
28
|
+
# Sets the nodes in the application request, processes signature and then returns the
|
29
|
+
# application request as an xml document.
|
30
|
+
#
|
31
|
+
# @return [String] the application request as an xml document
|
32
|
+
# @todo This method is obviously doing too much
|
14
33
|
def to_xml
|
15
34
|
set_common_nodes
|
16
35
|
set_nodes_contents
|
@@ -18,28 +37,48 @@ module Sepa
|
|
18
37
|
@application_request.to_xml
|
19
38
|
end
|
20
39
|
|
40
|
+
# Base64 encodes the whole application request
|
41
|
+
#
|
42
|
+
# @return [String] the base64 encoded application request
|
21
43
|
def to_base64
|
22
44
|
encode to_xml
|
23
45
|
end
|
24
46
|
|
47
|
+
# Returns the application request as a Nokogiri document
|
48
|
+
#
|
49
|
+
# @return [Nokogiri::XML::Document] the application request as a nokogiri document
|
25
50
|
def to_nokogiri
|
26
51
|
Nokogiri::XML to_xml
|
27
52
|
end
|
28
53
|
|
29
54
|
private
|
30
55
|
|
56
|
+
# Sets node to value
|
57
|
+
#
|
58
|
+
# @param node [String] the name of the node which value is to be set
|
59
|
+
# @param value [#to_s] the value which is going to be set to the node
|
31
60
|
def set_node(node, value)
|
32
61
|
@application_request.at_css(node).content = value
|
33
62
|
end
|
34
63
|
|
64
|
+
# Sets node to base64 encoded value
|
65
|
+
#
|
66
|
+
# @param node [String] name of the node
|
67
|
+
# @param value [#to_s] the value which is going to be set to the nodea base64 encoded
|
68
|
+
# @todo rename
|
35
69
|
def set_node_b(node, value)
|
36
70
|
set_node node, encode(value)
|
37
71
|
end
|
38
72
|
|
73
|
+
# Converts {#command} to string, removes underscores and capitalizes it.
|
74
|
+
#
|
75
|
+
# @example Example input and output
|
76
|
+
# :get_user_info --> GetUserInfo
|
39
77
|
def pretty_command
|
40
78
|
@command.to_s.split(/[\W_]/).map {|c| c.capitalize}.join
|
41
79
|
end
|
42
80
|
|
81
|
+
# Determines which content setting method to call depending on {#command}
|
43
82
|
def set_nodes_contents
|
44
83
|
case @command
|
45
84
|
when :create_certificate
|
@@ -57,6 +96,7 @@ module Sepa
|
|
57
96
|
end
|
58
97
|
end
|
59
98
|
|
99
|
+
# Sets nodes' values for download file request
|
60
100
|
def set_download_file_nodes
|
61
101
|
add_target_id_after 'FileReferences'
|
62
102
|
set_node("Status", @status)
|
@@ -64,6 +104,11 @@ module Sepa
|
|
64
104
|
set_node("FileReference", @file_reference)
|
65
105
|
end
|
66
106
|
|
107
|
+
# Sets Danske Bank's get bank certificate request's contents
|
108
|
+
#
|
109
|
+
# @raise [OnlyWorksWithDanske] if {#bank} is not danske
|
110
|
+
# @todo Investigate a better way to set the bank's root certificate's serial instead of
|
111
|
+
# hardcoding it
|
67
112
|
def set_get_bank_certificate_nodes
|
68
113
|
raise 'OnlyWorksWithDanske' if @bank != :danske
|
69
114
|
|
@@ -73,24 +118,34 @@ module Sepa
|
|
73
118
|
set_node("elem|RequestId", @request_id)
|
74
119
|
end
|
75
120
|
|
121
|
+
# Sets nodes' contents for upload file request
|
76
122
|
def set_upload_file_nodes
|
77
123
|
set_node_b("Content", @content)
|
78
124
|
set_node("FileType", @file_type)
|
79
125
|
add_target_id_after 'Environment'
|
80
126
|
end
|
81
127
|
|
128
|
+
# Sets nodes' contents for download file list request
|
82
129
|
def set_download_file_list_nodes
|
83
130
|
add_target_id_after 'Environment'
|
84
131
|
set_node("Status", @status)
|
85
132
|
set_node("FileType", @file_type)
|
86
133
|
end
|
87
134
|
|
135
|
+
# Sets nodes' contents for Nordea's get certificate request
|
136
|
+
#
|
137
|
+
# @todo Raise error if {#bank} is other than Nordea like in {#set_get_bank_certificate_nodes}
|
138
|
+
# @todo Check further into what service actually is
|
88
139
|
def set_get_certificate_nodes
|
89
140
|
set_node("Service", '')
|
90
141
|
set_node("Content", format_cert_request(@signing_csr))
|
91
142
|
set_node("HMAC", hmac(@pin, csr_to_binary(@signing_csr)))
|
92
143
|
end
|
93
144
|
|
145
|
+
# Sets nodes' contents for Danske Bank's create certificate request. Environment is set to
|
146
|
+
# customertest if {#environment} is `:test`
|
147
|
+
#
|
148
|
+
# @todo Raise error if {#bank} is other than Nordea like in {#set_get_bank_certificate_nodes}
|
94
149
|
def set_create_certificate_nodes
|
95
150
|
set_node("tns|CustomerId", @customer_id)
|
96
151
|
set_node("tns|KeyGeneratorType", 'software')
|
@@ -105,6 +160,8 @@ module Sepa
|
|
105
160
|
set_node("tns|PIN", @pin)
|
106
161
|
end
|
107
162
|
|
163
|
+
# Sets contents for nodes that are common to all requests except when {#command} is
|
164
|
+
# `:get_bank_certificate` or `:create_certificate`. {#environment} is upcased here.
|
108
165
|
def set_common_nodes
|
109
166
|
return if @command == :get_bank_certificate
|
110
167
|
return if @command == :create_certificate
|
@@ -116,25 +173,48 @@ module Sepa
|
|
116
173
|
set_node("Command", pretty_command)
|
117
174
|
end
|
118
175
|
|
176
|
+
# Removes a node from {#application_request}
|
177
|
+
#
|
178
|
+
# @param node [String] name of the node to remove
|
179
|
+
# @param xmlns [String] the namespace of the node
|
180
|
+
# @todo Move to {Utilities} and move document to parameters
|
119
181
|
def remove_node(node, xmlns)
|
120
182
|
@application_request.at_css("xmlns|#{node}", 'xmlns' => xmlns).remove
|
121
183
|
end
|
122
184
|
|
185
|
+
# Adds node to the root of the application request
|
186
|
+
#
|
187
|
+
# @todo Move to {Utilities} and move document to parameters
|
123
188
|
def add_node_to_root(node)
|
124
189
|
@application_request.root.add_child(node)
|
125
190
|
end
|
126
191
|
|
192
|
+
# Calculates the digest of {#application_request}
|
193
|
+
#
|
194
|
+
# @todo Use the digest calculation method in {Utilities} instead of implementing the
|
195
|
+
# functionality again here.
|
196
|
+
# @return [String] the base64 encoded digest of the {#application_request}
|
127
197
|
def calculate_digest
|
128
198
|
sha1 = OpenSSL::Digest::SHA1.new
|
129
199
|
encode(sha1.digest(@application_request.canonicalize))
|
130
200
|
end
|
131
201
|
|
202
|
+
# Adds value to signature node
|
203
|
+
#
|
204
|
+
# @param node [String] name of the signature node
|
205
|
+
# @param value [#to_s] the value to be set to the node
|
206
|
+
# @todo Remove this method and use {#set_node} method
|
132
207
|
def add_value_to_signature(node, value)
|
133
208
|
dsig = 'http://www.w3.org/2000/09/xmldsig#'
|
134
209
|
sig = @application_request.at_css("dsig|#{node}", 'dsig' => dsig)
|
135
210
|
sig.content = value
|
136
211
|
end
|
137
212
|
|
213
|
+
# Calculates the application request's signature value. Uses {#signing_private_key} for the
|
214
|
+
# calculation.
|
215
|
+
#
|
216
|
+
# @return [String] the base64 encoded signature
|
217
|
+
# @todo Move to {Utilities}
|
138
218
|
def calculate_signature
|
139
219
|
sha1 = OpenSSL::Digest::SHA1.new
|
140
220
|
dsig = 'http://www.w3.org/2000/09/xmldsig#'
|
@@ -143,6 +223,9 @@ module Sepa
|
|
143
223
|
encode signature
|
144
224
|
end
|
145
225
|
|
226
|
+
# Removes signature from the application request, calculates the application request's digest,
|
227
|
+
# calculates the signature and adds needed values to signature node. Also adds
|
228
|
+
# {#own_signing_certificate} to the signature node.
|
146
229
|
def process_signature
|
147
230
|
# No signature for Certificate Requests
|
148
231
|
return if @command == :get_certificate
|
@@ -157,6 +240,10 @@ module Sepa
|
|
157
240
|
add_value_to_signature('X509Certificate', format_cert(@own_signing_certificate))
|
158
241
|
end
|
159
242
|
|
243
|
+
# Adds target id to the application request after a specific node because the schema defines a
|
244
|
+
# sequence. Target id is only added if {#bank} is `:nordea`
|
245
|
+
#
|
246
|
+
# @param node [String] the name of the node after which the target id node will be added
|
160
247
|
def add_target_id_after(node)
|
161
248
|
return unless @bank == :nordea
|
162
249
|
|
@@ -1,22 +1,39 @@
|
|
1
1
|
module Sepa
|
2
|
+
|
3
|
+
# Contains functionality for the application response embedded in {Response}
|
4
|
+
# @todo Use functionality from this class more when validating response
|
2
5
|
class ApplicationResponse
|
3
6
|
include ActiveModel::Validations
|
4
7
|
include Utilities
|
5
8
|
|
9
|
+
# The raw xml of the application response
|
10
|
+
#
|
11
|
+
# @return [String] the raw xml of the application response
|
6
12
|
attr_reader :xml
|
7
13
|
|
8
14
|
validate :response_must_validate_against_schema
|
9
15
|
|
16
|
+
# Initializes the {ApplicationResponse} with an application response xml and bank
|
17
|
+
#
|
18
|
+
# @param app_resp [#to_s] the application response xml
|
19
|
+
# @param bank [Symbol] the bank from which the application response came from
|
10
20
|
def initialize(app_resp, bank)
|
11
21
|
@xml = app_resp
|
12
22
|
@bank = bank
|
13
23
|
end
|
14
24
|
|
25
|
+
# The application response as a nokogiri xml document
|
26
|
+
#
|
27
|
+
# @return [Nokogiri::XML::Document] the application response as a nokogiri document
|
15
28
|
def doc
|
16
29
|
@doc ||= xml_doc @xml
|
17
30
|
end
|
18
31
|
|
19
|
-
# Checks that the hash value reported in the signature matches the
|
32
|
+
# Checks that the hash value reported in the signature matches the one that is calculated
|
33
|
+
# locally
|
34
|
+
#
|
35
|
+
# @return [true] if hashes match
|
36
|
+
# @return [false] if hashes don't match
|
20
37
|
def hashes_match?
|
21
38
|
are = doc.clone
|
22
39
|
|
@@ -31,19 +48,36 @@ module Sepa
|
|
31
48
|
false
|
32
49
|
end
|
33
50
|
|
34
|
-
# Checks that the signature
|
51
|
+
# Checks that the signature has been calculated with the private key of the certificate's public
|
52
|
+
# key.
|
53
|
+
#
|
54
|
+
# @return [true] if signature can be verified
|
55
|
+
# @return [false] if signature fails to verify
|
35
56
|
def signature_is_valid?
|
36
57
|
validate_signature(doc, certificate, :normal)
|
37
58
|
end
|
38
59
|
|
60
|
+
# Returns the raw xml of the application response
|
61
|
+
#
|
62
|
+
# @return [String] the raw xml of the application response
|
39
63
|
def to_s
|
40
64
|
@xml
|
41
65
|
end
|
42
66
|
|
67
|
+
# The certificate which private key has been used to sign the application response
|
68
|
+
#
|
69
|
+
# @return [OpenSSL::X509::Certificate] if the certificate can be found
|
70
|
+
# @return [nil] if the certificate cannot be found
|
71
|
+
# @raise [OpenSSL::X509::CertificateError] if the certificate is not valid
|
43
72
|
def certificate
|
44
73
|
extract_cert(doc, 'X509Certificate', DSIG)
|
45
74
|
end
|
46
75
|
|
76
|
+
# Checks whether the embedded certificate has been signed by the private key of the bank's root
|
77
|
+
# certificate. The root certificate used varies by bank.
|
78
|
+
#
|
79
|
+
# @return [true] if the certificate is trusted
|
80
|
+
# @return [false] if the certificate is not trusted
|
47
81
|
def certificate_is_trusted?
|
48
82
|
root_certificate =
|
49
83
|
case @bank
|
@@ -58,6 +92,7 @@ module Sepa
|
|
58
92
|
|
59
93
|
private
|
60
94
|
|
95
|
+
# Validates that the response is valid against the application response schema
|
61
96
|
def response_must_validate_against_schema
|
62
97
|
check_validity_against_schema(doc, 'application_response.xsd')
|
63
98
|
end
|
@@ -1,7 +1,13 @@
|
|
1
1
|
module Sepa
|
2
|
+
|
3
|
+
# Contains functionality to check the attributes passed to {Client}. Uses
|
4
|
+
# ActiveModel::Validations for the actual validation.
|
2
5
|
module AttributeChecks
|
3
6
|
include ErrorMessages
|
4
7
|
|
8
|
+
# Commands which are allowed for a specific bank
|
9
|
+
#
|
10
|
+
# @return [Array<Symbol>] the commands which are allowed for {Client#bank}.
|
5
11
|
def allowed_commands
|
6
12
|
case bank
|
7
13
|
when :nordea
|
@@ -14,10 +20,12 @@ module Sepa
|
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|
23
|
+
# Checks that {Client#command} is included in {#allowed_commands}
|
17
24
|
def check_command
|
18
25
|
errors.add(:command, "Invalid command") unless allowed_commands.include? command
|
19
26
|
end
|
20
27
|
|
28
|
+
# Checks that signing keys and certificates can be initialized properly.
|
21
29
|
def check_keys
|
22
30
|
return if [:get_certificate, :get_bank_certificate, :create_certificate].include? command
|
23
31
|
|
@@ -34,6 +42,7 @@ module Sepa
|
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
45
|
+
# Checks that signing certificate signing request can be initialized properly.
|
37
46
|
def check_signing_csr
|
38
47
|
return unless [:get_certificate, :create_certificate].include? command
|
39
48
|
|
@@ -42,6 +51,7 @@ module Sepa
|
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
54
|
+
# Checks that encryption certificate signing request can be initialized properly.
|
45
55
|
def check_encryption_cert_request
|
46
56
|
return unless command == :create_certificate
|
47
57
|
|
@@ -50,6 +60,7 @@ module Sepa
|
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
63
|
+
# Checks that {Client#file_type} is proper
|
53
64
|
def check_file_type
|
54
65
|
return unless [:upload_file, :download_file_list, :download_file].include? command
|
55
66
|
|
@@ -58,6 +69,7 @@ module Sepa
|
|
58
69
|
end
|
59
70
|
end
|
60
71
|
|
72
|
+
# Checks that {Client#target_id} is valid.
|
61
73
|
def check_target_id
|
62
74
|
return if [:get_user_info,
|
63
75
|
:get_certificate,
|
@@ -70,6 +82,11 @@ module Sepa
|
|
70
82
|
check_presence_and_length(:target_id, 80, TARGET_ID_ERROR_MESSAGE)
|
71
83
|
end
|
72
84
|
|
85
|
+
# Checks presence and length of an attribute
|
86
|
+
#
|
87
|
+
# @param attribute [Symbol] the attribute to validate
|
88
|
+
# @param length [Integer] the maximum length of the attribute
|
89
|
+
# @param error_message [#to_s] the error message to display if the validation fails
|
73
90
|
def check_presence_and_length(attribute, length, error_message)
|
74
91
|
check = true
|
75
92
|
check &&= send(attribute)
|
@@ -80,6 +97,8 @@ module Sepa
|
|
80
97
|
errors.add(attribute, error_message) unless check
|
81
98
|
end
|
82
99
|
|
100
|
+
# Checks that the content (payload) of the request is somewhat correct. This validation is only
|
101
|
+
# run when {Client#command} is `:upload_file`.
|
83
102
|
def check_content
|
84
103
|
return unless command == :upload_file
|
85
104
|
|
@@ -91,12 +110,15 @@ module Sepa
|
|
91
110
|
errors.add(:content, CONTENT_ERROR_MESSAGE) unless check
|
92
111
|
end
|
93
112
|
|
113
|
+
# Checks that the {Client#pin} used in certificate requests in valid
|
94
114
|
def check_pin
|
95
115
|
return unless [:create_certificate, :get_certificate].include? command
|
96
116
|
|
97
117
|
check_presence_and_length(:pin, 20, PIN_ERROR_MESSAGE)
|
98
118
|
end
|
99
119
|
|
120
|
+
# Checks that {Client#environment} is included in {Client::ENVIRONMENTS}. Not run if
|
121
|
+
# {Client#command} is `:get_bank_certificate`.
|
100
122
|
def check_environment
|
101
123
|
return if command == :get_bank_certificate
|
102
124
|
|
@@ -105,12 +127,15 @@ module Sepa
|
|
105
127
|
end
|
106
128
|
end
|
107
129
|
|
130
|
+
# Checks that {Client#customer_id} is valid
|
108
131
|
def check_customer_id
|
109
132
|
unless customer_id && customer_id.respond_to?(:length) && customer_id.length.between?(1, 16)
|
110
133
|
errors.add(:customer_id, CUSTOMER_ID_ERROR_MESSAGE)
|
111
134
|
end
|
112
135
|
end
|
113
136
|
|
137
|
+
# Checks that {Client#bank_encryption_certificate} can be initialized properly. Only run if
|
138
|
+
# {Client#bank} is `:danske` and {Client#command} is not `:get_bank_certificate`.
|
114
139
|
def check_encryption_certificate
|
115
140
|
return unless bank == :danske
|
116
141
|
return if command == :get_bank_certificate
|
@@ -125,6 +150,7 @@ module Sepa
|
|
125
150
|
errors.add(:bank_encryption_certificate, ENCRYPTION_CERT_ERROR_MESSAGE)
|
126
151
|
end
|
127
152
|
|
153
|
+
# Checks that {Client#status} is included in {Client::STATUSES}.
|
128
154
|
def check_status
|
129
155
|
return unless [:download_file_list, :download_file].include? command
|
130
156
|
|
@@ -133,12 +159,16 @@ module Sepa
|
|
133
159
|
end
|
134
160
|
end
|
135
161
|
|
162
|
+
# Checks presence and length of {Client#file_reference} if {Client#command} is `:download_file`
|
136
163
|
def check_file_reference
|
137
164
|
return unless command == :download_file
|
138
165
|
|
139
166
|
check_presence_and_length :file_reference, 33, FILE_REFERENCE_ERROR_MESSAGE
|
140
167
|
end
|
141
168
|
|
169
|
+
# Checks that {Client#encryption_private_key} can be initialized properly. Is only run if
|
170
|
+
# {Client#bank} is `:danske` and {Client#command} is not `:create_certificate` or
|
171
|
+
# `:get_bank_certificate`.
|
142
172
|
def check_encryption_private_key
|
143
173
|
return unless bank == :danske
|
144
174
|
return if [:create_certificate, :get_bank_certificate].include? command
|
@@ -1,49 +1,90 @@
|
|
1
1
|
module Sepa
|
2
|
+
|
3
|
+
# Handles Danske Bank specific {Response} functionality. Mainly decryption and certificate
|
4
|
+
# specific stuff.
|
2
5
|
class DanskeResponse < Response
|
3
6
|
|
4
7
|
validate :valid_get_bank_certificate_response
|
5
8
|
validate :can_be_decrypted_with_given_key
|
6
9
|
|
10
|
+
# @return [String]
|
11
|
+
# @see Response#application_response
|
7
12
|
def application_response
|
8
13
|
@application_response ||= decrypt_application_response
|
9
14
|
end
|
10
15
|
|
16
|
+
# Returns the bank's encryption certificate which is used to encrypt messages sent to the bank.
|
17
|
+
# The certificate is only present in `:get_bank_certificate` responess.
|
18
|
+
#
|
19
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:get_bank_certificate`
|
20
|
+
# @return [nil] if command is any other
|
11
21
|
def bank_encryption_certificate
|
12
22
|
return unless @command == :get_bank_certificate
|
13
23
|
|
14
24
|
@bank_encryption_certificate ||= extract_cert(doc, 'BankEncryptionCert', DANSKE_PKI)
|
15
25
|
end
|
16
26
|
|
27
|
+
# Returns the bank's signing certificate which is used by the bank to sign the responses. The
|
28
|
+
# certificate is only present in `:get_bank_certificate` responses
|
29
|
+
#
|
30
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:get_bank_certificate`
|
31
|
+
# @return [nil] if {#command} is any other
|
17
32
|
def bank_signing_certificate
|
18
33
|
return unless @command == :get_bank_certificate
|
19
34
|
|
20
35
|
@bank_signing_certificate ||= extract_cert(doc, 'BankSigningCert', DANSKE_PKI)
|
21
36
|
end
|
22
37
|
|
38
|
+
# Returns the bank's root certificate which is the certificate that is used to sign bank's other
|
39
|
+
# certificates. Only present in `:get_bank_certificate` responses.
|
40
|
+
#
|
41
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:get_bank_certificate`
|
42
|
+
# @return [nil] if {#command} is any other
|
23
43
|
def bank_root_certificate
|
24
44
|
return unless @command == :get_bank_certificate
|
25
45
|
|
26
46
|
@bank_root_certificate ||= extract_cert(doc, 'BankRootCert', DANSKE_PKI)
|
27
47
|
end
|
28
48
|
|
49
|
+
# Returns own encryption certificate which has been signed by the bank. Only present in
|
50
|
+
# `:create_certificate` responses
|
51
|
+
#
|
52
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:create_certificate`
|
53
|
+
# @return [nil] if command is any other
|
29
54
|
def own_encryption_certificate
|
30
55
|
return unless @command == :create_certificate
|
31
56
|
|
32
57
|
@own_encryption_certificate ||= extract_cert(doc, 'EncryptionCert', DANSKE_PKI)
|
33
58
|
end
|
34
59
|
|
60
|
+
# Returns own signing certificate which has been signed by the bank. Is used to sign requests
|
61
|
+
# sent to the bank. Is only present in `:create_certificate` responses.
|
62
|
+
#
|
63
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:create_certificate`
|
64
|
+
# @return [nil] if command is any other
|
35
65
|
def own_signing_certificate
|
36
66
|
return unless @command == :create_certificate
|
37
67
|
|
38
68
|
@own_signing_certificate ||= extract_cert(doc, 'SigningCert', DANSKE_PKI)
|
39
69
|
end
|
40
70
|
|
71
|
+
# Returns the CA certificate that has been used to sign own signing and encryption certificates.
|
72
|
+
# Only present in `:create_certificate` responses
|
73
|
+
#
|
74
|
+
# @return [OpenSSL::X509::Certificate] if {#command} is `:create_certificate`
|
75
|
+
# @return [nil] if command is any other
|
41
76
|
def ca_certificate
|
42
77
|
return unless @command == :create_certificate
|
43
78
|
|
44
79
|
@ca_certificate ||= extract_cert(doc, 'CACert', DANSKE_PKI)
|
45
80
|
end
|
46
81
|
|
82
|
+
# Extract certificate that has been used to sign the response. This overrides
|
83
|
+
# {Response#certificate} method with specific functionality for `:get_bank_certificate` and
|
84
|
+
# `:create_certificate` commands. Otherwise just calls {Response#certificate}
|
85
|
+
#
|
86
|
+
# @return [OpenSSL::X509::Certificate]
|
87
|
+
# @raise [OpenSSL::X509::CertificateError] if certificate cannot be processed
|
47
88
|
def certificate
|
48
89
|
if [:get_bank_certificate, :create_certificate].include? @command
|
49
90
|
@certificate ||= begin
|
@@ -54,6 +95,13 @@ module Sepa
|
|
54
95
|
end
|
55
96
|
end
|
56
97
|
|
98
|
+
# Extract response code from the response. Overrides super method when {#command} is
|
99
|
+
# `:get_bank_certificate` or `:create_certificate` because response code node is named
|
100
|
+
# differently in those responses.
|
101
|
+
#
|
102
|
+
# @return [String] if response code is found
|
103
|
+
# @return [nil] if response code cannot be found
|
104
|
+
# @see Response#response_code
|
57
105
|
def response_code
|
58
106
|
return super unless [:get_bank_certificate, :create_certificate].include? @command
|
59
107
|
|
@@ -61,6 +109,12 @@ module Sepa
|
|
61
109
|
node.content if node
|
62
110
|
end
|
63
111
|
|
112
|
+
# Checks whether certificate embedded in the response has been signed with the bank's root
|
113
|
+
# certificate. Always returns true when {#command} is `:get_bank_certificate`, because the
|
114
|
+
# certificate is not present with that command.
|
115
|
+
#
|
116
|
+
# @return [true] if certificate is trusted
|
117
|
+
# @return [false] if certificate is not trusted
|
64
118
|
def certificate_is_trusted?
|
65
119
|
return true if @command == :get_bank_certificate
|
66
120
|
|
@@ -69,6 +123,14 @@ module Sepa
|
|
69
123
|
|
70
124
|
private
|
71
125
|
|
126
|
+
# Finds a node by its reference URI from Danske Bank's certificate responses. If {#command} is
|
127
|
+
# other than `:get_bank_certificate` or `:create_certificate` returns super. This method is
|
128
|
+
# needed because Danske Bank uses a different way to reference nodes in their certificate
|
129
|
+
# responses.
|
130
|
+
#
|
131
|
+
# @param uri [String] reference URI of the node to find
|
132
|
+
# @return [Nokogiri::XML::Node] node with signature removed from its document since signature
|
133
|
+
# has to be removed for canonicalization and hash calculation
|
72
134
|
def find_node_by_uri(uri)
|
73
135
|
return super unless [:get_bank_certificate, :create_certificate].include? @command
|
74
136
|
|
@@ -77,6 +139,15 @@ module Sepa
|
|
77
139
|
doc_without_signature.at("[xml|id='#{uri}']")
|
78
140
|
end
|
79
141
|
|
142
|
+
# Decrypts the application response in the response. Starts by calling {#decrypt_embedded_key}
|
143
|
+
# method to get the key used in encrypting the application response. After this the encrypted
|
144
|
+
# data is retrieved from the document and base64 decoded. After this the iv
|
145
|
+
# (initialization vector) is extracted from the encrypted data and a decipher with the
|
146
|
+
# 'DES-EDE3-CBC' algorithm is initialized (This is used by banks as encryption algorithm) and
|
147
|
+
# its key and iv set accordingly and mode changes to decrypt. After this the data is decrypted
|
148
|
+
# and returned as string.
|
149
|
+
#
|
150
|
+
# @return [String] the decrypted application response as raw xml
|
80
151
|
def decrypt_application_response
|
81
152
|
key = decrypt_embedded_key
|
82
153
|
|
@@ -96,6 +167,8 @@ module Sepa
|
|
96
167
|
decipher.update(encypted_data) + decipher.final
|
97
168
|
end
|
98
169
|
|
170
|
+
# Validates get bank certificate response. Response is valid if service fault is not returned
|
171
|
+
# from the bank.
|
99
172
|
def valid_get_bank_certificate_response
|
100
173
|
return unless @command == :get_bank_certificate
|
101
174
|
|
@@ -104,6 +177,11 @@ module Sepa
|
|
104
177
|
end
|
105
178
|
end
|
106
179
|
|
180
|
+
# Extracts the encrypted application response from the response and returns it as a nokogiri
|
181
|
+
# document
|
182
|
+
#
|
183
|
+
# @return [Nokogiri::XML] the encrypted application response if it is found
|
184
|
+
# @return [nil] if the application response cannot be found
|
107
185
|
def encrypted_application_response
|
108
186
|
@encrypted_application_response ||= begin
|
109
187
|
encrypted_application_response = extract_application_response(BXD)
|
@@ -111,6 +189,8 @@ module Sepa
|
|
111
189
|
end
|
112
190
|
end
|
113
191
|
|
192
|
+
# Validates that the encrypted key in the response can be decrypted with the private key given
|
193
|
+
# to the response in the parameters. Response is invalid if this cannot be done.
|
114
194
|
def can_be_decrypted_with_given_key
|
115
195
|
return if [:get_bank_certificate, :create_certificate].include? @command
|
116
196
|
return unless encrypted_application_response.css('CipherValue', 'xmlns' => XMLENC)[0]
|
@@ -120,6 +200,12 @@ module Sepa
|
|
120
200
|
end
|
121
201
|
end
|
122
202
|
|
203
|
+
# Decrypts (assymetrically) the symmetric encryption key embedded in the response with the
|
204
|
+
# private key given to the response in the parameters. The key is later used to decrypt the
|
205
|
+
# application response.
|
206
|
+
#
|
207
|
+
# @return [String] the encryption key as a string
|
208
|
+
# @return [nil] if the key cannot be decrypted with the given key
|
123
209
|
def decrypt_embedded_key
|
124
210
|
enc_key = encrypted_application_response.css('CipherValue', 'xmlns' => XMLENC)[0].content
|
125
211
|
enc_key = decode enc_key
|