forked from osmocom/wireshark
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:
parent
52a6671439
commit
e2e0fd1dbd
|
@ -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>time adjustmentE<gt> ]>
|
||||||
S<[ B<-T> E<lt>encapsulation typeE<gt> ]>
|
S<[ B<-T> E<lt>encapsulation typeE<gt> ]>
|
||||||
S<[ B<-v> ]>
|
S<[ B<-v> ]>
|
||||||
|
S<[ B<--inject-secrets> E<lt>secrets typeE<gt>,E<lt>fileE<gt> ]>
|
||||||
I<infile>
|
I<infile>
|
||||||
I<outfile>
|
I<outfile>
|
||||||
S<[ I<packet#>[-I<packet#>] ... ]>
|
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
|
If the packets are NOT in chronological order then the B<-w> duplication
|
||||||
removal option may not identify some duplicates.
|
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
|
=back
|
||||||
|
|
||||||
=head1 EXAMPLES
|
=head1 EXAMPLES
|
||||||
|
|
103
editcap.c
103
editcap.c
|
@ -43,6 +43,7 @@
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <wiretap/secrets-types.h>
|
||||||
#include <wiretap/wtap.h>
|
#include <wiretap/wtap.h>
|
||||||
|
|
||||||
#include "epan/etypes.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 struct time_adjustment strict_time_adj = {NSTIME_INIT_ZERO, 0}; /* strict time adjustment */
|
||||||
static nstime_t previous_time = NSTIME_INIT_ZERO; /* previous time */
|
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 int find_dct2000_real_data(guint8 *buf);
|
||||||
static void handle_chopping(chop_t chop, wtap_packet_header *out_phdr,
|
static void handle_chopping(chop_t chop, wtap_packet_header *out_phdr,
|
||||||
const wtap_packet_header *in_phdr, guint8 **buf,
|
const wtap_packet_header *in_phdr, guint8 **buf,
|
||||||
|
@ -900,6 +908,25 @@ list_encap_types(FILE *stream) {
|
||||||
g_free(encaps);
|
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
|
static int
|
||||||
framenum_compare(gconstpointer a, gconstpointer b, gpointer user_data _U_)
|
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},
|
{"novlan", no_argument, NULL, 0x8100},
|
||||||
{"skip-radiotap-header", no_argument, NULL, 0x8101},
|
{"skip-radiotap-header", no_argument, NULL, 0x8101},
|
||||||
{"seed", required_argument, NULL, 0x8102},
|
{"seed", required_argument, NULL, 0x8102},
|
||||||
|
{"inject-secrets", required_argument, NULL, 0x8103},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"version", no_argument, NULL, 'V'},
|
{"version", no_argument, NULL, 'V'},
|
||||||
{0, 0, 0, 0 }
|
{0, 0, 0, 0 }
|
||||||
|
@ -992,6 +1020,8 @@ real_main(int argc, char *argv[])
|
||||||
gchar *fsuffix = NULL;
|
gchar *fsuffix = NULL;
|
||||||
guint32 change_offset = 0;
|
guint32 change_offset = 0;
|
||||||
guint max_packet_number = 0;
|
guint max_packet_number = 0;
|
||||||
|
GArray *dsb_types = NULL;
|
||||||
|
GPtrArray *dsb_filenames = NULL;
|
||||||
const wtap_rec *rec;
|
const wtap_rec *rec;
|
||||||
wtap_rec temp_rec;
|
wtap_rec temp_rec;
|
||||||
wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;
|
wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;
|
||||||
|
@ -1073,6 +1103,36 @@ real_main(int argc, char *argv[])
|
||||||
break;
|
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':
|
case 'a':
|
||||||
{
|
{
|
||||||
guint frame_number;
|
guint frame_number;
|
||||||
|
@ -1400,6 +1460,45 @@ real_main(int argc, char *argv[])
|
||||||
|
|
||||||
wtap_dump_params_init(¶ms, wth);
|
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
|
* If an encapsulation type was specified, override the encapsulation
|
||||||
* type of the input file.
|
* type of the input file.
|
||||||
|
@ -1935,6 +2034,10 @@ real_main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
clean_exit:
|
clean_exit:
|
||||||
|
if (dsb_filenames) {
|
||||||
|
g_array_free(dsb_types, TRUE);
|
||||||
|
g_ptr_array_free(dsb_filenames, TRUE);
|
||||||
|
}
|
||||||
g_free(params.idb_inf);
|
g_free(params.idb_inf);
|
||||||
wtap_dump_params_cleanup(¶ms);
|
wtap_dump_params_cleanup(¶ms);
|
||||||
if (wth != NULL)
|
if (wth != NULL)
|
||||||
|
|
|
@ -111,6 +111,11 @@ def cmd_text2pcap(program):
|
||||||
return program('text2pcap')
|
return program('text2pcap')
|
||||||
|
|
||||||
|
|
||||||
|
@fixtures.fixture(scope='session')
|
||||||
|
def cmd_editcap(program):
|
||||||
|
return program('editcap')
|
||||||
|
|
||||||
|
|
||||||
@fixtures.fixture(scope='session')
|
@fixtures.fixture(scope='session')
|
||||||
def cmd_wireshark(program):
|
def cmd_wireshark(program):
|
||||||
return program('wireshark')
|
return program('wireshark')
|
||||||
|
|
|
@ -157,6 +157,64 @@ class case_fileformat_pcapng_dsb(subprocesstest.SubprocessTestCase):
|
||||||
(0x544c534b, len(dsb2_contents), dsb2_contents),
|
(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.mark_usefixtures('test_env')
|
||||||
@fixtures.uses_fixtures
|
@fixtures.uses_fixtures
|
||||||
|
|
Loading…
Reference in New Issue