]> git.draconx.ca Git - scripts.git/blob - jwk2pem.py
Add script to convert "JWK"-format RSA keys to normal.
[scripts.git] / jwk2pem.py
1 #!/usr/bin/env python
2 #
3 # Copyright © 2022 Nick Bowler
4 #
5 # Tool to convert certbot's account keys in JWK format to X.509 PEM format.
6 #
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.
10
11 import argparse
12 import base64
13 import json
14 import os
15 import subprocess
16 import sys
17
18 progname = "jwk2pem"
19 version = "0"
20
21 parser = argparse.ArgumentParser(
22     description='Convert RSA private keys in JWK format to PKCS#1 PEM format'
23 )
24
25 def errmsg(msg, prog=parser.prog):
26     print("%s: %s" % (prog, msg), file=sys.stderr)
27
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.''')
37         sys.exit(0)
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()
44
45 infile = sys.stdin
46 infilename = "stdin"
47 if args.input:
48     infilename = args.input
49     infile = open(infilename, "r")
50
51 outfile = sys.stdout
52 outfilename = "stdout"
53 if args.output:
54     outfilename = args.output
55     outfile = os.open(outfilename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o600)
56
57 data = json.load(infile)
58 if data['kty'] != "RSA":
59     errmsg("%s: input is not an RSA private key in JWK format")
60     sys.exit(1)
61
62 rsa = subprocess.Popen(
63     [ "openssl", "rsa", "-inform", "DER", "-outform", "PEM", "-out", "-" ],
64     stdin=subprocess.PIPE, stdout=outfile, stderr=sys.stderr
65 )
66
67 asn1parse = subprocess.Popen(
68     [ "openssl", "asn1parse", "-genconf", "-", "-noout", "-out", "-" ],
69     stdin=subprocess.PIPE, stdout=rsa.stdin, stderr=sys.stderr
70 )
71
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()
76     )
77     asn1parse.stdin.write(spec.encode())
78
79 asn1parse.stdin.close()
80 asn1parse.wait()
81 rsa.wait()