com.google.examples.JwtTool.java Source code

Java tutorial

Introduction

Here is the source code for com.google.examples.JwtTool.java

Source

// JwtTool.java
// ------------------------------------------------------------------
//
// This tool uses the Nimbus library to parse or generate a JWT.
//
// Copyright 2016-2018 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package com.google.examples;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONStyleIdent;
import net.minidev.json.JSONValue;
import org.apache.commons.lang3.time.DurationFormatUtils;

public class JwtTool extends JOSEToolBase {
    private final static int DEFAULT_EXPIRY_IN_SECONDS = 300;
    private final static String optString = "VGvt:c:H:k:P:s:x:i:I:A:hT"; // getopt style

    public JwtTool(String[] args) throws java.lang.Exception {
        super(args, optString);
    }

    public static class TimeResolver {
        private final static Pattern expiryPattern = Pattern.compile("^([1-9][0-9]*)(ms|s|m|h|d|w|)$",
                Pattern.CASE_INSENSITIVE);
        private final static Map<String, Long> timeMultipliers;
        static {
            Map<String, Long> m1 = new HashMap<String, Long>();
            m1.put("s", 1L);
            m1.put("m", 60L);
            m1.put("h", 60L * 60);
            m1.put("d", 60L * 60 * 24);
            m1.put("w", 60L * 60 * 24 * 7);
            //m1.put("y", 60*60*24*365*1000);
            timeMultipliers = m1;
        }
        private final static String defaultUnit = "s";

        public static Date getExpiryDate(String expiresInString) {
            Calendar cal = Calendar.getInstance();
            Long milliseconds = resolveExpression(expiresInString);
            Long seconds = milliseconds / 1000;
            int secondsToAdd = seconds.intValue();
            if (secondsToAdd <= 0)
                return null; /* no expiry */
            cal.add(Calendar.SECOND, secondsToAdd);
            Date then = cal.getTime();
            return then;
        }

        /*
         * convert a simple timespan string, expressed in days, hours, minutes, or
         * seconds, such as 30d, 12d, 8h, 24h, 45m, 30s, into a numeric quantity in
         * seconds. Default TimeUnit is ms. Eg. 30 is treated as 30ms.
         */
        public static Long resolveExpression(String subject) {
            Matcher m = expiryPattern.matcher(subject);
            if (m.find()) {
                String key = m.group(2);
                if (key.equals(""))
                    key = defaultUnit;
                return Long.parseLong(m.group(1), 10) * timeMultipliers.get(key);
            }
            return -1L;
        }
    }

    private Date getExpiryDate() {
        Date current = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(current);

        int lifetimeInSeconds = DEFAULT_EXPIRY_IN_SECONDS;
        String expiry = (String) this.options.get("x");
        if (expiry != null) {
            lifetimeInSeconds = TimeResolver.resolveExpression(expiry).intValue();
        }
        cal.add(Calendar.SECOND, lifetimeInSeconds);
        Date then = cal.getTime();
        return then;
    }

    public void run() throws Exception {
        JSONObject json;
        maybeShowOptions();
        determineAction();
        if (toolAction == ToolAction.NONE || toolAction == ToolAction.HELP) {
            usage();
            return;
        }
        String alg = (String) this.options.get("A");
        if (toolAction == ToolAction.VERIFY) {
            String token = (String) this.options.get("t");

            SignedJWT signedJwt = SignedJWT.parse(token);
            JWSHeader header = signedJwt.getHeader();
            json = header.toJSONObject();
            String prettyJson = json.toString(new JSONStyleIdent());
            System.out.println("header: " + prettyJson.replaceAll("\\\\/", "/"));

            JWTClaimsSet claims = signedJwt.getJWTClaimsSet();
            json = claims.toJSONObject();

            //System.out.println("payload: " + json.toString());
            prettyJson = json.toString(new JSONStyleIdent());
            System.out.println("payload: " + prettyJson.replaceAll("\\\\/", "/"));

            JWSVerifier verifier = getVerifier(header);

            if (verifier != null) {
                if (!signedJwt.verify(verifier)) {
                    System.out.printf("ERROR: The signature cannot be verified.\n");
                } else {
                    System.out.printf("INFO: The signature was verified.\n");
                }
                if (alg != null) {
                    JWSAlgorithm jwsAlg = new JWSAlgorithm(alg);

                    if (jwsAlg != null) {
                        if (!header.getAlgorithm().toString().equals(jwsAlg.toString())) {
                            System.out.printf("INFO: Algorithm mismatch. (expected %s, actual %s)\n",
                                    jwsAlg.toString(), header.getAlgorithm().toString());
                        }
                    }
                }
            }

            Date now = new Date();
            Date t1 = claims.getIssueTime();
            long ms;
            if (t1 != null) {
                ms = now.getTime() - t1.getTime();
                if (ms < 0L) {
                    System.out.printf("WARNING: The JWT issued-at time is invalid (in the future).\n");
                }
            } else {
                System.out.printf("INFO: The JWT has no issued-at time.\n");
            }

            Date t2 = claims.getExpirationTime();
            if (t2 != null) {
                ms = t2.getTime() - now.getTime(); // positive means still valid
                if (ms < 0L) {
                    System.out.printf("INFO: The JWT has expired.\n");
                } else {
                    long secsRemaining = ms / 1000;
                    System.out.printf("INFO: The JWT is valid for %d more seconds.\n", secsRemaining);
                    System.out.printf("INFO: which is %s\n",
                            DurationFormatUtils.formatDurationWords(ms, true, true));
                }
            } else {
                System.out.printf("INFO: The JWT has no expiry time.\n");
            }

            Date t3 = claims.getNotBeforeTime(); // optional
            if (t3 != null) {
                ms = now.getTime() - t3.getTime(); // positive means valid
                if (ms < 0L) {
                    System.out.printf("ERROR: The JWT is not yet valid.\n");
                }
            }
        }

        else if (toolAction == ToolAction.GENERATE) {
            if (alg == null)
                throw new IllegalStateException("Missing algorithm");
            JWSAlgorithm jwsAlg = new JWSAlgorithm(alg);

            String claimsJson = (String) this.options.get("c");
            if (claimsJson == null)
                throw new IllegalStateException("Missing claims payload");

            json = (JSONObject) JSONValue.parseWithException(claimsJson);
            JWTClaimsSet initialClaims = JWTClaimsSet.parse(json);
            JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder(initialClaims);

            // The passed-in claimset needs to include issuer, subject, and audience if desired.
            // also, any other claims, either standard or custom.

            if (this.options.get("T") == null)
                claimsBuilder.issueTime(new Date());

            if (this.options.get("x") != null)
                claimsBuilder.expirationTime(getExpiryDate());

            String jti = (String) this.options.get("I");
            if (jti != null)
                claimsBuilder.jwtID(jti);

            JWSSigner signer = getSigner(jwsAlg);

            JWSHeader.Builder headerBuilder = null;
            if (this.options.get("H") != null) {
                String headerJson = (String) this.options.get("H");
                json = (JSONObject) JSONValue.parseWithException(headerJson);
                if (!json.containsKey("alg")) {
                    json.put("alg", alg);
                }
                com.nimbusds.jose.JWSHeader initialHeader = com.nimbusds.jose.JWSHeader.parse(json);
                headerBuilder = new JWSHeader.Builder(initialHeader);
            } else {
                headerBuilder = new JWSHeader.Builder(jwsAlg).type(TYP_JWT);
            }

            String keyId = (String) this.options.get("i");
            if (keyId != null)
                headerBuilder.keyID(keyId);

            SignedJWT signedJWT = new SignedJWT(headerBuilder.build(), claimsBuilder.build());
            signedJWT.sign(signer);

            String jwt = signedJWT.serialize();
            System.out.println(jwt);
        }
    }

    public static void usage() {
        System.out.println(
                "\nJwtTool: decode and verify a JWT that was signed with HMAC, RSASSA-PKCS1, RSASSA-PSS, ECDSA,\nor encode and sign (generate) a JWT with any of those algorithms.\n");
        System.out.printf("Verify:\n    java %s -V ...options...\n", JwtTool.class.getName());
        System.out.println("  options:");
        System.out.println("     -v                   verbose");
        System.out.println("     -t <token>           required. the JWT to parse");
        System.out.println(
                "     -A <alg>             optional. Algorithm. Use {RS,HS,ES,PS}{256,384,512}. Default: RS256.");
        System.out.println(
                "     -k <publickeyfile>   optional. the PEM file containing the public key. For RS*, PS*, or ES*.\n");
        System.out.println("     -s <secret>          optional. the shared secret. For HS* verification.\n");

        System.out.printf("Generate:\n    java %s -G ...options...\n", JwtTool.class.getName());
        System.out.println("  options:");
        System.out.println("     -v                   verbose");
        System.out.println(
                "     -c <claimsjson>      optional. a json hash to include as claims in the payload of generated JWT.");
        System.out.println(
                "                          If you want iss, aud, sub, etc, you must place them within this json.");
        System.out.println(
                "     -H <headerjson>      optional. a json hash to include as claims in the header of the generated JWT.");
        System.out.println("                          If you want crit, cty, etc.. place them within this json.");
        System.out.println("     -i <keyid>           optional. The keyID (kid) to include in the JWT header.");
        System.out.println("     -I <jti>             optional. The JWT ID (jti) to include in the payload.");
        System.out.println(
                "     -x <expiry>          optional. Expiry. Use 10s, 10m, 10h, 10d... for 10 seconds, minutes, hours, days..");
        System.out.println("     -T                   optional. Omit the issued-at time from the generated token.");
        System.out.println("     -A <alg>             required. Algorithm. Use {RS,HS,ES,PS}{256,384,512}.");
        System.out.println(
                "     -k <privkeyfile>     optional. the PEM file containing the private key. For RS*, PS*, or ES*.\n");
        System.out.println(
                "     -P <privkeypwd>      optional. the password for an encrypted private key PEM file.\n");
        System.out.println("     -s <secret>          optional. the shared secret. For HS* signing.\n");

    }

    public static void main(String[] args) {
        try {
            JwtTool me = new JwtTool(args);
            me.run();
        } catch (java.lang.Exception exc1) {
            System.out.println("Exception:" + exc1.toString());
            exc1.printStackTrace();
            usage();
        }
    }

}