Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Brannon Hightower
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Brannon Hightower

Adding RSA Support To JSONWebTokens.cfc - My ColdFusion JWT Library

By Ben Nadel on
Tags: ColdFusion

Last week, I released JSONWebTokens.cfc, which is a small ColdFusion library to facilitate the encoding and decoding of JSON Web Tokens (JWT). When I released it, it only had support for the Hmac (Hashed Message Authentication Code) algorithms. However, I recently took some time to experiment with RSA encryption which uses both a public and a private key. Today, I'm happy to say that I have refactored the JSONWebTokens.cfc to support the RSA portion of the JWT specification.

Project: See the JsonWebTokens.cfc on my GitHub account.

Because the Hmac algorithms use a single key and the RSA algorithms use two keys - public and private - I had to refactor various method signatures to make the second key optional based on the type of algorithm. Ultimately, under the hood, these top-level methods create a Client which is injected with a "Signer" implementation. So, the underlying client doesn't actually care about the algorithm itself - just that it can hand-off the signing and verification of the JWT payload.

Now, the list of supported algorithms includes:

  • HS256 - uses HmacSHA256 as the underlying Java algorithm.
  • HS384 - uses HmacSHA384 as the underlying Java algorithm.
  • HS512 - uses HmacSHA512 as the underlying Java algorithm.
  • RS256 - uses SHA256withRSA as the underlying Java algorithm.
  • RS384 - uses SHA384withRSA as the underlying Java algorithm.
  • RS512 - uses SHA512withRSA as the underlying Java algorithm.

To see the RSA algorithms in action, take a look at this demo which will encode and decode a payload using both the top-level, one-off methods as well as creating a JWT client and using the client methods:

  • <cfscript>
  •  
  • // I am the payload to be delivered in a JSON Web Token (JWT).
  • payload = {
  • question: "How funky is your chicken?",
  • followUp: "How loose is your goose?",
  • answer: "Our goose is totally loose!"
  • };
  •  
  •  
  • // ------------------------------------------------------------------------------- //
  • // ------------------------------------------------------------------------------- //
  •  
  •  
  • // First, let's try to use the top-level one-off methods.
  • jwt = new lib.JsonWebTokens();
  •  
  • // Encode JWT token using 512-bit RSA signing algorithm.
  • token = jwt.encode( payload, "HS512", getPublicKey(), getPrivateKey() );
  •  
  • // Decode JWT token (and verity signature) using 512-bit RSA signing algorithm.
  • decodedPayload = jwt.decode( token, "HS512", getPublicKey(), getPrivateKey() );
  •  
  •  
  • // Verify that all the stuff worked!
  • if (
  • ( payload.question != decodedPayload.question ) ||
  • ( payload.followUp != decodedPayload.followUp ) ||
  • ( payload.answer != decodedPayload.answer )
  • ) {
  •  
  • throw( "StructEqualityFailed" );
  •  
  • }
  •  
  • // Output the decoded values.
  • writeOutput( "One-off Methods: <br />" );
  • writeOutput( "Question: #decodedPayload.question# <br />" );
  • writeOutput( "Follow-Up: #decodedPayload.followUp# <br />" );
  • writeOutput( "Answer: #decodedPayload.answer# <br />" );
  • writeOutput( "<br />" );
  •  
  •  
  • // ------------------------------------------------------------------------------- //
  • // ------------------------------------------------------------------------------- //
  •  
  •  
  • // Now, let's try creating a client with encapsulated keys.
  • jwtClient = new lib.JsonWebtokens().createClient( "HS512", getPublicKey(), getPrivateKey() );
  •  
  • // Encode JWT token using encapsulated algorithm and keys.
  • token = jwtClient.encode( payload );
  •  
  • // Decode JWT token (and verify signature) using encapsulated algorithm and keys.
  • decodedPayload = jwtClient.decode( token );
  •  
  •  
  • // Verify that all the stuff worked!
  • if (
  • ( payload.question != decodedPayload.question ) ||
  • ( payload.followUp != decodedPayload.followUp ) ||
  • ( payload.answer != decodedPayload.answer )
  • ) {
  •  
  • throw( "StructEqualityFailed" );
  •  
  • }
  •  
  • // Output the decoded values.
  • writeOutput( "Client Methods: <br />" );
  • writeOutput( "Question: #decodedPayload.question# <br />" );
  • writeOutput( "Follow-Up: #decodedPayload.followUp# <br />" );
  • writeOutput( "Answer: #decodedPayload.answer# <br />" );
  •  
  •  
  • // ------------------------------------------------------------------------------- //
  • // ------------------------------------------------------------------------------- //
  •  
  •  
  • // I get the private RSA key in PEM format.
  • public string function getPrivateKey() {
  •  
  • var lines = [
  • "-----BEGIN RSA PRIVATE KEY-----",
  • "MIICXAIBAAKBgQCq8RpMWK5mEt9qypbw6+zafEgQIpR1uUjHbIbZ6dadW/uxU3DZ",
  • "D0KW+KVAXnJb11dYCv48O5O/8h95z/SdAnHchc6hjk7wkAPwG1p3rpMVoSCftkoh",
  • "y+qzdELmDir8Sr61gaEXUnZfp2gkfmubiAITHovHFfRk5hNGZTHxol7LJwIDAQAB",
  • "AoGAYiVkMAmKuFiFpk8DMviCWT+aMIlqK91iB/4rvtofuuGhNULvO/EjDoNcfgS8",
  • "LDcLkyVcq0CZqE9f+xSHIc7RiBegv4uxGJQ6/rivfbOh2KjfLAIkniovN3FOR86B",
  • "GJ6MWm3Bo28gGrPoODkRrnZvDHtfRvUXnzyGTxH5yOCJxAECQQDTP4j9yMTuOaag",
  • "AG51RQ7cP6aVazZY8e4+MO8+8R0XrU5C1kEzfhh+FGS6laxoaay0LIFnhYB2ozXD",
  • "YjiVX+qBAkEAzyepeZQaH9/f6/l3Hnme9Qxfhl+4bnHjR2TEnzb+QLkBnVwYb42P",
  • "cO8VnZ1VJadZCX4k2+rEZ1hBc5+yxppRpwJAS4G9NIELqt7eaPhegvohGqaBo4zD",
  • "yz0GXCJfkY7bSDhA7fDpMz+R/5bIfky7aELFYU07H8Z/KWii8ehssy+qgQJAVGVI",
  • "OmwIKKxAwhakXRoXlKYx1MDylqx3eAKpyGPTOfMloUKAAhKeOdht6gTLR8fiEmf+",
  • "BEqlMaVXJRAO+bKtSQJBAIof8obXnfxr4ruNzv7bFSvRSLMXxqOi+5TRbpjpwtRz",
  • "yCRXDbakPi05ywzWRDJ/AoON63ZWyFVTFDZQEp2hRwQ=",
  • "-----END RSA PRIVATE KEY-----"
  • ];
  •  
  • return( arrayToList( lines, chr( 10 ) ) );
  •  
  • }
  •  
  •  
  • // I get the public RSA key in PEM format.
  • public string function getPublicKey() {
  •  
  • var lines = [
  • "-----BEGIN PUBLIC KEY-----",
  • "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq8RpMWK5mEt9qypbw6+zafEgQ",
  • "IpR1uUjHbIbZ6dadW/uxU3DZD0KW+KVAXnJb11dYCv48O5O/8h95z/SdAnHchc6h",
  • "jk7wkAPwG1p3rpMVoSCftkohy+qzdELmDir8Sr61gaEXUnZfp2gkfmubiAITHovH",
  • "FfRk5hNGZTHxol7LJwIDAQAB",
  • "-----END PUBLIC KEY-----"
  •  
  • ];
  •  
  • return( arrayToList( lines, chr( 10 ) ) );
  •  
  • }
  •  
  • </cfscript>

The RSA keys are assumed to be in plain-text PEM format and passed-in as Strings. Under the hood, they are converted to Java PrivateKey and PublicKey instances. This felt like the easiest way to consume them. And, when we run the above code, we get the following output:

One-off Methods:
Question: How funky is your chicken?
Follow-Up: How loose is your goose?
Answer: Our goose is totally loose!

Client Methods:
Question: How funky is your chicken?
Follow-Up: How loose is your goose?
Answer: Our goose is totally loose!

As you can see, the encoding and decoding worked quite nicely.

I still couldn't tell you exactly what RSA encryption is, or how asymmetric keys work in cryptography. But, at least I can get the JWT algorithms to work when they are supposed to work and fail when they are supposed to fail. So, I'm going to call that a victory!




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.