2 # Generate the array of Certificate Transparency Log ID to description mappings
3 # for the TLS dissector.
5 # To update the TLS dissector source file, run this from the source directory:
7 # python3 tools/make-tls-ct-logids.py --update
11 from base64
import b64decode
, b64encode
16 from hashlib
import sha256
19 # Begin of comment, followed by the actual array definition
20 HEADER
= "/* Generated by tools/make-tls-ct-logids.py\n"
21 # See also https://www.certificate-transparency.org/known-logs
22 CT_JSON_URL
= 'https://www.gstatic.com/ct/log_list/v3/all_logs_list.json'
24 SOURCE_FILE
= os
.path
.join('epan', 'dissectors', 'packet-tls-utils.c')
26 # Maximum elements per line in the value array. 11 is chosen because it results
27 # in output consistent with clang-format.
30 class SourceStage(Enum
):
38 return s
.replace('\\', '\\\\').replace('"', '\\"')
42 return " ".join("0x%02x," % b
for b
in bytearray(b
))
45 def process_json(obj
, lastmod
):
46 logs
= list(itertools
.chain(*[op
['logs'] for op
in obj
['operators']]))
47 metainfo
, block
= HEADER
, ''
48 metainfo
+= " * Last-Modified %s, %s entries. */\n" % (lastmod
, len(logs
))
49 block
+= "static const bytes_string ct_logids[] = {\n"
51 desc
= entry
["description"]
52 pubkey_der
= b64decode(entry
["key"])
53 key_id
= sha256(pubkey_der
).digest()
54 block
+= ' { (const uint8_t[]){\n'
55 for offset
in range(0, len(key_id
), BYTES_PER_LINE
):
57 byteshex(key_id
[offset
:offset
+BYTES_PER_LINE
])
59 block
+= ' %d, "%s" },\n' % (len(key_id
), escape_c(desc
))
60 block
+= " { NULL, 0, NULL }\n"
62 return metainfo
, block
65 def parse_source(source_path
):
67 Reads the source file and tries to split it in the parts before, inside and
70 begin
, metainfo
, block
, end
= '', '', '', ''
71 # Stages: BEGIN (before block), IN_METAINFO, IN_BLOCK (skip), END
72 stage
= SourceStage
.BEGIN
73 with
open(source_path
) as f
:
75 if line
.startswith('/* Generated by '):
76 stage
= SourceStage
.IN_METAINFO
79 if stage
== SourceStage
.BEGIN
:
81 elif stage
== SourceStage
.IN_METAINFO
:
83 elif stage
== SourceStage
.IN_BLOCK
:
85 if line
.startswith('}'):
86 stage
= SourceStage
.END
87 elif stage
== SourceStage
.END
:
90 if line
.startswith(' * Last-Modified '):
91 stage
= SourceStage
.IN_BLOCK
93 if stage
!= SourceStage
.END
:
94 raise RuntimeError("Could not parse file (in stage %s)" % stage
.name
)
95 return begin
, metainfo
, block
, end
98 parser
= argparse
.ArgumentParser()
99 parser
.add_argument("--update", action
="store_true",
100 help="Update %s as needed instead of writing to stdout" % SOURCE_FILE
)
104 args
= parser
.parse_args()
105 this_dir
= os
.path
.dirname(__file__
)
106 r
= requests
.get(CT_JSON_URL
)
107 j_metainfo
, j_block
= process_json(r
.json(), lastmod
=r
.headers
['Last-Modified'])
108 source_path
= os
.path
.join(this_dir
, '..', SOURCE_FILE
)
111 s_begin
, _
, s_block
, s_end
= parse_source(source_path
)
112 if s_block
== j_block
:
113 print("File is up-to-date")
115 with
open(source_path
, "w") as f
:
120 print("Updated %s" % source_path
)
122 print(j_metainfo
, j_block
)
125 if __name__
== '__main__':