blob: 35a4eec3568e81e62bf0e82c2f7b4357532c354a [file]
/* Copyright (C) 2019 Jeremy Thien <jeremy.thien@gmail.com>
Copyright (C) 2024-2025 maClara, LLC <info@maclara-llc.com>
This file is part of the JWT C Library
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h>
#include <stdlib.h>
#include <jwt.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <libgen.h>
#include "jwt-util.h"
_Noreturn static void usage(const char *error, int exit_state)
{
if (error)
fprintf(stderr, "ERROR: %s\n\n", error);
fprintf(stderr, "\
Usage: %1$s [OPTIONS] <token> [token(s)]\n\
%1$s [OPTIONS] -\n\
\n\
Decode and (optionally) verify the signature for a JSON Web Token\n\
\n\
-h, --help This help information\n\
-l, --list List supported algorithms and exit\n\
-a, --algorithm=ALG JWT algorithm to use (e.g. ES256). Only needed if the key\n\
provided with -k does not have an \"alg\" attribute\n\
-p, --print=CMD When printing JSON, pipe through CMD\n\
-k, --key=FILE Filename containing a JSON Web Key\n\
-q, --quiet No output. Exit value is number of errors\n\
-v, --verbose Show decoded header and payload while verifying\n\
\nThis program will decode and validate each token on the command line.\n\
If - is given as the only argument to token, then tokens will be read\n\
from stdin, one per line.\n\
\n\
For the --print option, output will be piped to the command's stdin. This\n\
is useful if you wanted to use something like `jq -C` to colorize it or\n\
another program to validate it. The program will be called twice; once\n\
for the HEAD, and once for the PAYLOAD. A non-0 exit status will cause\n\
the verification to fail.\n\
\n\
If you need to convert a key to JWK (e.g. from PEM or DER format) see\n\
key2jwk(1).\n", get_progname());
exit(exit_state);
}
static void print_token_trunc(const char *str, size_t max_len)
{
size_t len = strlen(str);
if (len <= max_len) {
printf("%s\n", str);
} else {
size_t part_len = (max_len - 3) / 2;
printf("%.*s...%.*s\n", (int)part_len, str,
(int)part_len, str + len - part_len);
}
}
static int process_one(jwt_checker_t *checker, jwt_alg_t alg, const char *token,
int quiet)
{
int err = 0;
if (!quiet) {
printf("\n%s %s[TOK]\033[0m ", alg == JWT_ALG_NONE ?
"\xF0\x9F\x94\x93" : "\xF0\x9F\x94\x90",
alg == JWT_ALG_NONE ? "\033[0;93m" : "\033[0;92m");
print_token_trunc(token, 60);
}
if (jwt_checker_verify(checker, token)) {
if (!quiet) {
printf("\xF0\x9F\x91\x8E \033[0;91m[BAD]\033[0m %s\n",
jwt_checker_error_msg(checker));
}
err = 1;
} else if (!quiet) {
printf("\xF0\x9F\x91\x8D \033[0;92m[YES]\033[0m Verified\n");
}
return err;
}
int main(int argc, char *argv[])
{
jwt_checker_auto_t *checker = NULL;
const char *key_file = NULL;
jwt_alg_t alg = JWT_ALG_NONE;
jwk_set_auto_t *jwk_set = NULL;
const jwk_item_t *item = NULL;
int oc, err, verbose = 0;
int quiet = 0;
char *optstr = "hk:alvqp:";
struct option opttbl[] = {
{ "help", no_argument, NULL, 'h' },
{ "key", required_argument, NULL, 'k' },
{ "algorithm", required_argument, NULL, 'a' },
{ "print", required_argument, NULL, 'p' },
{ "list", no_argument, NULL, 'l' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ NULL, 0, 0, 0 },
};
checker = jwt_checker_new();
if (checker == NULL) {
fprintf(stderr, "Could not allocate checker context\n");
exit(EXIT_FAILURE);
}
while ((oc = getopt_long(argc, argv, optstr, opttbl, NULL)) != -1) {
switch (oc) {
case 'h':
usage(NULL, EXIT_SUCCESS);
case 'l':
printf("Algorithms supported:\n");
for (alg = JWT_ALG_NONE; alg < JWT_ALG_INVAL; alg++) {
const char *name = jwt_alg_str(alg);
/* An alg compiled out of this build (e.g.
* ML-DSA without WITH_ML_DSA) has no name. */
if (name != NULL)
printf(" %s\n", name);
}
exit(EXIT_SUCCESS);
break;
case 'p':
pipe_cmd = optarg;
break;
case 'q':
if (verbose)
usage("Using -q and -v makes no sense",
EXIT_FAILURE);
quiet = 1;
break;
case 'v':
if (quiet)
usage("Using -q and -v makes no sense",
EXIT_FAILURE);
verbose = 1;
break;
case 'k':
key_file = optarg;
break;
case 'a':
alg = jwt_str_alg(optarg);
if (alg >= JWT_ALG_INVAL) {
fprintf(stderr, "Unknown algorithm [%s]\nUse "
"-l to see a list of supported "
"algorithms)\n", optarg);
exit(EXIT_FAILURE);
}
break;
default: /* '?' */
usage("Unknown option", EXIT_FAILURE);
break;
}
}
argc -= optind;
argv += optind;
if (argc == 0)
usage("No token(s) given", EXIT_FAILURE);
if (key_file == NULL && alg != JWT_ALG_NONE)
usage("An algorithm other than 'none' requires a key",
EXIT_FAILURE);
/* Load JWK key */
if (key_file) {
jwk_set = jwks_create_fromfile(key_file);
if (jwk_set == NULL || jwks_error(jwk_set)) {
fprintf(stderr, "ERR: Could not read JWK: %s\n",
jwks_error_msg(jwk_set));
exit(EXIT_FAILURE);
}
/* Get the first key */
item = jwks_item_get(jwk_set, 0);
if (jwks_item_error(item)) {
fprintf(stderr, "ERR: Could not read JWK: %s\n",
jwks_item_error_msg(item));
exit(EXIT_FAILURE);
}
if (jwks_item_alg(item) == JWT_ALG_NONE &&
alg == JWT_ALG_NONE) {
usage("Key does not contain an \"alg\" attribute and no --alg given",
EXIT_FAILURE);
}
if (jwt_checker_setkey(checker, alg, item)) {
fprintf(stderr, "ERR Loading key: %s\n",
jwt_checker_error_msg(checker));
exit(EXIT_FAILURE);
}
}
if (verbose && jwt_checker_setcb(checker, __jwt_wcb, NULL)) {
fprintf(stderr, "ERR setting callback: %s\n",
jwt_checker_error_msg(checker));
exit(EXIT_FAILURE);
}
if (item && !quiet) {
printf("\xF0\x9F\x94\x91 \033[0;92m[KEY]\033[0m %s\n",
key_file);
}
if (!quiet)
printf("\xF0\x9F\x93\x83 ");
if (item && jwks_item_alg(item) != JWT_ALG_NONE) {
if (!quiet) {
printf("\033[0;92m[ALG]\033[0m %s (from key)",
jwt_alg_str(jwks_item_alg(item)));
}
alg = jwks_item_alg(item);
} else if (!quiet){
if (alg == JWT_ALG_NONE)
printf("\033[0;91m[ALG]\033[0m %s (from options)",
jwt_alg_str(alg));
else
printf("\033[0;92m[ALG]\033[0m %s (from options)",
jwt_alg_str(alg));
}
if (!quiet)
printf("\n");
err = 0;
if (!strcmp(argv[0], "-")) {
char token[BUFSIZ];
while (fgets(token, sizeof(token), stdin) != NULL) {
token[strcspn(token, "\n")] = '\0';
err += process_one(checker, alg, token, quiet);
}
} else {
for (oc = 0; oc < argc; oc++) {
const char *token = argv[oc];
err += process_one(checker, alg, token, quiet);
}
}
exit(err);
}