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 +4 -4
- data/lib/aliquot/error.rb +3 -0
- data/lib/aliquot/payment.rb +25 -17
- data/lib/aliquot/validator.rb +14 -14
- metadata +23 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4b575c575e5104e23692cbf9ddf45fd40555862d306a88995d33c7c2fa2085b
|
4
|
+
data.tar.gz: b2be001378fea01010ae490230d8cd14c0767b6fec3155a7a4d6c24c95262c7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41e6616072916a6bf54baa76e62e8e9ccd07afc5056f7bcbf3886292d941f66c451f434924837e667592543184c91089f7032b25a1bb9daf646d9a69ef647529
|
7
|
+
data.tar.gz: adfe0fe58146dd4824c1677a482e948b4405973d6c18b7bcd31f516d446921f3b6266b8600148ec05b7c03ba78650c7f33de39f100e93eed8587abcee28999c2
|
data/lib/aliquot/error.rb
CHANGED
data/lib/aliquot/payment.rb
CHANGED
@@ -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
|
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
|
-
#
|
20
|
+
# recipient_id:: Google Pay recipient ID
|
21
21
|
# signing_keys:: Signing keys fetched from Google
|
22
|
-
def initialize(token_string, shared_secret,
|
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
|
-
@
|
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, "
|
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', @
|
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, '
|
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
|
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
|
data/lib/aliquot/validator.rb
CHANGED
@@ -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
|
-
|
95
|
-
|
96
|
-
|
96
|
+
required(:protocolVersion).filled(:str?).when(eql?: 'ECv2') do
|
97
|
+
required(:intermediateSigningKey)
|
98
|
+
end
|
97
99
|
|
98
|
-
|
100
|
+
required(:signedMessage).filled(:str?, :json_string?)
|
99
101
|
|
100
|
-
|
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?)
|
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(
|
123
|
-
method.eql?('CRYPTOGRAM_3DS') > cryptogram
|
122
|
+
rule(cryptogram: %i[authMethod cryptogram]) do |method, cryptogram|
|
123
|
+
method.eql?('CRYPTOGRAM_3DS') > required(:cryptogram)
|
124
124
|
end
|
125
125
|
|
126
|
-
rule(
|
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(
|
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
|
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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
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
|
-
|
127
|
-
|
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: []
|