4

I am currently trying to verify the signature of a JWT using an RSA public key built from a JWKS URL. I am using some Java objects for this, but my problem is that the Java Signature verifier doesn't just take a plain-text string decoded to binary as an argument, it takes it's own proprietary binary decoded "signature".

So I'm just wondering what I can do to convert my plain text signature into the data-type the Java Signature verify() function expects.

Here is my current code:

private function validate_jwt_signature(rc) {
    var id_token = listToArray(rc.id_token, ".");

    if (listToArray(id_token[2], "")[len(id_token[2])] != "=") {
            id_token[2] = id_token[2] & "=";
        }

    var header = deserializeJSON(base64urldecode(id_token[1]));
    var payload = deserializeJSON(base64urldecode(id_token[2]));
    var body = id_token[1] & "." & id_token[2];
    var signature = id_token[3];

    cfhttp(url="https://lti-ri.imsglobal.org/platforms/53/platform_keys/48.json", method="GET", result="key");

    var platformPubKey = deserializeJSON(key.filecontent).keys[1].n;

    createObject( "java", "java.security.Security" )
        .addProvider( createObject( "java", "org.bouncycastle.jce.provider.BouncyCastleProvider" ).init());

    var platformPubKey = reReplace( platformPubKey, "-", "+", "all" );
    var platformPubKey = reReplace( platformPubKey, "_", "/", "all" );
    var pemKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA" & platformPubKey & "IDAQAB";
    var publicKeySpec = createObject( "java", "java.security.spec.X509EncodedKeySpec" )
        .init(binaryDecode( pemKey, "base64" ));

    var publicKey = createObject( "java", "java.security.KeyFactory" )
        .getInstance( javaCast( "string", "RSA" ) )
        .generatePublic( publicKeySpec );

    var verifier = createObject( "java", "java.security.Signature" )
        .getInstance( javaCast( "string", 'SHA256withRSA' ));

    var verifier.initVerify( publicKey );
    var verifier.update( charsetDecode( body, "utf-8" ) );
    var signature = reReplace( signature, "-", "+", "all" );
    var signature = reReplace( signature, "_", "/", "all" );
    var verified = verifier.verify( charsetDecode(signature, "utf-8"));

    return verified;
}

This code runs and does not error, but it always returns false even when I know the signature is valid.

Miguel-F
  • 13,450
  • 6
  • 38
  • 63
  • 1
    It is needed to provided the binary signature to the verifier. Use `verifier.verify(Base64.getUrlDecoder().decode(signature));`. To avoid errors, you can construct the public key using `new RSAPublicKeySpec (modulus, pubExp)` instead of manually building a PEM key (I do not know if your code works) – pedrofb Mar 25 '19 at 21:01
  • Good spot. I noticed all the replace() statements and was just going to ask if the signature was base64 encoded. If it is, use `binaryDecode(signature, "base64")` instead of `charsetDecode()`. – SOS Mar 25 '19 at 21:11
  • I noticed you included the `coldbox` tag in your question so I wanted to pass this along in case it might be helpful. In ForgeBox, there's a module called `cbSecurity` which has the capability of generating, decoding, and authenticating JWT tokens (https://www.forgebox.io/view/cbsecurity). If you look at the JWT service, you may be able to get some additional clues. – Dave L Oct 06 '19 at 06:02
  • This is probably the best JWT package - https://www.forgebox.io/view/jwt-cfml This and https://www.forgebox.io/view/jwt are the two most popular packages – Gavin Pickin Jan 22 '20 at 21:21
  • @pedrofb could you give an example of how to obtain the modulus and Pubexp using coldfusion? I'm really struggling with that part as it involves using the 'Interface RSAPublicKey' class - https://docs.oracle.com/javase/8/docs/api/java/security/interfaces/RSAPublicKey.html – user2677034 Aug 09 '21 at 22:08

0 Answers0