From 0ee9ae28fa1021190b49a3a687b0ac947c688060 Mon Sep 17 00:00:00 2001 From: Max Lambrecht Date: Mon, 8 Feb 2021 16:05:36 -0300 Subject: [PATCH] Validate JWT 'typ' header. (#62) * Validate JWT 'typ' header. Signed-off-by: Max Lambrecht --- .../java/io/spiffe/svid/jwtsvid/JwtSvid.java | 65 ++++++++---- .../jwtsvid/JwtSvidParseAndValidateTest.java | 100 +++++++++++++----- .../jwtsvid/JwtSvidParseInsecureTest.java | 74 +++++++++++-- .../java/io/spiffe/utils/TestUtils.java | 12 ++- 4 files changed, 192 insertions(+), 59 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 8594102..59477e3 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 @@ -1,6 +1,7 @@ package io.spiffe.svid.jwtsvid; import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSVerifier; @@ -8,12 +9,12 @@ import com.nimbusds.jose.crypto.ECDSAVerifier; import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import io.spiffe.internal.JwtSignatureAlgorithm; import io.spiffe.bundle.BundleSource; import io.spiffe.bundle.jwtbundle.JwtBundle; import io.spiffe.exception.AuthorityNotFoundException; import io.spiffe.exception.BundleNotFoundException; import io.spiffe.exception.JwtSvidException; +import io.spiffe.internal.JwtSignatureAlgorithm; import io.spiffe.spiffeid.SpiffeId; import lombok.NonNull; import lombok.Value; @@ -62,11 +63,14 @@ public class JwtSvid { */ String token; + public static final String HEADER_TYP_JWT = "JWT"; + public static final String HEADER_TYP_JOSE = "JOSE"; + private JwtSvid(final SpiffeId spiffeId, - final Set audience, - final Date expiry, - final Map claims, - final String token) { + final Set audience, + final Date expiry, + final Map claims, + final String token) { this.spiffeId = spiffeId; this.audience = audience; this.expiry = expiry; @@ -85,18 +89,18 @@ public class JwtSvid { * @param audience audience as a list of strings used to validate the 'aud' claim * @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry * from 'exp' claim. - * @throws JwtSvidException when the token expired or the expiration claim is missing, - * when the algorithm is not supported (See {@link JwtSignatureAlgorithm}), - * when the header 'kid' is missing, - * when the signature cannot be verified, or - * 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 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 - * the 'kid' header + * @throws JwtSvidException when the token expired or the expiration claim is missing, + * when the algorithm is not supported (See {@link JwtSignatureAlgorithm}), + * when the header 'kid' is missing, + * when the header 'typ' is present and is not 'JWT' or 'JOSE' + * 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 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 + * the 'kid' header */ public static JwtSvid parseAndValidate(@NonNull final String token, @NonNull final BundleSource jwtBundleSource, @@ -108,6 +112,8 @@ public class JwtSvid { } val signedJwt = getSignedJWT(token); + validateTypeHeader(signedJwt.getHeader()); + JwtSignatureAlgorithm algorithm = parseAlgorithm(signedJwt.getHeader().getAlgorithm()); val claimsSet = getJwtClaimsSet(signedJwt); @@ -137,10 +143,11 @@ public class JwtSvid { * @param audience audience as a list of strings used to validate the 'aud' claim * @return an instance of a {@link JwtSvid} with a SPIFFE ID parsed from the 'sub', audience from 'aud', and expiry * from 'exp' claim. - * @throws JwtSvidException when the token expired or the expiration claim is missing, or when - * the 'aud' has an audience that is not in the audience provided as parameter, - * or when the 'alg' is not supported (See {@link JwtSignatureAlgorithm}). - * @throws IllegalArgumentException when the token cannot be parsed + * @throws JwtSvidException when the token expired or the expiration claim is missing, + * 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 */ public static JwtSvid parseInsecure(@NonNull final String token, @NonNull final Set audience) throws JwtSvidException { if (StringUtils.isBlank(token)) { @@ -148,6 +155,8 @@ public class JwtSvid { } val signedJwt = getSignedJWT(token); + validateTypeHeader(signedJwt.getHeader()); + parseAlgorithm(signedJwt.getHeader().getAlgorithm()); val claimsSet = getJwtClaimsSet(signedJwt); @@ -290,7 +299,7 @@ public class JwtSvid { private static JwtSignatureAlgorithm parseAlgorithm(JWSAlgorithm algorithm) throws JwtSvidException { if (algorithm == null) { - throw new JwtSvidException("jwt header 'alg' is required"); + throw new JwtSvidException("JWT header 'alg' is required"); } try { @@ -299,4 +308,16 @@ public class JwtSvid { throw new JwtSvidException(e.getMessage(), e); } } + + private static void validateTypeHeader(JWSHeader headers) throws JwtSvidException { + final JOSEObjectType type = headers.getType(); + // if it's not present -> OK + if (type == null || StringUtils.isBlank(type.toString())) { + return; + } + final String typValue = type.toString(); + if (!HEADER_TYP_JWT.equals(typValue) && !HEADER_TYP_JOSE.equals(typValue)) { + throw new JwtSvidException(String.format("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: '%s'.", type.toString())); + } + } } 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 44429c4..1dd5b93 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 @@ -26,6 +26,7 @@ import java.util.stream.Stream; import static io.spiffe.svid.jwtsvid.JwtSvidParseInsecureTest.newJwtSvidInstance; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; class JwtSvidParseAndValidateTest { @@ -34,9 +35,8 @@ class JwtSvidParseAndValidateTest { "dCI6MTUxNjIzOTAyMiwiYXVkIjoiYXVkaWVuY2UifQ.wNm5pQGSLCw5N9ddgSF2hkgmQpGnG9le_gpiFmyBhao"; @ParameterizedTest - @MethodSource("provideJwtScenarios") - void parseAndValidateJwt(TestCase testCase) { - + @MethodSource("provideSuccessScenarios") + void parseAndValidateValidJwt(TestCase testCase) { try { String token = testCase.generateToken.get(); JwtSvid jwtSvid = JwtSvid.parseAndValidate(token, testCase.jwtBundle, testCase.audience); @@ -46,6 +46,18 @@ class JwtSvidParseAndValidateTest { assertEquals(testCase.expectedJwtSvid.getExpiry().toInstant().getEpochSecond(), jwtSvid.getExpiry().toInstant().getEpochSecond()); assertEquals(token, jwtSvid.getToken()); assertEquals(token, jwtSvid.marshal()); + } catch (Exception e) { + fail(e); + } + } + + @ParameterizedTest + @MethodSource("provideFailureScenarios") + void parseAndValidateInvalidJwt(TestCase testCase) { + try { + String token = testCase.generateToken.get(); + JwtSvid.parseAndValidate(token, testCase.jwtBundle, testCase.audience); + fail("expected error: " + testCase.expectedException.getMessage()); } catch (Exception e) { assertEquals(testCase.expectedException.getClass(), e.getClass()); assertEquals(testCase.expectedException.getMessage(), e.getMessage()); @@ -100,7 +112,7 @@ class JwtSvidParseAndValidateTest { } } - static Stream provideJwtScenarios() { + static Stream provideSuccessScenarios() { KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521); KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521); KeyPair key3 = TestUtils.generateRSAKeyPair(2048); @@ -112,126 +124,166 @@ class JwtSvidParseAndValidateTest { jwtBundle.putJwtAuthority("authority3", key3.getPublic()); SpiffeId spiffeId = trustDomain.newSpiffeId("host"); - Date expiration = new Date(System.currentTimeMillis() + 3600000); + Date expiration = new Date(System.currentTimeMillis() + (60 * 60 * 1000)); Set audience = new HashSet() {{add("audience1"); add("audience2");}}; JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration); return Stream.of( Arguments.of(TestCase.builder() - .name("1. success using EC signature") + .name("using EC signature") .jwtBundle(jwtBundle) .expectedAudience(Collections.singleton("audience1")) - .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE)) .expectedException(null) .expectedJwtSvid(newJwtSvidInstance( trustDomain.newSpiffeId("host"), audience, expiration, - claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1") )) + claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE) )) .build()), Arguments.of(TestCase.builder() - .name("2. success using RSA signature") + .name("using RSA signature") .jwtBundle(jwtBundle) .expectedAudience(audience) - .generateToken(() -> TestUtils.generateToken(claims, key3, "authority3")) + .generateToken(() -> TestUtils.generateToken(claims, key3, "authority3", JwtSvid.HEADER_TYP_JWT)) + .expectedException(null) + .expectedJwtSvid(newJwtSvidInstance( + trustDomain.newSpiffeId("host"), + audience, + expiration, + claims.getClaims(), TestUtils.generateToken(claims, key3, "authority3", JwtSvid.HEADER_TYP_JWT))) + .build()), + Arguments.of(TestCase.builder() + .name("using empty typ") + .jwtBundle(jwtBundle) + .expectedAudience(audience) + .generateToken(() -> TestUtils.generateToken(claims, key3, "authority3", "")) .expectedException(null) .expectedJwtSvid(newJwtSvidInstance( trustDomain.newSpiffeId("host"), audience, expiration, claims.getClaims(), TestUtils.generateToken(claims, key3, "authority3"))) - .build()), + .build()) + ); + } + + static Stream provideFailureScenarios() { + KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521); + KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521); + KeyPair key3 = TestUtils.generateRSAKeyPair(2048); + + TrustDomain trustDomain = TrustDomain.of("test.domain"); + JwtBundle jwtBundle = new JwtBundle(trustDomain); + jwtBundle.putJwtAuthority("authority1", key1.getPublic()); + jwtBundle.putJwtAuthority("authority2", key2.getPublic()); + jwtBundle.putJwtAuthority("authority3", key3.getPublic()); + + SpiffeId spiffeId = trustDomain.newSpiffeId("host"); + Date expiration = new Date(System.currentTimeMillis() + (60 * 60 * 1000)); + Set audience = new HashSet() {{add("audience1"); add("audience2");}}; + + JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration); + + return Stream.of( Arguments.of(TestCase.builder() - .name("3. malformed") + .name("malformed") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> "invalid token") .expectedException(new IllegalArgumentException("Unable to parse JWT token")) .build()), Arguments.of(TestCase.builder() - .name("4. unsupported algorithm") + .name("unsupported algorithm") .jwtBundle(jwtBundle) .expectedAudience(Collections.singleton("audience")) .generateToken(() -> HS256TOKEN) .expectedException(new JwtSvidException("Unsupported JWT algorithm: HS256")) .build()), Arguments.of(TestCase.builder() - .name("5. missing subject") + .name("missing subject") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, "", expiration), key1, "authority1")) .expectedException(new JwtSvidException("Token missing subject claim")) .build()), Arguments.of(TestCase.builder() - .name("6. missing expiration") + .name("missing expiration") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), null), key1, "authority1")) .expectedException(new JwtSvidException("Token missing expiration claim")) .build()), Arguments.of(TestCase.builder() - .name("7. token has expired") + .name("token has expired") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), new Date()), key1, "authority1")) .expectedException(new JwtSvidException("Token has expired")) .build()), Arguments.of(TestCase.builder() - .name("8. unexpected audience") + .name("unexpected audience") .jwtBundle(jwtBundle) .expectedAudience(Collections.singleton("another")) .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) .expectedException(new JwtSvidException("expected audience in [another] (audience=[audience2, audience1])")) .build()), Arguments.of(TestCase.builder() - .name("9. invalid subject claim") + .name("invalid subject claim") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(TestUtils.buildJWTClaimSet(audience, "non-spiffe-subject", expiration), key1, "authority1")) .expectedException(new JwtSvidException("Subject non-spiffe-subject cannot be parsed as a SPIFFE ID")) .build()), Arguments.of(TestCase.builder() - .name("10. missing key id") + .name("missing key id") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key1, null)) .expectedException(new JwtSvidException("Token header missing key id")) .build()), Arguments.of(TestCase.builder() - .name("11. key id contains an empty value") + .name("key id contains an empty value") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key1, " ")) .expectedException(new JwtSvidException("Token header key id contains an empty value")) .build()), Arguments.of(TestCase.builder() - .name("12. no bundle for trust domain") + .name("no bundle for trust domain") .jwtBundle(new JwtBundle(TrustDomain.of("other.domain"))) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) .expectedException(new BundleNotFoundException("No JWT bundle found for trust domain test.domain")) .build()), Arguments.of(TestCase.builder() - .name("13. no authority found for key id") + .name("no authority found for key id") .jwtBundle(new JwtBundle(TrustDomain.of("test.domain"))) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) .expectedException(new AuthorityNotFoundException("No authority found for the trust domain test.domain and key id authority1")) .build()), Arguments.of(TestCase.builder() - .name("14. signature cannot be verified with authority") + .name("signature cannot be verified with authority") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key2, "authority1")) .expectedException(new JwtSvidException("Signature invalid: cannot be verified with the authority with keyId=authority1")) .build()), Arguments.of(TestCase.builder() - .name("15. authority algorithm mismatch") + .name("authority algorithm mismatch") .jwtBundle(jwtBundle) .expectedAudience(audience) .generateToken(() -> TestUtils.generateToken(claims, key3, "authority1")) .expectedException(new JwtSvidException("Error verifying signature with the authority with keyId=authority1")) + .build()), + Arguments.of(TestCase.builder() + .name("not valid header 'typ'") + .jwtBundle(jwtBundle) + .expectedAudience(audience) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", "OTHER")) + .expectedException(new JwtSvidException("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: 'OTHER'.")) .build()) ); } 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 5b6539f..4801964 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 @@ -25,6 +25,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; class JwtSvidParseInsecureTest { @@ -33,9 +34,8 @@ class JwtSvidParseInsecureTest { "dCI6MTUxNjIzOTAyMiwiYXVkIjoiYXVkaWVuY2UifQ.wNm5pQGSLCw5N9ddgSF2hkgmQpGnG9le_gpiFmyBhao"; @ParameterizedTest - @MethodSource("provideJwtScenarios") - void parseJwt(TestCase testCase) { - + @MethodSource("provideSuccessScenarios") + void parseValidJwt(TestCase testCase) { try { String token = testCase.generateToken.get(); JwtSvid jwtSvid = JwtSvid.parseInsecure(token, testCase.audience); @@ -44,6 +44,18 @@ class JwtSvidParseInsecureTest { assertEquals(testCase.expectedJwtSvid.getAudience(), jwtSvid.getAudience()); assertEquals(testCase.expectedJwtSvid.getExpiry().toInstant().getEpochSecond(), jwtSvid.getExpiry().toInstant().getEpochSecond()); assertEquals(token, jwtSvid.getToken()); + } catch (Exception e) { + fail(e); + } + } + + @ParameterizedTest + @MethodSource("provideFailureScenarios") + void parseInvalidJwt(TestCase testCase) { + try { + String token = testCase.generateToken.get(); + JwtSvid.parseInsecure(token, testCase.audience); + fail("expected error: " + testCase.expectedException.getMessage()); } catch (Exception e) { assertEquals(testCase.expectedException.getClass(), e.getClass()); assertEquals(testCase.expectedException.getMessage(), e.getMessage()); @@ -89,7 +101,7 @@ class JwtSvidParseInsecureTest { } } - static Stream provideJwtScenarios() { + static Stream provideSuccessScenarios() { KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521); KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521); @@ -106,16 +118,56 @@ class JwtSvidParseInsecureTest { return Stream.of( Arguments.of(TestCase.builder() - .name("success") + .name("using typ as JWT") .expectedAudience(audience) - .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1")) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT)) .expectedException(null) .expectedJwtSvid(newJwtSvidInstance( trustDomain.newSpiffeId("host"), audience, expiration, - claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1"))) + claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT))) .build()), + Arguments.of(TestCase.builder() + .name("using typ as JOSE") + .expectedAudience(audience) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JOSE)) + .expectedException(null) + .expectedJwtSvid(newJwtSvidInstance( + trustDomain.newSpiffeId("host"), + audience, + expiration, + claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1", JwtSvid.HEADER_TYP_JWT))) + .build()), + Arguments.of(TestCase.builder() + .name("using empty typ") + .expectedAudience(audience) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", "")) + .expectedException(null) + .expectedJwtSvid(newJwtSvidInstance( + trustDomain.newSpiffeId("host"), + audience, + expiration, + claims.getClaims(), TestUtils.generateToken(claims, key1, "authority1", ""))) + .build())); + } + + static Stream provideFailureScenarios() { + KeyPair key1 = TestUtils.generateECKeyPair(Curve.P_521); + KeyPair key2 = TestUtils.generateECKeyPair(Curve.P_521); + + TrustDomain trustDomain = TrustDomain.of("test.domain"); + JwtBundle jwtBundle = new JwtBundle(trustDomain); + jwtBundle.putJwtAuthority("authority1", key1.getPublic()); + jwtBundle.putJwtAuthority("authority2", key2.getPublic()); + + SpiffeId spiffeId = trustDomain.newSpiffeId("host"); + Date expiration = new Date(System.currentTimeMillis() + 3600000); + Set audience = Collections.singleton("audience"); + + JWTClaimsSet claims = TestUtils.buildJWTClaimSet(audience, spiffeId.toString(), expiration); + + return Stream.of( Arguments.of(TestCase.builder() .name("malformed") .expectedAudience(audience) @@ -153,10 +205,10 @@ class JwtSvidParseInsecureTest { .expectedException(new JwtSvidException("Subject non-spiffe-subject cannot be parsed as a SPIFFE ID")) .build()), Arguments.of(TestCase.builder() - .name("unsupported algorithm") - .expectedAudience(Collections.singleton("audience")) - .generateToken(() -> HS256TOKEN) - .expectedException(new JwtSvidException("Unsupported JWT algorithm: HS256")) + .name("not valid header 'typ'") + .expectedAudience(audience) + .generateToken(() -> TestUtils.generateToken(claims, key1, "authority1", "OTHER")) + .expectedException(new JwtSvidException("If JWT header 'typ' is present, it must be either 'JWT' or 'JOSE'. Got: 'OTHER'.")) .build()) ); } diff --git a/java-spiffe-core/src/testFixtures/java/io/spiffe/utils/TestUtils.java b/java-spiffe-core/src/testFixtures/java/io/spiffe/utils/TestUtils.java index f78fa34..9fc3e57 100644 --- a/java-spiffe-core/src/testFixtures/java/io/spiffe/utils/TestUtils.java +++ b/java-spiffe-core/src/testFixtures/java/io/spiffe/utils/TestUtils.java @@ -1,6 +1,7 @@ package io.spiffe.utils; import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; @@ -9,6 +10,7 @@ import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import io.spiffe.svid.jwtsvid.JwtSvid; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -57,10 +59,14 @@ public class TestUtils { public static String generateToken(Map claims, KeyPair keyPair, String keyId) { JWTClaimsSet jwtClaimsSet = buildJWTClaimSetFromClaimsMap(claims); - return generateToken(jwtClaimsSet, keyPair, keyId); + return generateToken(jwtClaimsSet, keyPair, keyId, JwtSvid.HEADER_TYP_JWT); } public static String generateToken(JWTClaimsSet claims, KeyPair keyPair, String keyId) { + return generateToken(claims, keyPair, keyId, JwtSvid.HEADER_TYP_JWT); + } + + public static String generateToken(JWTClaimsSet claims, KeyPair keyPair, String keyId, String typ) { try { JWSAlgorithm algorithm; JWSSigner signer; @@ -74,7 +80,9 @@ public class TestUtils { throw new IllegalArgumentException("Algorithm not supported"); } - SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(algorithm).keyID(keyId).build(), claims); + final JOSEObjectType joseTyp = new JOSEObjectType(typ); + final JWSHeader header = new JWSHeader.Builder(algorithm).keyID(keyId).type(joseTyp).build(); + SignedJWT signedJWT = new SignedJWT(header, claims); signedJWT.sign(signer); return signedJWT.serialize(); } catch (JOSEException e) {