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