spring-attic / spring-security-oauth

Support for adding OAuth1(a) and OAuth2 features (consumer and provider) for Spring web applications.
http://github.com/spring-projects/spring-security-oauth
Apache License 2.0
4.7k stars 4.04k forks source link

Unable to store URL's into JWT header, invalid parsing of json #1400

Open lucwillems opened 6 years ago

lucwillems commented 6 years ago

we use JWT Helper to sign json objects for transfer between untrusted parties and trusted services. this allows us to verify that provided content is not modified. it uses the same principal as a JWT oauth token so it has

as documented in RFC7115 , you can add custom headers to this token in the header part (as payload is our document) , see 4.1.2. "jku" (JWK Set URL) Header Parameter

we put a URI reference in our header , but during decode , the content is stript because of bad parsing in JwtHeaderHelper.parseMapInternal() function which expects there is only 1 ":" , but the URI also has a ":"

this junit test demonstrates the bug

import org.junit.Assert;
import org.junit.Test;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;

import java.util.HashMap;
import java.util.Map;

public class SpringJWTHeaderBug {

    @Test
    public void jwtHelperHeaderBug() {
        HashMap<String,String> header=new HashMap<>();
        String url="https://my-strong-server.com";

        //create a JWT token
        header.put("href",url);
        String json="[{ \"hello\" : \"world\"}]";
        MacSigner signer=new MacSigner("123456");
        String token = JwtHelper.encode(json, signer, header).getEncoded();

        //now decode and check headers
        Jwt jwt = JwtHelper.decodeAndVerify(token, signer);
        Map<String, String> headers = JwtHelper.headers(token);

        Assert.assertEquals(url,headers.get("href"));

    }
}

the lines code that causes the bug :

String[] values = pair.split(":");
String key = strip(values[0], '"');
String value = null;
if (values.length > 0) {
        value = strip(values[1], '"');
}

in this case values.length is 3 , but it only use the first 2 elements , causing the content to be stripped causing this result:

org.junit.ComparisonFailure: 
Expected :https://my-strong-server.com
Actual   :https
 <Click to see difference>

    at org.junit.Assert.assertEquals(Assert.java:115)
    at org.junit.Assert.assertEquals(Assert.java:144)
    at SpringJWTHeaderBug.jwtHelperHeaderBug(SpringJWTHeaderBug.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
ndrwdn commented 6 years ago

I'm seeing similar behavior for kid parameters that contain colons, i.e., given a kid of the format 20180523-05:32:12, the header JSON parser returns 20180523-05.

https://github.com/spring-projects/spring-security-oauth/blob/98e6027861622cb6dd7020f2e7a9433ad9479c54/spring-security-jwt/src/main/java/org/springframework/security/jwt/JwtHelper.java#L147