From 0c65729bc117c5350cd8a602e388a765a41bce4e Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 1 Nov 2022 23:12:09 -0400 Subject: [PATCH] Add script to convert "JWK"-format RSA keys to normal. --- jwk2pem.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 jwk2pem.py diff --git a/jwk2pem.py b/jwk2pem.py new file mode 100755 index 0000000..733b2c7 --- /dev/null +++ b/jwk2pem.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# Copyright © 2022 Nick Bowler +# +# Tool to convert certbot's account keys in JWK format to X.509 PEM format. +# +# License WTFPL2: Do What The Fuck You Want To Public License, version 2. +# This is free software: you are free to do what the fuck you want to. +# There is NO WARRANTY, to the extent permitted by law. + +import argparse +import base64 +import json +import os +import subprocess +import sys + +progname = "jwk2pem" +version = "0" + +parser = argparse.ArgumentParser( + description='Convert RSA private keys in JWK format to PKCS#1 PEM format' +) + +def errmsg(msg, prog=parser.prog): + print("%s: %s" % (prog, msg), file=sys.stderr) + +class VersionAction(argparse.Action): + def __init__(self, **kw): + super().__init__(nargs=0, help="show a version message and then exit", **kw) + def __call__(self, parser, namespace, values, option_string=None): + print("%s %s" % (progname, version)) + print('''Copyright © 2022 Nick Bowler +License WTFPL2: Do What The Fuck You Want To Public License, version 2. +This is free software: you are free to do what the fuck you want to. +There is NO WARRANTY, to the extent permitted by law.''') + sys.exit(0) +parser.add_argument('--version', action=VersionAction) +parser.add_argument('input', metavar='FILE', nargs='?', + help='''read JWK data from FILE, instead of standard input.''') +parser.add_argument('-o', '--output', metavar='FILE', + help='''write PEM data to FILE, instead of standard output.''') +args = parser.parse_args() + +infile = sys.stdin +infilename = "stdin" +if args.input: + infilename = args.input + infile = open(infilename, "r") + +outfile = sys.stdout +outfilename = "stdout" +if args.output: + outfilename = args.output + outfile = os.open(outfilename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o600) + +data = json.load(infile) +if data['kty'] != "RSA": + errmsg("%s: input is not an RSA private key in JWK format") + sys.exit(1) + +rsa = subprocess.Popen( + [ "openssl", "rsa", "-inform", "DER", "-outform", "PEM", "-out", "-" ], + stdin=subprocess.PIPE, stdout=outfile, stderr=sys.stderr +) + +asn1parse = subprocess.Popen( + [ "openssl", "asn1parse", "-genconf", "-", "-noout", "-out", "-" ], + stdin=subprocess.PIPE, stdout=rsa.stdin, stderr=sys.stderr +) + +asn1parse.stdin.write(b"asn1=SEQUENCE:jwk\n[jwk]\nversion=INTEGER:0\n") +for param in ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']: + spec = "%s=INTEGER:0x%s\n" % ( + param, base64.b64decode(data[param] + "===", '-_').hex() + ) + asn1parse.stdin.write(spec.encode()) + +asn1parse.stdin.close() +asn1parse.wait() +rsa.wait() -- 2.43.0