#!/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()