diff --git a/app/src/main/res/raw/apple_root_ca_g3.pem b/app/src/main/res/raw/apple_root_ca_g3.pem
new file mode 100644
index 00000000..561fc80d
--- /dev/null
+++ b/app/src/main/res/raw/apple_root_ca_g3.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
+QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN
+MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS
+b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf
+TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517
+IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA
+MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4
+at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM
+6BgD56KyKA==
+-----END CERTIFICATE-----
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index 4c29bb30..40b3f2c2 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -19,19 +19,32 @@
not: this is an authentication flow, and disabling verification would make it
interceptable by anything on the network path.
- Instead the one missing root is added, and only for these hosts. The certificate is
- Apple's own, published at https://www.apple.com/appleca/, SHA-256
- b0b1730ecbc7ff4505142c49f1295e6eda6bcaed7e2c68c5be91b5a11001f024, and stored as PEM
- text rather than a binary .cer so it can be reviewed in a diff.
+ Instead the missing roots are added, and only for these hosts. Both certificates are
+ Apple's own, published at https://www.apple.com/certificateauthority/, and stored as
+ PEM text rather than binary .cer so they can be reviewed in a diff:
- "system" is kept alongside it so the usual roots still apply - this adds a trust
- anchor, it does not replace them.
+ - "Apple Root CA" (2006), SHA-256
+ b0b1730ecbc7ff4505142c49f1295e6eda6bcaed7e2c68c5be91b5a11001f024 - what
+ gsa.apple.com (the URL bag and sign-in) chains to.
+ - "Apple Root CA - G3" (2014), SHA-256
+ 63343abfb89a6a03ebb57e9b3f5fa7be7c4f5c756f3017b3a8c488c3653e9179 - what
+ gsd.apple.com (the two provisioning endpoints the URL bag points at) chains to.
+ Without it, provisioning fails the handshake. Public Anisette servers running
+ the same libraries began answering "invalid Trust Key (-45003)" and 502s in
+ October 2026, which this is the likely cause of (issue #248).
+
+ "system" is kept alongside them so the usual roots still apply - this adds trust
+ anchors, it does not replace them. gsd.apple.com gets both because the URL bag decides
+ which host provisioning talks to, and a change of host must not become a handshake
+ failure that reads as Apple refusing the app.
-->
Android trusts neither root, so without these anchors the handshake fails, provisioning
+ * falls back to a remote server, and the user sees whatever that server says rather than a
+ * certificate error. {@code gsd.apple.com} began chaining to Apple Root CA - G3 in October 2026,
+ * which is how this was found (issue #248). Nothing else fails when an anchor or a host goes
+ * missing from the XML, so this pins both, and pins each PEM to Apple's published SHA-256 so a
+ * swapped certificate cannot pass as the real one.
+ */
+public class TrustingApplesProvisioningHostsTest {
+
+ // Unit tests run from the module directory
+ private static final String RES = "src/main/res/";
+
+ private static final String APPLE_ROOT_CA =
+ "b0b1730ecbc7ff4505142c49f1295e6eda6bcaed7e2c68c5be91b5a11001f024";
+ private static final String APPLE_ROOT_CA_G3 =
+ "63343abfb89a6a03ebb57e9b3f5fa7be7c4f5c756f3017b3a8c488c3653e9179";
+
+ @Test
+ public void bothRootsAreApplesOwn() throws Exception {
+ assertEquals(APPLE_ROOT_CA, sha256Of("raw/apple_root_ca.pem"));
+ assertEquals(APPLE_ROOT_CA_G3, sha256Of("raw/apple_root_ca_g3.pem"));
+ }
+
+ @Test
+ public void signInAndProvisioningHostsTrustBothRootsAndTheSystemOnes() throws Exception {
+ for (final String host : List.of("gsa.apple.com", "gsd.apple.com")) {
+ final List