pedicel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 402a60cbe58a1a300764a3a37d53434c0e764f60
4
+ data.tar.gz: 1551470f672d864db8fec34b25eb0521c59de87b
5
+ SHA512:
6
+ metadata.gz: 3ec3218ec7911fbf7e2cd3d11b5fb3fc839be0f8e51694565f7cef655fdebc0d5133a906ea0bfc215e21da4030281f6c7f5c202fc3332e67b98a108db5d3c0ab
7
+ data.tar.gz: b770f9aff8ace1d06fda313fcdf9798bd84c9d78394b42539d22bbcd359db63956d201997477aa1e243b3c31fb936592db25cbd0d7cd29c79fdca47290258d91
data/lib/pedicel.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'pedicel/base'
2
+ require 'pedicel/ec'
3
+ require 'pedicel/rsa'
4
+
5
+ module Pedicel
6
+ class Error < StandardError; end
7
+ class TokenFormatError < Error; end
8
+ class SignatureError < Error; end
9
+ class VersionError < Error; end
10
+ class CertificateError < Error; end
11
+ class EcKeyError < Error; end
12
+
13
+ DEFAULTS = {
14
+ oids: {
15
+ intermediate_certificate: '1.2.840.113635.100.6.2.14',
16
+ leaf_certificate: '1.2.840.113635.100.6.29',
17
+ merchant_identifier_field: '1.2.840.113635.100.6.32',
18
+ },
19
+ replay_threshold_seconds: 3*60,
20
+ apple_root_ca_g3_cert_pem: <<~PEM
21
+ -----BEGIN CERTIFICATE-----
22
+ MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
23
+ QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
24
+ IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN
25
+ MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS
26
+ b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y
27
+ aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49
28
+ AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf
29
+ TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517
30
+ IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr
31
+ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA
32
+ MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4
33
+ at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM
34
+ 6BgD56KyKA==
35
+ -----END CERTIFICATE-----
36
+ PEM
37
+ }
38
+
39
+ def self.config
40
+ @@config ||= DEFAULTS
41
+ end
42
+
43
+ def self.config=(other)
44
+ @@config = other
45
+ end
46
+ end
@@ -0,0 +1,197 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Pedicel
5
+ class Base
6
+ SUPPORTED_VERSIONS = [:EC_v1]
7
+
8
+ def initialize(token, now: Time.now)
9
+ @token = token
10
+ end
11
+
12
+ def validate_content(now: Time.now)
13
+ raise VersionError, "unsupported version: #{version}" unless SUPPORTED_VERSIONS.include?(@token['version'])
14
+
15
+ raise ReplayAttackError, "token signature time indicates a replay attack (age #{now-cms_signing_time})" unless signing_time_ok?(now: now)
16
+
17
+ raise SignatureError unless valid_signature?
18
+ end
19
+
20
+ def version
21
+ @token['version']&.to_sym
22
+ end
23
+
24
+ def encrypted_data
25
+ return nil unless @token['data']
26
+
27
+ Base64.decode64(@token['data'])
28
+ end
29
+
30
+ def signature
31
+ return nil unless @token['signature']
32
+
33
+ Base64.decode64(@token['signature'])
34
+ end
35
+
36
+ def transaction_id
37
+ [@token['header']['transactionId']].pack('H*')
38
+ end
39
+
40
+ def application_data
41
+ return nil unless @token['applicationData']
42
+
43
+ [@token['applicationData']].pack('H*')
44
+ end
45
+
46
+ def signing_time_ok?(now: Time.now)
47
+ # "Inspect the CMS signing time of the signature, as defined by section 11.3
48
+ # of RFC 5652. If the time signature and the transaction time differ by more
49
+ # than a few minutes, it's possible that the token is a replay attack."
50
+ # https://developer.apple.com/library/content/documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html
51
+
52
+ delta = Pedicel.config[:replay_threshold_seconds]
53
+
54
+ cms_signing_time.between?(now - delta, now + delta)
55
+ # Deliberately ignoring leap seconds.
56
+ end
57
+
58
+ def private_key_class
59
+ {EC_v1: OpenSSL::PKey::EC, RSA_v1: OpenSSL::PKey::RSA}[version]
60
+ end
61
+
62
+ def symmetric_algorithm
63
+ {EC_v1: 'aes-256-gcm', RSA_v1: 'aes-128-gcm'}[version]
64
+ end
65
+
66
+ def decrypt_aes(key:)
67
+ raise TokenFormatError, 'no encrypted data present' unless encrypted_data
68
+
69
+ if OpenSSL::Cipher.new('aes-256-gcm').respond_to?(:iv_len=)
70
+ # Either because you use Ruby >=2.4's native openssl lib, or if you have a
71
+ # "recent enough" version of the openssl gem available.
72
+
73
+ cipher = OpenSSL::Cipher.new(symmetric_algorithm)
74
+ cipher.decrypt
75
+
76
+ cipher.key = key
77
+ cipher.iv_len = 16 # Must be set before the IV because default is 12 and
78
+ # only IVs of length `iv_len` will be accepted.
79
+ cipher.iv = "\x00".b * cipher.iv_len
80
+
81
+ split_position = encrypted_data.length - cipher.iv_len
82
+ tag = encrypted_data.slice(split_position, cipher.iv_len)
83
+ untagged_encrypted_data = encrypted_data.slice(0, split_position)
84
+
85
+ cipher.auth_tag = tag
86
+ cipher.auth_data = ''.b
87
+
88
+ cipher.update(untagged_encrypted_data) << cipher.final
89
+ else
90
+ require 'aes256gcm_decrypt'
91
+
92
+ Aes256GcmDecrypt::decrypt(encrypted_data, key)
93
+ end
94
+ end
95
+
96
+ def valid_signature?(now: Time.now)
97
+ true if validate_signature(now: now)
98
+ rescue
99
+ false
100
+ end
101
+
102
+ def verify_signature(ca_certificate_pem: Pedicel.config[:apple_root_ca_g3_cert_pem], now: Time.now)
103
+ raise SignatureError, 'no signature present' unless signature
104
+
105
+ begin
106
+ s = OpenSSL::PKCS7.new(signature)
107
+ rescue => e
108
+ raise SignatureError, "invalid PKCS #7 signature: #{e.message}"
109
+ end
110
+
111
+ # 1.a
112
+ # Ensure that the certificates contain the correct custom OIDs: (...).
113
+ # The value for these marker OIDs doesn't matter, only their presence.
114
+ leaf, intermediate = self.class.verify_signature_certificate_oids(signature: s)
115
+
116
+ begin
117
+ root = OpenSSL::X509::Certificate.new(ca_certificate_pem)
118
+ rescue => e
119
+ raise CertificateError, "invalid root certificate: #{e.message}"
120
+ end
121
+
122
+ # 1.b
123
+ # Ensure that the root CA is the Apple Root CA - G3. (...)
124
+ self.class.verify_root_certificate(root: root, intermediate: intermediate)
125
+
126
+ # 1.c
127
+ # Ensure that there is a valid X.509 chain of trust from the signature to the root CA.
128
+ self.class.verify_x509_chain(root: root, intermediate: intermediate, leaf: leaf)
129
+
130
+ # 1.d
131
+ # Validate the token's signature.
132
+ #
133
+ # Implemented in the subclass.
134
+ validate_signature(signature: s, leaf: leaf)
135
+
136
+ # 1.e
137
+ # Inspect the CMS signing time of the signature (...)
138
+ self.class.verify_signed_time(signature: s, now: now)
139
+
140
+ self
141
+ end
142
+
143
+ private
144
+
145
+ def self.verify_signature_certificate_oids(signature:)
146
+ leaf = signature.certificates.find{|c| c.extensions.find{|e| e.oid == Pedicel.config[:oids][:leaf_certificate]}}
147
+ unless leaf
148
+ raise SignatureError, "no leaf certificate found (OID #{Pedicel.config[:oids][:leaf_certificate]})"
149
+ end
150
+
151
+ intermediate = signature.certificates.find{|c| c.extensions.find{|e| e.oid == Pedicel.config[:oids][:intermediate_certificate]}}
152
+ unless intermediate
153
+ raise SignatureError, "no intermediate certificate found (OID #{Pedicel.config[:oids][:leaf_certificate]})"
154
+ end
155
+
156
+ [leaf, intermediate]
157
+ end
158
+
159
+ def self.verify_root_certificate(root:, intermediate:)
160
+ unless intermediate.issuer == root.subject
161
+ raise SignatureError, 'root certificate has not issued intermediate certificate'
162
+ end
163
+ end
164
+
165
+ def self.verify_x509_chain(root:, intermediate:, leaf:)
166
+ valid_chain = OpenSSL::X509::Store.new.
167
+ add_cert(root).
168
+ add_cert(intermediate).
169
+ verify(leaf)
170
+
171
+ raise SignatureError, 'invalid chain of trust' unless valid_chain
172
+ end
173
+
174
+ def self.verify_signed_time(signature:, now:)
175
+ # Inspect the CMS signing time of the signature, as defined by section
176
+ # 11.3 of RFC 5652. If the time signature and the transaction time differ
177
+ # by more than a few minutes, it's possible that the token is a replay
178
+ # attack.
179
+
180
+ unless signature.signers.length == 1
181
+ raise SignatureError, 'not 1 signer, unable to determine signing time'
182
+ end
183
+ signed_time = signature.signers.first.signed_time
184
+
185
+ few_min = Pedicel.config[:replay_threshold_seconds]
186
+
187
+ # Time objects. DST aware. Ignoring leap seconds.
188
+ return if signed_time.between?(now - few_min, now + few_min) # Both ends included.
189
+
190
+ diff = signed_time - now
191
+ if diff.negative?
192
+ raise SignatureError, "signature too old; signed #{-diff.to_i}s ago"
193
+ end
194
+ raise SignatureError, "signature too new; signed #{diff.to_i}s in the future"
195
+ end
196
+ end
197
+ end
data/lib/pedicel/ec.rb ADDED
@@ -0,0 +1,168 @@
1
+ require 'pedicel/base'
2
+
3
+ module Pedicel
4
+ class EC < Base
5
+ def ephemeral_public_key
6
+ Base64.decode64(@token['header']['ephemeralPublicKey'])
7
+ end
8
+
9
+ def decrypt(symmetric_key: nil, merchant_id: nil, certificate: nil, private_key: nil,
10
+ ca_certificate_pem: Pedicel.config[:apple_root_ca_g3_cert_pem], now: Time.now)
11
+ raise ArgumentError 'invalid argument combination' unless \
12
+ !symmetric_key.nil? ^ ((!merchant_id.nil? ^ !certificate.nil?) && !private_key.nil?)
13
+ # .-------------------'--------. .----------'----. .-------------''---.
14
+ # | symmetric_key can be | | merchant_id | | Both private_key |
15
+ # | derived from private_key | | (byte string) | | and merchant_id |
16
+ # | and the shared_secret--- | | can be | | is necessary to |
17
+ # | which can be derived from | | derived from | | derive the |
18
+ # | the private_key and the | | the public | | symmetric key |
19
+ # | token's ephemeralPublicKey | | certificate | '------------------'
20
+ # '----------------------------' '---------------'
21
+
22
+ if private_key
23
+ symmetric_key = symmetric_key(private_key: private_key,
24
+ certificate: certificate,
25
+ merchant_id: merchant_id)
26
+ end
27
+
28
+ verify_signature(ca_certificate_pem: ca_certificate_pem, now: now)
29
+ decrypt_aes(key: symmetric_key)
30
+ end
31
+
32
+ def symmetric_key(shared_secret: nil, private_key: nil, merchant_id: nil, certificate: nil)
33
+ raise ArgumentError 'invalid argument combination' unless \
34
+ (!shared_secret.nil? ^ !private_key.nil?) && (!merchant_id.nil? ^ !certificate.nil?)
35
+ # .--------------------'. .----------------'| .-----------------'--.
36
+ # | shared_secret can | | shared_secret | | merchant_id (byte |
37
+ # | be derived from the | | and merchant_id | | string can be |
38
+ # | private_key and the | | is necessary to | | derived from the |
39
+ # | ephemeralPublicKey | | derive the | | public certificate |
40
+ # '---------------------' | symmetric_key | '--------------------'
41
+ # '-----------------'
42
+
43
+ shared_secret = shared_secret(private_key: private_key) if private_key
44
+ merchant_id = self.class.merchant_id(certificate: certificate) if certificate
45
+
46
+ self.class.symmetric_key(shared_secret: shared_secret, merchant_id: merchant_id)
47
+ end
48
+
49
+ # Extract the shared secret from one public key (the ephemeral) and one
50
+ # private key.
51
+ def shared_secret(private_key:)
52
+ begin
53
+ privkey = OpenSSL::PKey::EC.new(private_key)
54
+ rescue => e
55
+ raise EcKeyError, "invalid PEM format of private key for EC: #{e.message}"
56
+ end
57
+
58
+ begin
59
+ pubkey = OpenSSL::PKey::EC.new(ephemeral_public_key).public_key
60
+ rescue => e
61
+ raise EcKeyError, "invalid format of ephemeralPublicKey (from token) for EC: #{e.message}"
62
+ end
63
+
64
+ unless privkey.group == pubkey.group
65
+ raise EcKeyError,
66
+ "private_key curve '#{privkey.group.curve_name}' differ from " \
67
+ "ephemeralPublicKey (from token) curve '#{pubkey.group.curve_name}'"
68
+ end
69
+
70
+ privkey.dh_compute_key(pubkey)
71
+ end
72
+
73
+ def self.symmetric_key(merchant_id:, shared_secret:)
74
+ # http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
75
+ # Section 5.8.1.1, The Single-Step KDF Specification.
76
+ #
77
+ # With slight adjustments:
78
+ # > 1. Set `reps = ceil(keydatalen/hashlen)`
79
+ # > 2. If `reps > (2^32 - 1)`, then return an error indicator without
80
+ # > performing the remaining actions.
81
+ # > 3. Initialize a 32-bit, big-endian bit string `counter` as 00000001 base
82
+ # > 16 (i.e. 0x00000001).
83
+ # > 4. If `counter || Z || OtherInfo` is more than `max_H_inputlen` bits
84
+ # > long, then return an error indicator without performing the remaining
85
+ # > actions.
86
+ # > 5. For `i = 1` to `reps` by `1`, do the following:
87
+ # > 5.1 Compute `K(i) = H(counter || Z || OtherInfo)`.
88
+ # > 5.2 Increment `counter` (modulo `2^32`), treating it as an
89
+ # > unsigned 32-bit integer.
90
+ # > 6. Let `K_Last` be set to `K(reps)` if `keydatalen / hashlen` is an
91
+ # > integer; otherwise, let `K_Last` be set to the `keydatalen mod
92
+ # > hashlen` leftmost bits of `K(reps)`.
93
+ # > 7. Return `K(1) || K(2) || ... || K(reps-1) || K_Last`.
94
+ #
95
+ # Digest::SHA256 will do the calculations when we throw Z and OtherInfo into
96
+ # the digest.
97
+
98
+ sha256 = Digest::SHA256.new
99
+
100
+ # Step 3:
101
+ sha256 << "\x00\x00\x00\x01"
102
+
103
+ # Z:
104
+ sha256 << shared_secret
105
+
106
+ # OtherInfo:
107
+ # https://developer.apple.com/library/content/documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html
108
+ sha256 << "\x0d" + 'id-aes256-GCM' # AlgorithmID
109
+ sha256 << 'Apple' # PartyUInfo
110
+ sha256 << merchant_id # PartyVInfo
111
+
112
+ sha256.digest
113
+ end
114
+
115
+ def self.merchant_id(certificate:)
116
+ begin
117
+ cert = OpenSSL::X509::Certificate.new(certificate)
118
+ rescue => e
119
+ raise CertificateError, "invalid PEM format of certificate: #{e.message}"
120
+ end
121
+
122
+ merchant_id_hex =
123
+ cert.
124
+ extensions.
125
+ find { |x| x.oid == Pedicel.config[:oids][:merchant_identifier_field] }&.
126
+ value&. # Hex encoded Merchant ID plus perhaps extra non-hex chars.
127
+ delete("^[0-9a-fA-F]") # Remove non-hex chars.
128
+
129
+ raise CertificateError, 'no merchant identifier in certificate' unless merchant_id_hex
130
+
131
+ [merchant_id_hex].pack('H*')
132
+ end
133
+
134
+ private
135
+
136
+ def validate_signature(signature:, leaf:)
137
+ # (...) ensure that the signature is a valid ECDSA signature
138
+ # (ecdsa-with-SHA256 1.2.840.10045.4.3.2) of the concatenated values of
139
+ # the ephemeralPublicKey, data, transactionId, and applicationData keys.
140
+
141
+ unless leaf.signature_algorithm == 'ecdsa-with-SHA256'
142
+ raise SignatureError, 'signature algorithm is not ecdsa-with-SHA256'
143
+ end
144
+
145
+ message = [
146
+ ephemeral_public_key,
147
+ encrypted_data,
148
+ transaction_id,
149
+ application_data,
150
+ ].compact.join
151
+
152
+ # https://wiki.openssl.org/index.php/Manual:PKCS7_verify(3)#VERIFY_PROCESS
153
+ flags = \
154
+ OpenSSL::PKCS7::NOCHAIN | # Ignore certs in the message.
155
+ OpenSSL::PKCS7::NOINTERN | # Only look at the supplied certificate.
156
+ OpenSSL::PKCS7::NOVERIFY # Do not verify the chain; already done.
157
+
158
+ # Trust exactly the leaf which has already been verified.
159
+ certificates = [leaf]
160
+
161
+ store = OpenSSL::X509::Store.new
162
+
163
+ unless signature.verify(certificates, store, message, flags)
164
+ raise SignatureError, 'signature does not match the message'
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,15 @@
1
+ require 'pedicel/base'
2
+
3
+ module Pedicel
4
+ class RSA < Base
5
+
6
+ private
7
+
8
+ def symmetric_key(private_key)
9
+ # RSA/ECB/OAEPWithSHA256AndMGF1Padding
10
+
11
+ # OpenSSL::PKey::RSA#private_decrypt will use SHA1. Only. :-(
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,138 @@
1
+ require 'dry-validation'
2
+ require 'base64'
3
+
4
+ module Pedicel
5
+ class Validator
6
+ class Error < StandardError; end
7
+ class TokenFormatError < Error; end
8
+ class TokenDataFormatError < Error; end
9
+
10
+ DRY_CUSTOM_PREDICATE_ERRORS = {
11
+ base64?: 'invalid base64',
12
+ hex?: 'invalid hex',
13
+ pan?: 'invalid pan',
14
+ yymmdd?: 'invalid date format YYMMDD',
15
+ }
16
+
17
+ module Predicates
18
+ include Dry::Logic::Predicates
19
+
20
+ predicate(:base64?) { |value| !Base64.decode64(value).nil? rescue false }
21
+
22
+ predicate(:hex?) { |value| !Regexp.new(/\A[a-f0-9-]*\z/i).match(value).nil? }
23
+
24
+ predicate(:pan?) { |value| !Regexp.new(/\A[0-9]{13,19}\z/).match(value).nil? }
25
+
26
+ predicate(:yymmdd?) do |value|
27
+ return false unless value.length == 6
28
+ Time.new(value[0..1],value[2..3], value[4..5]).is_a?(Time) rescue false
29
+ end
30
+ end
31
+
32
+ TokenSchema = Dry::Validation.Schema do
33
+ configure do
34
+ predicates(Predicates)
35
+ def self.messages
36
+ super.merge(en: { errors: DRY_CUSTOM_PREDICATE_ERRORS })
37
+ end
38
+ end
39
+
40
+ required(:data).filled(:str?, :base64?)
41
+
42
+ required(:header).schema do
43
+ optional(:applicationData).filled(:str?, :hex?)
44
+
45
+ optional(:ephemeralPublicKey).filled(:str?, :base64?)
46
+ optional(:wrappedKey).filled(:str?, :base64?)
47
+ rule('ephemeralPublicKey xor wrappedKey': [:ephemeralPublicKey, :wrappedKey]) do |e, w|
48
+ e.filled? ^ w.filled?
49
+ end
50
+
51
+ required(:publicKeyHash).filled(:str?, :base64?)
52
+
53
+ required(:transactionId).filled(:str?, :hex?)
54
+ end
55
+
56
+ required(:signature).filled(:str?, :base64?)
57
+
58
+ required(:version).filled(:str?, included_in?: ['EC_v1', 'RSA_v1'])
59
+ end
60
+
61
+ # Pedicel::Validator::TokenSchema.call({data: 'asdf', header: {ephemeralPublicKey: 'e', publicKeyHash: 'p', transactionId: 'f'}, signature: 's', version: 'EC_v1'})
62
+
63
+ TokenDataSchema = Dry::Validation.Schema do
64
+ configure do
65
+ predicates(Predicates)
66
+ def self.messages
67
+ super.merge(en: { errors: DRY_CUSTOM_PREDICATE_ERRORS })
68
+ end
69
+ end
70
+
71
+ required(:applicationPrimaryAccountNumber).filled(:str?, :pan?)
72
+
73
+ required(:applicationExpirationDate).filled(:str?, :yymmdd?)
74
+
75
+ required(:currencyCode).filled(:str?, format?: /\A[0-9]{3}\z/)
76
+
77
+ required(:transactionAmount).filled(:int?)
78
+
79
+ optional(:cardholderName).filled(:str?)
80
+
81
+ required(:deviceManufacturerIdentifier).filled(:str?, :hex?)
82
+
83
+ required(:paymentDataType).filled(:str?, included_in?: ['3DSecure', 'EMV'])
84
+
85
+ required(:paymentData).schema do
86
+ optional(:onlinePaymentCryptogram).filled(:str?, :base64?)
87
+ optional(:eciIndicator).filled(:str?)
88
+
89
+ optional(:emvData).filled(:str?, :base64?)
90
+ optional(:encryptedPINData).filled(:str?, :hex?)
91
+ end
92
+
93
+ rule('consistent paymentDataType and paymentData': [:paymentDataType, [:paymentData, :onlinePaymentCryptogram]]) do |t, cryptogram|
94
+ t.eql?('3DSecure') > cryptogram.filled?
95
+ end
96
+ end
97
+
98
+ # Pedicel::Validator::TokenDataSchema.call(
99
+ # applicationPrimaryAccountNumber: '1234567890123',
100
+ # applicationExpirationDate: '101112',
101
+ # currencyCode: '123',
102
+ # transactionAmount: 12.34,
103
+ # cardholderName: 'asdf',
104
+ # deviceManufacturerIdentifier: 'adsf',
105
+ # paymentDataType: 'asdf',
106
+ # )
107
+
108
+ def self.validate_token(token, now: Time.now)
109
+ validation = TokenSchema.call(token)
110
+
111
+ raise TokenFormatError, validation.hints.map{|key,msg| "#{key} #{msg}"}.join(', and ') unless validation.errors.empty?
112
+
113
+ true
114
+ end
115
+
116
+ def self.valid_token?(token, now: Time.now)
117
+ validate_token(token, now: now) rescue false
118
+ end
119
+
120
+ def self.validate_token_data(token_data)
121
+ validation = TokenDataSchema.call(token_data)
122
+
123
+ raise TokenDataFormatError, validation.hints.map{|key,msg| "#{key} #{msg}"}.join(', and ') unless validation.errors.empty?
124
+
125
+ true
126
+ end
127
+
128
+ def self.valid_token_data?(token_data)
129
+ validate_token_data(token_data) rescue false
130
+ end
131
+
132
+ def validate_content(now: Time.now)
133
+ raise ReplayAttackError, "token signature time indicates a replay attack (age #{now-cms_signing_time})" unless signing_time_ok?(now: now)
134
+
135
+ raise SignatureError unless valid_signature?
136
+ end
137
+ end
138
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pedicel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Clearhaus
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-validation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.11.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: aes256gcm_decrypt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.11'
69
+ description:
70
+ email: hello@clearhaus.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/pedicel.rb
76
+ - lib/pedicel/base.rb
77
+ - lib/pedicel/ec.rb
78
+ - lib/pedicel/rsa.rb
79
+ - lib/pedicel/validator.rb
80
+ homepage: https://github.com/clearhaus/pedicel-pay
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.5.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Backend and client part of Apple Pay
104
+ test_files: []