libjwt/tools/jwk2key.c
Ben Collins 546ce331b5
tools: Hopefully some capitability for win32
Signed-off-by: Ben Collins <bcollins@libjwt.io>
2025-02-13 07:31:06 -05:00

239 lines
5.1 KiB
C

/* Copyright (C) 2024-2025 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 <sys/stat.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <jwt.h>
#include "jwt-util.h"
static char *out_dir;
static int retry;
static void write_key_file(const jwk_item_t *item)
{
const char *pre, *name;
int priv = jwks_item_is_private(item);
char file_name[BUFSIZ];
char *ext = ".pem";
char bits[8];
FILE *fp;
int i;
if (jwks_item_error(item))
return;
switch (jwks_item_kty(item)) {
case JWK_KEY_TYPE_OCT:
pre = "oct";
ext = ".bin";
sprintf(bits, "%d", jwks_item_key_bits(item));
name = bits;
break;
case JWK_KEY_TYPE_EC:
pre = "ec";
name = jwks_item_curve(item);
break;
case JWK_KEY_TYPE_RSA:
sprintf(bits, "%d", jwks_item_key_bits(item));
name = bits;
switch (jwks_item_alg(item)) {
case JWT_ALG_PS256:
case JWT_ALG_PS384:
case JWT_ALG_PS512:
pre = "rsa_pss";
break;
default:
pre = "rsa";
}
break;
case JWK_KEY_TYPE_OKP:
pre = "eddsa";
name = "ED25519";
break;
default:
fprintf(stderr, "Unknown kty\n");
return;
}
if (jwks_item_kid(item) == NULL) {
snprintf(file_name, sizeof(file_name), "%s/%s_%s%s%s",
out_dir, pre, name, priv ? "" : "_pub", ext);
} else {
snprintf(file_name, sizeof(file_name), "%s/%s_%s_%s%s%s",
out_dir, pre, name, jwks_item_kid(item),
priv ? "" : "_pub", ext);
}
for (i = 0; i < 10; i++) {
char *p;
fp = fopen(file_name, "wx");
if (fp || !retry || errno != EEXIST || i >= 9)
break;
p = file_name + strlen(file_name) - strlen(ext);
if (i == 0) {
*p++ = '-';
*p++ = '1';
strcpy(p, ext);
} else {
p = p - 1;
*p = '1' + i;
}
}
if (fp == NULL) {
perror(file_name);
return;
}
if (jwks_item_kty(item) == JWK_KEY_TYPE_OCT) {
const unsigned char *buf;
size_t len;
jwks_item_key_oct(item, &buf, &len);
fwrite(buf, 1, len, fp);
} else {
fputs(jwks_item_pem(item), fp);
}
fclose(fp);
}
static int check_directory(const char *path)
{
struct stat info;
if (stat(path, &info) != 0)
return 0;
if (!(info.st_mode & S_IFDIR))
return 0;
if (access(path, R_OK | X_OK) != 0)
return 0;
return 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 JSON Web Key format and write out individual key files\n\
\n\
-h, --help This help information\n\
-r, --retry Retry if output file exists\n\
-d, --dir=DIR Directory to write key files to\n\
\n\
This program will parse a JSON Web Key or Set and write out the individual\n\
files to DIR (by default '.'). Output directory must exist. You should make\n\
sure the permissions on the output directory are such that they cannot be\n\
accessed by others.\n\
\n\
JWK files must be listed after any options. A '-' will be interpreted as\n\
stdin.\n\
\n\
All RSA key types will be written as plain RSA keys, including RSASSA-PSS\n\
keys, unless it has a PS256, PS384, or PS512 'alg' attribute.\n\
\n\
All keys are written in PKCS8 PEM format, except key type 'OCT', which is\n\
written as a binary file (.bin extension).\n\
\n\
By default, existing files will not be overwritten. If you use the --retry\n\
option, an attempt will be made to add -X to the file name, up to -9, in\n\
an attempt to create the file.\n", get_progname());
exit(exit_state);
}
int main(int argc, char **argv)
{
jwk_set_t *jwk_set = NULL;
const jwk_item_t *item;
int i, oc;
char *optstr = "hd:r";
struct option opttbl[] = {
{ "help", no_argument, NULL, 'h' },
{ "dir", required_argument, NULL, 'd' },
{ "retry", no_argument, NULL, 'r' },
{ NULL, 0, 0, 0 },
};
while ((oc = getopt_long(argc, argv, optstr, opttbl, NULL)) != -1) {
switch (oc) {
case 'h':
usage(NULL, EXIT_SUCCESS);
case 'd':
out_dir = optarg;
break;
case 'r':
retry = 1;
break;
default: /* '?' */
usage("Unknown option", EXIT_FAILURE);
break;
}
}
argc -= optind;
argv += optind;
if (argc == 0)
usage("No files to read", EXIT_FAILURE);
if (out_dir == NULL || out_dir[0] == '\0')
out_dir = ".";
if (!check_directory(out_dir)) {
fprintf(stderr, "%s: doesn't exist or no access\n",
out_dir);
}
jwk_set = jwks_create(NULL);
if (jwk_set == NULL) {
perror("Failed to create JWKS");
exit(EXIT_FAILURE);
}
for (i = 0; i < argc; i++) {
char *file = argv[i];
if (file[0] == '-' && file[1] == '\0')
jwks_load_fromfp(jwk_set, stdin);
else
jwks_load_fromfile(jwk_set, file);
if (jwks_error(jwk_set)) {
fprintf(stderr, "Error reading %s: %s\n", file,
jwks_error_msg(jwk_set));
}
}
for (i = 0; (item = jwks_item_get(jwk_set, i)); i++)
write_key_file(item);
exit(EXIT_SUCCESS);
}