From 4bc8a7922068b501a3f6c90331d9fec1c2a4b39a Mon Sep 17 00:00:00 2001 From: Max Lambrecht Date: Sat, 9 May 2026 14:22:14 -0500 Subject: [PATCH] fix(jwt): reject empty JWT-SVID audiences Signed-off-by: Max Lambrecht --- .../java/io/spiffe/svid/jwtsvid/JwtSvid.java | 22 ++++++++++++++----- .../jwtsvid/JwtSvidParseAndValidateTest.java | 7 ++++++ .../jwtsvid/JwtSvidParseInsecureTest.java | 6 +++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/java-spiffe-core/src/main/java/io/spiffe/svid/jwtsvid/JwtSvid.java b/java-spiffe-core/src/main/java/io/spiffe/svid/jwtsvid/JwtSvid.java index a117ead5..46b19b52 100644 --- a/java-spiffe-core/src/main/java/io/spiffe/svid/jwtsvid/JwtSvid.java +++ b/java-spiffe-core/src/main/java/io/spiffe/svid/jwtsvid/JwtSvid.java @@ -103,7 +103,8 @@ private JwtSvid(SpiffeId spiffeId, * when the signature cannot be verified, * when the 'aud' claim has an audience that is not in the audience list * provided as parameter - * @throws IllegalArgumentException when the token is blank or cannot be parsed + * @throws IllegalArgumentException when the token is blank, when the audience is empty, or when the token + * cannot be parsed * @throws BundleNotFoundException if the bundle for the trust domain of the spiffe id from the 'sub' * cannot be found in the JwtBundleSource * @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from @@ -111,11 +112,12 @@ private JwtSvid(SpiffeId spiffeId, */ public static JwtSvid parseAndValidate(String token, BundleSource jwtBundleSource, - Set audience) + Set audience) throws JwtSvidException, BundleNotFoundException, AuthorityNotFoundException { Objects.requireNonNull(token, "token must not be null"); Objects.requireNonNull(jwtBundleSource, "jwtBundleSource must not be null"); Objects.requireNonNull(audience, "audience must not be null"); + requireNonEmptyAudience(audience); return parseAndValidate(token, jwtBundleSource, audience, null); } @@ -139,7 +141,8 @@ public static JwtSvid parseAndValidate(String token, * when the signature cannot be verified, * when the 'aud' claim has an audience that is not in the audience list * provided as parameter - * @throws IllegalArgumentException when the token is blank or cannot be parsed + * @throws IllegalArgumentException when the token is blank, when the audience is empty, or when the token + * cannot be parsed * @throws BundleNotFoundException if the bundle for the trust domain of the spiffe id from the 'sub' * cannot be found in the JwtBundleSource * @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from @@ -154,6 +157,7 @@ public static JwtSvid parseAndValidate(String token, Objects.requireNonNull(token, "token must not be null"); Objects.requireNonNull(jwtBundleSource, "jwtBundleSource must not be null"); Objects.requireNonNull(audience, "audience must not be null"); + requireNonEmptyAudience(audience); if (StringUtils.isBlank(token)) { throw new IllegalArgumentException("token cannot be blank"); @@ -198,11 +202,12 @@ public static JwtSvid parseAndValidate(String token, * when the 'aud' has an audience that is not in the audience provided as parameter, * when the 'alg' is not supported (See {@link JwtSignatureAlgorithm}), * when the header 'typ' is present and is not 'JWT' or 'JOSE'. - * @throws IllegalArgumentException when the token cannot be parsed + * @throws IllegalArgumentException when the audience is empty or when the token cannot be parsed */ public static JwtSvid parseInsecure(String token, Set audience) throws JwtSvidException { Objects.requireNonNull(token, "token must not be null"); Objects.requireNonNull(audience, "audience must not be null"); + requireNonEmptyAudience(audience); return parseInsecure(token, audience, null); } @@ -220,11 +225,12 @@ public static JwtSvid parseInsecure(String token, Set audience) throws J * when the 'aud' has an audience that is not in the audience provided as parameter, * when the 'alg' is not supported (See {@link JwtSignatureAlgorithm}), * when the header 'typ' is present and is not 'JWT' or 'JOSE'. - * @throws IllegalArgumentException when the token cannot be parsed + * @throws IllegalArgumentException when the audience is empty or when the token cannot be parsed */ public static JwtSvid parseInsecure(String token, Set audience, final String hint) throws JwtSvidException { Objects.requireNonNull(token, "token must not be null"); Objects.requireNonNull(audience, "audience must not be null"); + requireNonEmptyAudience(audience); if (StringUtils.isBlank(token)) { throw new IllegalArgumentException("token cannot be blank"); } @@ -401,6 +407,12 @@ private static void validateAudience(List audClaim, Set expected } } + private static void requireNonEmptyAudience(Set audience) { + if (audience.isEmpty()) { + throw new IllegalArgumentException("audience cannot be empty"); + } + } + private static JwtSignatureAlgorithm parseAlgorithm(JWSAlgorithm algorithm) throws JwtSvidException { if (algorithm == null) { throw new JwtSvidException("JWT header 'alg' is required"); diff --git a/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseAndValidateTest.java b/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseAndValidateTest.java index 693a9057..e585be4c 100644 --- a/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseAndValidateTest.java +++ b/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseAndValidateTest.java @@ -259,6 +259,13 @@ static Stream provideFailureScenarios() { .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) .expectedException(new JwtSvidException("expected audience in [another] (audience=[audience2, audience1])")) .build()), + Arguments.of(TestCase.builder() + .name("empty expected audience") + .jwtBundle(jwtBundle) + .expectedAudience(Collections.emptySet()) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) + .expectedException(new IllegalArgumentException("audience cannot be empty")) + .build()), Arguments.of(TestCase.builder() .name("missing audience claim") .jwtBundle(jwtBundle) diff --git a/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseInsecureTest.java b/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseInsecureTest.java index 1d2d4d8e..25b72ea7 100644 --- a/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseInsecureTest.java +++ b/java-spiffe-core/src/test/java/io/spiffe/svid/jwtsvid/JwtSvidParseInsecureTest.java @@ -209,6 +209,12 @@ static Stream provideFailureScenarios() { .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) .expectedException(new JwtSvidException("expected audience in [another] (audience=[audience])")) .build()), + Arguments.of(TestCase.builder() + .name("empty expected audience") + .expectedAudience(Collections.emptySet()) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) + .expectedException(new IllegalArgumentException("audience cannot be empty")) + .build()), Arguments.of(TestCase.builder() .name("invalid subject claim") .expectedAudience(audience)