Search code examples
javamavenjarcoldfusion-11

Uncompiled Java program works, but produces an error, when Coldfusion calls a method from its 'jar' file?


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


Solution

    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.