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 <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
Peter Wu 2018-11-17 22:43:14 +01:00 committed by Anders Broman
parent 52a6671439
commit e2e0fd1dbd
4 changed files with 181 additions and 0 deletions

View File

@ -28,6 +28,7 @@ S<[ B<-S> E<lt>strict time adjustmentE<gt> ]>
S<[ B<-t> E<lt>time adjustmentE<gt> ]>
S<[ B<-T> E<lt>encapsulation typeE<gt> ]>
S<[ B<-v> ]>
S<[ B<--inject-secrets> E<lt>secrets typeE<gt>,E<lt>fileE<gt> ]>
I<infile>
I<outfile>
S<[ I<packet#>[-I<packet#>] ... ]>
@ -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 E<lt>secrets typeE<gt>,E<lt>fileE<gt>
Inserts the contents of E<lt>fileE<gt> 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 E<lt>secrets typeE<gt> which can be one of:
I<tls> TLS Key Log as described at
L<https://developer.mozilla.org/NSS_Key_Log_Format>
This option may be specified multiple times. The available options for
E<lt>secrets typeE<gt> can be listed with B<--inject-secrets help>.
=back
=head1 EXAMPLES

103
editcap.c
View File

@ -43,6 +43,7 @@
#include <getopt.h>
#endif
#include <wiretap/secrets-types.h>
#include <wiretap/wtap.h>
#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(&params, 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(&params);
if (wth != NULL)

View File

@ -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')

View File

@ -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