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