From 57073e10c45233e6d96799e6eca455dfc20f3ff3 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 31 Oct 2025 18:42:50 -0700 Subject: [PATCH 1/4] Adding RETRYABLE_VERIFICATION_FAILURE for OCSP network failures --- appstoreserverlibrary/signed_data_verifier.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/appstoreserverlibrary/signed_data_verifier.py b/appstoreserverlibrary/signed_data_verifier.py index 581fc7d1..068f74db 100644 --- a/appstoreserverlibrary/signed_data_verifier.py +++ b/appstoreserverlibrary/signed_data_verifier.py @@ -244,12 +244,18 @@ def check_ocsp_status(self, cert: crypto.X509, issuer: crypto.X509, root: crypto ) ocsps = [val for val in authority_values if val.access_method == x509.oid.AuthorityInformationAccessOID.OCSP] for o in ocsps: - r = requests.post( - o.access_location.value, - headers={"Content-Type": "application/ocsp-request"}, - data=req.public_bytes(serialization.Encoding.DER), - ) - if r.status_code == 200: + try: + r = requests.post( + o.access_location.value, + headers={"Content-Type": "application/ocsp-request"}, + data=req.public_bytes(serialization.Encoding.DER), + timeout=30, + ) + except (requests.exceptions.RequestException, OSError) as e: + raise VerificationException(VerificationStatus.RETRYABLE_VERIFICATION_FAILURE) from e + if r.status_code != 200: + raise VerificationException(VerificationStatus.RETRYABLE_VERIFICATION_FAILURE) + else: ocsp_resp = ocsp.load_der_ocsp_response(r.content) if ocsp_resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL: certs = [issuer] @@ -352,6 +358,7 @@ class VerificationStatus(IntEnum): INVALID_CHAIN_LENGTH = 4 INVALID_CHAIN = 5 INVALID_ENVIRONMENT = 6 + RETRYABLE_VERIFICATION_FAILURE = 7 class VerificationException(Exception): From cddc2f6c86ebd7443cb9562330da14b8f49f5365 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 31 Oct 2025 19:04:39 -0700 Subject: [PATCH 2/4] Add timeout to AppStoreServerAPIClient --- appstoreserverlibrary/api_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appstoreserverlibrary/api_client.py b/appstoreserverlibrary/api_client.py index bd583464..7932d4f2 100644 --- a/appstoreserverlibrary/api_client.py +++ b/appstoreserverlibrary/api_client.py @@ -682,7 +682,7 @@ def _make_request(self, path: str, method: str, queryParameters: Dict[str, Union return self._parse_response(response.status_code, response.headers, lambda: response.json(), destination_class) def _execute_request(self, method: str, url: str, params: Dict[str, Union[str, List[str]]], headers: Dict[str, str], json: Optional[Dict[str, Any]], data: Optional[bytes]) -> requests.Response: - return requests.request(method, url, params=params, headers=headers, json=json, data=data) + return requests.request(method, url, params=params, headers=headers, json=json, data=data, timeout=30) def extend_renewal_date_for_all_active_subscribers(self, mass_extend_renewal_date_request: MassExtendRenewalDateRequest) -> MassExtendRenewalDateResponse: """ @@ -1000,7 +1000,7 @@ async def _make_request(self, path: str, method: str, queryParameters: Dict[str, return self._parse_response(response.status_code, response.headers, lambda: response.json(), destination_class) async def _execute_request(self, method: str, url: str, params: Dict[str, Union[str, List[str]]], headers: Dict[str, str], json: Optional[Dict[str, Any]], data: Optional[bytes]): - return await self.http_client.request(method, url, params=params, headers=headers, json=json, data=data) + return await self.http_client.request(method, url, params=params, headers=headers, json=json, data=data, timeout=30) async def extend_renewal_date_for_all_active_subscribers(self, mass_extend_renewal_date_request: MassExtendRenewalDateRequest) -> MassExtendRenewalDateResponse: """ From dd0727d6ec3a031fa054778cf3351a372de18b85 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 31 Oct 2025 19:16:04 -0700 Subject: [PATCH 3/4] Updating signing cert for recent rotation --- tests/test_x509_verifiction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_x509_verifiction.py b/tests/test_x509_verifiction.py index 98d1af99..048d58c7 100644 --- a/tests/test_x509_verifiction.py +++ b/tests/test_x509_verifiction.py @@ -19,9 +19,9 @@ REAL_APPLE_ROOT_BASE64_ENCODED = "MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtfTjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySrMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM6BgD56KyKA==" REAL_APPLE_INTERMEDIATE_BASE64_ENCODED = "MIIDFjCCApygAwIBAgIUIsGhRwp0c2nvU4YSycafPTjzbNcwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMjEwMzE3MjAzNzEwWhcNMzYwMzE5MDAwMDAwWjB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbsQKC94PrlWmZXnXgtxzdVJL8T0SGYngDRGpngn3N6PT8JMEb7FDi4bBmPhCnZ3/sq6PF/cGcKXWsL5vOteRhyJ45x3ASP7cOB+aao90fcpxSv/EZFbniAbNgZGhIhpIo4H6MIH3MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNhZzMwNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwHQYDVR0OBBYEFD8vlCNR01DJmig97bB85c+lkGKZMA4GA1UdDwEB/wQEAwIBBjAQBgoqhkiG92NkBgIBBAIFADAKBggqhkjOPQQDAwNoADBlAjBAXhSq5IyKogMCPtw490BaB677CaEGJXufQB/EqZGd6CSjiCtOnuMTbXVXmxxcxfkCMQDTSPxarZXvNrkxU3TkUMI33yzvFVVRT4wxWJC994OsdcZ4+RGNsYDyR5gmdr0nDGg=" -REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED = "MIIEMDCCA7agAwIBAgIQaPoPldvpSoEH0lBrjDPv9jAKBggqhkjOPQQDAzB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTIxMDgyNTAyNTAzNFoXDTIzMDkyNDAyNTAzM1owgZIxQDA+BgNVBAMMN1Byb2QgRUNDIE1hYyBBcHAgU3RvcmUgYW5kIGlUdW5lcyBTdG9yZSBSZWNlaXB0IFNpZ25pbmcxLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOoTcaPcpeipNL9eQ06tCu7pUcwdCXdN8vGqaUjd58Z8tLxiUC0dBeA+euMYggh1/5iAk+FMxUFmA2a1r4aCZ8SjggIIMIICBDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFD8vlCNR01DJmig97bB85c+lkGKZMHAGCCsGAQUFBwEBBGQwYjAtBggrBgEFBQcwAoYhaHR0cDovL2NlcnRzLmFwcGxlLmNvbS93d2RyZzYuZGVyMDEGCCsGAQUFBzABhiVodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLXd3ZHJnNjAyMIIBHgYDVR0gBIIBFTCCAREwggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wHQYDVR0OBBYEFCOCmMBq//1L5imvVmqX1oCYeqrMMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAKBggqhkjOPQQDAwNoADBlAjEAl4JB9GJHixP2nuibyU1k3wri5psGIxPME05sFKq7hQuzvbeyBu82FozzxmbzpogoAjBLSFl0dZWIYl2ejPV+Di5fBnKPu8mymBQtoE/H2bES0qAs8bNueU3CBjjh1lwnDsI=" +REAL_APPLE_SIGNING_CERTIFICATE_BASE64_ENCODED = "MIIEMTCCA7agAwIBAgIQR8KHzdn554Z/UoradNx9tzAKBggqhkjOPQQDAzB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTI1MDkxOTE5NDQ1MVoXDTI3MTAxMzE3NDcyM1owgZIxQDA+BgNVBAMMN1Byb2QgRUNDIE1hYyBBcHAgU3RvcmUgYW5kIGlUdW5lcyBTdG9yZSBSZWNlaXB0IFNpZ25pbmcxLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNnVvhcv7iT+7Ex5tBMBgrQspHzIsXRi0Yxfek7lv8wEmj/bHiWtNwJqc2BoHzsQiEjP7KFIIKg4Y8y0/nynuAmjggIIMIICBDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFD8vlCNR01DJmig97bB85c+lkGKZMHAGCCsGAQUFBwEBBGQwYjAtBggrBgEFBQcwAoYhaHR0cDovL2NlcnRzLmFwcGxlLmNvbS93d2RyZzYuZGVyMDEGCCsGAQUFBzABhiVodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLXd3ZHJnNjAyMIIBHgYDVR0gBIIBFTCCAREwggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wHQYDVR0OBBYEFIFioG4wMMVA1ku9zJmGNPAVn3eqMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAKBggqhkjOPQQDAwNpADBmAjEA+qXnREC7hXIWVLsLxznjRpIzPf7VHz9V/CTm8+LJlrQepnmcPvGLNcX6XPnlcgLAAjEA5IjNZKgg5pQ79knF4IbTXdKv8vutIDMXDmjPVT3dGvFtsGRwXOywR2kZCdSrfeot" -EFFECTIVE_DATE = 1681312846 +EFFECTIVE_DATE = 1761962975 CLOCK_DATE = 41231 class X509Verification(unittest.TestCase): From ed4f70aef2bc43ee919cb73bab35a09c1a1d08e3 Mon Sep 17 00:00:00 2001 From: Riyaz Panjwani Date: Mon, 10 Nov 2025 19:53:30 -0800 Subject: [PATCH 4/4] Migrating to toml setup --- .github/workflows/ci-release-docs.yml | 2 +- pyproject.toml | 34 +++++++++++++++++++++++++++ setup.py | 25 -------------------- 3 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/ci-release-docs.yml b/.github/workflows/ci-release-docs.yml index cc70d703..4b7dd427 100644 --- a/.github/workflows/ci-release-docs.yml +++ b/.github/workflows/ci-release-docs.yml @@ -20,7 +20,7 @@ jobs: pip install -r requirements.txt pip install -r docs/requirements.txt - name: Sphinx Api Docs - run: sphinx-apidoc -F -H "App Store Server Library" -A "Apple Inc." -V "0.2.1" -e -a -o _staging . tests setup.py + run: sphinx-apidoc -F -H "App Store Server Library" -A "Apple Inc." -V "0.2.1" -e -a -o _staging . tests pyproject.toml - name: Spinx build run: sphinx-build -b html _staging _build - name: Upload docs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..f5f8b79f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +# Copyright (c) 2025 Apple Inc. Licensed under MIT License. + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "app-store-server-library" +version = "1.9.0" +description = "The App Store Server Library" +readme = {file = "README.md", content-type = "text/markdown"} +license = {text = "MIT"} +requires-python = ">=3.7, <4" +classifiers = [ + "License :: OSI Approved :: MIT License" +] +dependencies = [ + "attrs>=21.3.0", + "PyJWT>=2.6.0,<3", + "requests>=2.28.0,<3", + "cryptography>=40.0.0", + "pyOpenSSL>=23.1.1", + "asn1==2.8.0", + "cattrs>=23.1.2", +] + +[project.optional-dependencies] +async = ["httpx"] + +[tool.setuptools] +packages = {find = {exclude = ["tests"]}} + +[tool.setuptools.package-data] +appstoreserverlibrary = ["py.typed"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 31ba801c..00000000 --- a/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2023 Apple Inc. Licensed under MIT License. - -import pathlib - -from setuptools import find_packages, setup - -here = pathlib.Path(__file__).parent.resolve() -long_description = (here / "README.md").read_text(encoding="utf-8") - -setup( - name="app-store-server-library", - version="1.9.0", - description="The App Store Server Library", - long_description=long_description, - long_description_content_type="text/markdown", - packages=find_packages(exclude=["tests"]), - python_requires=">=3.7, <4", - install_requires=["attrs >= 21.3.0", 'PyJWT >= 2.6.0, < 3', 'requests >= 2.28.0, < 3', 'cryptography >= 40.0.0', 'pyOpenSSL >= 23.1.1', 'asn1==2.8.0', 'cattrs >= 23.1.2'], - extras_require={ - "async": ["httpx"], - }, - package_data={"appstoreserverlibrary": ["py.typed"]}, - license="MIT", - classifiers=["License :: OSI Approved :: MIT License"], -)