Skip to content

Commit b3f71e8

Browse files
authored
Merge pull request #19 from jschlyter/keyconv
Add simple key conversion tool
2 parents 2062fca + 36245de commit b3f71e8

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
entry_points={
5050
"console_scripts": [
5151
"jwkgen = cryptojwt.tools.keygen:main",
52+
"jwkconv = cryptojwt.tools.keyconv:main",
5253
]
5354
}
5455
)

src/cryptojwt/tools/keyconv.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env python3
2+
3+
"""Convert JWK from/to PEM and other formats"""
4+
5+
import argparse
6+
import json
7+
from binascii import hexlify
8+
from getpass import getpass
9+
from typing import Optional
10+
11+
from cryptography.hazmat.primitives import serialization
12+
from cryptojwt.jwk import JWK
13+
from cryptojwt.jwk.ec import (ECKey, import_private_key_from_file,
14+
import_public_key_from_file)
15+
from cryptojwt.jwk.hmac import SYMKey
16+
from cryptojwt.jwk.rsa import (RSAKey, import_private_rsa_key_from_file,
17+
import_public_rsa_key_from_file)
18+
from cryptojwt.jwx import key_from_jwk_dict
19+
20+
21+
def jwk_from_file(filename: str, private: bool = True) -> JWK:
22+
"""Read JWK from file"""
23+
with open(filename, mode='rt') as input_file:
24+
jwk_dict = json.loads(input_file.read())
25+
return key_from_jwk_dict(jwk_dict, private=private)
26+
27+
28+
def pem2rsa(filename: str, kid: str = None, private: bool = False, passphrase: str = None) -> JWK:
29+
"""Convert RSA key from PEM to JWK"""
30+
if private:
31+
key = import_private_rsa_key_from_file(filename, passphrase)
32+
else:
33+
key = import_public_rsa_key_from_file(filename)
34+
jwk = RSAKey(kid=kid)
35+
jwk.load_key(key)
36+
return jwk
37+
38+
39+
def pem2ec(filename: str, kid: str = None, private: bool = False, passphrase: str = None) -> JWK:
40+
"""Convert EC key from PEM to JWK"""
41+
if private:
42+
key = import_private_key_from_file(filename, passphrase)
43+
else:
44+
key = import_public_key_from_file(filename)
45+
jwk = ECKey(kid=kid)
46+
jwk.load_key(key)
47+
return jwk
48+
49+
50+
def bin2jwk(filename: str, kid: str) -> bytes:
51+
"""Read raw key from filename and return JWK"""
52+
with open(filename, 'rb') as file:
53+
content = file.read()
54+
return SYMKey(kid=kid, key=content)
55+
56+
57+
def pem2jwk(filename: str, kid: str, private: bool = False) -> JWK:
58+
"""Read PEM from filename and return JWK"""
59+
with open(filename, 'rt') as file:
60+
content = file.readlines()
61+
header = content[0]
62+
63+
if private:
64+
passphrase = getpass('Private key passphrase: ')
65+
if len(passphrase) == 0:
66+
passphrase = None
67+
else:
68+
passphrase = None
69+
70+
if 'BEGIN EC PRIVATE KEY' in header:
71+
jwk = pem2ec(filename, kid, private=True, passphrase=passphrase)
72+
elif 'BEGIN EC PUBLIC KEY' in header:
73+
jwk = pem2ec(filename, kid, private=False)
74+
elif 'BEGIN RSA PRIVATE KEY' in header:
75+
jwk = pem2rsa(filename, kid, private=True, passphrase=passphrase)
76+
elif 'BEGIN RSA PUBLIC KEY' in header:
77+
jwk = pem2rsa(filename, kid, private=False)
78+
else:
79+
raise ValueError("Unknown PEM format")
80+
81+
return jwk
82+
83+
84+
def export_jwk(jwk: JWK, private: bool = False) -> bytes:
85+
"""Export JWK as PEM/bin"""
86+
87+
if jwk.kty == 'oct':
88+
return jwk.key
89+
90+
if private:
91+
passphrase = getpass('Private key passphrase: ')
92+
if passphrase:
93+
enc = serialization.BestAvailableEncryption(passphrase.encode())
94+
else:
95+
enc = serialization.NoEncryption
96+
serialized = jwk.priv_key.private_bytes(
97+
encoding=serialization.Encoding.PEM,
98+
format=serialization.PrivateFormat.PKCS8,
99+
encryption_algorithm=enc)
100+
else:
101+
serialized = jwk.pub_key.public_bytes(
102+
encoding=serialization.Encoding.PEM,
103+
format=serialization.PublicFormat.SubjectPublicKeyInfo)
104+
105+
return serialized
106+
107+
108+
def output_jwk(jwk: JWK, private: bool = False, filename: Optional[str] = None) -> None:
109+
"""Output JWK to file"""
110+
serialized = jwk.serialize(private=private)
111+
if filename is not None:
112+
with open(filename, mode='wt') as file:
113+
file.write(json.dumps(serialized))
114+
else:
115+
print(json.dumps(serialized, indent=4))
116+
117+
118+
def output_bytes(data: bytes, binary: bool = False, filename: Optional[str] = None) -> None:
119+
"""Output data to file"""
120+
if filename is not None:
121+
with open(filename, mode='wb') as file:
122+
file.write(data)
123+
else:
124+
if binary:
125+
print(hexlify(data).decode())
126+
else:
127+
print(data.decode())
128+
129+
def main():
130+
""" Main function"""
131+
parser = argparse.ArgumentParser(description='JWK Conversion Utility')
132+
133+
parser.add_argument('--kid',
134+
dest='kid',
135+
metavar='key_id',
136+
help='Key ID')
137+
parser.add_argument('--private',
138+
dest='private',
139+
action='store_true',
140+
help="Output private key")
141+
parser.add_argument('--output',
142+
dest='output',
143+
metavar='filename',
144+
help='Output file name')
145+
parser.add_argument('filename', metavar='filename', nargs=1, help='filename')
146+
args = parser.parse_args()
147+
148+
f = args.filename[0]
149+
150+
if f.endswith('.json'):
151+
jwk = jwk_from_file(f, args.private)
152+
serialized = export_jwk(jwk, args.private)
153+
output_bytes(data=serialized, binary=(jwk.kty == 'oct'), filename=args.output)
154+
elif f.endswith('.bin'):
155+
jwk = bin2jwk(f, args.kid)
156+
output_jwk(jwk=jwk, private=True, filename=args.output)
157+
elif f.endswith('.pem'):
158+
jwk = pem2jwk(f, args.kid, args.private)
159+
output_jwk(jwk=jwk, private=args.private, filename=args.output)
160+
else:
161+
exit(-1)
162+
163+
164+
if __name__ == "__main__":
165+
main()

0 commit comments

Comments
 (0)