Skip to content

Commit 9238443

Browse files
committed
feat: import bytes/str keys without specified key type, #73
1 parent 2b630c1 commit 9238443

File tree

8 files changed

+76
-14
lines changed

8 files changed

+76
-14
lines changed

src/joserfc/_rfc7517/pem.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
from ..util import to_bytes
2424

2525

26-
def load_pem_key(raw: bytes, password: bytes | None = None) -> Any:
26+
def import_from_ssh_key(raw: bytes) -> Any:
27+
return load_ssh_public_key(raw, backend=default_backend())
28+
29+
30+
def import_from_pem_key(raw: bytes, password: bytes | None = None) -> Any:
2731
key: Any
2832

2933
if b"OPENSSH PRIVATE" in raw:
@@ -91,15 +95,15 @@ def dump_pem_key(
9195
class CryptographyBinding(NativeKeyBinding, metaclass=ABCMeta):
9296
key_type: str
9397
ssh_type: bytes
94-
cryptography_native_keys: Any
98+
_cryptography_key_types: Any
9599

96100
@classmethod
97101
def check_ssh_type(cls, value: bytes) -> bool:
98102
return value.startswith(cls.ssh_type)
99103

100104
@classmethod
101-
def check_cryptography_native_key(cls, native_key: Any) -> bool:
102-
return isinstance(native_key, cls.cryptography_native_keys)
105+
def check_cryptography_key(cls, native_key: Any) -> bool:
106+
return isinstance(native_key, cls._cryptography_key_types)
103107

104108
@classmethod
105109
def convert_raw_key_to_dict(cls, raw_key: Any, private: bool) -> DictKey:
@@ -118,13 +122,13 @@ def import_from_dict(cls, value: DictKey) -> Any:
118122
@classmethod
119123
def import_from_bytes(cls, value: bytes, password: Any | None = None) -> Any:
120124
if cls.check_ssh_type(value):
121-
return load_ssh_public_key(value, backend=default_backend())
125+
return import_from_ssh_key(value)
122126

123127
if password is not None:
124128
password = to_bytes(password)
125129

126-
key = load_pem_key(value, password)
127-
if not cls.check_cryptography_native_key(key):
130+
key = import_from_pem_key(value, password)
131+
if not cls.check_cryptography_key(key):
128132
raise InvalidKeyTypeError(f"Not a key of: '{cls.key_type}'")
129133
return key
130134

@@ -135,11 +139,13 @@ def as_bytes(
135139
private: bool | None = False,
136140
password: Any | None = None,
137141
) -> bytes:
142+
if private is None:
143+
private = key.is_private
144+
138145
if private:
139146
return dump_pem_key(key.private_key, encoding, private, password)
140-
elif private is False:
147+
else:
141148
return dump_pem_key(key.public_key, encoding, private, password)
142-
return dump_pem_key(key.raw_value, encoding, key.is_private, password)
143149

144150
@staticmethod
145151
@abstractmethod

src/joserfc/_rfc7518/ec_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
class ECBinding(CryptographyBinding):
3939
key_type = "EC"
4040
ssh_type = b"ecdsa-sha2-"
41-
cryptography_native_keys = (EllipticCurvePrivateKey, EllipticCurvePublicKey)
41+
_cryptography_key_types = (EllipticCurvePrivateKey, EllipticCurvePublicKey)
4242

4343
_dss_curves: dict[str, t.Type[EllipticCurve]] = {}
4444
_curves_dss: dict[str, str] = {}

src/joserfc/_rfc7518/rsa_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
class RSABinding(CryptographyBinding):
4242
key_type = "RSA"
4343
ssh_type = b"ssh-rsa"
44-
cryptography_native_keys = (RSAPrivateKey, RSAPublicKey)
44+
_cryptography_key_types = (RSAPrivateKey, RSAPublicKey)
4545

4646
@staticmethod
4747
def import_private_key(obj: RSADictKey) -> RSAPrivateKey:

src/joserfc/_rfc8037/okp_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
class OKPBinding(CryptographyBinding):
4949
key_type = "OKP"
5050
ssh_type = b"ssh-ed25519"
51-
cryptography_native_keys = (
51+
_cryptography_key_types = (
5252
Ed25519PublicKey,
5353
Ed25519PrivateKey,
5454
Ed448PublicKey,

src/joserfc/jwk.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from __future__ import annotations
22
import typing as t
3+
import warnings
34
from ._keys import (
45
JWKRegistry,
56
KeySet,
67
Key,
78
KeySetSerialization,
89
)
10+
from ._rfc7517.pem import import_from_pem_key, import_from_ssh_key
911
from ._rfc7517.types import AnyKey, DictKey, KeyParameters
1012
from ._rfc7518.oct_key import OctKey
1113
from ._rfc7518.rsa_key import RSAKey
@@ -14,7 +16,9 @@
1416
from ._rfc8812 import register_secp256k1
1517
from ._rfc7638 import calculate_thumbprint as thumbprint
1618
from ._rfc9278 import calculate_thumbprint_uri as thumbprint_uri
19+
from .errors import SecurityWarning, InvalidKeyTypeError
1720
from .registry import Header
21+
from .util import to_bytes
1822

1923

2024
__all__ = [
@@ -116,7 +120,7 @@ def import_key(data: AnyKey, key_type: t.Literal["OKP"], parameters: KeyParamete
116120

117121

118122
@t.overload
119-
def import_key(data: DictKey, key_type: None = None, parameters: KeyParameters | None = None) -> Key: ...
123+
def import_key(data: AnyKey, key_type: None = None, parameters: KeyParameters | None = None) -> Key: ...
120124

121125

122126
def import_key(
@@ -133,6 +137,28 @@ def import_key(
133137
:param parameters: extra key parameters
134138
:return: OctKey, RSAKey, ECKey, or OKPKey
135139
"""
140+
if isinstance(data, (str, bytes)) and key_type is None:
141+
warnings.warn("Using implicit key type is not recommended.", SecurityWarning)
142+
143+
value = to_bytes(data)
144+
ssh_types = tuple(
145+
cls.binding.ssh_type for cls in JWKRegistry.key_types.values() if hasattr(cls.binding, "ssh_type")
146+
)
147+
if value.startswith(ssh_types):
148+
try:
149+
raw_key = import_from_ssh_key(value)
150+
except ValueError:
151+
return OctKey.import_key(value, parameters)
152+
else:
153+
try:
154+
raw_key = import_from_pem_key(value)
155+
except ValueError:
156+
return OctKey.import_key(value, parameters)
157+
158+
for cls in JWKRegistry.key_types.values():
159+
if hasattr(cls.binding, "check_cryptography_key") and cls.binding.check_cryptography_key(raw_key):
160+
return cls(raw_key, data, parameters)
161+
raise InvalidKeyTypeError("Not a key of any supported type") # pragma: no cover
136162
return JWKRegistry.import_key(data, key_type, parameters)
137163

138164

tests/jwk/test_key_methods.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
from unittest import TestCase
22

33
from joserfc import jws
4-
from joserfc.jwk import guess_key, import_key, generate_key, thumbprint_uri
4+
from joserfc.jwk import (
5+
guess_key,
6+
import_key,
7+
generate_key,
8+
thumbprint_uri,
9+
)
510
from joserfc.jwk import KeySet, OctKey, RSAKey, ECKey, OKPKey
611
from joserfc.errors import (
712
UnsupportedKeyAlgorithmError,
@@ -11,6 +16,7 @@
1116
MissingKeyTypeError,
1217
InvalidKeyIdError,
1318
)
19+
from tests.keys import read_key
1420

1521

1622
class Guest:
@@ -170,3 +176,25 @@ def test_find_correct_key_with_alg(self):
170176
jws.serialize_compact({"alg": "HS256"}, "foo", key_set)
171177
# get by kid
172178
jws.serialize_compact({"alg": "HS256", "kid": key2.kid}, "foo", key_set)
179+
180+
def test_import_bytes_keys(self):
181+
# check by ssh types
182+
key = import_key(read_key("ssh-rsa-public.pem"))
183+
self.assertEqual(key.key_type, "RSA")
184+
key = import_key(read_key("ssh-ecdsa-public.pem"))
185+
self.assertEqual(key.key_type, "EC")
186+
key = import_key(read_key("ssh-ed25519-public.pem"))
187+
self.assertEqual(key.key_type, "OKP")
188+
key = import_key("ssh-rsa-oct")
189+
self.assertEqual(key.key_type, "oct")
190+
191+
# check by pem types
192+
key = import_key(read_key("rsa-openssl-public.pem"))
193+
self.assertEqual(key.key_type, "RSA")
194+
key = import_key(read_key("ec-p256-public.pem"))
195+
self.assertEqual(key.key_type, "EC")
196+
key = import_key(read_key("okp-ed448-public.pem"))
197+
self.assertEqual(key.key_type, "OKP")
198+
199+
key = import_key(b"oct-key")
200+
self.assertEqual(key.key_type, "oct")

tests/keys/ssh-ecdsa-public.pem

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTEVnhsZrRnFhOlXN68wUslJD6/Xo5N4TprdkyiG3UZYKN7tT8ot0ZLP7XOv3lusPd9+A128hRjYhKQIFHPCyY=

tests/keys/ssh-ed25519-public.pem

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILuDaT2DEQNFTGvcnOsDp+cvSppjVRPajco10eDh76LB

0 commit comments

Comments
 (0)