package com.unblau.javajwt;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unblau.javajwt.model.Args;
import com.unblau.javajwt.model.JWTClaims;
import com.unblau.javajwt.model.JWTHeader;
public class JWT
{
private static final Logger logger = Logger.getLogger("JWT");
/**
* @see <a href="http://commons.apache.org/proper/commons-codec/archives/1.9/apidocs/index.html">Apache Commons Codec 1.9 API</a>
*/
private Base64 decoder;
/**
* @see <a href="https://github.com/FasterXML/jackson">https://github.com/FasterXML/jackson</a>
*/
private ObjectMapper mapper;
/**
* @see <a href="http://download.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Mac">Java Standard Algorithm Name Documentation</a>
*/
private Map<String, String> algorithms;
public JWT()
{
decoder = new Base64(true);
mapper = new ObjectMapper();
algorithms = new HashMap<String, String>();
algorithms.put("none", "none");
algorithms.put("HS256", "HmacSHA256");
algorithms.put("HS384", "HmacSHA384");
algorithms.put("HS512", "HmacSHA512");
}
/**
* Performs JWT validation
*
* @param args JSON object with token, key, issuer and audience arguments
* @throws SignatureException when the signature is invalid
* @throws IllegalStateException when the token's structure isn't valid or expiration, issuer or audience are invalid
*/
@SuppressWarnings("static-access")
public void verify(Args args)
throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, JsonParseException, JsonMappingException,
IOException, SignatureException
{
// check number of segments
if ("".equals(args.getTok()))
throw new IllegalStateException("token not set");
String[] pieces = args.getTok().split("\\.");
if (pieces.length!=3 && pieces.length!=2)
throw new IllegalStateException("wrong number of segments: " + pieces.length);
// get JWTHeader JSON object. Extract algorithm
JWTHeader jwtHeader = (JWTHeader) decodeAndParse(pieces[0], JWTHeader.class);
if (jwtHeader.getAlg()==null)
throw new IllegalStateException("algorithm not set");
if (algorithms.get(jwtHeader.getAlg())==null)
throw new IllegalStateException("unsupported algorithm");
String algorithm = algorithms.get(jwtHeader.getAlg());
// get JWTClaims JSON object
JWTClaims jwtClaims = (JWTClaims) decodeAndParse(pieces[1], JWTClaims.class);
// check signature
if (!"none".equals(algorithm))
{
if (pieces.length!=3)
throw new IllegalStateException("wrong number of segments: " + pieces.length);
if (args.getKey()==null)
throw new IllegalStateException("key not set");
Mac hmac = Mac.getInstance(algorithm);
hmac.init(new SecretKeySpec(decoder.decodeBase64(args.getKey()), algorithm));
byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes());
if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2])))
throw new SignatureException("signature verification failed");
}
// additional JWTClaims checks
if (jwtClaims.getExp()!=0 && System.currentTimeMillis()/1000L >= jwtClaims.getExp())
throw new IllegalStateException("jwt expired");
if ((jwtClaims.getIss()!=null && (args.getIss()==null || !args.getIss().equals(jwtClaims.getIss()))) ||
(jwtClaims.getIss()==null && args.getIss()!=null)) throw new IllegalStateException("jwt issuer invalid");
if ((jwtClaims.getAud()!=null && (args.getAud()==null || args!=null && !args.getAud().equals(jwtClaims.getAud()))) ||
(jwtClaims.getAud()==null && args.getAud()!=null)) throw new IllegalStateException("jwt audience invalid");
}
@SuppressWarnings({ "static-access", "unchecked", "rawtypes" })
private Object decodeAndParse(String b64String, Class jsonClass) throws JsonParseException, JsonMappingException, IOException
{
String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8");
Object obj = mapper.readValue(jsonString, jsonClass);
logger.info(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
return obj;
}
/*@SuppressWarnings("static-access")
public void getJWTExample(String secretKey) throws NoSuchAlgorithmException, InvalidKeyException
{
String header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
//String claims = "{\"email\":\"root@1blau.com\",\"given_name\":\"Domain\",\"family_name\":\"Root\",\"iss\":\"https://1blau.auth0.com/\",\"aud\":\"MaiIcH7U4hhj3MbGbK3kwtQEbZtWQXvt\"}";
String claims = "{\"email\":\"lluis@superumm.com\",\"given_name\":\"Lluís\",\"family_name\":\"Faja\",\"iss\":\"https://superumm.auth0.com/\",\"aud\":\"7tgUhwIoDK8Pw1oSIqnY8qRgPstINbZy\"}";
String headerEncoded = decoder.encodeBase64URLSafeString(header.getBytes());
String claimsEncoded = decoder.encodeBase64URLSafeString(claims.getBytes());
Mac hmac = Mac.getInstance(algorithms.get("HS256"));
hmac.init(new SecretKeySpec(decoder.decodeBase64(secretKey), algorithms.get("HS256")));
byte[] sig = hmac.doFinal(new StringBuilder(headerEncoded).append(".").append(claimsEncoded).toString().getBytes());
logger.info("JWT=" + headerEncoded + "." + claimsEncoded + "." + decoder.encodeBase64URLSafeString(sig));
}*/
}