1

Project:

Converting JWT to a signed encrypted JWE

Environment:

  1. Windows 10
  2. JDK1.8
  3. Apache Maven 3.5.4
  4. VSCode 1.25.1 with all the required Java extensions
  5. Adobe Coldfusion 11 Application Server with JRE1.8

Dependencies:

  1. nimbus-jose-jwt-6.0
  2. json-smart-2.3
  3. asm-1.0.2

Issue:

Firstly, please understand that I am new to Java, but I have a good understanding of Coldfusion [CFML, like PHP].

When I run my program from within VSCode, I get the expected result of a serialised JWT string.

When I try & access the method from within a 'jar' file, using my server side language Coldfusion [similar to PHP], I get an error, coming from the very last line.

The constructor is initialised successfully, and the majority of the method call executes.

I have carefully tested every line, and the vast majority of the code works when externally calling the 'Encrypt()' method of the 'JwtSignEncrypt' class, but the last line fails with the following error.

The JWE object must be in an encrypted or decrypted state

Important Part of Stack Trace:

java.lang.IllegalStateException: The JWE object must be in an encrypted or decrypted state
at com.nimbusds.jose.JWEObject.ensureEncryptedOrDecryptedState(JWEObject.java:320)
at com.nimbusds.jose.JWEObject.serialize(JWEObject.java:456)
at com.chamika.jwt.JwtSignEncrypt.Encrypt(JwtSignEncrypt.java:153)
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:483)
at coldfusion.runtime.StructBean.invoke(StructBean.java:508)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2553)
at cftest412ecfm1275900201.runPage(C:\ColdFusion11\cfusion\wwwroot\establishmindfulness\unit-test\test41.cfm:129)
at coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:246)
at coldfusion.tagext.lang.IncludeTag.handlePageInvoke(IncludeTag.java:736)
at coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:572)
at coldfusion.filter.CfincludeFilter.invoke(CfincludeFilter.java:65)
at coldfusion.filter.IpFilter.invoke(IpFilter.java:45)
at coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:466)
at coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:42)
at coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40)
at coldfusion.filter.PathFilter.invoke(PathFilter.java:142)
at coldfusion.filter.LicenseFilter.invoke(LicenseFilter.java:30)
at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:94)
at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28)
at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38)
at coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:58)
at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38)
at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22)
at coldfusion.filter.CachingFilter.invoke(CachingFilter.java:62)
at coldfusion.CfmServlet.service(CfmServlet.java:219)
at coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:42)
at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at coldfusion.inspect.weinre.MobileDeviceDomInspectionFilter.doFilter(MobileDeviceDomInspectionFilter.java:121)
at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:422)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

JwtSignEncrypt.java

package com.chamika.jwt;

import java.util.*;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;


public class JwtSignEncrypt 
{

    String issuer;
    String subject;
    List<String> audience;
    Date expirationTime;
    Date notBeforeTime;
    Date issueTime;
    String jwtID;
    Map<String, Object> claim;

    public JwtSignEncrypt(final String iss, 
            final String sub,
            final String aud,
            final Date exp,
            final Date nbf,
            final Date iat,
            final String jti,
            Map<String, Object> cla) {
        if(iss != null) {        
            this.issuer = iss;
        }
        if(sub != null) {
            this.subject = sub;
        }
        if(aud != null) {
            List<String> items = Arrays.asList(aud.split("\\s*,\\s*")); 
            this.audience = items;
        }
        if(exp != null) {
            this.expirationTime = exp;
        }
        if(nbf != null) {
            this.notBeforeTime = nbf;
        }
        if(iat != null) {
            this.issueTime = iat;
        }
        if(jti != null) {
            this.jwtID = jti;
        }
        if(cla != null) {
            this.claim = cla;
        }
    }


    public String Encrypt(byte[] secretKeyEncoded) {

        String key = null;
        Object value = null;

        for (Map.Entry<String, Object> entry : claim.entrySet()) {
            key = entry.getKey();
            value = entry.getValue();
        }

        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(this.issuer).subject(this.subject).audience(this.audience).expirationTime(this.expirationTime).notBeforeTime(this.notBeforeTime).issueTime(this.issueTime).jwtID(this.jwtID).claim(key,value).build();

        String jweobject = "";
        JWSSigner signer;

        try {

            signer = new MACSigner(secretKeyEncoded);
            SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
            try {
                signedJWT.sign(signer);
            } catch (JOSEException e) {
                e.printStackTrace();
            }

            JWEObject jweObject = new JWEObject(
                new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
                    .contentType("JWT") 
                    .build(),
                new Payload(signedJWT));

            try {
                jweObject.encrypt(new DirectEncrypter(secretKeyEncoded));

            } catch (KeyLengthException e) {
                e.printStackTrace();
            } catch (JOSEException e) {
                e.printStackTrace();
            }

            jweobject = jweObject.serialize();

        } catch (KeyLengthException e) {
            e.printStackTrace();
        }

        return jweobject;
    }

}

Error occurs at the following line in the java file:

jweObject.encrypt(new DirectEncrypter(secretKeyEncoded));

Java Loader in Coldfusion onRequestStart method:

  <cfset request.lckchamikajwtlibinit = true />

  <cfif NOT StructKeyExists(APPLICATION,"chamikajwtlib") OR request.appreload>
    <cftry>
      <cflock name="chamikajwtlib" type="exclusive" timeout="#request.writelocktimeout#">
        <cfset local.jbClasschamikajwt = "#request.filepathasset#\lib\chamika-jwt-sign-encrypt\chamika-jwt-sign-encrypt-1.0.2.jar" />
        <cfset local.javaloader = createObject('component','com.javaloader.JavaLoader') />
        <cfset application.chamikajwtlib = local.javaloader.init([local.jbClasschamikajwt]) />
      </cflock>
      <cfcatch>
        <cfset request.lckchamikajwtlibinit = false />
      </cfcatch>
    </cftry>
  </cfif>

  <cfif request.lckchamikajwtlibinit>
    <cflock NAME="chamikajwtliblck" TIMEOUT="#request.readlocktimeout#" TYPE="READONLY">
      <cfset request.chamikaJwtSignEncryptJar= application.chamikajwtlib />
    </cflock>
  <cfelse>
    <cfset request.chamikaJwtSignEncryptJar= "" />
  </cfif>

test.cfm

<cfscript>

  local = {};
  local.loader = request.chamikaJwtSignEncryptJar;
  local.issuer = JavaCast("string","https://openid.net");
  local.subject = JavaCast("string","Charles Robertson");
  local.audience = "https://app-one.com,https://app-two.com";
  local.expirationTime = createObject("java","java.util.Date").init().getTime() + 60 * 1000;
  local.expirationTime = createObject("java","java.util.Date").init(local.expirationTime);
  local.currentDateTime = createObject("java","java.util.Date").init();
  local.notBeforeTime = local.currentDateTime;
  local.issueTime = local.currentDateTime;
  local.jwtID = JavaCast("string",CreateUUID());
  local.claim = createObject("java", "java.util.LinkedHashMap").init();
  local.json = {forename="Charles",surname='Robertson'};
  local.claim['json'] = SerializeJson(local.json);
  local.JwtSignEncrypt = local.loader.create("com.chamika.jwt.JwtSignEncrypt").init(local.issuer,local.subject,local.audience,local.expirationTime,local.notBeforeTime,local.issueTime,local.jwtID,local.claim);

  local.keyGen = local.loader.create("javax.crypto.KeyGenerator").getInstance("AES");
  local.keyGen.init(256);
  local.secretKeyEncoded = local.keyGen.generateKey().getEncoded();

  local.jweString = local.JwtSignEncrypt.Encrypt(local.secretKeyEncoded);

  writeDump(var=local.jweString);

</cfscript>

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.chamika.jwt</groupId>
  <artifactId>chamika-jwt-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>chamika-jwt-app</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.nimbusds</groupId>
      <artifactId>nimbus-jose-jwt</artifactId>
      <version>6.0</version>
    </dependency>
    <dependency>
        <groupId>net.minidev</groupId>
        <artifactId>json-smart</artifactId>
        <version>2.3</version>
    </dependency>
    <dependency>
    <groupId>net.minidev</groupId>
        <artifactId>asm</artifactId>
        <version>1.0.2</version>
    </dependency>
  </dependencies>
</project>

I have correctly included all the requisite libraries inside my 'jar'. Is there some other step I need to take, when I package the 'jar'. I am using a 'pom.xml' file, with 3 dependencies. For some reason, when I run:

nvm package

The dependencies do not get included. So, I have resorted to using 'jarsplice' to bundle the dependencies instead. All of the dependencies can be accessed correctly, independently, externally.

Questions:

  1. Why I am getting the error, when I try to generate the signed encrytped JWT, when using Coldfusion to call a method in the '.jar' file?
  2. Why are my dependencies not being included in the packaged '.jar' file? ​

UPDATE:

Here is my git repo:

https://bitbucket.org/charlesrobertson/chamika-jwt-app/src/master/

My java class is based on an official documentation snippet:

https://connect2id.com/products/nimbus-jose-jwt/examples/signed-and-encrypted-jwt

Cœur
  • 37,241
  • 25
  • 195
  • 267
Charles Robertson
  • 1,760
  • 16
  • 21
  • 1
    1. Could we see the the whole trace? If it's a dependency or initialization problem, the cause is further down the chain. 2. Maven doesn't include dependencies unless it's specified in the [pom.xml](https://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven#574650), which it isn't for this project. Personally I avoid that. Packaging everything in one uber jar is convenient, but makes it harder to handle version changes in the dependencies. I'd just add the dependent jars individually if possible. Keep in mind CF may already include some of them – SOS Aug 18 '18 at 18:19
  • @Ageax I have added the full stack trace. Would it help you if I give you the URL to my git repo? – Charles Robertson Aug 18 '18 at 18:25
  • (Update) Heh... yes, please. Unfortunately I don't see anything obvious in the trace. To be on the safe side, check the CF logs too. – SOS Aug 18 '18 at 18:33
  • @Ageax I have added my git repo URL – Charles Robertson Aug 18 '18 at 18:46
  • 1
    "check the CF logs too...." All of them, because error handling like `e.printStackTrace();` won't be displayed on screen. They're sent to one of the default log files... – SOS Aug 18 '18 at 18:47
  • Will do. Thanks! – Charles Robertson Aug 18 '18 at 18:51
  • Thanks. 1. In the CF code, how are you initializing `loader`? 2. How do you know it's working from java (beyond compiling successfully)? Didn't see anything in test besides the default AppTest class. – SOS Aug 18 '18 at 18:54
  • Yes. The java loader is working. I have plenty of cfdump data to prove that the jar is being correctly loaded. I load the jar file in my application RequestStart method. I will add some code to my question to show you where this comes from... – Charles Robertson Aug 18 '18 at 18:57
  • 1
    Oooh, didn't realize you were using JavaLoader. Thought you were using `this.javasettings` (CF10+). Let me try it with that first. If I can't reproduce the error I'll try the javaloader. – SOS Aug 18 '18 at 18:59
  • @Ageax Thanks. Just popping out for supper now, but I really appreciate any help. I have spent 5 days on this error!!!!! – Charles Robertson Aug 18 '18 at 19:01
  • 1
    (Update) You're welcome. Might have figured it out. I got the same error with CF11. The code is essentially swallowing the errors because CF doesn't display them on screen. Once I got rid of all the try/catch's and added a `throws XYZException` to all of the methods, I could see the error on screen. *Couldn't create AES/GCM/NoPadding cipher: Illegal key size*. – SOS Aug 18 '18 at 19:28
  • 1
    (cont'd) Meaning you can't create a 256 bit key (or higher) unless you install the [Unlimited JCE files](https://stackoverflow.com/a/41232470/8895292). It's simple. Just download the .zip for your java version, unzip and copy the 2 jars to the correct location and restart CF. After that it worked fine. – SOS Aug 18 '18 at 19:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178271/discussion-between-ageax-and-charles-robertson). – SOS Aug 18 '18 at 19:35

1 Answers1

1
  1. Why I am getting the error, when I try to generate the signed encrytped JWT, when using Coldfusion to call a method in the '.jar' file?

I got the same error with CF11. The problem is the try/catch code essentially swallows errors because CF doesn't display the output of e.printStackTrace() on screen. It's sent to the default log file instead. So you won't even know an exception occurred - unless you check the CF logs.

Error handling all depends on the app, but my thought is if the method can't do anything useful with the error, may as well let it bubble up and let the caller decide how to handle it. Anyway, once I got rid of all the try/catch's and added a throws XYZException to all of the methods, like this:

public String Encrypt(byte[] secretKeyEncoded) throws KeyLengthException, JOSEException {
    // ...
    jweObject.encrypt(new DirectEncrypter(secretKeyEncoded));
    // ...
}

... I could see the error message on screen. Couldn't create AES/GCM/NoPadding cipher: Illegal key size. Meaning you can't create a 256 bit key (or higher) unless you've installed the Unlimited JCE files. The solution is to download and install the Unlimited JCE files and restart CF. After that, the jar works fine (with the 3 dependencies).

  • Download the files for your java version. Example, for java 8 - jce_policy-8.zip
  • Make a backup of the existing local_policy.jar and US_export_policy.jar files in <java-home>\lib\security
  • Unzip the files and copy the new local_policy.jar and US_export_policy.jar into <java-home>\lib\security
  • Restart the CF server (required)
  1. Why are my dependencies not being included in the packaged '.jar' file? ​

Maven doesn't include dependencies unless it's specified in the pom.xml, which it isn't for this project. Personally I avoid doing that. Packaging everything into one big uber jar is convenient, but makes it harder to handle version changes in the dependencies. I'd just load the dependent jars separately.

SOS
  • 6,430
  • 2
  • 11
  • 29
  • Great answer. Just one question. Now that the issue is resolved, should I use the 'throws' error methodology, permanently, or keep the 'try catch' methodology, in the final code base? – Charles Robertson Aug 19 '18 at 19:48
  • 1
    My preference is to use throws. Since there is nothing the java class can do to fix the error it should notify the caller and let them handle it. That way the error handling is flexible and could differ depending on the caller environment (CF or java app). – SOS Aug 19 '18 at 21:48
  • 1
    Unrelated, but packaging everything into one big jar avoids *conflicting* dependencies with the runtime environment, which is very important. Since it's its own Maven project, it's no more or less convenient. (I don't know how CF handles its runtime environment vs. plugins or whatever they're called in CF, so this may not be relevant.) As an example, I had to depend on a relatively old version of a jar due to a library's dependencies, and it would have conflicted with classes in my relatively-modern web app. – Dave Newton Aug 19 '18 at 22:52
  • @DaveNewton - That is an interesting point. How does it work in the environments you've worked with? I'm wondering if CF handles things differently. Later versions of allow loading of multiple versions through an application specific dynamic class loader. Usually that avoids any conflicts. Though I've read about a few libs that don't work due to how they handle the context class loader. (cont'd) – SOS Aug 20 '18 at 01:34
  • (update) Also, In older versions using JRUN, I remember having the opposite type of conflict with a servlet. Tried updating a lib to a newer version by placing the jar in WEB-INF\lib, but JRUN ignored it. Turns out it was because there was an older version of that same lib already packaged in one of the jars. Took me a while to track it down. Think I had to place the jar in /ext to get it to use the newer version. So I'm curious how it's handled in the modern web apps you've used, or if I'm just misunderstanding the structure. – SOS Aug 20 '18 at 01:40
  • 1
    @Ageax The way über-jars work is by rewriting the package names so you can have multiple versions of the same libraries. Multiple class loaders is another approach, but it’s pretty easy to run into conflicts in the isolated class loader as well, particularly when dealing with legacy stuff. Fortunately I don’t have to do that much anymore :) – Dave Newton Aug 20 '18 at 01:47
  • @DaveNewton - Ohh, so they'd use something like `com.newpackagename.*` instead of `com.standardpackagename.*`? That makes sense, and yeah I could see how it would work where dynamic class loaders sometimes wouldn't. I'd forgotten you could do that! – SOS Aug 20 '18 at 02:34
  • 1
    @Ageax The developer wouldn’t, but the byte code manipulator would. – Dave Newton Aug 20 '18 at 02:46
  • @DaveNewton - Hmm, I'll have to read up more on that one. (If you know of any good links offhand, feel free to post them.) Thanks for all the good info! – SOS Aug 20 '18 at 02:48
  • 1
    @Ageax The maven shade package does that, although it may have been deprecated. It’s pretty simple, though, and various byte code manipulation libs make it pretty easy. Playing with byte code is fun, if somewhat arcane. – Dave Newton Aug 20 '18 at 03:13
  • @DaveNewton - Now that is cool! I was thinking of on the dev side. Didn't know it could be done at the byte code level. Will have to play around with it, learn how it works (even if it has been deprecated). Awesome, thanks! – SOS Aug 20 '18 at 04:06
  • @Ageax I have now finished the repo at: https://bitbucket.org/charlesrobertson/jwt-sign-encrypt/src/master/ – Charles Robertson Aug 25 '18 at 14:26
  • 1
    Thanks. I'll take a look tomorrow. – SOS Aug 28 '18 at 12:50
  • Sorry, been very busy. So the final version is the CF one? – SOS Sep 02 '18 at 18:15