aliquot 0.12.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a327bbbe5f475b924454c120979cb3bc965c5363c2740366a9f1abea19a59707
4
- data.tar.gz: 044dd4f0c2daa2d72685133fbd27408247281449276c0e36cba1141778d98176
3
+ metadata.gz: b4b575c575e5104e23692cbf9ddf45fd40555862d306a88995d33c7c2fa2085b
4
+ data.tar.gz: b2be001378fea01010ae490230d8cd14c0767b6fec3155a7a4d6c24c95262c7d
5
5
  SHA512:
6
- metadata.gz: f25a55664501aa93322675512975d3471c4586112855d2d30f32b0f61fcad6240fdd3750d36e418feaa415f3a49ee752271f2d7c1dc5fe28cd6615fc5d16811b
7
- data.tar.gz: e48a4d748adc053caa2e8a7770d5ef580fee0867497fa2d98dfac8cfc202a8183e3e8c900b56bac1cd121a95fadc4d8699511442263e93076d10e95ce53407af
6
+ metadata.gz: 41e6616072916a6bf54baa76e62e8e9ccd07afc5056f7bcbf3886292d941f66c451f434924837e667592543184c91089f7032b25a1bb9daf646d9a69ef647529
7
+ data.tar.gz: adfe0fe58146dd4824c1677a482e948b4405973d6c18b7bcd31f516d446921f3b6266b8600148ec05b7c03ba78650c7f33de39f100e93eed8587abcee28999c2
@@ -28,4 +28,7 @@ module Aliquot
28
28
 
29
29
  # When shared_secret is invalid
30
30
  class InvalidSharedSecretError < Error; end
31
+
32
+ # When recipient_id is invalid
33
+ class InvalidRecipientIDError < Error; end
31
34
  end
@@ -10,29 +10,29 @@ module Aliquot
10
10
  ##
11
11
  # A Payment represents a single payment using Google Pay.
12
12
  # It is used to verify/decrypt the supplied token by using the shared secret,
13
- # thus avoiding having knowledge of merchant primary keys.
13
+ # thus avoiding having knowledge of any private keys involved.
14
14
  class Payment
15
15
  SUPPORTED_PROTOCOL_VERSIONS = %w[ECv1 ECv2].freeze
16
16
  ##
17
17
  # Parameters:
18
18
  # token_string:: Google Pay token (JSON string)
19
19
  # shared_secret:: Base64 encoded shared secret
20
- # merchant_id:: Google Pay merchant ID ("merchant:<SOMETHING>")
20
+ # recipient_id:: Google Pay recipient ID
21
21
  # signing_keys:: Signing keys fetched from Google
22
- def initialize(token_string, shared_secret, merchant_id,
22
+ def initialize(token_string, shared_secret, recipient_id,
23
23
  signing_keys: ENV['GOOGLE_SIGNING_KEYS'])
24
24
 
25
25
  begin
26
26
  validation = Aliquot::Validator::Token.new(JSON.parse(token_string))
27
27
  validation.validate
28
28
  rescue JSON::JSONError => e
29
- raise InputError, "token JSON invalid, #{e.message}"
29
+ raise InputError, "token JSON is invalid, #{e.message}"
30
30
  end
31
31
 
32
32
  @token = validation.output
33
33
 
34
34
  @shared_secret = shared_secret
35
- @merchant_id = merchant_id
35
+ @recipient_id = recipient_id
36
36
  @signing_keys = signing_keys
37
37
  end
38
38
 
@@ -43,13 +43,15 @@ module Aliquot
43
43
  raise Error, "supported protocol versions are #{SUPPORTED_PROTOCOL_VERSIONS.join(', ')}"
44
44
  end
45
45
 
46
+ @recipient_id = validate_recipient_id
47
+
48
+ check_shared_secret
49
+
46
50
  if protocol_version == 'ECv2'
47
51
  @intermediate_key = validate_intermediate_key
48
- raise InvalidSignatureError, 'intermediate certificate expired' if intermediate_key_expired?
52
+ raise InvalidSignatureError, 'intermediate certificate is expired' if intermediate_key_expired?
49
53
  end
50
54
 
51
- check_shared_secret
52
-
53
55
  check_signature
54
56
 
55
57
  @signed_message = validate_signed_message
@@ -57,22 +59,22 @@ module Aliquot
57
59
  begin
58
60
  aes_key, mac_key = derive_keys(@signed_message[:ephemeralPublicKey], @shared_secret, 'Google')
59
61
  rescue => e
60
- raise KeyDerivationError, "unable to derive keys, #{e.message}"
62
+ raise KeyDerivationError, "cannot derive keys, #{e.message}"
61
63
  end
62
64
 
63
- raise InvalidMacError unless valid_mac?(mac_key)
65
+ raise InvalidMacError, 'MAC does not match' unless valid_mac?(mac_key)
64
66
 
65
67
  begin
66
68
  @message = JSON.parse(decrypt(aes_key, @signed_message[:encryptedMessage]))
67
69
  rescue JSON::JSONError => e
68
- raise InputError, "encryptedMessage JSON invalid, #{e.message}"
70
+ raise InputError, "encryptedMessage JSON is invalid, #{e.message}"
69
71
  rescue => e
70
72
  raise DecryptionError, "decryption failed, #{e.message}"
71
73
  end
72
74
 
73
75
  @message = validate_message
74
76
 
75
- raise TokenExpiredError if expired?
77
+ raise TokenExpiredError, 'token is expired' if expired?
76
78
 
77
79
  @message
78
80
  end
@@ -100,6 +102,12 @@ module Aliquot
100
102
  @intermediate_key[:keyExpiration].to_i < cur_millis
101
103
  end
102
104
 
105
+ def validate_recipient_id
106
+ raise InvalidRecipientIDError, 'recipient_id must be alphanumeric and punctuation' unless /\A[[:graph:]]+\z/ =~ @recipient_id
107
+
108
+ @recipient_id
109
+ end
110
+
103
111
  def check_shared_secret
104
112
  begin
105
113
  decoded = Base64.strict_decode64(@shared_secret)
@@ -111,7 +119,7 @@ module Aliquot
111
119
  end
112
120
 
113
121
  def check_signature
114
- signed_string_message = ['Google', @merchant_id, protocol_version, @token[:signedMessage]].map do |str|
122
+ signed_string_message = ['Google', @recipient_id, protocol_version, @token[:signedMessage]].map do |str|
115
123
  [str.length].pack('V') + str
116
124
  end.join
117
125
  message_signature = Base64.strict_decode64(@token[:signature])
@@ -126,7 +134,7 @@ module Aliquot
126
134
  key.verify(new_digest, message_signature, signed_string_message)
127
135
  end.any?
128
136
 
129
- raise InvalidSignatureError unless success
137
+ raise InvalidSignatureError, 'signature of signedMessage does not match' unless success
130
138
  when 'ECv2'
131
139
  signed_key_signature = ['Google', 'ECv2', @token[:intermediateSigningKey][:signedKey]].map do |str|
132
140
  [str.length].pack('V') + str
@@ -134,7 +142,7 @@ module Aliquot
134
142
 
135
143
  # Check that the intermediate key signed the message
136
144
  pkey = OpenSSL::PKey::EC.new(Base64.strict_decode64(@intermediate_key[:keyValue]))
137
- raise InvalidSignatureError, 'intermediate did not sign message' unless pkey.verify(new_digest, message_signature, signed_string_message)
145
+ raise InvalidSignatureError, 'signature of signedMessage does not match' unless pkey.verify(new_digest, message_signature, signed_string_message)
138
146
 
139
147
  intermediate_signatures = @token[:intermediateSigningKey][:signatures]
140
148
 
@@ -145,12 +153,12 @@ module Aliquot
145
153
  signed_key_signature
146
154
  )
147
155
 
148
- raise InvalidSignatureError, 'intermediate not signed' unless success
156
+ raise InvalidSignatureError, 'no valid signature of intermediate key' unless success
149
157
  end
150
158
  rescue OpenSSL::PKey::PKeyError => e
151
159
  # Catches problems with verifying signature. Can be caused by signature
152
160
  # being valid ASN1 but having invalid structure.
153
- raise InvalidSignatureError, e.message
161
+ raise InvalidSignatureError, "error verifying signature, #{e.message}"
154
162
  end
155
163
 
156
164
  def root_keys
@@ -22,6 +22,7 @@ module Aliquot
22
22
  month?: 'must be a month (1..12)',
23
23
  year?: 'must be a year (2000..3000)',
24
24
  base64_asn1?: 'must be base64 encoded asn1 value',
25
+ json_object?: 'must be a JSON object',
25
26
 
26
27
  authMethodCryptogram3DS: 'authMethod CRYPTOGRAM_3DS requires eciIndicator',
27
28
  authMethodCard: 'eciIndicator/cryptogram must be omitted when PAN_ONLY',
@@ -64,6 +65,8 @@ module Aliquot
64
65
  predicate(:year?) { |x| x.between?(2000, 3000) }
65
66
 
66
67
  predicate(:base64_asn1?) { |x| OpenSSL::ASN1.decode(Base64.strict_decode64(x)) rescue false }
68
+
69
+ predicate(:json_object?) { |x| hash?(x) }
67
70
  end
68
71
 
69
72
  # Base for DRY-Validation schemas used in Aliquot.
@@ -78,7 +81,6 @@ module Aliquot
78
81
  IntermediateSigningKeySchema = Dry::Validation.Schema(BaseSchema) do
79
82
  required(:signedKey).filled(:str?, :json_string?)
80
83
 
81
- # TODO: Check if elements of array are valid signatures
82
84
  required(:signatures).filled(:array?) { each { base64? & base64_asn1? } }
83
85
  end
84
86
 
@@ -91,21 +93,19 @@ module Aliquot
91
93
  TokenSchema = Dry::Validation.Schema(BaseSchema) do
92
94
  required(:signature).filled(:str?, :base64?, :base64_asn1?)
93
95
 
94
- # Currently supposed to be ECv1, but may evolve.
95
- required(:protocolVersion).filled(:str?)
96
- required(:signedMessage).filled(:str?, :json_string?)
96
+ required(:protocolVersion).filled(:str?).when(eql?: 'ECv2') do
97
+ required(:intermediateSigningKey)
98
+ end
97
99
 
98
- optional(:intermediateSigningKey).schema(IntermediateSigningKeySchema)
100
+ required(:signedMessage).filled(:str?, :json_string?)
99
101
 
100
- rule('ECv2 implies intermediateSigningKey': %i[protocolVersion intermediateSigningKey]) do |version, intermediatekey|
101
- version.eql?('ECv2') > intermediatekey.filled?
102
- end
102
+ optional(:intermediateSigningKey).value(:json_object?) { schema(IntermediateSigningKeySchema) }
103
103
  end
104
104
 
105
105
  # DRY-Validation schema for signedMessage component Google Pay token
106
106
  SignedMessageSchema = Dry::Validation.Schema(BaseSchema) do
107
107
  required(:encryptedMessage).filled(:str?, :base64?)
108
- required(:ephemeralPublicKey).filled(:str?, :base64?).value(size?: 44)
108
+ required(:ephemeralPublicKey).filled(:str?, :base64?)
109
109
  required(:tag).filled(:str?, :base64?)
110
110
  end
111
111
 
@@ -119,15 +119,15 @@ module Aliquot
119
119
  optional(:cryptogram).filled(:str?)
120
120
  optional(:eciIndicator).filled(:str?, :eci?)
121
121
 
122
- rule('when authMethod is CRYPTOGRAM_3DS, cryptogram': %i[authMethod cryptogram]) do |method, cryptogram|
123
- method.eql?('CRYPTOGRAM_3DS') > cryptogram.filled?
122
+ rule(cryptogram: %i[authMethod cryptogram]) do |method, cryptogram|
123
+ method.eql?('CRYPTOGRAM_3DS') > required(:cryptogram)
124
124
  end
125
125
 
126
- rule('when authMethod is PAN_ONLY, eciIndicator': %i[authMethod eciIndicator]) do |method, eci|
126
+ rule(eciIndicator: %i[authMethod eciIndicator]) do |method, eci|
127
127
  method.eql?('PAN_ONLY').then(eci.none?)
128
128
  end
129
129
 
130
- rule('when authMethod is PAN_ONLY, cryptogram': %i[authMethod cryptogram]) do |method, cryptogram|
130
+ rule(cryptogram: %i[authMethod cryptogram]) do |method, cryptogram|
131
131
  method.eql?('PAN_ONLY').then(cryptogram.none?)
132
132
  end
133
133
  end
@@ -137,7 +137,7 @@ module Aliquot
137
137
  required(:messageExpiration).filled(:str?, :integer_string?)
138
138
  required(:messageId).filled(:str?)
139
139
  required(:paymentMethod).filled(:str?, eql?: 'CARD')
140
- required(:paymentMethodDetails).schema(PaymentMethodDetailsSchema)
140
+ required(:paymentMethodDetails).value(:json_object?) { schema PaymentMethodDetailsSchema }
141
141
  end
142
142
 
143
143
  module InstanceMethods
metadata CHANGED
@@ -1,71 +1,77 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aliquot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clearhaus
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-11 00:00:00.000000000 Z
11
+ date: 2020-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-validation
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.0
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '0'
22
+ version: '0.13'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.11.0
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '0'
32
+ version: '0.13'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: excon
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
39
+ version: 0.71.0
34
40
  type: :runtime
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
44
  - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '0'
46
+ version: 0.71.0
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: hkdf
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
51
  - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: '0'
53
+ version: '0.3'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
58
  - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: '0'
60
+ version: '0.3'
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: aliquot-pay
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
65
  - - "~>"
60
66
  - !ruby/object:Gem::Version
61
- version: '0.8'
67
+ version: 2.0.0
62
68
  type: :development
63
69
  prerelease: false
64
70
  version_requirements: !ruby/object:Gem::Requirement
65
71
  requirements:
66
72
  - - "~>"
67
73
  - !ruby/object:Gem::Version
68
- version: '0.8'
74
+ version: 2.0.0
69
75
  - !ruby/object:Gem::Dependency
70
76
  name: rspec
71
77
  requirement: !ruby/object:Gem::Requirement
@@ -80,21 +86,7 @@ dependencies:
80
86
  - - "~>"
81
87
  - !ruby/object:Gem::Version
82
88
  version: '3'
83
- - !ruby/object:Gem::Dependency
84
- name: pry
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- description:
89
+ description:
98
90
  email: hello@clearhaus.com
99
91
  executables: []
100
92
  extensions: []
@@ -108,7 +100,7 @@ homepage: https://github.com/clearhaus/aliquot
108
100
  licenses:
109
101
  - MIT
110
102
  metadata: {}
111
- post_install_message:
103
+ post_install_message:
112
104
  rdoc_options: []
113
105
  require_paths:
114
106
  - lib
@@ -123,9 +115,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
115
  - !ruby/object:Gem::Version
124
116
  version: '0'
125
117
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.7.7
128
- signing_key:
118
+ rubygems_version: 3.1.4
119
+ signing_key:
129
120
  specification_version: 4
130
121
  summary: Validates Google Pay tokens
131
122
  test_files: []