From 1aa009c0782a4e1f82ea1a96de49d381c068d111 Mon Sep 17 00:00:00 2001 From: Shane Gill Date: Sat, 5 Sep 2026 11:02:23 -0700 Subject: [PATCH] Port GSA authentication fixes from AltSign PRs #50/#51 to Windows - Use AuthKit/1 user agent instead of 2019-era akd/1.0 CFNetwork string - Retry GSA requests up to 5 times on HTTP 5xx with 1/2/4/8s backoff, using a fresh http_client per attempt to avoid Apple's edge pinning a failing backend connection - Surface HTTP status, Content-Type, and body snippet when Apple returns a non-plist response instead of a bare InvalidResponse error - Route trusted-device 2FA verify response through the same validation Co-Authored-By: Claude Fable 5 --- AltSign/AppleAPI+Authentication.cpp | 169 ++++++++++++++++++---------- 1 file changed, 111 insertions(+), 58 deletions(-) diff --git a/AltSign/AppleAPI+Authentication.cpp b/AltSign/AppleAPI+Authentication.cpp index 377edcc..57b8bfa 100755 --- a/AltSign/AppleAPI+Authentication.cpp +++ b/AltSign/AppleAPI+Authentication.cpp @@ -18,6 +18,9 @@ extern "C" { } #include +#include +#include +#include using namespace std; using namespace utility; // Common utilities like string conversions @@ -39,6 +42,33 @@ extern bool decompress(const uint8_t* input, size_t input_size, std::vector(body.size(), 256)); + std::replace(snippet.begin(), snippet.end(), '\n', ' '); + std::replace(snippet.begin(), snippet.end(), '\r', ' '); + + if (!snippet.empty()) + { + ss << " Body: " << snippet; + } + + return LocalizedAPIError((int)statusCode, ss.str()); +} + struct ccrng_state* RNG = NULL; std::vector DataFromBytes(const char* bytes, size_t count) @@ -800,33 +830,37 @@ pplx::task AppleAPI::RequestTrustedDeviceTwoFactorCode( .then([=](http_response response) { odslog("Received 2FA response status code: " << response.status_code()); - return response.extract_vector(); - }) - .then([=](std::vector compressedData) - { - std::vector decompressedData; - if (compressedData.size() > 2 && compressedData[0] == '<' && compressedData[1] == '?') - { - // Already decompressed - decompressedData = compressedData; - } - else + auto statusCode = response.status_code(); + auto contentType = response.headers().content_type(); + + return response.extract_vector() + .then([=](std::vector compressedData) { - decompress((const uint8_t*)compressedData.data(), (size_t)compressedData.size(), decompressedData); - } + std::vector decompressedData; - std::string decompressedXML = std::string(decompressedData.begin(), decompressedData.end()); + if (compressedData.size() > 2 && compressedData[0] == '<' && compressedData[1] == '?') + { + // Already decompressed + decompressedData = compressedData; + } + else + { + decompress((const uint8_t*)compressedData.data(), (size_t)compressedData.size(), decompressedData); + } - plist_t plist = nullptr; - plist_from_xml(decompressedXML.c_str(), (int)decompressedXML.size(), &plist); + std::string decompressedXML = std::string(decompressedData.begin(), decompressedData.end()); - if (plist == nullptr) - { - throw APIError(APIErrorCode::InvalidResponse); - } + plist_t plist = nullptr; + plist_from_xml(decompressedXML.c_str(), (int)decompressedXML.size(), &plist); - return plist; + if (plist == nullptr) + { + throw BadGSAResponseError(statusCode, contentType, decompressedXML); + } + + return plist; + }); }) .then([this](plist_t plist) { @@ -1012,55 +1046,77 @@ pplx::task AppleAPI::SendAuthenticationRequest(std::map headers = { {L"Content-Type", L"text/x-xml-plist"}, {L"X-Mme-Client-Info", WideStringFromString(anisetteData->deviceDescription())}, {L"Accept", L"*/*"}, - {L"User-Agent", L"akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0"} + {L"User-Agent", L"AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)"} }; - uri_builder builder(U("/grandslam/GsService2")); + auto task = pplx::create_task([=]() -> plist_t + { + http_response response; - http_request request(methods::POST); - request.set_request_uri(builder.to_string()); - request.set_body(plistXML); + for (int attempt = 0;; attempt++) + { + uri_builder builder(U("/grandslam/GsService2")); - for (auto& pair : headers) - { - if (request.headers().has(pair.first)) - { - request.headers().remove(pair.first); - } + http_request request(methods::POST); + request.set_request_uri(builder.to_string()); + request.set_body(bodyXML); - request.headers().add(pair.first, pair.second); - } + for (auto& pair : headers) + { + if (request.headers().has(pair.first)) + { + request.headers().remove(pair.first); + } + + request.headers().add(pair.first, pair.second); + } + + // Apple's GSA edge keeps a connection pinned to a backend node, and once that node + // starts failing every subsequent request on the same keep-alive connection returns + // 5xx and never recovers. A fresh http_client per attempt forces a new connection. + http_client_config config; + config.set_validate_certificates(false); + + http_client client(U("https://gsa.apple.com"), config); + + response = client.request(request).get(); + response.content_ready().get(); - auto task = this->gsaClient().request(request) - .then([=](http_response response) - { - return response.content_ready(); - }) - .then([=](http_response response) - { odslog("Received auth response status code: " << response.status_code()); - return response.extract_vector(); - }) - .then([=](std::vector compressedData) - { - std::vector decompressedData = compressedData; - std::string decompressedXML = std::string(decompressedData.begin(), decompressedData.end()); + // A 5xx means the request was never processed, so retrying is safe. + if (response.status_code() >= 500 && response.status_code() <= 599 && attempt < ALTMaximumGSARetries - 1) + { + int delay = std::min(1 << attempt, 8); + std::this_thread::sleep_for(std::chrono::seconds(delay)); + continue; + } + + break; + } - plist_t plist = nullptr; - plist_from_xml(decompressedXML.c_str(), (int)decompressedXML.size(), &plist); + auto data = response.extract_vector().get(); + std::string responseXML = std::string(data.begin(), data.end()); - if (plist == nullptr) - { - throw APIError(APIErrorCode::InvalidResponse); - } + plist_t responsePlist = nullptr; + plist_from_xml(responseXML.c_str(), (int)responseXML.size(), &responsePlist); - return plist; - }) + if (responsePlist == nullptr) + { + throw BadGSAResponseError(response.status_code(), response.headers().content_type(), responseXML); + } + + return responsePlist; + }) .then([=](plist_t plist) { auto dictionary = plist_dict_get_item(plist, "Response"); @@ -1141,9 +1197,6 @@ pplx::task AppleAPI::SendAuthenticationRequest(std::map