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.
- 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
|