From 66d4fbfc70ed6a00af36d50942baef3909234fd8 Mon Sep 17 00:00:00 2001 From: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:54:28 -0700 Subject: [PATCH 1/2] core: reject a JWK entry missing x5c when loading a SPIFFE trust bundle SpiffeUtil.extractCert did `break` when a keys[] entry had no x5c, silently returning only the certificates gathered before it and truncating the trust bundle. Every other error path in the method throws IllegalArgumentException, and the method contract states that an invalid element makes the whole bundle invalid. Throw instead of silently dropping the remaining certificates. Signed-off-by: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com> --- core/src/main/java/io/grpc/internal/SpiffeUtil.java | 3 ++- .../test/java/io/grpc/internal/SpiffeUtilTest.java | 5 +++++ .../io/grpc/internal/spiffebundle_missing_x5c.json | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json diff --git a/core/src/main/java/io/grpc/internal/SpiffeUtil.java b/core/src/main/java/io/grpc/internal/SpiffeUtil.java index 9eafc9950e2..21232ad293b 100644 --- a/core/src/main/java/io/grpc/internal/SpiffeUtil.java +++ b/core/src/main/java/io/grpc/internal/SpiffeUtil.java @@ -232,7 +232,8 @@ private static List extractCert(List> keysNode, checkJwkEntry(keyNode, trustDomainName); List rawCerts = JsonUtil.getListOfStrings(keyNode, "x5c"); if (rawCerts == null) { - break; + throw new IllegalArgumentException(String.format("'x5c' parameter is required. Certificate " + + "loading for trust domain '%s' failed.", trustDomainName)); } if (rawCerts.size() != 1) { throw new IllegalArgumentException(String.format("Exactly 1 certificate is expected, but " diff --git a/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java index 57824cf207f..8888046c586 100644 --- a/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java +++ b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java @@ -230,6 +230,7 @@ public static class CertificateApiTest { private static final String SPIFFE_TRUST_BUNDLE_DUPLICATES = "spiffebundle_duplicates.json"; private static final String SPIFFE_TRUST_BUNDLE_WRONG_ROOT = "spiffebundle_wrong_root.json"; private static final String SPIFFE_TRUST_BUNDLE_WRONG_SEQ = "spiffebundle_wrong_seq_type.json"; + private static final String SPIFFE_TRUST_BUNDLE_MISSING_X5C = "spiffebundle_missing_x5c.json"; private static final String DOMAIN_ERROR_MESSAGE = " Certificate loading for trust domain 'google.com' failed."; @@ -351,6 +352,10 @@ public void loadTrustBundleFromFileFailureTest() { iae = assertThrows(IllegalArgumentException.class, () -> SpiffeUtil .loadTrustBundleFromFile(copyFileToTmp(SPIFFE_TRUST_BUNDLE_CORRUPTED_CERT))); assertEquals("Certificate can't be parsed." + DOMAIN_ERROR_MESSAGE, iae.getMessage()); + // Check the exception if a key entry is missing the 'x5c' parameter + iae = assertThrows(IllegalArgumentException.class, () -> SpiffeUtil + .loadTrustBundleFromFile(copyFileToTmp(SPIFFE_TRUST_BUNDLE_MISSING_X5C))); + assertEquals("'x5c' parameter is required." + DOMAIN_ERROR_MESSAGE, iae.getMessage()); // Check the exception if 'kty' value differs from 'RSA' iae = assertThrows(IllegalArgumentException.class, () -> SpiffeUtil .loadTrustBundleFromFile(copyFileToTmp(SPIFFE_TRUST_BUNDLE_WRONG_KTY))); diff --git a/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json b/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json new file mode 100644 index 00000000000..53883a7bd67 --- /dev/null +++ b/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json @@ -0,0 +1,12 @@ +{ + "trust_domains": { + "google.com": { + "keys": [ + { + "kty": "RSA", + "use": "x509-svid" + } + ] + } + } +} From d26dc83343013f3c18cd0930431da2b9699116a0 Mon Sep 17 00:00:00 2001 From: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:40:31 -0700 Subject: [PATCH 2/2] core: guard missing-x5c test with a preceding valid key Per review, add a well-formed JWK entry (valid x5c) ahead of the missing-x5c entry in spiffebundle_missing_x5c.json. This forces the loader to process a valid key first and still reject the whole bundle on the later missing-x5c entry, so the test also guards against a regression that only validates the first key. Signed-off-by: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com> --- .../internal/spiffebundle_missing_x5c.json | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json b/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json index 53883a7bd67..6c018751b23 100644 --- a/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json +++ b/core/src/test/resources/io/grpc/internal/spiffebundle_missing_x5c.json @@ -2,6 +2,33 @@ "trust_domains": { "google.com": { "keys": [ + { + "kty": "RSA", + "use": "x509-svid", + "x5c": ["MIIELTCCAxWgAwIBAgIUVXGlXjNENtOZbI12epjgIhMaShEwDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTI0 + MDkxNzE2MTk0NFoXDTM0MDkxNTE2MTk0NFowTjELMAkGA1UEBhMCVVMxCzAJBgNV + BAgMAkNBMQwwCgYDVQQHDANTVkwxDTALBgNVBAoMBGdSUEMxFTATBgNVBAMMDHRl + c3QtY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOcTjjcS + SfG/EGrr6G+f+3T2GXyHHfroQFi9mZUz80L7uKBdECOImID+YhoK8vcxLQjPmEEv + FIYgJT5amugDcYIgUhMjBx/8RPJaP/nGmBngAqsuuNCaZfyaHBRqN8XdS/AwmsI5 + Wo+nru0+0/7aQFdqqtd2+e9dHjUWwgHxXvMgC4hkHpsdCGIZWVzWyBliwTYQYb1Y + yYe1LzqqQA5OMbZfKOY9MYDCEYOliRiunOn30iIOHj9V5qLzWGfSyxCRuvLRdEP8 + iDeNweHbdaKuI80nQmxuBdRIspE9k5sD1WA4vLZpeg3zggxp4rfLL5zBJgb/33D3 + d9Rkm14xfDPihhkCAwEAAaOB+jCB9zBZBgNVHREEUjBQhiZzcGlmZmU6Ly9mb28u + YmFyLmNvbS9jbGllbnQvd29ya2xvYWQvMYYmc3BpZmZlOi8vZm9vLmJhci5jb20v + Y2xpZW50L3dvcmtsb2FkLzIwHQYDVR0OBBYEFG9GkBgdBg/p0U9/lXv8zIJ+2c2N + MHsGA1UdIwR0MHKhWqRYMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0 + YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMM + BnRlc3RjYYIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQELBQADggEB + AJ4Cbxv+02SpUgkEu4hP/1+8DtSBXUxNxI0VG4e3Ap2+Rhjm3YiFeS/UeaZhNrrw + UEjkSTPFODyXR7wI7UO9OO1StyD6CMkp3SEvevU5JsZtGL6mTiTLTi3Qkywa91Bt + GlyZdVMghA1bBJLBMwiD5VT5noqoJBD7hDy6v9yNmt1Sw2iYBJPqI3Gnf5bMjR3s + UICaxmFyqaMCZsPkfJh0DmZpInGJys3m4QqGz6ZE2DWgcSr1r/ML7/5bSPjjr8j4 + WFFSqFR3dMu8CbGnfZTCTXa4GTX/rARXbAO67Z/oJbJBK7VKayskL+PzKuohb9ox + jGL772hQMbwtFCOFXu5VP0s="] + }, { "kty": "RSA", "use": "x509-svid"