aliquot 0.12.0 → 2.0.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: 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: []