app-store-server-library 0.0.1 → 0.0.3
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c393a6e8b61ce0a2639b8d70cd63f7157d6d3493bb5ca1aacf84bd40e562116e
|
4
|
+
data.tar.gz: ac9b3992dfff499a76e604804e66e9516c320f02706124c47a75e0c256098182
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b59770239a30efe2c889e3ae6478be50262b21b5d5099e44b379b3dffbfbecd8058288a106d5d287ca247e55d3585781d926b1c76bfe3a3878677808012c09
|
7
|
+
data.tar.gz: 8bc948d2348b30e00bfb965eb41fead0056cc8b3317c8516bd081ed9c20e5fa28c4dbcfd13cc5541f7bdc1dddf8c9b1adc5db82ebb9749b429626677335081b3
|
data/README.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# Apple App Store Server Ruby Library
|
2
|
+
The Ruby server library for the [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi) and [App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications).
|
3
|
+
|
4
|
+
Inspired by [Apple App Store Server Node.js Library](https://github.com/apple/app-store-server-library-node)
|
5
|
+
|
6
|
+
## TODO: readme
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
require 'jwt'
|
7
|
+
|
8
|
+
module AppStore
|
9
|
+
class SignedDataVerifier
|
10
|
+
ENVIRONMENTS = {
|
11
|
+
sandbox: 'Sandbox',
|
12
|
+
production: 'Production',
|
13
|
+
xcode: 'Xcode',
|
14
|
+
local_testing: 'LocalTesting'
|
15
|
+
}.freeze
|
16
|
+
MAX_SKEW = 60_000
|
17
|
+
|
18
|
+
attr_reader :root_certificates, :bundle_id, :environment, :app_apple_id
|
19
|
+
|
20
|
+
def initialize(root_certificates, environment, bundle_id, app_apple_id = nil)
|
21
|
+
@root_certificates = root_certificates.map { |cert| OpenSSL::X509::Certificate.new(cert) }
|
22
|
+
@bundle_id = bundle_id
|
23
|
+
@environment = environment
|
24
|
+
@app_apple_id = app_apple_id
|
25
|
+
return unless environment == ENVIRONMENTS[:production] && app_apple_id.nil?
|
26
|
+
|
27
|
+
raise 'app_apple_id is required when the environment is Production'
|
28
|
+
end
|
29
|
+
|
30
|
+
def verify_and_decode_transaction(signed_transaction_info)
|
31
|
+
decoded_jwt = verify_jwt(signed_transaction_info)
|
32
|
+
raise VerificationException, :invalid_app_identifier if decoded_jwt['bundleId'] != bundle_id
|
33
|
+
raise VerificationException, :invalid_environment if decoded_jwt['environment'] != environment
|
34
|
+
|
35
|
+
decoded_jwt
|
36
|
+
end
|
37
|
+
|
38
|
+
def verify_and_decode_renewal_info(signed_renewal_info)
|
39
|
+
decoded_renewal_info = verify_jwt(signed_renewal_info)
|
40
|
+
raise VerificationException, :invalid_environment if decoded_renewal_info['environment'] != @environment
|
41
|
+
|
42
|
+
decoded_renewal_info
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify_and_decode_notification(signed_payload)
|
46
|
+
decoded_jwt = verify_jwt(signed_payload)
|
47
|
+
payload = decoded_jwt['data'] || decoded_jwt['summary'] || decoded_jwt['externalPurchaseToken']
|
48
|
+
app_apple_id = payload['appAppleId']
|
49
|
+
bundle_id = payload['bundleId']
|
50
|
+
environment = payload['environment']
|
51
|
+
if payload['externalPurchaseId']
|
52
|
+
environment = payload['externalPurchaseId']&.start_with?('SANDBOX') ? ENVIRONMENTS[:sandbox] : ENVIRONMENTS[:production]
|
53
|
+
end
|
54
|
+
verify_notification(bundle_id, app_apple_id, environment)
|
55
|
+
decoded_jwt
|
56
|
+
end
|
57
|
+
|
58
|
+
def verify_and_decode_app_transaction(signed_app_transaction)
|
59
|
+
decoded_app_transaction = verify_jwt(signed_app_transaction) do |t|
|
60
|
+
t['receiptCreationDate'].nil? ? Time.now : Time.parse(t['receiptCreationDate'])
|
61
|
+
end
|
62
|
+
environment = decoded_app_transaction['receiptType']
|
63
|
+
if @bundle_id != decoded_app_transaction['bundleId'] || (@environment == :production && @app_apple_id != decoded_app_transaction['appAppleId'])
|
64
|
+
raise VerificationException, :invalid_app_identifier
|
65
|
+
end
|
66
|
+
raise VerificationException, :invalid_environment if @environment != environment
|
67
|
+
|
68
|
+
decoded_app_transaction
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def verify_jwt(jws)
|
74
|
+
decoded_jwt = JWT.decode(jws, nil, false)
|
75
|
+
payload = decoded_jwt[0]
|
76
|
+
chain = decoded_jwt[1]['x5c'] || []
|
77
|
+
raise VerificationException, :invalid_chain_length if chain.size != 3
|
78
|
+
|
79
|
+
certificate_chain = chain[0..1].map { |cert| OpenSSL::X509::Certificate.new(Base64.decode64(cert)) }
|
80
|
+
effective_date = payload['signedDate'] ? Time.at(payload['signedDate'] / 1000) : Time.current
|
81
|
+
public_key = verify_certificate_chain(root_certificates, certificate_chain[0], certificate_chain[1], effective_date)
|
82
|
+
JWT.decode(jws, public_key, true, algorithm: 'ES256')
|
83
|
+
|
84
|
+
payload
|
85
|
+
rescue JWT::DecodeError, JWT::VerificationError => e
|
86
|
+
raise VerificationException, :verification_failure
|
87
|
+
end
|
88
|
+
|
89
|
+
def verify_certificate_chain(trusted_roots, leaf, intermediate, effective_date)
|
90
|
+
root_cert = trusted_roots.find do |root|
|
91
|
+
intermediate.verify(root.public_key) && intermediate.issuer == root.subject
|
92
|
+
rescue OpenSSL::X509::CertificateError => _e
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
validity = !root_cert.nil?
|
97
|
+
validity &&= leaf.verify(intermediate.public_key) && leaf.issuer == intermediate.subject
|
98
|
+
validity &&= intermediate.extensions.any? { |ext| ext.oid == 'basicConstraints' && ext.value.start_with?('CA:TRUE') }
|
99
|
+
validity &&= leaf.extensions.any? { |ext| ext.oid == '1.2.840.113635.100.6.11.1' }
|
100
|
+
validity &&= intermediate.extensions.any? { |ext| ext.oid == '1.2.840.113635.100.6.2.1' }
|
101
|
+
|
102
|
+
raise VerificationException, :verification_failure unless validity
|
103
|
+
|
104
|
+
check_dates(leaf, effective_date)
|
105
|
+
check_dates(intermediate, effective_date)
|
106
|
+
check_dates(root_cert, effective_date)
|
107
|
+
|
108
|
+
leaf.public_key
|
109
|
+
end
|
110
|
+
|
111
|
+
def check_dates(cert, effective_date)
|
112
|
+
valid_from = cert.not_before
|
113
|
+
valid_to = cert.not_after
|
114
|
+
return unless valid_from > effective_date + MAX_SKEW || valid_to < effective_date - MAX_SKEW
|
115
|
+
|
116
|
+
raise VerificationException, :invalid_certificate
|
117
|
+
end
|
118
|
+
|
119
|
+
def verify_notification(bundle_id, app_apple_id, environment)
|
120
|
+
if @bundle_id != bundle_id || (environment == ENVIRONMENTS[:production] && @app_apple_id != app_apple_id)
|
121
|
+
raise VerificationException, :invalid_app_identifier
|
122
|
+
end
|
123
|
+
raise VerificationException, :invalid_environment if @environment != environment
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppStore
|
4
|
+
module VerificationStatus
|
5
|
+
OK = :ok
|
6
|
+
VERIFICATION_FAILURE = :verification_failure
|
7
|
+
INVALID_APP_IDENTIFIER = :invalid_app_identifier
|
8
|
+
INVALID_ENVIRONMENT = :invalid_environment
|
9
|
+
INVALID_CHAIN_LENGTH = :invalid_chain_length
|
10
|
+
INVALID_CERTIFICATE = :invalid_certificate
|
11
|
+
FAILURE = :failure
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app-store-server-library
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Illia Kasianenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jwt
|
@@ -30,7 +30,11 @@ executables: []
|
|
30
30
|
extensions: []
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
|
-
-
|
33
|
+
- README.md
|
34
|
+
- lib/app-store-server-library.rb
|
35
|
+
- lib/app_store/signed_data_verifier.rb
|
36
|
+
- lib/app_store/verification_exception.rb
|
37
|
+
- lib/app_store/verification_status.rb
|
34
38
|
homepage: https://github.com/got2be/app-store-server-library
|
35
39
|
licenses: []
|
36
40
|
metadata:
|
File without changes
|