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

13
  • 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, 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 Commented Aug 18, 2018 at 18:19
  • 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... Commented Aug 18, 2018 at 18:47
  • 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. Commented Aug 18, 2018 at 18:59
  • 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. Commented Aug 18, 2018 at 19:28
  • 1
    (cont'd) Meaning you can't create a 256 bit key (or higher) unless you install the Unlimited JCE files. 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. Commented Aug 18, 2018 at 19:28

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

14 Comments

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).
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.
@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 :)
@Ageax The developer wouldn’t, but the byte code manipulator would.
@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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.