Skip to content

Commit

Permalink
Breaking change to fix JWT header to store String, Object. Bump to 5.0.
Browse files Browse the repository at this point in the history
Fixes #38.
  • Loading branch information
robotdan committed Sep 28, 2021
1 parent c7129fb commit ce55851
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 13 deletions.
8 changes: 8 additions & 0 deletions CHANGES
@@ -1,5 +1,13 @@
FusionAuth JWT Changes

Changes in 5.0.0

* Correct JWT header to be a Sting, Object map to support embedded JWK.
This is a potentially a breaking change.

Resolves https://github.com/FusionAuth/fusionauth-jwt/issues/38
Thanks to @tommed for opening the issue.

Changes in 4.3.1

* Modify `JSONWebKeySetResponse` to be public in support of JPMS.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -41,23 +41,23 @@ We are very interested in compensating anyone that can identify a security relat
<dependency>
<groupId>io.fusionauth</groupId>
<artifactId>fusionauth-jwt</artifactId>
<version>4.3.1</version>
<version>5.0.0</version>
</dependency>
```

### Gradle
```groovy
implementation 'io.fusionauth:fusionauth-jwt:4.3.1'
implementation 'io.fusionauth:fusionauth-jwt:5.0.0'
```

### Gradle Kotlin
```kotlin
implementation("io.fusionauth:fusionauth-jwt:4.3.1")
implementation("io.fusionauth:fusionauth-jwt:5.0.0")
```

### Savant
```groovy
dependency(id: "io.fusionauth:fusionauth-jwt:4.3.1")
dependency(id: "io.fusionauth:fusionauth-jwt:5.0.0")
```

For others see [https://search.maven.org](https://search.maven.org/artifact/io.fusionauth/fusionauth-jwt/4.0.1/jar).
Expand Down
2 changes: 1 addition & 1 deletion build.savant
Expand Up @@ -17,7 +17,7 @@
savantVersion = "1.0.0"
jacksonVersion = "2.12.2"

project(group: "io.fusionauth", name: "fusionauth-jwt", version: "4.3.1", licenses: ["ApacheV2_0"]) {
project(group: "io.fusionauth", name: "fusionauth-jwt", version: "5.0.0", licenses: ["ApacheV2_0"]) {

workflow {
standard()
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -6,7 +6,7 @@

<groupId>io.fusionauth</groupId>
<artifactId>fusionauth-jwt</artifactId>
<version>4.3.1</version>
<version>5.0.0</version>
<packaging>jar</packaging>

<name>FusionAuth JWT</name>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/fusionauth/jwt/JWTDecoder.java
Expand Up @@ -87,7 +87,7 @@ public JWTDecoder withClockSkew(int clockSkew) {
* @return a decoded JWT.
*/
public JWT decode(String encodedJWT, Map<String, Verifier> verifiers) {
return decode(encodedJWT, verifiers, h -> h.get("kid"));
return decode(encodedJWT, verifiers, h -> h.getString("kid"));
}

/**
Expand Down Expand Up @@ -234,7 +234,7 @@ protected ZonedDateTime now() {
*/
private void verifySignature(Verifier verifier, Header header, String signature, String encodedJWT) {
// The message comprises the first two segments of the entire JWT, the signature is the last segment.
int index = encodedJWT.lastIndexOf(".");
int index = encodedJWT.lastIndexOf('.');
byte[] message = encodedJWT.substring(0, index).getBytes(StandardCharsets.UTF_8);

byte[] signatureBytes = base64Decode(signature);
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/io/fusionauth/jwt/domain/Header.java
Expand Up @@ -36,7 +36,7 @@ public class Header {
public Algorithm algorithm;

@JsonIgnore
public Map<String, String> properties = new LinkedHashMap<>();
public Map<String, Object> properties = new LinkedHashMap<>();

@JsonProperty("typ")
public String type = "JWT";
Expand All @@ -55,11 +55,24 @@ public Header(Algorithm algorithm) {
* @return a map of properties to be serialized as if they were actual properties of this class.
*/
@JsonAnyGetter
public Map<String, String> anyGetter() {
public Map<String, Object> anyGetter() {
return properties;
}

public String get(String name) {
/**
* @param name the name of the key
* @return the string value of the object found by the requested key.
*/
public String getString(String name) {
Object result = properties.get(name);
return result != null ? result.toString() : null;
}

/**
* @param name the name of the key
* @return the string value of the object found by the requested key.
*/
public Object get(String name) {
return properties.get(name);
}

Expand All @@ -71,7 +84,7 @@ public String get(String name) {
* @return this.
*/
@JsonAnySetter
public Header set(String name, String value) {
public Header set(String name, Object value) {
if (name == null || value == null) {
return this;
}
Expand Down
31 changes: 31 additions & 0 deletions src/test/java/io/fusionauth/jwks/JSONWebKeyBuilderTest.java
Expand Up @@ -18,6 +18,11 @@

import io.fusionauth.jwks.domain.JSONWebKey;
import io.fusionauth.jwt.BaseJWTTest;
import io.fusionauth.jwt.JWTUtils;
import io.fusionauth.jwt.Signer;
import io.fusionauth.jwt.domain.Header;
import io.fusionauth.jwt.domain.JWT;
import io.fusionauth.jwt.rsa.RSASigner;
import io.fusionauth.pem.domain.PEM;
import org.testng.annotations.Test;

Expand All @@ -29,6 +34,9 @@
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Map;

import static org.testng.Assert.assertEquals;

/**
* @author Daniel DeGroff
Expand Down Expand Up @@ -112,6 +120,29 @@ public void rsa_private() throws Exception {
assertJSONEquals(JSONWebKey.build(privateKey), "src/test/resources/jwk/rsa_private_key_jwk_control.json");
}

@Test
public void embedded_jwk() {
JWT jwt = new JWT();
jwt.addClaim("foo", "bar");

RSAPrivateKey privateKey = PEM.decode(Paths.get("src/test/resources/rsa_private_key_2048.pem")).getPrivateKey();
RSAPublicKey publicKey = PEM.decode(Paths.get("src/test/resources/rsa_public_key_2048.pem")).getPublicKey();
JSONWebKey jwk = JSONWebKey.build(publicKey);

Signer signer = RSASigner.newSHA256Signer(privateKey);
String encodedJWT = JWT.getEncoder().encode(jwt, signer, h -> {
h.set("cty", "application/json");
h.set("jwk", jwk);
});

Header header = JWTUtils.decodeHeader(encodedJWT);
assertEquals(header.get("cty"), "application/json");
assertEquals(((Map<?, ?>) header.get("jwk")).get("e"), jwk.e);
assertEquals(((Map<?, ?>) header.get("jwk")).get("kty"), jwk.kty.name());
assertEquals(((Map<?, ?>) header.get("jwk")).get("n"), jwk.n);
assertEquals(((Map<?, ?>) header.get("jwk")).get("use"), jwk.use);
}

@Test
public void rsa_public() throws Exception {
// PKCS#1 RSA PEM Encoded Public Key
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/io/fusionauth/jwt/VulnerabilityTest.java
Expand Up @@ -55,7 +55,7 @@ public void test_encodedJwtWithSignatureRemoved() {
JWT jwt = new JWT().setSubject("art");
String encodedJWT = JWT.getEncoder().encode(jwt, HMACSigner.newSHA256Signer("secret"));

String hackedJWT = encodedJWT.substring(0, encodedJWT.lastIndexOf("."));
String hackedJWT = encodedJWT.substring(0, encodedJWT.lastIndexOf('.'));

expectException(InvalidJWTException.class, () -> JWT.getDecoder().decode(hackedJWT, HMACVerifier.newVerifier("secret")));
}
Expand Down

0 comments on commit ce55851

Please sign in to comment.