Validate JWT 'typ' header. (#62)
* Validate JWT 'typ' header. Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
e33417b10b
commit
0ee9ae28fa
|
|
@ -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,6 +63,9 @@ 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<String> audience,
|
||||
final Date expiry,
|
||||
|
|
@ -88,13 +92,13 @@ public class JwtSvid {
|
|||
* @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 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
|
||||
* cannot be found in the JwtBundleSource
|
||||
* @throws AuthorityNotFoundException if the authority cannot be found in the bundle using the value from
|
||||
* the 'kid' header
|
||||
*/
|
||||
|
|
@ -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,9 +143,10 @@ 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 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<String> audience) throws JwtSvidException {
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Arguments> provideJwtScenarios() {
|
||||
static Stream<Arguments> 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<String> audience = new HashSet<String>() {{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<Arguments> 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<String> audience = new HashSet<String>() {{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())
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Arguments> provideJwtScenarios() {
|
||||
static Stream<Arguments> 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<Arguments> 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<String> 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())
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Object> 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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue