3 # Copyright © 2022 Nick Bowler
5 # Tool to convert certbot's account keys in JWK format to X.509 PEM format.
7 # License WTFPL2: Do What The Fuck You Want To Public License, version 2.
8 # This is free software: you are free to do what the fuck you want to.
9 # There is NO WARRANTY, to the extent permitted by law.
21 parser = argparse.ArgumentParser(
22 description='Convert RSA private keys in JWK format to PKCS#1 PEM format'
25 def errmsg(msg, prog=parser.prog):
26 print("%s: %s" % (prog, msg), file=sys.stderr)
28 class VersionAction(argparse.Action):
29 def __init__(self, **kw):
30 super().__init__(nargs=0, help="show a version message and then exit", **kw)
31 def __call__(self, parser, namespace, values, option_string=None):
32 print("%s %s" % (progname, version))
33 print('''Copyright © 2022 Nick Bowler
34 License WTFPL2: Do What The Fuck You Want To Public License, version 2.
35 This is free software: you are free to do what the fuck you want to.
36 There is NO WARRANTY, to the extent permitted by law.''')
38 parser.add_argument('--version', action=VersionAction)
39 parser.add_argument('input', metavar='FILE', nargs='?',
40 help='''read JWK data from FILE, instead of standard input.''')
41 parser.add_argument('-o', '--output', metavar='FILE',
42 help='''write PEM data to FILE, instead of standard output.''')
43 args = parser.parse_args()
48 infilename = args.input
49 infile = open(infilename, "r")
52 outfilename = "stdout"
54 outfilename = args.output
55 outfile = os.open(outfilename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o600)
57 data = json.load(infile)
58 if data['kty'] != "RSA":
59 errmsg("%s: input is not an RSA private key in JWK format")
62 rsa = subprocess.Popen(
63 [ "openssl", "rsa", "-inform", "DER", "-outform", "PEM", "-out", "-" ],
64 stdin=subprocess.PIPE, stdout=outfile, stderr=sys.stderr
67 asn1parse = subprocess.Popen(
68 [ "openssl", "asn1parse", "-genconf", "-", "-noout", "-out", "-" ],
69 stdin=subprocess.PIPE, stdout=rsa.stdin, stderr=sys.stderr
72 asn1parse.stdin.write(b"asn1=SEQUENCE:jwk\n[jwk]\nversion=INTEGER:0\n")
73 for param in ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']:
74 spec = "%s=INTEGER:0x%s\n" % (
75 param, base64.b64decode(data[param] + "===", '-_').hex()
77 asn1parse.stdin.write(spec.encode())
79 asn1parse.stdin.close()