Skip to content

Commit 676d9d4

Browse files
committed
Add detached payload support for JWS encoding and decoding
Specifications allow to have JWS with unencoded detached payloads. This changeset adds detached payload support for encoding and decoding functions. For encoding, detached payload can be enabled by setting the "is_payload_detached" arg or having the "b64=False" inside the headers. For decoding, the detached payload content (bytes) has to be provided with the "detached_payload" arg and "b64=False" has to be found inside the decoded headers. Functionnally, when this feature is used, the signature will be computed over the raw data bytes of the payload, without being base64 encoded and obviously, the payload will not be provided inside the generated JWS. So, the generated JWS will look like: base64url(header)..base64url(signature) Relevant specifications: RFC 7515: "JSON Web Signature (JWS)". (Annexe F) RFC 7797: "JSON Web Signature (JWS) Unencoded Payload Option".
1 parent 77d7916 commit 676d9d4

File tree

1 file changed

+36
-9
lines changed

1 file changed

+36
-9
lines changed

jwt/api_jws.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,34 +80,54 @@ def encode(
8080
algorithm: Optional[str] = "HS256",
8181
headers: Optional[Dict] = None,
8282
json_encoder: Optional[Type[json.JSONEncoder]] = None,
83+
is_payload_detached: bool = False,
8384
) -> str:
8485
segments = []
8586

8687
if algorithm is None:
8788
algorithm = "none"
8889

89-
# Prefer headers["alg"] if present to algorithm parameter.
90-
if headers and "alg" in headers and headers["alg"]:
91-
algorithm = headers["alg"]
90+
# Prefer headers values if present to function parameters.
91+
if headers:
92+
headers_alg = headers.get("alg")
93+
if headers_alg:
94+
algorithm = headers["alg"]
95+
96+
headers_b64 = headers.get("b64")
97+
if headers_b64 is False:
98+
is_payload_detached = True
9299

93100
# Header
94-
header = {"typ": self.header_typ, "alg": algorithm}
101+
header = {"typ": self.header_typ, "alg": algorithm} # type: Dict[str, Any]
95102

96103
if headers:
97104
self._validate_headers(headers)
98105
header.update(headers)
99-
if not header["typ"]:
100-
del header["typ"]
106+
107+
if not header["typ"]:
108+
del header["typ"]
109+
110+
if is_payload_detached:
111+
header["b64"] = False
112+
elif "b64" in header:
113+
# True is the standard value for b64, so no need for it
114+
del header["b64"]
101115

102116
json_header = json.dumps(
103117
header, separators=(",", ":"), cls=json_encoder
104118
).encode()
105119

106120
segments.append(base64url_encode(json_header))
107-
segments.append(base64url_encode(payload))
121+
122+
if is_payload_detached:
123+
msg_payload = payload
124+
else:
125+
msg_payload = base64url_encode(payload)
126+
segments.append(msg_payload)
108127

109128
# Segments
110129
signing_input = b".".join(segments)
130+
111131
try:
112132
alg_obj = self._algorithms[algorithm]
113133
key = alg_obj.prepare_key(key)
@@ -119,11 +139,13 @@ def encode(
119139
"Algorithm '%s' could not be found. Do you have cryptography "
120140
"installed?" % algorithm
121141
) from e
122-
else:
123-
raise NotImplementedError("Algorithm not supported") from e
142+
raise NotImplementedError("Algorithm not supported") from e
124143

125144
segments.append(base64url_encode(signature))
126145

146+
# Don't put the payload content inside the encoded token when detached
147+
if is_payload_detached:
148+
segments[1] = b""
127149
encoded_string = b".".join(segments)
128150

129151
return encoded_string.decode("utf-8")
@@ -134,6 +156,7 @@ def decode_complete(
134156
key: str = "",
135157
algorithms: Optional[List[str]] = None,
136158
options: Optional[Dict] = None,
159+
detached_payload: Optional[bytes] = None,
137160
**kwargs,
138161
) -> Dict[str, Any]:
139162
if options is None:
@@ -148,6 +171,10 @@ def decode_complete(
148171

149172
payload, signing_input, header, signature = self._load(jwt)
150173

174+
if detached_payload is not None and header.get("b64", True) is False:
175+
payload = detached_payload
176+
signing_input = b".".join([signing_input.rsplit(b".", 1)[0], payload])
177+
151178
if verify_signature:
152179
self._verify_signature(signing_input, header, signature, key, algorithms)
153180

0 commit comments

Comments
 (0)