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: 68c6886cd483c131d26e25b15df4a451e99a37e516411f707ccb93a99b29ab1e
4
- data.tar.gz: 8395d65f9f774a231796b1dd244436e929085cdb77ddc0fa3e46abffdaf382a0
3
+ metadata.gz: c393a6e8b61ce0a2639b8d70cd63f7157d6d3493bb5ca1aacf84bd40e562116e
4
+ data.tar.gz: ac9b3992dfff499a76e604804e66e9516c320f02706124c47a75e0c256098182
5
5
  SHA512:
6
- metadata.gz: 4f96b3130f5c882be04265e4314725c8d2074594c8bfd3d46ad42ff37b96f9c10211019f94eed55ae1ff556353440ae17000cfd216ce6ccaec4fc4942e2d9ad5
7
- data.tar.gz: e27151ab5ca970b3e4ab4d0125d6bc1c403fd65a960aae1176b10c4e189fe6f3cdf790025313c7c0954b6f46183c0045aeb348149df3c0ae7214d8779b74fee3
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppStore
4
+ class VerificationException < StandardError; end
5
+ 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.1
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-15 00:00:00.000000000 Z
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
- - lib/app_store_server_library.rb
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: