aliquot-pay 0.12.0 → 0.13.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/lib/aliquot-pay.rb +222 -4
- data/lib/aliquot-pay/util.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efe2c37b1391901980795e2bf48846eb753b5189e9d6a9158b399eb13be452dc
|
4
|
+
data.tar.gz: bb99c1418c9c3f83cce4a940c9d3983b4ba227eff8e88be729409a33a4e0a8f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef84809aaec9b3e8e4d88268f3daf6c96288150daf67cbda6e02066d1b61cfc1baf4cae8e8c541f9c320d309a431216081e7c2960382dcfb08ef3f54e570b490
|
7
|
+
data.tar.gz: 2a6bef39650b08231597b420f4227877cd017b0544ff857a6b0c373634a0a7a260ea6d2da5b3682963b136b015aa488f85f8e37f9a98840ed33e217fb8d12c29
|
data/lib/aliquot-pay.rb
CHANGED
@@ -4,7 +4,7 @@ require 'json'
|
|
4
4
|
|
5
5
|
require 'aliquot-pay/util'
|
6
6
|
|
7
|
-
|
7
|
+
class AliquotPay
|
8
8
|
class Error < StandardError; end
|
9
9
|
|
10
10
|
EC_CURVE = 'prime256v1'.freeze
|
@@ -14,6 +14,21 @@ module AliquotPay
|
|
14
14
|
merchant_id: '0123456789',
|
15
15
|
}.freeze
|
16
16
|
|
17
|
+
attr_accessor :signature, :intermediate_signing_key, :signed_message
|
18
|
+
attr_accessor :signed_key, :signatures
|
19
|
+
attr_accessor :key_expiration, :key_value
|
20
|
+
attr_accessor :encrypted_message, :cleartext_message, :ephemeral_public_key, :tag
|
21
|
+
attr_accessor :message_expiration, :message_id, :payment_method, :payment_method_details
|
22
|
+
attr_accessor :pan, :expiration_month, :expiration_year, :auth_method
|
23
|
+
attr_accessor :cryptogram, :eci_indicator
|
24
|
+
|
25
|
+
attr_accessor :recipient, :info, :root_key, :intermediate_key
|
26
|
+
attr_writer :merchant_id, :shared_secret, :token, :signed_key_string
|
27
|
+
|
28
|
+
def initialize(protocol_version = :ECv2)
|
29
|
+
@protocol_version = protocol_version
|
30
|
+
end
|
31
|
+
|
17
32
|
def self.sign(key, message)
|
18
33
|
d = OpenSSL::Digest::SHA256.new
|
19
34
|
def key.private?; private_key?; end
|
@@ -43,9 +58,9 @@ module AliquotPay
|
|
43
58
|
tag = AliquotPay::Util.calculate_tag(keys[:mac_key], encrypted_message)
|
44
59
|
|
45
60
|
{
|
46
|
-
encryptedMessage
|
47
|
-
ephemeralPublicKey
|
48
|
-
tag
|
61
|
+
'encryptedMessage' => Base64.strict_encode64(encrypted_message),
|
62
|
+
'ephemeralPublicKey' => Base64.strict_encode64(eph.public_key.to_bn.to_s(2)),
|
63
|
+
'tag' => Base64.strict_encode64(tag),
|
49
64
|
}
|
50
65
|
end
|
51
66
|
|
@@ -138,4 +153,207 @@ module AliquotPay
|
|
138
153
|
},
|
139
154
|
}
|
140
155
|
end
|
156
|
+
|
157
|
+
def token
|
158
|
+
build_token
|
159
|
+
end
|
160
|
+
|
161
|
+
def extract_root_signing_keys
|
162
|
+
key = Base64.strict_encode64(eckey_to_public(ensure_root_key).to_der)
|
163
|
+
{
|
164
|
+
'keys' => [
|
165
|
+
'protocolVersion' => @protocol_version,
|
166
|
+
'keyValue' => key,
|
167
|
+
]
|
168
|
+
}.to_json
|
169
|
+
end
|
170
|
+
|
171
|
+
def eckey_to_public(key)
|
172
|
+
p = OpenSSL::PKey::EC.new(EC_CURVE)
|
173
|
+
|
174
|
+
p.public_key = key.public_key
|
175
|
+
|
176
|
+
p
|
177
|
+
end
|
178
|
+
|
179
|
+
#private
|
180
|
+
|
181
|
+
def sign(key, message)
|
182
|
+
d = OpenSSL::Digest::SHA256.new
|
183
|
+
def key.private?; private_key?; end
|
184
|
+
Base64.strict_encode64(key.sign(d, message))
|
185
|
+
end
|
186
|
+
|
187
|
+
def encrypt(cleartext_message)
|
188
|
+
@recipient ||= OpenSSL::PKey::EC.new('prime256v1').generate_key
|
189
|
+
@info ||= 'Google'
|
190
|
+
|
191
|
+
eph = AliquotPay::Util.generate_ephemeral_key
|
192
|
+
@shared_secret ||= AliquotPay::Util.generate_shared_secret(eph, @recipient.public_key)
|
193
|
+
ss = @shared_secret
|
194
|
+
|
195
|
+
case @protocol_version
|
196
|
+
when :ECv1
|
197
|
+
cipher = OpenSSL::Cipher::AES128.new(:CTR)
|
198
|
+
when :ECv2
|
199
|
+
cipher = OpenSSL::Cipher::AES256.new(:CTR)
|
200
|
+
else
|
201
|
+
raise StandardError, "Invalid protocol_version #{protocol_version}"
|
202
|
+
end
|
203
|
+
|
204
|
+
keys = AliquotPay::Util.derive_keys(eph.public_key.to_bn.to_s(2), ss, @info, @protocol_version)
|
205
|
+
|
206
|
+
cipher.encrypt
|
207
|
+
cipher.key = keys[:aes_key]
|
208
|
+
|
209
|
+
encrypted_message = cipher.update(cleartext_message) + cipher.final
|
210
|
+
|
211
|
+
tag = AliquotPay::Util.calculate_tag(keys[:mac_key], encrypted_message)
|
212
|
+
|
213
|
+
{
|
214
|
+
'encryptedMessage' => Base64.strict_encode64(encrypted_message),
|
215
|
+
'ephemeralPublicKey' => Base64.strict_encode64(eph.public_key.to_bn.to_s(2)),
|
216
|
+
'tag' => Base64.strict_encode64(tag),
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
def build_payment_method_details
|
221
|
+
return @payment_method_details if @payment_method_details
|
222
|
+
value = {
|
223
|
+
'pan' => @pan || '4111111111111111',
|
224
|
+
'expirationYear' => @expiration_year || 2023,
|
225
|
+
'expirationMonth' => @expiration_month || 12,
|
226
|
+
'authMethod' => @auth_method || 'PAN_ONLY',
|
227
|
+
}
|
228
|
+
|
229
|
+
if @auth_method == 'CRYPTOGRAM_3DS'
|
230
|
+
value.merge!(
|
231
|
+
'cryptogram' => @cryptogram || 'SOME CRYPTOGRAM',
|
232
|
+
'eciIndicator' => @eci_indicator || '05'
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
value
|
237
|
+
end
|
238
|
+
|
239
|
+
def build_cleartext_message
|
240
|
+
return @cleartext_message if @cleartext_message
|
241
|
+
default_message_id = Base64.strict_encode64(OpenSSL::Random.random_bytes(24))
|
242
|
+
default_message_expiration = ((Time.now.to_f + 60 * 5) * 1000).round.to_s
|
243
|
+
|
244
|
+
@cleartext_message = {
|
245
|
+
'messageExpiration' => @message_expiration || default_message_expiration,
|
246
|
+
'messageId' => @message_id || default_message_id,
|
247
|
+
'paymentMethod' => @payment_method || 'CARD',
|
248
|
+
'paymentMethodDetails' => build_payment_method_details
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
def build_signed_message
|
253
|
+
return @signed_message if @signed_message
|
254
|
+
|
255
|
+
signed_message = encrypt(build_cleartext_message.to_json)
|
256
|
+
signed_message['encryptedMessage'] = @encrypted_message if @encrypted_message
|
257
|
+
signed_message['ephemeralPublicKey'] = @ephemeral_public_key if @ephemeral_public_key
|
258
|
+
signed_message['tag'] = @tag if @tag
|
259
|
+
|
260
|
+
@signed_message = signed_message
|
261
|
+
end
|
262
|
+
|
263
|
+
def signed_message_string
|
264
|
+
@signed_message_string ||= build_signed_message.to_json
|
265
|
+
end
|
266
|
+
|
267
|
+
def build_signed_key
|
268
|
+
return @signed_key if @signed_key
|
269
|
+
ensure_intermediate_key
|
270
|
+
|
271
|
+
if @intermediate_key.private_key? || @intermediate_key.public_key?
|
272
|
+
public_key = eckey_to_public(@intermediate_key)
|
273
|
+
else
|
274
|
+
fail 'Intermediate key must be public and private key'
|
275
|
+
end
|
276
|
+
|
277
|
+
default_key_value = Base64.strict_encode64(public_key.to_der)
|
278
|
+
default_key_expiration = "#{Time.now.to_i + 3600}000"
|
279
|
+
|
280
|
+
@signed_key = {
|
281
|
+
'keyExpiration' => @key_expiration || default_key_expiration,
|
282
|
+
'keyValue' => @key_value || default_key_value,
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
def signed_key_string
|
287
|
+
@signed_key_string ||= build_signed_key.to_json
|
288
|
+
end
|
289
|
+
|
290
|
+
def ensure_root_key
|
291
|
+
@root_key ||= OpenSSL::PKey::EC.new(EC_CURVE).generate_key
|
292
|
+
end
|
293
|
+
|
294
|
+
def ensure_intermediate_key
|
295
|
+
@intermediate_key ||= OpenSSL::PKey::EC.new(EC_CURVE).generate_key
|
296
|
+
end
|
297
|
+
|
298
|
+
def build_signature
|
299
|
+
return @signature if @signature
|
300
|
+
key = case @protocol_version
|
301
|
+
when :ECv1
|
302
|
+
ensure_root_key
|
303
|
+
when :ECv2
|
304
|
+
ensure_intermediate_key
|
305
|
+
end
|
306
|
+
|
307
|
+
signature_string =
|
308
|
+
signed_string_message = ['Google',
|
309
|
+
"merchant:#{merchant_id}",
|
310
|
+
@protocol_version.to_s,
|
311
|
+
signed_message_string].map do |str|
|
312
|
+
[str.length].pack('V') + str
|
313
|
+
end.join
|
314
|
+
@signature = sign(key, signature_string)
|
315
|
+
end
|
316
|
+
|
317
|
+
def build_signatures
|
318
|
+
return @signatures if @signatures
|
319
|
+
|
320
|
+
signature_string =
|
321
|
+
signed_key_signature = ['Google', 'ECv2', signed_key_string].map do |str|
|
322
|
+
[str.to_s.length].pack('V') + str.to_s
|
323
|
+
end.join
|
324
|
+
|
325
|
+
@signatures = [sign(ensure_root_key, signature_string)]
|
326
|
+
end
|
327
|
+
|
328
|
+
def build_token
|
329
|
+
return @token if @token
|
330
|
+
res = {
|
331
|
+
'protocolVersion' => @protocol_version.to_s,
|
332
|
+
'signedMessage' => @signed_message || signed_message_string,
|
333
|
+
'signature' => build_signature,
|
334
|
+
}
|
335
|
+
|
336
|
+
if @protocol_version == :ECv2
|
337
|
+
intermediate = {
|
338
|
+
'intermediateSigningKey' => @intermediate_signing_key || {
|
339
|
+
'signedKey' => signed_key_string,
|
340
|
+
'signatures' => build_signatures,
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
344
|
+
res.merge!(intermediate)
|
345
|
+
end
|
346
|
+
|
347
|
+
@token = res
|
348
|
+
end
|
349
|
+
|
350
|
+
def merchant_id
|
351
|
+
@merchant_id ||= DEFAULTS[:merchant_id]
|
352
|
+
end
|
353
|
+
|
354
|
+
def shared_secret
|
355
|
+
return Base64.strict_encode64(@shared_secret) if @shared_secret
|
356
|
+
@shared_secret ||= Random.new.bytes(32)
|
357
|
+
shared_secret
|
358
|
+
end
|
141
359
|
end
|
data/lib/aliquot-pay/util.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aliquot-pay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clearhaus
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hkdf
|