Skip to content

Commit 9da0543

Browse files
feat: prevent from Base64 Malleability (#51)
* feat: add non canonical base64 detection * test: add util non canonical base64 detection * test: non canonical signature * fix: non canonical signature test
1 parent 7982683 commit 9da0543

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

src/joserfc/util.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import struct
55
import binascii
66
import json
7-
7+
from .errors import DecodeError
88

99
def to_bytes(x: Any, charset: str = "utf-8", errors: str = "strict") -> bytes:
1010
if isinstance(x, bytes):
@@ -21,10 +21,23 @@ def to_str(x: bytes | str, charset: str = "utf-8") -> str:
2121
return x.decode(charset)
2222
return x
2323

24+
def __is_urlsafe_b64_encoding_non_canonical(s: bytes) -> bool:
25+
# https://github.com/FrancoisCapon/Base64SteganographyTools/blob/main/tools/b64_print_regular_characters.sh
26+
p = len(s) % 4 # padding?
27+
if p == 0:
28+
return False
29+
p = 4 - p # number of padding characters
30+
if p == 2 and s[-1] in b"AQgw":
31+
return False
32+
if p == 1 and s[-1] in b"AEIMQUYcgkosw048":
33+
return False
34+
return True
2435

2536
def urlsafe_b64decode(s: bytes) -> bytes:
2637
if b"+" in s or b"/" in s:
2738
raise binascii.Error
39+
if __is_urlsafe_b64_encoding_non_canonical(s):
40+
raise DecodeError
2841
s += b"=" * (-len(s) % 4)
2942
return base64.b64decode(s, b"-_", validate=True)
3043

tests/jws/test_compact.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,12 @@ def test_strict_check_header(self):
7373

7474
registry = JWSRegistry(strict_check_header=False)
7575
serialize_compact(header, b"hi", key, registry=registry)
76+
77+
def test_non_canonical_signature_encoding(self):
78+
text = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.VI29GgHzuh2xfF0bkRYvZIsSuQnbTXSIvuRyt7RDrwo"[:-1] + "p"
79+
self.assertRaises(
80+
DecodeError,
81+
deserialize_compact,
82+
text,
83+
OctKey.import_key("secret")
84+
)

tests/test_util.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import binascii
22
from unittest import TestCase
33
from joserfc import util
4-
4+
from joserfc import errors
55

66
class TestUtil(TestCase):
77
def test_to_bytes(self):
@@ -23,3 +23,7 @@ def test_json_b64encode(self):
2323
def test_urlsafe_b64decode(self):
2424
self.assertEqual(util.urlsafe_b64decode(b"_foo123-"), b"\xfd\xfa(\xd7m\xfe")
2525
self.assertRaises(binascii.Error, util.urlsafe_b64decode, b"+foo123/")
26+
for c in "RSTUVWXYZabdef": # A -> QQ==
27+
self.assertRaises(errors.DecodeError, util.urlsafe_b64decode, b"Q" + c.encode())
28+
for c in "FGH": # AAAAAAAAAAAAAA -> QUFBQUFBQUFBQUFBQUE=
29+
self.assertRaises(errors.DecodeError, util.urlsafe_b64decode, b"QUFBQUFBQUFBQUFBQU" + c.encode())

0 commit comments

Comments
 (0)