Skip to content

Commit 6f8dbb1

Browse files
authored
Merge pull request #35 from anange/import-EC-keys-from-pem
Support loading EC keys from pem in KeyBundle
2 parents 45b6878 + 4da5162 commit 6f8dbb1

File tree

5 files changed

+92
-19
lines changed

5 files changed

+92
-19
lines changed

src/cryptojwt/key_bundle.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .exception import UnknownKeyType
1515
from .exception import UpdateFailed
1616
from .jwk.ec import ECKey
17+
from .jwk.ec import import_private_key_from_file
1718
from .jwk.ec import new_ec_key
1819
from .jwk.hmac import SYMKey
1920
from .jwk.jwk import dump_jwk
@@ -134,14 +135,15 @@ def ec_init(spec):
134135
135136
:return: A KeyBundle instance
136137
"""
138+
curve = spec.get("crv", "P-256")
137139

138140
_kb = KeyBundle(keytype="EC")
139141
if 'use' in spec:
140142
for use in spec["use"]:
141-
eck = new_ec_key(crv=spec['crv'], use=use)
143+
eck = new_ec_key(crv=curve, use=use)
142144
_kb.append(eck)
143145
else:
144-
eck = new_ec_key(crv=spec['crv'])
146+
eck = new_ec_key(crv=curve)
145147
_kb.append(eck)
146148

147149
return _kb
@@ -167,7 +169,7 @@ def __init__(self, keys=None, source="", cache_time=300, verify_ssl=True,
167169
:param verify_ssl: Verify the SSL cert used by the server
168170
:param fileformat: For a local file either "jwks" or "der"
169171
:param keytype: Iff local file and 'der' format what kind of key it is.
170-
presently only 'rsa' is supported.
172+
presently 'rsa' and 'ec' are supported.
171173
:param keyusage: What the key loaded from file should be used for.
172174
Only applicable for DER files
173175
:param httpc: A HTTP client function
@@ -229,7 +231,7 @@ def _set_source(self, source, fileformat):
229231
def _do_local(self, kid):
230232
if self.fileformat in ['jwks', "jwk"]:
231233
self.do_local_jwk(self.source)
232-
elif self.fileformat == "der": # Only valid for RSA keys
234+
elif self.fileformat == "der":
233235
self.do_local_der(self.source, self.keytype, self.keyusage, kid)
234236

235237
def do_keys(self, keys):
@@ -285,12 +287,16 @@ def do_local_der(self, filename, keytype, keyusage=None, kid=''):
285287
Load a DER encoded file amd create a key from it.
286288
287289
:param filename: Name of the file
288-
:param keytype: Presently only 'rsa' supported
290+
:param keytype: Presently 'rsa' and 'ec' supported
289291
:param keyusage: encryption ('enc') or signing ('sig') or both
290292
"""
291-
_bkey = import_private_rsa_key_from_file(filename)
292-
293-
if keytype.lower() != 'rsa':
293+
if keytype.lower() == 'rsa':
294+
_bkey = import_private_rsa_key_from_file(filename)
295+
_key = RSAKey().load_key(_bkey)
296+
elif keytype.lower() == 'ec':
297+
_bkey = import_private_key_from_file(filename)
298+
_key = ECKey().load_key(_bkey)
299+
else:
294300
raise NotImplementedError('No support for DER decoding of that key type')
295301

296302
if not keyusage:
@@ -299,7 +305,6 @@ def do_local_der(self, filename, keytype, keyusage=None, kid=''):
299305
keyusage = harmonize_usage(keyusage)
300306

301307
for use in keyusage:
302-
_key = RSAKey().load_key(_bkey)
303308
_key.use = use
304309
if kid:
305310
_key.kid = kid
@@ -632,21 +637,25 @@ def difference(self, bundle):
632637
return [k for k in self._keys if k not in bundle]
633638

634639

635-
def keybundle_from_local_file(filename, typ, usage):
640+
def keybundle_from_local_file(filename, typ, usage, keytype="RSA"):
636641
"""
637642
Create a KeyBundle based on the content in a local file.
638643
639644
:param filename: Name of the file
640645
:param typ: Type of content
641646
:param usage: What the key should be used for
647+
:param keytype: Type of key, e.g. "RSA", "EC". Only used with typ='der'
642648
:return: The created KeyBundle
643649
"""
644650
usage = harmonize_usage(usage)
645651

646652
if typ.lower() == "jwks":
647653
_bundle = KeyBundle(source=filename, fileformat="jwks", keyusage=usage)
648-
elif typ.lower() == 'der':
649-
_bundle = KeyBundle(source=filename, fileformat="der", keyusage=usage)
654+
elif typ.lower() == "der":
655+
_bundle = KeyBundle(source=filename,
656+
fileformat="der",
657+
keyusage=usage,
658+
keytype=keytype)
650659
else:
651660
raise UnknownKeyType("Unsupported key type")
652661

@@ -713,8 +722,8 @@ def build_key_bundle(key_conf, kid_template=""):
713722
The type of key. Presently only 'rsa', 'ec' and 'oct' supported.
714723
715724
key
716-
A name of a file where a key can be found. Only works with PEM encoded
717-
RSA keys
725+
A name of a file where a key can be found. Works with PEM encoded
726+
RSA and EC private keys.
718727
719728
use
720729
What the key should be used for
@@ -752,7 +761,17 @@ def build_key_bundle(key_conf, kid_template=""):
752761
else:
753762
_bundle = rsa_init(spec)
754763
elif typ == "EC":
755-
_bundle = ec_init(spec)
764+
if "key" in spec and spec["key"]:
765+
error_to_catch = (OSError, IOError,
766+
DeSerializationNotPossible)
767+
try:
768+
_bundle = KeyBundle(source="file://%s" % spec["key"],
769+
fileformat="der",
770+
keytype=typ, keyusage=spec["use"])
771+
except error_to_catch:
772+
_bundle = ec_init(spec)
773+
else:
774+
_bundle = ec_init(spec)
756775
elif typ.upper() == "OCT":
757776
_bundle = sym_init(spec)
758777
else:

src/cryptojwt/key_jar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,8 @@ def build_keyjar(key_conf, kid_template="", keyjar=None, owner=''):
672672
The type of key. Presently only 'rsa', 'oct' and 'ec' supported.
673673
674674
key
675-
A name of a file where a key can be found. Only works with PEM encoded
676-
RSA keys
675+
A name of a file where a key can be found. Works with PEM encoded
676+
RSA and EC private keys.
677677
678678
use
679679
What the key should be used for

tests/test_03_key_bundle.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import responses
1010
from cryptography.hazmat.primitives.asymmetric import rsa
1111
from cryptojwt.jwk.ec import new_ec_key
12+
from cryptojwt.jwk.ec import ECKey
1213
from cryptojwt.jwk.hmac import SYMKey
1314
from cryptojwt.jwk.rsa import RSAKey
1415
from cryptojwt.jwk.rsa import import_rsa_key_from_cert_file
@@ -39,6 +40,7 @@ def full_path(local_file):
3940

4041
RSAKEY = os.path.join(BASE_PATH, "cert.key")
4142
RSA0 = os.path.join(BASE_PATH, "rsa.key")
43+
EC0 = os.path.join(BASE_PATH, 'ec.key')
4244
CERT = full_path("cert.pem")
4345

4446
JWK0 = {"keys": [
@@ -319,17 +321,27 @@ def test_get_all():
319321

320322
def test_keybundle_from_local_der():
321323
kb = keybundle_from_local_file(
322-
"{}".format(os.path.join(BASE_PATH, 'rsa.key')),
324+
"{}".format(RSA0),
323325
"der", ['enc'])
324326
assert len(kb) == 1
325327
keys = kb.get('rsa')
326328
assert len(keys) == 1
327329
assert isinstance(keys[0], RSAKey)
328330

329331

332+
def test_ec_keybundle_from_local_der():
333+
kb = keybundle_from_local_file(
334+
"{}".format(EC0),
335+
"der", ['enc'], keytype='EC')
336+
assert len(kb) == 1
337+
keys = kb.get('ec')
338+
assert len(keys) == 1
339+
assert isinstance(keys[0], ECKey)
340+
341+
330342
def test_keybundle_from_local_der_update():
331343
kb = keybundle_from_local_file(
332-
"file://{}".format(os.path.join(BASE_PATH, 'rsa.key')),
344+
"file://{}".format(RSA0),
333345
"der", ['enc'])
334346
assert len(kb) == 1
335347
keys = kb.get('rsa')

tests/test_04_key_jar.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"test_keys"))
2424
RSAKEY = os.path.join(BASE_PATH, "cert.key")
2525
RSA0 = os.path.join(BASE_PATH, "rsa.key")
26+
EC0 = os.path.join(BASE_PATH, "ec.key")
2627
BASEDIR = os.path.abspath(os.path.dirname(__file__))
2728

2829

@@ -238,6 +239,42 @@ def test_build_keyjar_missing(tmpdir):
238239
assert len(key_jar[""]) == 1
239240

240241

242+
def test_build_RSA_keyjar_from_file(tmpdir):
243+
keys = [
244+
{
245+
"type": "RSA", "key": RSA0,
246+
"use": ["enc", "sig"]
247+
}]
248+
249+
key_jar = build_keyjar(keys)
250+
251+
assert len(key_jar[""]) == 1
252+
253+
254+
def test_build_EC_keyjar_missing(tmpdir):
255+
keys = [
256+
{
257+
"type": "EC", "key": os.path.join(tmpdir.dirname, "missing_file"),
258+
"use": ["enc", "sig"]
259+
}]
260+
261+
key_jar = build_keyjar(keys)
262+
263+
assert len(key_jar[""]) == 1
264+
265+
266+
def test_build_EC_keyjar_from_file(tmpdir):
267+
keys = [
268+
{
269+
"type": "EC", "key": EC0,
270+
"use": ["enc", "sig"]
271+
}]
272+
273+
key_jar = build_keyjar(keys)
274+
275+
assert len(key_jar[""]) == 1
276+
277+
241278
class TestKeyJar(object):
242279
def test_keyjar_add(self):
243280
kj = KeyJar()

tests/test_keys/ec.key

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIATa+jC26Vl3hw5oVDpiufCZuUOnvHdlaxW2VusNGKGqoAoGCCqGSM49
3+
AwEHoUQDQgAEoLqcipC3uBcQA7AfzSI5A9GrEpLl1fJsBPjukveTkOkL2Z5BI4Ja
4+
5eByAQku71nVtQQhZ9tnP2BR9IrBRK/KpQ==
5+
-----END EC PRIVATE KEY-----

0 commit comments

Comments
 (0)