blob: 83f1c2e40325d282e1308a70bdf0bfe25e00c8f5 [file]
/* Copyright (C) 2024-2026 maClara, LLC <info@maclara-llc.com>
This file is part of the JWT C Library
SPDX-License-Identifier: MPL-2.0
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 <string.h>
#include <time.h>
#include <getopt.h>
#include <unistd.h>
#include <jwt.h>
#include "jwt-util.h"
/* The PEM/DER/HMAC -> JWK conversion now lives in libjwt as a public API
* (jwks_load_fromkey* and jwks_export), so this tool only uses the public
* interface and links the shared library like every other tool. */
static int with_kid = 1;
static int assume_hmac = 1;
_Noreturn static void usage(const char *error, int exit_state)
{
if (error)
fprintf(stderr, "ERROR: %s\n\n", error);
fprintf(stderr, "\
Usage: %s [OPTIONS] <FILE> [FILE]...\n\
\n\
Parse PEM/DER file(s) into JSON Web Key format.\n\
\n\
-h, --help This help information\n\
-q, --quiet No output other than JWKS file\n\
-l, --list List supported algorithms and exit\n\
-k, --disable-kid Disable generating \"kid\" attribute\n\
-m, --disable-hmac Disable fallback to HMAC\n\
-o, --output=FILE File to write JWKS to\n\
\n\
This program will parse PEM/DER key files (public and private) into JSON Web\n\
Keys and output a JWK Set. Note that HMAC keys are \"guessed\" based on them\n\
not being parsed as a PEM/DER key. This may cause some issues. You can disable\n\
this with the -m option.\n\
\n\
You can use '-' as the argument to the -o option to write to stdout.\n\
\n\
RSA keys will not have an algorithm set as they are valid for RS256, RS384,\n\
and RS512. RSA keys must be at least 1024 bits.\n\
\n\
RSA-PSS keys will be set to PS256, otherwise they will look no different\n\
than an RSA key.\n\
\n\
All keys will get a generated randomized uuidv4 \"kid\" attribute unless you\n\
use the -k option..\n", get_progname());
exit(exit_state);
}
/* Print the JWKS with a small libjwt.io: provenance header injected after the
* opening brace of the jwks_export() output. */
static void write_jwks(FILE *outfp, const char *keys_json)
{
char comment[256];
const char *brace;
time_t now;
char *time_str;
/* jwks_export() always produces an indented object starting with "{\n". */
brace = strchr(keys_json, '{');
if (brace == NULL || brace[1] != '\n') {
/* Should not happen, but degrade gracefully. */
fprintf(outfp, "%s\n", keys_json); // LCOV_EXCL_LINE
return; // LCOV_EXCL_LINE
}
/* Emit the opening brace and newline, then our metadata members. */
fputs("{\n", outfp);
fprintf(outfp, " \"libjwt.io:comment\": \"%s\",\n",
"Generated by LibJWT");
now = time(NULL);
time_str = ctime(&now);
time_str[strlen(time_str) - 1] = '\0';
fprintf(outfp, " \"libjwt.io:date\": \"%s\",\n", time_str);
#ifdef _WIN32
DWORD hostnamesize = sizeof(comment);
GetComputerNameA(comment, &hostnamesize);
#else
gethostname(comment, sizeof(comment));
#endif
comment[sizeof(comment) - 1] = '\0';
fprintf(outfp, " \"libjwt.io:hostname\": \"%s\",\n", comment);
/* The remainder of the export, starting after the "{\n" we replaced:
* the "keys" member and closing brace. */
fprintf(outfp, "%s\n", brace + 2);
}
int main(int argc, char **argv)
{
jwk_set_auto_t *jwk_set = NULL;
jwt_alg_t alg;
int quiet = 0;
char *jwk_str = NULL;
FILE *outfp = NULL;
FILE *msg = stdout;
unsigned int flags;
int i, oc;
char *optstr = "hlqo:km";
struct option opttbl[] = {
{ "help", no_argument, NULL, 'h' },
{ "list", no_argument, NULL, 'l' },
{ "quiet", no_argument, NULL, 'q' },
{ "output", required_argument, NULL, 'o' },
{ "disable-kid", no_argument, NULL, 'k' },
{ "disable-hmac", no_argument, NULL, 'm' },
{ NULL, 0, 0, 0 },
};
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 'q':
quiet = 1;
break;
case 'k':
with_kid = 0;
break;
case 'm':
assume_hmac = 0;
break;
case 'o':
if (optarg[0] == '-') {
outfp = stdout;
msg = stderr;
} else {
outfp = fopen(optarg, "wx");
if (outfp == NULL)
perror(optarg);
}
break;
default: /* '?' */
usage("Unknown option", EXIT_FAILURE);
break;
}
}
argc -= optind;
argv += optind;
if (argc == 0)
usage("No key(s) given", EXIT_FAILURE);
if (outfp == NULL)
usage("The --output argument is required", EXIT_FAILURE);
flags = JWK_KEY_NONE;
if (with_kid)
flags |= JWK_KEY_GEN_KID;
if (assume_hmac)
flags |= JWK_KEY_TRY_HMAC;
if (!quiet)
fprintf(msg, "Parsing %d files (", argc);
for (i = 0; i < argc; i++) {
jwk_set = jwks_load_fromkey_file(jwk_set, argv[i], flags);
if (jwk_set == NULL) {
fprintf(stderr, "Error parsing key file: %s\n", argv[i]);
exit(EXIT_FAILURE);
}
if (jwks_error(jwk_set)) {
fprintf(stderr, "%s: %s\n", argv[i],
jwks_error_msg(jwk_set));
exit(EXIT_FAILURE);
}
if (!quiet)
fprintf(msg, ".");
}
if (!quiet) {
fprintf(msg, ") done\n");
fprintf(msg, "Generating JWKS...\n");
}
jwk_str = jwks_export(jwk_set, 1);
if (jwk_str == NULL) {
fprintf(stderr, "Error generating JWKS output\n"); // LCOV_EXCL_LINE
exit(EXIT_FAILURE); // LCOV_EXCL_LINE
}
write_jwks(outfp, jwk_str);
free(jwk_str);
exit(EXIT_SUCCESS);
}