2 * Routines for Bundle Protocol Version 7 Security (BPSec) dissection
4 * RFC 9172: https://www.rfc-editor.org/rfc/rfc9172.html
6 * Copyright 2019-2024, Brian Sipos <brian.sipos@gmail.com>
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * SPDX-License-Identifier: LGPL-2.1-or-later
17 #include "packet-bpsec.h"
18 #include <epan/packet.h>
19 #include <epan/prefs.h>
20 #include <epan/proto.h>
21 #include <epan/expert.h>
23 #include <epan/wscbor.h>
25 void proto_register_bpsec(void);
26 void proto_reg_handoff_bpsec(void);
29 static int proto_bpsec
;
31 /// Dissect opaque CBOR data
32 static dissector_handle_t handle_cbor
;
33 /// Context ID sub-dissectors
34 static dissector_table_t secctx_dissectors
;
35 /// Parameter value sub-dissectors
36 static dissector_table_t param_dissectors
;
37 /// Result value sub-dissectors
38 static dissector_table_t result_dissectors
;
43 static int hf_asb_target_list
;
44 static int hf_asb_target
;
45 static int hf_asb_ctxid
;
46 static int hf_asb_flags
;
47 static int hf_asb_flags_has_params
;
48 static int hf_asb_secsrc_nodeid
;
49 static int hf_asb_secsrc_uri
;
50 static int hf_asb_param_list
;
51 static int hf_asb_param_pair
;
52 static int hf_asb_param_id
;
53 static int hf_asb_result_all_list
;
54 static int hf_asb_result_tgt_list
;
55 static int hf_asb_result_tgt_ref
;
56 static int hf_asb_result_pair
;
57 static int hf_asb_result_id
;
59 static int *const asb_flags
[] = {
60 &hf_asb_flags_has_params
,
66 static int ett_asb_flags
;
67 static int ett_tgt_list
;
68 static int ett_param_list
;
69 static int ett_param_pair
;
70 static int ett_result_all_list
;
71 static int ett_result_tgt_list
;
72 static int ett_result_pair
;
74 static expert_field ei_secsrc_diff
;
75 static expert_field ei_ctxid_zero
;
76 static expert_field ei_ctxid_priv
;
77 static expert_field ei_target_invalid
;
78 static expert_field ei_value_partial_decode
;
80 bpsec_id_t
* bpsec_id_new(wmem_allocator_t
*alloc
, int64_t context_id
, int64_t type_id
) {
83 obj
= wmem_new(alloc
, bpsec_id_t
);
86 obj
= g_new(bpsec_id_t
, 1);
88 obj
->context_id
= context_id
;
89 obj
->type_id
= type_id
;
93 void bpsec_id_free(wmem_allocator_t
*alloc
, void *ptr
) {
94 //bpsec_id_t *obj = (bpsec_id_t *)ptr;
95 wmem_free(alloc
, ptr
);
98 gboolean
bpsec_id_equal(const void *a
, const void *b
) {
99 const bpsec_id_t
*aobj
= a
;
100 const bpsec_id_t
*bobj
= b
;
103 && (aobj
->context_id
== bobj
->context_id
)
104 && (aobj
->type_id
== bobj
->type_id
)
108 unsigned bpsec_id_hash(const void *key
) {
109 const bpsec_id_t
*obj
= key
;
111 g_int64_hash(&(obj
->context_id
))
112 ^ g_int64_hash(&(obj
->type_id
))
116 /** Dissect an ID-value pair within a context.
119 static int dissect_value(dissector_handle_t dissector
, bpsec_dissector_data_t
*const data
, tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
) {
122 sublen
= call_dissector_with_data(dissector
, tvb
, pinfo
, tree
, data
);
123 if ((sublen
< 0) || ((unsigned)sublen
< tvb_captured_length(tvb
))) {
124 expert_add_info(pinfo
, proto_tree_get_parent(tree
), &ei_value_partial_decode
);
128 sublen
= call_dissector(handle_cbor
, tvb
, pinfo
, tree
);
133 /** Dissector for abstract security block structure.
135 static int dissect_block_asb(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, const bp_dissector_data_t
*const data
, int root_hfindex
) {
136 proto_item
*item_asb
= proto_tree_add_item(tree
, root_hfindex
, tvb
, 0, -1, ENC_NA
);
137 proto_tree
*tree_asb
= proto_item_add_subtree(item_asb
, ett_asb
);
140 wmem_array_t
*targets
;
141 targets
= wmem_array_new(pinfo
->pool
, sizeof(uint64_t));
143 wscbor_chunk_t
*chunk_tgt_list
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
144 wscbor_require_array(chunk_tgt_list
);
145 proto_item
*item_tgt_list
= proto_tree_add_cbor_container(tree_asb
, hf_asb_target_list
, pinfo
, tvb
, chunk_tgt_list
);
146 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_tgt_list
)) {
147 proto_tree
*tree_tgt_list
= proto_item_add_subtree(item_tgt_list
, ett_tgt_list
);
149 // iterate all targets
150 for (uint64_t param_ix
= 0; param_ix
< chunk_tgt_list
->head_value
; ++param_ix
) {
151 wscbor_chunk_t
*chunk_tgt
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
152 uint64_t *tgt_blknum
= wscbor_require_uint64(pinfo
->pool
, chunk_tgt
);
153 proto_item
*item_tgt
= proto_tree_add_cbor_uint64(tree_tgt_list
, hf_asb_target
, pinfo
, tvb
, chunk_tgt
, tgt_blknum
);
155 wmem_array_append(targets
, tgt_blknum
, 1);
157 wmem_map_t
*map
= NULL
;
158 if (*tgt_blknum
== 0) {
159 map
= (root_hfindex
== hf_bib
)
160 ? data
->bundle
->primary
->sec
.data_i
161 : data
->bundle
->primary
->sec
.data_c
;
164 bp_block_canonical_t
*found
= wmem_map_lookup(data
->bundle
->block_nums
, tgt_blknum
);
166 map
= (root_hfindex
== hf_bib
)
171 expert_add_info(pinfo
, item_tgt
, &ei_target_invalid
);
174 if (map
&& (data
->block
->block_number
)) {
177 data
->block
->block_number
,
184 proto_item_set_len(item_tgt_list
, offset
- chunk_tgt_list
->start
);
187 wscbor_chunk_t
*chunk_ctxid
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
188 int64_t *ctxid
= wscbor_require_int64(pinfo
->pool
, chunk_ctxid
);
189 proto_item
*item_ctxid
= proto_tree_add_cbor_int64(tree_asb
, hf_asb_ctxid
, pinfo
, tvb
, chunk_ctxid
, ctxid
);
190 if (ctxid
&& item_ctxid
) {
192 expert_add_info(pinfo
, item_ctxid
, &ei_ctxid_zero
);
194 else if (*ctxid
< 0) {
195 expert_add_info(pinfo
, item_ctxid
, &ei_ctxid_priv
);
198 // apply label if registered
199 dissector_handle_t ctx_dis
= dissector_get_custom_table_handle(secctx_dissectors
, ctxid
);
200 const char *dis_name
= dissector_handle_get_description(ctx_dis
);
202 const header_field_info
*hfinfo
= PITEM_HFINFO(item_ctxid
);
203 proto_item_set_text(item_ctxid
, "%s: %s (%" PRId64
")", hfinfo
? hfinfo
->name
: NULL
, dis_name
, *ctxid
);
207 wscbor_chunk_t
*chunk_flags
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
208 uint64_t *flags
= wscbor_require_uint64(pinfo
->pool
, chunk_flags
);
209 proto_tree_add_cbor_bitmask(tree_asb
, hf_asb_flags
, ett_asb_flags
, asb_flags
, pinfo
, tvb
, chunk_flags
, flags
);
212 bp_eid_t
*secsrc
= bp_eid_new(pinfo
->pool
);
213 proto_item
*item_secsrc
= proto_tree_add_cbor_eid(tree_asb
, hf_asb_secsrc_nodeid
, hf_asb_secsrc_uri
, pinfo
, tvb
, &offset
, secsrc
);
214 if (!bp_eid_equal(data
->bundle
->primary
->src_nodeid
, secsrc
)) {
215 expert_add_info(pinfo
, item_secsrc
, &ei_secsrc_diff
);
219 if (flags
&& (*flags
& BPSEC_ASB_HAS_PARAMS
)) {
220 wscbor_chunk_t
*chunk_param_list
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
221 wscbor_require_array(chunk_param_list
);
222 proto_item
*item_param_list
= proto_tree_add_cbor_container(tree_asb
, hf_asb_param_list
, pinfo
, tvb
, chunk_param_list
);
223 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_param_list
)) {
224 proto_tree
*tree_param_list
= proto_item_add_subtree(item_param_list
, ett_param_list
);
226 // iterate all parameters
227 for (uint64_t param_ix
= 0; param_ix
< chunk_param_list
->head_value
; ++param_ix
) {
228 wscbor_chunk_t
*chunk_param_pair
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
229 wscbor_require_array_size(chunk_param_pair
, 2, 2);
230 proto_item
*item_param_pair
= proto_tree_add_cbor_container(tree_param_list
, hf_asb_param_pair
, pinfo
, tvb
, chunk_param_pair
);
231 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_param_pair
)) {
232 proto_tree
*tree_param_pair
= proto_item_add_subtree(item_param_pair
, ett_param_pair
);
234 wscbor_chunk_t
*chunk_paramid
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
235 int64_t *paramid
= wscbor_require_int64(pinfo
->pool
, chunk_paramid
);
236 proto_tree_add_cbor_int64(tree_param_pair
, hf_asb_param_id
, pinfo
, tvb
, chunk_paramid
, paramid
);
238 const int offset_value
= offset
;
239 if (!wscbor_skip_next_item(pinfo
->pool
, tvb
, &offset
)) {
242 tvbuff_t
*tvb_value
= tvb_new_subset_length(tvb
, offset_value
, offset
- offset_value
);
244 bpsec_dissector_data_t bpsec_data
= { .bp
= data
};
245 dissector_handle_t value_dissect
= NULL
;
246 if (ctxid
&& paramid
) {
247 bpsec_data
.id
.context_id
= *ctxid
;
248 bpsec_data
.id
.type_id
= *paramid
;
249 value_dissect
= dissector_get_custom_table_handle(param_dissectors
, &(bpsec_data
.id
));
251 const char *dis_name
= dissector_handle_get_description(value_dissect
);
253 proto_item_append_text(item_param_pair
, ": %s (%" PRId64
")", dis_name
, *paramid
);
255 dissect_value(value_dissect
, &bpsec_data
, tvb_value
, pinfo
, tree_param_pair
);
257 proto_item_set_len(item_param_pair
, offset
- chunk_param_pair
->start
);
261 proto_item_set_len(item_param_list
, offset
- chunk_param_list
->start
);
265 // array sizes should agree
266 const unsigned tgt_size
= wmem_array_get_count(targets
);
268 wscbor_chunk_t
*chunk_result_all_list
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
269 wscbor_require_array_size(chunk_result_all_list
, tgt_size
, tgt_size
);
270 proto_item
*item_result_all_list
= proto_tree_add_cbor_container(tree_asb
, hf_asb_result_all_list
, pinfo
, tvb
, chunk_result_all_list
);
271 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_result_all_list
)) {
272 proto_tree
*tree_result_all_list
= proto_item_add_subtree(item_result_all_list
, ett_result_all_list
);
274 // iterate each target's results
275 for (unsigned tgt_ix
= 0; tgt_ix
< tgt_size
; ++tgt_ix
) {
276 wscbor_chunk_t
*chunk_result_tgt_list
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
277 wscbor_require_array(chunk_result_tgt_list
);
278 proto_item
*item_result_tgt_list
= proto_tree_add_cbor_container(tree_result_all_list
, hf_asb_result_tgt_list
, pinfo
, tvb
, chunk_result_tgt_list
);
279 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_result_tgt_list
)) {
280 proto_tree
*tree_result_tgt_list
= proto_item_add_subtree(item_result_tgt_list
, ett_result_tgt_list
);
282 // Hint at the associated target number
283 if (tgt_ix
< tgt_size
) {
284 const uint64_t *tgt_blknum
= wmem_array_index(targets
, tgt_ix
);
285 proto_item
*item_tgt_blknum
= proto_tree_add_uint64(tree_result_tgt_list
, hf_asb_result_tgt_ref
, tvb
, 0, 0, *tgt_blknum
);
286 proto_item_set_generated(item_tgt_blknum
);
289 // iterate all results for this target
290 for (uint64_t result_ix
= 0; result_ix
< chunk_result_tgt_list
->head_value
; ++result_ix
) {
291 wscbor_chunk_t
*chunk_result_pair
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
292 wscbor_require_array_size(chunk_result_pair
, 2, 2);
293 proto_item
*item_result_pair
= proto_tree_add_cbor_container(tree_result_tgt_list
, hf_asb_result_pair
, pinfo
, tvb
, chunk_result_pair
);
294 if (!wscbor_skip_if_errors(pinfo
->pool
, tvb
, &offset
, chunk_result_pair
)) {
295 proto_tree
*tree_result_pair
= proto_item_add_subtree(item_result_pair
, ett_result_pair
);
297 wscbor_chunk_t
*chunk_resultid
= wscbor_chunk_read(pinfo
->pool
, tvb
, &offset
);
298 int64_t *resultid
= wscbor_require_int64(pinfo
->pool
, chunk_resultid
);
299 proto_tree_add_cbor_int64(tree_result_pair
, hf_asb_result_id
, pinfo
, tvb
, chunk_resultid
, resultid
);
301 const int offset_value
= offset
;
302 if (!wscbor_skip_next_item(pinfo
->pool
, tvb
, &offset
)) {
305 tvbuff_t
*tvb_value
= tvb_new_subset_length(tvb
, offset_value
, offset
- offset_value
);
307 bpsec_dissector_data_t bpsec_data
= { .bp
= data
};
308 dissector_handle_t value_dissect
= NULL
;
309 if (ctxid
&& resultid
) {
310 bpsec_data
.id
.context_id
= *ctxid
;
311 bpsec_data
.id
.type_id
= *resultid
;
312 value_dissect
= dissector_get_custom_table_handle(result_dissectors
, &(bpsec_data
.id
));
314 const char *dis_name
= dissector_handle_get_description(value_dissect
);
316 proto_item_append_text(item_result_pair
, ": %s (%" PRId64
")", dis_name
, *resultid
);
318 dissect_value(value_dissect
, &bpsec_data
, tvb_value
, pinfo
, tree_result_pair
);
320 proto_item_set_len(item_result_pair
, offset
- chunk_result_pair
->start
);
324 proto_item_set_len(item_result_tgt_list
, offset
- chunk_result_tgt_list
->start
);
328 proto_item_set_len(item_result_all_list
, offset
- chunk_result_all_list
->start
);
331 proto_item_set_len(item_asb
, offset
);
335 /** Dissector for Bundle Integrity block.
337 static int dissect_block_bib(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
) {
338 return dissect_block_asb(tvb
, pinfo
, tree
, (bp_dissector_data_t
*)data
, hf_bib
);
341 /** Dissector for Bundle Confidentiality block.
343 static int dissect_block_bcb(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
) {
344 return dissect_block_asb(tvb
, pinfo
, tree
, (bp_dissector_data_t
*)data
, hf_bcb
);
347 /// Re-initialize after a configuration change
348 static void reinit_bpsec(void) {
351 /// Overall registration of the protocol
352 void proto_register_bpsec(void) {
353 static hf_register_info fields
[] = {
354 {&hf_bib
, {"BPSec Block Integrity Block", "bpsec.bib", FT_PROTOCOL
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
355 {&hf_bcb
, {"BPSec Block Confidentiality Block", "bpsec.bcb", FT_PROTOCOL
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
356 {&hf_asb_target_list
, {"Security Targets, Count", "bpsec.asb.target_count", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
357 {&hf_asb_target
, {"Target Block Number", "bpsec.asb.target", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
358 {&hf_asb_ctxid
, {"Context ID", "bpsec.asb.ctxid", FT_INT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
359 {&hf_asb_flags
, {"Flags", "bpsec.asb.flags", FT_UINT64
, BASE_HEX
, NULL
, 0x0, NULL
, HFILL
}},
360 {&hf_asb_flags_has_params
, {"Parameters Present", "bpsec.asb.flags.has_params", FT_BOOLEAN
, 8, TFS(&tfs_set_notset
), BPSEC_ASB_HAS_PARAMS
, NULL
, HFILL
}},
361 {&hf_asb_secsrc_nodeid
, {"Security Source", "bpsec.asb.secsrc.nodeid", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
362 {&hf_asb_secsrc_uri
, {"Security Source URI", "bpsec.asb.secsrc.uri", FT_STRING
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
363 {&hf_asb_param_list
, {"Security Parameters, Count", "bpsec.asb.param_count", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
364 {&hf_asb_param_pair
, {"Parameter", "bpsec.asb.param", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
365 {&hf_asb_param_id
, {"Type ID", "bpsec.asb.param.id", FT_INT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
366 {&hf_asb_result_all_list
, {"Security Result Targets, Count", "bpsec.asb.result_count", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
367 {&hf_asb_result_tgt_ref
, {"Associated Target Block Number", "bpsec.asb.result_tgt_ref", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
368 {&hf_asb_result_tgt_list
, {"Security Results, Count", "bpsec.asb.result_count", FT_UINT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
369 {&hf_asb_result_pair
, {"Result", "bpsec.asb.result", FT_NONE
, BASE_NONE
, NULL
, 0x0, NULL
, HFILL
}},
370 {&hf_asb_result_id
, {"Type ID", "bpsec.asb.result.id", FT_INT64
, BASE_DEC
, NULL
, 0x0, NULL
, HFILL
}},
372 static int *ett
[] = {
378 &ett_result_all_list
,
379 &ett_result_tgt_list
,
382 static ei_register_info expertitems
[] = {
383 {&ei_secsrc_diff
, {"bpsec.secsrc_diff", PI_SECURITY
, PI_CHAT
, "BPSec Security Source different from bundle Source", EXPFILL
}},
384 {&ei_ctxid_zero
, {"bpsec.ctxid_zero", PI_SECURITY
, PI_WARN
, "BPSec Security Context ID zero is reserved", EXPFILL
}},
385 {&ei_ctxid_priv
, {"bpsec.ctxid_priv", PI_SECURITY
, PI_NOTE
, "BPSec Security Context ID from private/experimental block", EXPFILL
}},
386 {&ei_target_invalid
, {"bpsec.target_invalid", PI_PROTOCOL
, PI_WARN
, "Target block number not present", EXPFILL
}},
387 {&ei_value_partial_decode
, {"bpsec.value_partial_decode", PI_UNDECODED
, PI_WARN
, "Value data not fully dissected", EXPFILL
}},
390 proto_bpsec
= proto_register_protocol(
391 "DTN Bundle Protocol Security",
396 proto_register_field_array(proto_bpsec
, fields
, array_length(fields
));
397 proto_register_subtree_array(ett
, array_length(ett
));
398 expert_module_t
*expert
= expert_register_protocol(proto_bpsec
);
399 expert_register_field_array(expert
, expertitems
, array_length(expertitems
));
401 secctx_dissectors
= register_custom_dissector_table("bpsec.ctx", "BPSec Context", proto_bpsec
, g_int64_hash
, g_int64_equal
, g_free
);
402 param_dissectors
= register_custom_dissector_table("bpsec.param", "BPSec Parameter", proto_bpsec
, bpsec_id_hash
, bpsec_id_equal
, g_free
);
403 result_dissectors
= register_custom_dissector_table("bpsec.result", "BPSec Result", proto_bpsec
, bpsec_id_hash
, bpsec_id_equal
, g_free
);
405 prefs_register_protocol(proto_bpsec
, reinit_bpsec
);
408 void proto_reg_handoff_bpsec(void) {
409 handle_cbor
= find_dissector("cbor");
411 /* Packaged extensions */
413 uint64_t *key
= g_new(uint64_t, 1);
414 *key
= BP_BLOCKTYPE_BIB
;
415 dissector_handle_t hdl
= create_dissector_handle_with_name_and_description(dissect_block_bib
, proto_bpsec
, NULL
, "Block Integrity Block");
416 dissector_add_custom_table_handle("bpv7.block_type", key
, hdl
);
419 uint64_t *key
= g_new(uint64_t, 1);
420 *key
= BP_BLOCKTYPE_BCB
;
421 dissector_handle_t hdl
= create_dissector_handle_with_name_and_description(dissect_block_bcb
, proto_bpsec
, NULL
, "Block Confidentiality Block");
422 dissector_add_custom_table_handle("bpv7.block_type", key
, hdl
);