-
Notifications
You must be signed in to change notification settings - Fork 0
/
josejson.py
123 lines (101 loc) · 3.94 KB
/
josejson.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
This custom pretty-printer for mitmproxy will decode the base64url-encoded
'payload' and 'protected' fields. This pretty-printer is useful for
understanding the POST requests made to an ACME server, since these requests are
made using JWS as JSON, which, contrary to JWT tokens that are very easy to
decode, aren't as common. The whole JWS as JSON and application/jose+json are
detailed in https://tools.ietf.org/html/rfc8555. To use this pretty-printer, run
mitmproxy with
mitmproxy -s josejson.py
For example, the following body of an HTTP request:
{
"protected": "eyJhbGciOiJSUzI1Ni...E1MzY1Mjg1MCJ9",
"payload": "eyJjc3IiOiJNSUlDblRDQ0FZ...EU3lHQ3BjLTlfanVBIn0",
"signature": "qqYGqZDSSUwuLLxm6-...nygkb5S8igKPrw"
}
will be displayed as:
{
"payload": {
"csr": "MIICnTCCAYUCAQAwADCCAS...duroowkXh3tqgVFDSyGCpc-9_juA"
},
"protected": {
"alg": "RS256",
"kid": "https://acme-v02.api.letsencrypt.org/acme/acct/204416270",
"nonce": "01017mM9r6R_TpKL-5zxAmMF5JmTCBI-v6AsLlGedj3pD1E",
"url": "https://acme-v02.api.letsencrypt.org/acme/finalize/204416270/25153652850"
},
"signature": "qqYGqZDSSUwuLLxm6-...nygkb5S8igKPrw"
}
Note that only the 'payload' and 'protected' fields are base64-decoded inline.
The signature is not decoded.
"""
from mitmproxy import contentviews, ctx, flow, http
from mitmproxy.contentviews import base
import json
import base64
import typing
import re
def format_json(data: typing.Any) -> typing.Iterator[base.TViewLine]:
encoder = json.JSONEncoder(indent=4, sort_keys=True, ensure_ascii=False)
current_line: base.TViewLine = []
for chunk in encoder.iterencode(data):
if "\n" in chunk:
rest_of_last_line, chunk = chunk.split("\n", maxsplit=1)
# rest_of_last_line is a delimiter such as , or [
current_line.append(("text", rest_of_last_line))
yield current_line
current_line = []
if re.match(r'\s*"', chunk):
current_line.append(("json_string", chunk))
elif re.match(r"\s*\d", chunk):
current_line.append(("json_number", chunk))
elif re.match(r"\s*(true|null|false)", chunk):
current_line.append(("json_boolean", chunk))
else:
current_line.append(("text", chunk))
yield current_line
class ViewJoseJson(contentviews.View):
name = "jose+json"
content_types = ["application/jose+json"]
def __call__(self, data: bytes, **metadata) -> contentviews.TViewResult:
data = json.loads(data.decode("utf-8"))
return (
"protected & payload base64-decoded inline",
format_json(josejson(data)),
)
def render_priority(
self,
data: bytes,
*,
content_type: typing.Optional[str] = None,
flow: typing.Optional[flow.Flow] = None,
http_message: typing.Optional[http.Message] = None,
**unknown_metadata,
) -> float:
if content_type == "application/jose+json":
return 1
else:
return 0
def josejson(data: typing.Any) -> typing.Any:
if len(data["protected"]) == 0:
data["protected"] = "e30" # '{}' in base64
# https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding
data["protected"] += "==="
try:
data["protected"] = json.loads(base64.urlsafe_b64decode(data["protected"]))
except Exception as e:
ctx.log.info(e)
if len(data["payload"]) == 0:
data["payload"] = "e30" # '{}' in base64
# https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding
data["payload"] += "==="
try:
data["payload"] = json.loads(base64.urlsafe_b64decode(data["payload"]))
except Exception as e:
ctx.log.info(e)
return data
view = ViewJoseJson()
def load(l):
contentviews.add(view)
def done():
contentviews.remove(view)