Skip to content

Commit cf0ad49

Browse files
authored
fix: add "none" algorithm for JWS (#44)
1 parent 90140ed commit cf0ad49

File tree

5 files changed

+51
-25
lines changed

5 files changed

+51
-25
lines changed

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Unreleased
1717

1818
- Use "import as" to prioritize the modules for editors.
1919
- Added parameter ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` for ``jwt.decode``.
20+
- Added ``none`` algorithm for JWS.
2021

2122
**Breaking changes**:
2223

src/joserfc/errors.py

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class UnsupportedAlgorithmError(JoseError):
7272
error = "unsupported_algorithm"
7373

7474

75+
class MissingKeyError(JoseError):
76+
error = "missing_key"
77+
78+
7579
class UnsupportedHeaderError(JoseError):
7680
error = "unsupported_header"
7781

src/joserfc/jws.py

+22-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .rfc7518.jws_algs import JWS_ALGORITHMS
3535
from .rfc8037.jws_eddsa import EdDSA
3636
from .rfc8812 import ES256K
37-
from .errors import BadSignatureError
37+
from .errors import BadSignatureError, MissingKeyError
3838
from .jwk import Key, KeyFlexible, KeySet, guess_key
3939
from .util import to_bytes
4040
from .registry import Header
@@ -84,7 +84,7 @@ def register_algorithms() -> None:
8484
def serialize_compact(
8585
protected: Header,
8686
payload: bytes | str,
87-
private_key: KeyFlexible,
87+
private_key: KeyFlexible | None,
8888
algorithms: list[str] | None = None,
8989
registry: JWSRegistry | None = None,
9090
) -> str:
@@ -111,6 +111,15 @@ def serialize_compact(
111111
registry.check_header(protected)
112112
obj = CompactSignature(protected, to_bytes(payload))
113113
alg: JWSAlgModel = registry.get_alg(protected["alg"])
114+
115+
# "none" algorithm requires no key
116+
if alg.name == "none":
117+
out = sign_compact(obj, alg, None)
118+
return out.decode("utf-8")
119+
120+
if private_key is None:
121+
raise MissingKeyError()
122+
114123
key: Key = guess_key(private_key, obj, True)
115124
key.check_use("sig")
116125
alg.check_key_type(key)
@@ -121,7 +130,7 @@ def serialize_compact(
121130

122131
def validate_compact(
123132
obj: CompactSignature,
124-
public_key: KeyFlexible,
133+
public_key: KeyFlexible | None,
125134
algorithms: list[str] | None = None,
126135
registry: JWSRegistry | None = None,
127136
) -> bool:
@@ -138,16 +147,24 @@ def validate_compact(
138147

139148
headers = obj.headers()
140149
registry.check_header(headers)
150+
alg: JWSAlgModel = registry.get_alg(headers["alg"])
151+
152+
# "none" algorithm requires no key
153+
if headers["alg"] == "none":
154+
return verify_compact(obj, alg, None)
155+
156+
if public_key is None:
157+
raise MissingKeyError()
158+
141159
key: Key = guess_key(public_key, obj)
142160
key.check_use("sig")
143-
alg: JWSAlgModel = registry.get_alg(headers["alg"])
144161
alg.check_key_type(key)
145162
return verify_compact(obj, alg, key)
146163

147164

148165
def deserialize_compact(
149166
value: bytes | str,
150-
public_key: KeyFlexible,
167+
public_key: KeyFlexible | None,
151168
algorithms: list[str] | None = None,
152169
registry: JWSRegistry | None = None,
153170
) -> CompactSignature:

src/joserfc/rfc7518/jws_algs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def sign(self, msg: bytes, key: t.Any) -> bytes:
3535
return b""
3636

3737
def verify(self, msg: bytes, sig: bytes, key: t.Any) -> bool:
38-
return False
38+
return sig == b""
3939

4040

4141
class HMACAlgModel(JWSAlgModel):

tests/jws/test_errors.py

+23-19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from joserfc.registry import HeaderParameter
55
from joserfc.errors import (
66
BadSignatureError,
7+
MissingKeyError,
78
UnsupportedKeyUseError,
89
UnsupportedKeyAlgorithmError,
910
UnsupportedKeyOperationError,
@@ -17,27 +18,32 @@
1718

1819

1920
class TestJWSErrors(TestCase):
21+
key = OctKey.import_key("secret")
22+
2023
def test_without_alg(self):
21-
key = OctKey.import_key("secret")
22-
# missing alg
23-
self.assertRaises(MissingHeaderError, jws.serialize_compact, {"kid": "123"}, "i", key)
24+
self.assertRaises(MissingHeaderError, jws.serialize_compact, {"kid": "123"}, "i", self.key)
25+
26+
def test_without_key(self):
27+
self.assertRaises(MissingKeyError, jws.serialize_compact, {"alg": "HS256"}, "i", None)
2428

2529
def test_none_alg(self):
2630
header = {"alg": "none"}
27-
key = OctKey.import_key("secret")
28-
text = jws.serialize_compact(header, "i", key, algorithms=["none"])
29-
self.assertRaises(BadSignatureError, jws.deserialize_compact, text, key, algorithms=["none"])
31+
text = jws.serialize_compact(header, "i", None, algorithms=["none"])
32+
obj = jws.deserialize_compact(text, None, algorithms=["none"])
33+
self.assertEqual(obj.payload, b"i")
34+
# none alg has no signature
35+
text += 'aQ'
36+
self.assertRaises(BadSignatureError, jws.deserialize_compact, text, None, algorithms=["none"])
3037

3138
def test_header_invalid_type(self):
3239
# kid should be a string
3340
header = {"alg": "HS256", "kid": 123}
34-
key = OctKey.import_key("secret")
3541
self.assertRaises(
3642
ValueError,
3743
jws.serialize_compact,
3844
header,
3945
"i",
40-
key,
46+
self.key,
4147
)
4248

4349
# jwk should be a dict
@@ -47,7 +53,7 @@ def test_header_invalid_type(self):
4753
jws.serialize_compact,
4854
header,
4955
"i",
50-
key,
56+
self.key,
5157
)
5258

5359
# jku should be a URL
@@ -57,7 +63,7 @@ def test_header_invalid_type(self):
5763
jws.serialize_compact,
5864
header,
5965
"i",
60-
key,
66+
self.key,
6167
)
6268

6369
# x5c should be a chain of string
@@ -67,50 +73,48 @@ def test_header_invalid_type(self):
6773
jws.serialize_compact,
6874
header,
6975
"i",
70-
key,
76+
self.key,
7177
)
7278
header = {"alg": "HS256", "x5c": [1, 2]}
7379
self.assertRaises(
7480
ValueError,
7581
jws.serialize_compact,
7682
header,
7783
"i",
78-
key,
84+
self.key,
7985
)
8086

8187
def test_crit_header(self):
8288
header = {"alg": "HS256", "crit": ["kid"]}
83-
key = OctKey.import_key("secret")
8489
# missing kid header
8590
self.assertRaises(
8691
MissingCritHeaderError,
8792
jws.serialize_compact,
8893
header,
8994
"i",
90-
key,
95+
self.key,
9196
)
9297

9398
header = {"alg": "HS256", "kid": "1", "crit": ["kid"]}
94-
jws.serialize_compact(header, "i", key)
99+
jws.serialize_compact(header, "i", self.key)
95100

96101
def test_extra_header(self):
97102
header = {"alg": "HS256", "extra": "hi"}
98-
key = OctKey.import_key("secret")
99103
self.assertRaises(
100104
UnsupportedHeaderError,
101105
jws.serialize_compact,
102106
header,
103107
"i",
104-
key,
108+
self.key,
105109
)
106110
# bypass extra header
107111
registry = jws.JWSRegistry(strict_check_header=False)
108-
jws.serialize_compact(header, "i", key, registry=registry)
112+
jws.serialize_compact(header, "i", self.key, registry=registry)
109113

110114
# or use a header registry
111115
extra_header = {"extra": HeaderParameter("Extra header", "str", False)}
112116
registry = jws.JWSRegistry(header_registry=extra_header)
113-
jws.serialize_compact(header, "i", key, registry=registry)
117+
jws.serialize_compact(header, "i", self.key, registry=registry)
114118

115119
def test_rsa_invalid_signature(self):
116120
key1 = RSAKey.generate_key()

0 commit comments

Comments
 (0)