From e2e0fd1dbdb07f2a1bd8822ab86bcd7144025f97 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sat, 17 Nov 2018 22:43:14 +0100 Subject: [PATCH] editcap: add --inject-secrets option Add a new option to insert decryption secrets into a pcapng file. Change-Id: I0e024585cac9a8a328e88d32f9eb03d37d350e2a Ping-Bug: 15252 Reviewed-on: https://code.wireshark.org/review/30693 Petri-Dish: Peter Wu Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman --- doc/editcap.pod | 15 ++++++ editcap.c | 103 ++++++++++++++++++++++++++++++++++++++ test/fixtures_ws.py | 5 ++ test/suite_fileformats.py | 58 +++++++++++++++++++++ 4 files changed, 181 insertions(+) diff --git a/doc/editcap.pod b/doc/editcap.pod index 7f65c6268b..0be66ce95e 100644 --- a/doc/editcap.pod +++ b/doc/editcap.pod @@ -28,6 +28,7 @@ S<[ B<-S> Estrict time adjustmentE ]> S<[ B<-t> Etime adjustmentE ]> S<[ B<-T> Eencapsulation typeE ]> S<[ B<-v> ]> +S<[ B<--inject-secrets> Esecrets typeE,EfileE ]> I I S<[ I[-I] ... ]> @@ -335,6 +336,20 @@ NOTE: The B<-w> option assumes that the packets are in chronological order. If the packets are NOT in chronological order then the B<-w> duplication removal option may not identify some duplicates. +=item --inject-secrets Esecrets typeE,EfileE + +Inserts the contents of EfileE into a Decryption Secrets Block (DSB) +within the pcapng output file. This enables decryption without requiring +additional configuration in protocol preferences. + +The file format is described by Esecrets typeE which can be one of: + +I TLS Key Log as described at + L + +This option may be specified multiple times. The available options for +Esecrets typeE can be listed with B<--inject-secrets help>. + =back =head1 EXAMPLES diff --git a/editcap.c b/editcap.c index 18523a1c23..389266e05e 100644 --- a/editcap.c +++ b/editcap.c @@ -43,6 +43,7 @@ #include #endif +#include #include #include "epan/etypes.h" @@ -175,6 +176,13 @@ static int do_strict_time_adjustment = FALSE; static struct time_adjustment strict_time_adj = {NSTIME_INIT_ZERO, 0}; /* strict time adjustment */ static nstime_t previous_time = NSTIME_INIT_ZERO; /* previous time */ +static const struct { + const char *str; + guint32 id; +} secrets_types[] = { + { "tls", SECRETS_TYPE_TLS }, +}; + static int find_dct2000_real_data(guint8 *buf); static void handle_chopping(chop_t chop, wtap_packet_header *out_phdr, const wtap_packet_header *in_phdr, guint8 **buf, @@ -900,6 +908,25 @@ list_encap_types(FILE *stream) { g_free(encaps); } +static void +list_secrets_types(FILE *stream) +{ + for (guint i = 0; i < G_N_ELEMENTS(secrets_types); i++) { + fprintf(stream, " %s\n", secrets_types[i].str); + } +} + +static guint32 +lookup_secrets_type(const char *type) +{ + for (guint i = 0; i < G_N_ELEMENTS(secrets_types); i++) { + if (!strcmp(secrets_types[i].str, type)) { + return secrets_types[i].id; + } + } + return 0; +} + static int framenum_compare(gconstpointer a, gconstpointer b, gpointer user_data _U_) { @@ -965,6 +992,7 @@ real_main(int argc, char *argv[]) {"novlan", no_argument, NULL, 0x8100}, {"skip-radiotap-header", no_argument, NULL, 0x8101}, {"seed", required_argument, NULL, 0x8102}, + {"inject-secrets", required_argument, NULL, 0x8103}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {0, 0, 0, 0 } @@ -992,6 +1020,8 @@ real_main(int argc, char *argv[]) gchar *fsuffix = NULL; guint32 change_offset = 0; guint max_packet_number = 0; + GArray *dsb_types = NULL; + GPtrArray *dsb_filenames = NULL; const wtap_rec *rec; wtap_rec temp_rec; wtap_dump_params params = WTAP_DUMP_PARAMS_INIT; @@ -1073,6 +1103,36 @@ real_main(int argc, char *argv[]) break; } + case 0x8103: /* --inject-secrets */ + { + guint32 secrets_type_id = 0; + const char *secrets_filename = NULL; + if (strcmp("help", optarg) == 0) { + list_secrets_types(stdout); + goto clean_exit; + } + gchar **splitted = g_strsplit(optarg, ",", 2); + if (splitted[0]) { + secrets_type_id = lookup_secrets_type(splitted[0]); + secrets_filename = splitted[1]; + } + + if (secrets_type_id == 0) { + fprintf(stderr, "editcap: \"%s\" isn't a valid secrets type\n", secrets_filename); + g_strfreev(splitted); + ret = INVALID_OPTION; + goto clean_exit; + } + if (!dsb_filenames) { + dsb_types = g_array_new(FALSE, FALSE, sizeof(guint32)); + dsb_filenames = g_ptr_array_new_with_free_func(g_free); + } + g_array_append_val(dsb_types, secrets_type_id); + g_ptr_array_add(dsb_filenames, g_strdup(secrets_filename)); + g_strfreev(splitted); + break; + } + case 'a': { guint frame_number; @@ -1400,6 +1460,45 @@ real_main(int argc, char *argv[]) wtap_dump_params_init(¶ms, wth); + if (dsb_filenames) { + for (guint k = 0; k < dsb_filenames->len; k++) { + guint32 secrets_type_id = g_array_index(dsb_types, guint32, k); + const char *secrets_filename = (const char *)g_ptr_array_index(dsb_filenames, k); + char *data; + gsize data_len; + wtap_block_t block; + wtapng_dsb_mandatory_t *dsb; + GError *err = NULL; + + if (!g_file_get_contents(secrets_filename, &data, &data_len, &err)) { + fprintf(stderr, "editcap: \"%s\" could not be read: %s\n", secrets_filename, err->message); + g_clear_error(&err); + ret = INVALID_OPTION; + goto clean_exit; + } + if (data_len == 0) { + fprintf(stderr, "editcap: \"%s\" is an empty file, ignoring\n", secrets_filename); + g_free(data); + continue; + } + if (data_len >= G_MAXINT) { + fprintf(stderr, "editcap: \"%s\" is too large, ignoring\n", secrets_filename); + g_free(data); + continue; + } + + block = wtap_block_create(WTAP_BLOCK_DSB); + dsb = (wtapng_dsb_mandatory_t *)wtap_block_get_mandatory_data(block); + dsb->secrets_type = secrets_type_id; + dsb->secrets_len = (guint)data_len; + dsb->secrets_data = data; + if (params.dsbs_initial == NULL) { + params.dsbs_initial = g_array_new(FALSE, FALSE, sizeof(wtap_block_t)); + } + g_array_append_val(params.dsbs_initial, block); + } + } + /* * If an encapsulation type was specified, override the encapsulation * type of the input file. @@ -1935,6 +2034,10 @@ real_main(int argc, char *argv[]) } clean_exit: + if (dsb_filenames) { + g_array_free(dsb_types, TRUE); + g_ptr_array_free(dsb_filenames, TRUE); + } g_free(params.idb_inf); wtap_dump_params_cleanup(¶ms); if (wth != NULL) diff --git a/test/fixtures_ws.py b/test/fixtures_ws.py index 053d89e0ce..df9b9149ac 100644 --- a/test/fixtures_ws.py +++ b/test/fixtures_ws.py @@ -111,6 +111,11 @@ def cmd_text2pcap(program): return program('text2pcap') +@fixtures.fixture(scope='session') +def cmd_editcap(program): + return program('editcap') + + @fixtures.fixture(scope='session') def cmd_wireshark(program): return program('wireshark') diff --git a/test/suite_fileformats.py b/test/suite_fileformats.py index 66c9880929..8bf341c1cd 100644 --- a/test/suite_fileformats.py +++ b/test/suite_fileformats.py @@ -157,6 +157,64 @@ class case_fileformat_pcapng_dsb(subprocesstest.SubprocessTestCase): (0x544c534b, len(dsb2_contents), dsb2_contents), )) + def test_pcapng_dsb_2(self, cmd_editcap, dirs, capture_file, check_pcapng_dsb_fields): + '''Insert a single DSB into a pcapng file.''' + key_file = os.path.join(dirs.key_dir, 'dhe1_keylog.dat') + outfile = self.filename_from_id('dhe1-dsb.pcapng') + self.runProcess((cmd_editcap, + '--inject-secrets', 'tls,%s' % key_file, + capture_file('dhe1.pcapng.gz'), outfile + )) + with open(key_file, 'rb') as f: + keylog_contents = f.read() + check_pcapng_dsb_fields(outfile, ( + (0x544c534b, len(keylog_contents), keylog_contents), + )) + + def test_pcapng_dsb_3(self, cmd_editcap, dirs, capture_file, check_pcapng_dsb_fields): + '''Insert two DSBs into a pcapng file.''' + key_file1 = os.path.join(dirs.key_dir, 'dhe1_keylog.dat') + key_file2 = os.path.join(dirs.key_dir, 'http2-data-reassembly.keys') + outfile = self.filename_from_id('dhe1-dsb.pcapng') + self.runProcess((cmd_editcap, + '--inject-secrets', 'tls,%s' % key_file1, + '--inject-secrets', 'tls,%s' % key_file2, + capture_file('dhe1.pcapng.gz'), outfile + )) + with open(key_file1, 'rb') as f: + keylog1_contents = f.read() + with open(key_file2, 'rb') as f: + keylog2_contents = f.read() + check_pcapng_dsb_fields(outfile, ( + (0x544c534b, len(keylog1_contents), keylog1_contents), + (0x544c534b, len(keylog2_contents), keylog2_contents), + )) + + def test_pcapng_dsb_4(self, cmd_editcap, dirs, capture_file, check_pcapng_dsb_fields): + '''Insert a single DSB into a pcapng file with existing DSBs.''' + dsb_keys1 = os.path.join(dirs.key_dir, 'tls12-dsb-1.keys') + dsb_keys2 = os.path.join(dirs.key_dir, 'tls12-dsb-2.keys') + key_file = os.path.join(dirs.key_dir, 'dhe1_keylog.dat') + outfile = self.filename_from_id('tls12-dsb-extra.pcapng') + self.runProcess((cmd_editcap, + '--inject-secrets', 'tls,%s' % key_file, + capture_file('tls12-dsb.pcapng'), outfile + )) + with open(dsb_keys1, 'r') as f: + dsb1_contents = f.read().encode('utf8') + with open(dsb_keys2, 'r') as f: + dsb2_contents = f.read().encode('utf8') + with open(key_file, 'rb') as f: + keylog_contents = f.read() + # New DSBs are inserted before the first record. Due to the current + # implementation, this is inserted before other (existing) DSBs. This + # might change in the future if it is deemed more logical. + check_pcapng_dsb_fields(outfile, ( + (0x544c534b, len(keylog_contents), keylog_contents), + (0x544c534b, len(dsb1_contents), dsb1_contents), + (0x544c534b, len(dsb2_contents), dsb2_contents), + )) + @fixtures.mark_usefixtures('test_env') @fixtures.uses_fixtures