aliquot-pay 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5d8f8763b6da348c202d9ac6b747deb0f4c4e22b7ee63301dffc9c483f54369
4
- data.tar.gz: 00a805257ee340456dfa3aa9920fac250f8831ed2061c8634402910e42c579d0
3
+ metadata.gz: efe2c37b1391901980795e2bf48846eb753b5189e9d6a9158b399eb13be452dc
4
+ data.tar.gz: bb99c1418c9c3f83cce4a940c9d3983b4ba227eff8e88be729409a33a4e0a8f7
5
5
  SHA512:
6
- metadata.gz: b64c6ceaa8c0645557a1ec10c54fd10458ed4c03b5d52e223d10b0245ef222bed81cae829094a1eeac4958948e578cf577041c01eeaa0f7085f14aedf8692c9e
7
- data.tar.gz: c2a7637c5bf0f7f6238045dbde732766267782de8b1d60af57ae533bd83a45c1a6610a73783356fe397609a48c5d904316199b16ac37570f4320dbf5fd2ad621
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
- module AliquotPay
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: Base64.strict_encode64(encrypted_message),
47
- ephemeralPublicKey: Base64.strict_encode64(eph.public_key.to_bn.to_s(2)),
48
- tag: Base64.strict_encode64(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
@@ -1,7 +1,7 @@
1
1
  require 'openssl'
2
2
  require 'hkdf'
3
3
 
4
- module AliquotPay
4
+ class AliquotPay
5
5
  class Util
6
6
  def self.generate_ephemeral_key
7
7
  OpenSSL::PKey::EC.new(AliquotPay::EC_CURVE).generate_key
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.12.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-14 00:00:00.000000000 Z
11
+ date: 2019-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hkdf