Monday, 12 May 2014

Handling a JSON Web Token (JWT)

So I've been working on using some Google authentication for a Uniface web application, and it's clever stuff.  However, being the security conscious people that they are, they use a JSON Web Token (JWT) - pronounced "jot", apparently.

To quote the abstract...
The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS) and/or encrypted using JSON Web Encryption (JWE).

The Google API documentation is pretty good.  It gives you an endpoint that you can use to verify the token for debugging purposes, but suggests that in production you should be doing the verification locally...
Fortunately, there are well-debugged libraries available in a wide variety of languages to accomplish this.

I did look, of course I did, but did I expect to find Uniface on that list?  No I did not.  They have examples for .NET, Java, PHP, Python and Ruby.  So this is me, trying it the hard way, in Uniface.

I should point out, there are a few caveats to this...

  1. I've not done anything with encrypted tokens (JWEs).
  2. For signed tokens (JWSs) I've not validated the signature - I tried, but I can't get Uniface to do the encryption properly - I have a FrontLine call open about this.
  3. I use an included procedure "json_to_list" in a few places - this is something I'd previously written which uses string manipulation to convert a JSON string into a Uniface list.
  4. This code is provided as is, with no guarantee that it will work, it is merely for demonstration purposes. 

entry jwt_to_list
  string pToken : in ;JSON Web Token (JWT)
  string pList : out ;Uniface list of data
  string vToken,vHeader,vHeaderJson,vHeaderList,vAlgorithm,vTokenMode,vTokenType
  string vEncryption,vKeyId,vKeyUrl,vPartTwo,vPartTwoJson,vPartTwoList,vPartThree
  ;check parameters
  if ( pToken = "" )
    return -101 ;no token

  ;split token into 3 parts
  vToken = $replace(pToken,1,".","·;",-1)
  if ( $itemcount(vToken) != 3 )
    return -102 ;token doesn't have 3 parts
  getitem vHeader,vToken,1
  getitem vPartTwo,vToken,2
  getitem vPartThree,vToken,3
  if ( vHeader = "" | vPartTwo = "" )
    return -103 ;token parts are missing (check third part later, depends on mode)

  ;decode header
  vHeaderJson = $replace($replace(vHeader,1,"_","/",-1),1,"-","+",-1)
  vHeaderJson = $encode("USTRING",$decode("BASE64",vHeaderJson))
  if ( $status < 0 | $procerror < 0 | vHeaderJson = "" )
    return -104 ;header could not be decoded
  call json_to_list(vHeaderJson,vHeaderList)
  if ( vHeaderList = "" )
    return -105 ;header JSON is invalid

  ;extract header values
  getitem/id vTokenType,vHeaderList,"typ"
  delitem/id vHeaderList,"typ"
  getitem/id vAlgorithm,vHeaderList,"alg"
  delitem/id vHeaderList,"alg"
  getitem/id vEncryption,vHeaderList,"enc"
  delitem/id vHeaderList,"enc"
  getitem/id vKeyId,vHeaderList,"kid"
  delitem/id vHeaderList,"kid"
  getitem/id vKeyUrl,vHeaderList,"jku" 
  delitem/id vHeaderList,"jku"
  if ( vHeaderList != "" )
    return -106 ;unknown header values

  ;check signature algorithm
  selectcase ( vAlgorithm )
    case "none"
      vAlgorithm = "" ;plaintext token
      vTokenMode = "JWT"
    case "HS256"      
      vAlgorithm = "HMAC_SHA256" ;HMAC using SHA-256 hash
      vTokenMode = "JWS"
    case "HS384"
      vAlgorithm = "HMAC_SHA384" ;HMAC using SHA-384 hash
      vTokenMode = "JWS"
    case "HS512"
      vAlgorithm = "HMAC_SHA512" ;HMAC using SHA-512 hash
      vTokenMode = "JWS"
    case "RS256"
      vAlgorithm = "RSASSA_PKCS1V15_SHA256" ;RSA SSA (PKCS) using SHA-256 hash
      vTokenMode = "JWS"
    case "RS384"
      vAlgorithm = "RSASSA_PKCS1V15_SHA384" ;RSA SSA (PKCS) using SHA-384 hash
      vTokenMode = "JWS"
    case "RS512"
      vAlgorithm = "RSASSA_PKCS1V15_SHA512" ;RSA SSA (PKCS) using SHA-512 hash
      vTokenMode = "JWS"
    case "PS256"
      vAlgorithm = "RSASSA_PSS_SHA256" ;RSA SSA (PSS) using SHA-256 hash
      vTokenMode = "JWS"
    case "PS384"
      vAlgorithm = "RSASSA_PSS_SHA384" ;RSA SSA (PSS) using SHA-384 hash
      vTokenMode = "JWS"
    case "PS512"
      vAlgorithm = "RSASSA_PSS_SHA512" ;RSA SSA (PSS) using SHA-512 hash
      vTokenMode = "JWS"
    case "RAS1_5"
      vAlgorithm = "RSAES_PKCS1V15" ;RSA ES (PKCS)
      vTokenMode = "JWE"
    case "RSA-OAEP-256"
      vAlgorithm = "RSAES_OAEP_SHA256" ;RSA ES (OAEP) using SHA-256 hash
      vTokenMode = "JWE"
    case "ES256","ES384","ES512","RSA-OAEP","A128KW","A192KW","A256KW","dir"
      return -107 ;valid, but not supported by Uniface   
    case "","alg"
      return -108 ;no algorithm (mandatory value)
      return -109 ;unknown algorithm specified

  ;check mode/third part
  selectcase ( vTokenMode )
    case "JWT"
      if ( vPartThree != "" )
        return -110 ;third part specified in plaintext token
    case "JWS"
      if ( vPartThree = "" )
        return -111 ;third part missing in signed token
    case "JWE"
      if ( vPartThree = "" )
        return -111 ;third part missing in encrypted token
      return -112 ;todo - handle this type
      return -112 ;unknown token mode

  ;decode second part
  vPartTwoJson = $replace($replace(vPartTwo,1,"_","/",-1),1,"-","+",-1)
  vPartTwoJson = $encode("USTRING",$decode("BASE64",vPartTwoJson))
  if ( $status < 0 | $procerror < 0 | vPartTwoJson = "" )
    return -113 ;second part could not be decoded
  call json_to_list(vPartTwoJson,vPartTwoList)
  if ( vPartTwoList = "" )
    return -114 ;second part JSON is invalid

  ;signature mode
  if ( vTokenMode = "JWS" )
    ;todo – validate signature
  ;return data
  if ( vTokenType = "JWS" | vTokenType = "JWE" )
    call jwt_to_list(vPartTwoJson,pList) ;handle nested tokens
    pList = vPartTwoList ;no nesting, just return data

  return 0

Summary:  It is possible to handle JSON Web Tokens (JWTs), but so far I've only looked at plaintext and signed tokens, and I've not managed to validate the signature for signed tokens yet.

Thursday, 8 May 2014

Boolean values in an if statement

Last week I wrote a post about casting in if statements, focusing on the difference between equality and identity.  Well that post started out in my mind as being about boolean values, but then I found I needed to cover that ground first.

Boolean is a rather interesting datatype in Uniface.  The description in the manuals goes like this....

The Boolean data type is interpreted as either TRUE or FALSE. An empty value, and the values 0, F, f, N, and n are interpreted as FALSE. All other values are interpreted as TRUE. 

The Uniface default packing code for boolean stores the value as "T" or "F", but a number of different packing codes can be used...

  • B - Optimum DBMS Boolean default
  • B1 - ASCII Boolean (0 or 1)
  • B2 - ASCII Boolean (F or T) *Uniface default
  • B3 - ASCII Boolean (N or Y)
  • B4 - Binary Boolean (0 or 1)
  • B5 - Binary Boolean (0 or -1)

This means that it is always safest to reference a boolean in an if statement without a relational operator, like this...

    boolean b

  if ( b )
    putmess "b is True"
  if ( !b )
    putmess "b is False"


As empty values are interpreted as false, this example would output "b is False".

This is fine, as long as you know that you've got a boolean datatype.  However, I've seen developers get into trouble when they use this type of if statement (without a relational operator) but with other datatypes.

For example, to check a blank string in javascript, you could easily do something like this...

  var s = "";
  if(!s) {
    alert "s is blank";


If you translate this directly into Uniface, then you get something like this...

    string s

  s = ""
  if ( !s )
    putmess "s is blank"


And this works, no problem here.  Empty string is interpreted as false and you get the same result.

Consider this though; what if s is set to "0"?  

In javascript, we would not get the alert - this is because the string is not blank, so quite right!  However, in Uniface we do get the message saying that s is blank, even though it's not.  This is because "0" is interpreted as false.

Summary: In an if statement, if you've got a boolean datatype then don't use a relational operator, but if you've got any other datatype then do use one.