EmbeddedJwkVerificationKeyResolverTest.java

package org.jose4j.keys.resolvers;

import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.jwt.consumer.JwtContext;
import org.jose4j.jwt.consumer.SimpleJwtConsumerTestHelp;
import org.jose4j.jwx.HeaderParameterNames;
import org.jose4j.lang.HashUtil;
import org.junit.Test;

import java.security.Key;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class EmbeddedJwkVerificationKeyResolverTest
{
    @Test
    public void testDpopExamples() throws InvalidJwtException, MalformedClaimException
    {
        // from pre -03 https://tools.ietf.org/html/draft-fett-oauth-dpop there's a JWT with a jwk header
        String jwt = "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoU" +
                "klDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R" +
                "2R1JEQSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM3lFU2MwNGFjYzc3bFRjMjZ4IiwiaHRtIjoiUE9TVCIsImh0dSI6" +
                "Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwiaWF0IjoxNTYyMjYyNjE2fQ.1bviyKqc_-6h3bGPT7jL27jO3KG" +
                "55wEONmboEoZou0fbIIifsFmp_UwPE3kj0G9sEA5AN9jjzHeRNF6rn-scrw";

        EmbeddedJwkVerificationKeyResolver embeddedJwkVerificationKeyResolver = new EmbeddedJwkVerificationKeyResolver();
        JwtConsumerBuilder b = new JwtConsumerBuilder()
                .setEvaluationTime(NumericDate.fromSeconds(1562262626))
                .setRequireIssuedAt()
                .setIssuedAtRestrictions(10, 60)
                .setVerificationKeyResolver(embeddedJwkVerificationKeyResolver)
                .setExpectedType(true,"application/dpop+jwt");
        JwtConsumer consumer = b.build();
        JwtClaims claims = consumer.processToClaims(jwt);

        assertThat("POST", is(equalTo(claims.getStringClaimValue("htm"))));
        assertThat("https://server.example.com/token", is(equalTo(claims.getStringClaimValue("htu"))));

        PublicJsonWebKey jwk = embeddedJwkVerificationKeyResolver.getJwk();
        String thumbprint = jwk.calculateBase64urlEncodedThumbprint(HashUtil.SHA_256);

        // compare (by thumbprint) the key in the header to the document
        assertThat("0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I", is(equalTo(thumbprint)));

        // no jwk header in this one so the jwt processing should fail
        jwt = "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2In0.eyJqdGkiOiItQndDM3lFU2MwNGFjYzc3bFRjMjZ4IiwiaHRtIjoiUE9TV" +
                "CIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwiaWF0IjoxNTYyMjYyNjE2fQ.DlWrKDQE15d6lN14J" +
                "525P877gT_pmYYKFwpFmUvs3uQ96wTHV-4ZdnozfXVmlHmpgF5DA_3Ld8x5iyS7MrOjhw";
        SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, consumer);
    }

    @Test
    public void testRoundTripping() throws Exception
    {
        String jwkJson = "{  \n" +
                "   \"kty\":\"RSA\",\n" +
                "   \"n\":\"pVxL168uDaEFo9vMjLBcwtrOSi1kzceo4IGclkRtaxjOhxfYIBYw1UgdxkEn-3xUXadBFgQI9kas4EJH3vpZUPcVyHRKmNy9VVwTyq-ka9qfSl3bLkgkzLrWve8vxRoTAhDrX3EHxwZ00yQ0F7Rk1JOx9Dly-Cv4_NCdKFC5ULukDxWEKVFCLw1FjabNSnpQuGxGmzN7vgLc9V68pB809kctIsL5M4YLdoWOqo4YVMmLgg8v9uLz05vKnaoJ7g3Tf9PqCIwNAhMswvIiHmt_ipfu2wNrhAmEtsf-kBhtHnmp7jWZxaUZRoNVjwCEeERdAObVVYvZ2Y9sRVUR46LYsw\",\n" +
                "   \"e\":\"AQAB\",\n" +
                "   \"d\":\"hHA0ii1S3DWP99nNrQx_bsyiFgTfTHTRy-XjDPMHE5SNrOMrBR_gwqF8v1Fl_WRpiYywczqOFvkp8n8DYxHtQQx0FNUW_fElbt1NOLOOI5e4pm4fYqUDXDl0TjDoeJtWh_wXF5zGlt-T55uCYU3ox9z21NzCOQO26n0Gschdc8tU6wSG0YhnXGsIESkcJUG28YOjf-jnsbEus-V9dq3Ft5OuJI5TTQMm2gxKJGFi9V7pLXYF5iKc0tvU0HtIjIT7sYbgUw4yQHk50OgcmP-iWPYKj1sRIUuDYQFR_1KeHgh9m6dRd3EJRN0MQnWAMSEtykKEomlkczP-rabbDYzPCQ\",\n" +
                "   \"p\":\"-5IQbu2UmL4papzjwHqzPMbxJ4uu4O07FrYbod0hRDsPEsGArOBAYUd9vPVxJfXMb6xSYnSiuCsmvGxYDlqTQx-bUjxgh_rP6zZK2V_PKSrxipPZRN_yx8gXGgNK8etlpLS20bCoMRWwWdhfmfrOMDz0xU2DtiQ28RySlgcTHa8\",\n" +
                "   \"q\":\"qEWljcS7XXRy0Y-HaPW7mJF13lZbhqK3OB6w8ArXs3oJxBnTNehwgIMHs4EnTybFDCDjth8tdznbvypopGCbWjjAe4oPzQrB4bdoTqA-eUglEKRAwSO46qsZYxTOPC2KI3Pt8oFRb3SwY3p-ZFMcaQBw5SG6zV07U2ClI5s_Gj0\",\n" +
                "   \"dp\":\"9PnQzOTIPlF3rV0oH8icgAPO1E6etmPtlXkywVW_zlygmgga0L3zk4d1tytfyrJoKRsqgrvHtQY4S2ZJ_XhQTR4bN2KaMfCYxhjxnGpDJniuC99bxUk7dzau4GLyeVBcg56DJQEdV-ch-uvMdPqaDLlfNj78ksMDSZokWLp87_s\",\n" +
                "   \"dq\":\"j1_IT6LwghBWRHgmyCeYT8agx3CNS4oY0phT6jNS1nmFGLFoZOZH9TevuCKze51tB4h-fQ7TjmKd-aQIxQYLWDRCzQA1tl8UE15SYavnjy8JZcSN6AKn7Esctm9jyaKDsPF2LMpCuNST9i7IwnKOImldx92VbKWjhscx7cS5X4E\",\n" +
                "   \"qi\":\"T8Of3qCf6snckcSWUOGim3LnfAnzT87cR_qZZOHzF8wD_rvfBi0DGWuOaBavTFgiIow0wBZBd71MqYdIFfHm4muI-z08d0ZeZrKPljNGVl46_mICTWA-W6yGk6ez5lcam84xJhSXH5xDEwMtbWMiCs5vqq5JkU9vq0DCbyDT5yk\"\n" +
                "}";
        PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(jwkJson);

        JsonWebSignature jws = new JsonWebSignature();
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
        jws.setHeader(HeaderParameterNames.TYPE, "dpop+jwt");
        jws.setJwkHeader(jwk);
        jws.setKey(jwk.getPrivateKey());
        JwtClaims claims = new JwtClaims();
        claims.setJwtId("abc123");
        claims.setStringClaim("htm", "GET");
        claims.setStringClaim("htu", "https://api.example.com/whatever");
        claims.setIssuedAtToNow();
        jws.setPayload(claims.toJson());

        String jwt = jws.getCompactSerialization();

        EmbeddedJwkVerificationKeyResolver embeddedJwkResolver = new EmbeddedJwkVerificationKeyResolver();
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setVerificationKeyResolver(embeddedJwkResolver)
                .setExpectedType(true, "dpop+jwt")
                .setRequireIssuedAt()
                .setIssuedAtRestrictions(5, 30)
                .build();
        JwtContext context = jwtConsumer.process(jwt);

        assertThat("GET", is(equalTo(context.getJwtClaims().getStringClaimValue("htm"))));
        assertThat("https://api.example.com/whatever", is(equalTo(context.getJwtClaims().getStringClaimValue("htu"))));

        PublicJsonWebKey inlineJwk = embeddedJwkResolver.getJwk();
        byte[] thumbprint = inlineJwk.calculateThumbprint(HashUtil.SHA_256);
        assertThat(jwk.calculateThumbprint(HashUtil.SHA_256), is(equalTo(thumbprint)));

        Key key = context.getJoseObjects().get(0).getKey();
        assertThat(key, is(equalTo(jwk.getKey())));


    }

    @Test
    public void testMakeSureJwkHeaderWithPrivateKeyIsRejected() throws Exception
    {
        String jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Ijc2blk4UGtTVkE4MG" +
                "lPUHEzVUVHbm9jdU9HaFFqR09rY1BwYnlXcHNXbWciLCJ5IjoiMDBMWkIySWNPeEVya05ad2NRWF9kbXVkay1hdE9STUtqR" +
                "EJUc1VlTXZvVSIsImNydiI6IlAtMjU2IiwiZCI6IklJYlhwVWJJSGVtT0FyZWVRX0xNMmFrcTd5NjZEY1lsdXZneWRPcml0" +
                "SlUifX0.eyJqdGkiOiJiYzc4OSIsImh0bSI6IkdFVCIsImh0dSI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiaWF0Ijo" +
                "xNjQ3OTU5MTMyfQ.-GKT4h58oZzS4LGk8b44Dh4GoJ9Y2extHUOr_LzbFIibO_XXfanPZ8ePZkXd8s7cuQyFKagePUVCdu1" +
                "T2UKbTQ";

        EmbeddedJwkVerificationKeyResolver embeddedJwkResolver = new EmbeddedJwkVerificationKeyResolver();
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setVerificationKeyResolver(embeddedJwkResolver)
                .setEvaluationTime(NumericDate.fromSeconds(1647959133))
                .setExpectedType(true, "dpop+jwt")
                .setRequireIssuedAt()
                .setIssuedAtRestrictions(5, 30)
                .build();
        SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, jwtConsumer);
    }
}