2 * Routines for IEEE 802.1AE MACsec dissection
3 * Copyright 2013, Allan W. Nielsen <anielsen@vitesse.com>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include <epan/packet.h>
15 #include <epan/etypes.h>
17 #include <wsutil/array.h>
18 #include <wsutil/wsgcrypt.h>
20 void proto_register_macsec(void);
21 void proto_reg_handoff_macsec(void);
23 static dissector_handle_t macsec_handle
;
24 static dissector_handle_t ethertype_handle
;
26 /* TCI/AN field masks */
28 #define TCI_V_MASK 0x80
29 #define TCI_ES_MASK 0x40
30 #define TCI_SC_MASK 0x20
31 #define TCI_SCB_MASK 0x10
32 #define TCI_E_MASK 0x08
33 #define TCI_C_MASK 0x04
36 #define AES_KEY_LEN (16)
40 #define HWADDR_LEN (6)
41 #define ETHERTYPE_LEN (2)
42 #define ETHHDR_LEN ((HWADDR_LEN * 2) + ETHERTYPE_LEN)
44 #define SECTAG_LEN_WITH_SC (14)
45 #define SECTAG_LEN_WITHOUT_SC (6)
47 #define AAD_ENCRYPTED_LEN (28)
49 #define MAX_PAYLOAD_LEN (1500)
52 static int proto_macsec
;
53 static int hf_macsec_TCI
;
54 static int hf_macsec_TCI_V
;
55 static int hf_macsec_TCI_ES
;
56 static int hf_macsec_TCI_SC
;
57 static int hf_macsec_TCI_SCB
;
58 static int hf_macsec_TCI_E
;
59 static int hf_macsec_TCI_C
;
60 static int hf_macsec_AN
;
61 static int hf_macsec_SL
;
62 static int hf_macsec_PN
;
63 static int hf_macsec_SCI_system_identifier
;
64 static int hf_macsec_SCI_port_identifier
;
65 static int hf_macsec_etype
;
66 static int hf_macsec_eth_padding
;
67 static int hf_macsec_ICV
;
68 static int hf_macsec_ICV_check_success
;
69 static int hf_macsec_decrypted_data
;
71 /* Initialize the subtree pointers */
72 static int ett_macsec
;
73 static int ett_macsec_tci
;
75 /* Decrypting payload buffer */
76 static uint8_t macsec_payload
[MAX_PAYLOAD_LEN
];
79 static uint8_t aad
[MAX_PAYLOAD_LEN
];
81 static const char *psk
= NULL
;
82 static unsigned char *psk_bin
= NULL
;
84 /* convert a 0-terminated preference key_string that contains a hex number
85 * into its binary representation
86 * e.g. key_string "abcd" will be converted into two bytes 0xab, 0xcd
87 * return the number of binary bytes or -1 for error */
89 pref_key_string_to_bin(const char *key_string
, unsigned char **key_bin
)
95 ws_return_val_if(key_bin
== NULL
, -1);
97 if (NULL
== key_string
) {
102 key_string_len
= (int)strlen(key_string
);
103 if (key_string_len
!= 2 * AES_KEY_LEN
) {
105 return (key_string_len
/ 2);
108 *key_bin
= (unsigned char *)g_malloc(key_string_len
/ 2);
111 for (i
= 0, j
= 0; i
< (key_string_len
- 1); i
+= 2, j
++) {
112 input
[0] = key_string
[0 + i
];
113 input
[1] = key_string
[1 + i
];
115 /* attention, brackets are required */
116 (*key_bin
)[j
] = (unsigned char)strtoul((const char *)&input
, NULL
, 16);
119 return (key_string_len
/ 2);
122 /* Code to actually dissect the packets */
123 static int dissect_macsec(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
124 unsigned sectag_length
, data_length
, short_length
, icv_length
;
125 unsigned fcs_length
= 0;
126 unsigned data_offset
, icv_offset
;
127 uint8_t tci_an_field
;
129 int icv_check_success
= PROTO_CHECKSUM_E_BAD
;
130 bool key_provided
= false;
131 bool encrypted
= false;
132 unsigned payload_len
;
135 gcry_cipher_hd_t handle
= 0;
137 proto_item
*macsec_item
;
138 proto_tree
*macsec_tree
= NULL
;
142 /* Construct the 14-byte ethernet header (6-byte dst MAC, 6-byte src MAC, 2-byte ethernet type)(part of aad) */
143 uint8_t header
[ETHHDR_LEN
] = {0};
144 if (pinfo
->dl_dst
.data
!= NULL
)
146 memcpy(header
, pinfo
->dl_dst
.data
, HWADDR_LEN
);
148 if (pinfo
->dl_src
.data
!= NULL
)
150 memcpy((header
+ HWADDR_LEN
), pinfo
->dl_src
.data
, HWADDR_LEN
);
153 uint8_t e_type
[ETHERTYPE_LEN
] = {(uint8_t)(ETHERTYPE_MACSEC
>> 8), (uint8_t)(ETHERTYPE_MACSEC
& 0xff)};
154 memcpy(header
+ (ETHHDR_LEN
- ETHERTYPE_LEN
), &e_type
, ETHERTYPE_LEN
);
156 /* Parse the encryption key, and set the flag to indicate if the key is provided*/
157 if (pref_key_string_to_bin(psk
, &psk_bin
) == AES_KEY_LEN
) {
161 tci_an_field
= tvb_get_uint8(tvb
, 0);
163 /* if the frame is an encrypted MACsec frame, remember that */
164 if (((tci_an_field
& TCI_E_MASK
) == TCI_E_MASK
) || ((tci_an_field
& TCI_C_MASK
) == TCI_C_MASK
)) {
168 if ((tci_an_field
& TCI_V_MASK
) != 0) { /* version must be zero */
172 icv_length
= ICV_LEN
; /* Fixed size for version 0 */
174 if (tci_an_field
& TCI_SC_MASK
) {
175 sectag_length
= SECTAG_LEN_WITH_SC
; /* optional SCI present */
177 sectag_length
= SECTAG_LEN_WITHOUT_SC
;
180 /* Check for length too short */
181 if (tvb_captured_length(tvb
) <= (sectag_length
+ icv_length
)) {
185 /* short length field: 1..47 bytes, 0 means 48 bytes or more */
186 short_length
= (uint32_t)tvb_get_uint8(tvb
, 1);
188 /* Get the payload section */
189 if (short_length
!= 0) {
190 data_length
= short_length
;
191 fcs_length
= tvb_reported_length(tvb
) - sectag_length
- icv_length
- short_length
;
194 * We know the length, so set it here for the previous ethertype
195 * dissector. This will allow us to calculate the FCS correctly.
197 set_actual_length(tvb
, short_length
+ sectag_length
+ icv_length
);
200 * This assumes that no FCS is present after the ICV, which might not be true!
201 * Workaround: turn Ethernet "Assume packets have FCS" = Always, when FCS present.
202 * If there's another (non FCS) trailer afterwards, set Ethernet
203 * "Fixed ethernet trailer length".
205 * TODO: Find better heuristic to detect presence of FCS / trailers.
207 data_length
= tvb_reported_length(tvb
) - sectag_length
- icv_length
;
209 data_offset
= sectag_length
;
210 icv_offset
= data_length
+ data_offset
;
212 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "MACSEC");
213 col_set_str(pinfo
->cinfo
, COL_INFO
, "MACsec frame");
217 macsec_item
= proto_tree_add_item(tree
, proto_macsec
, tvb
, 0, sectag_length
, ENC_NA
);
219 /* Add the EtherType too since this is authentication only. */
220 macsec_item
= proto_tree_add_item(tree
, proto_macsec
, tvb
, 0, sectag_length
+ ETHERTYPE_LEN
, ENC_NA
);
222 macsec_tree
= proto_item_add_subtree(macsec_item
, ett_macsec
);
224 static int * const flags
[] = {
234 proto_tree_add_bitmask_with_flags(macsec_tree
, tvb
, 0,
235 hf_macsec_TCI
, ett_macsec_tci
, flags
, ENC_NA
, BMT_NO_TFS
);
238 proto_tree_add_item(macsec_tree
, hf_macsec_AN
, tvb
, offset
, 1, ENC_NA
);
241 proto_tree_add_item(macsec_tree
, hf_macsec_SL
, tvb
, offset
, 1, ENC_NA
);
244 proto_tree_add_item(macsec_tree
, hf_macsec_PN
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
247 if (sectag_length
== SECTAG_LEN_WITH_SC
) {
248 proto_tree_add_item(macsec_tree
, hf_macsec_SCI_system_identifier
, tvb
, offset
, HWADDR_LEN
, ENC_NA
);
249 offset
+= HWADDR_LEN
;
251 proto_tree_add_item(macsec_tree
, hf_macsec_SCI_port_identifier
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
255 next_tvb
= tvb_new_subset_length(tvb
, data_offset
, data_length
);
257 /* Try to decrypt/authenticate the data if a key is provided */
260 uint8_t iv
[IV_LEN
] = {0};
261 tvb_memcpy(tvb
, iv
, 6, HWADDR_LEN
); // SI System identifier (source MAC)
262 tvb_memcpy(tvb
, iv
+ 6, 12, 2); // PI Port identifier
263 tvb_memcpy(tvb
, iv
+ 8, 2, 4); // PN Packet number
265 if (gcry_cipher_open(&handle
, GCRY_CIPHER_AES
, GCRY_CIPHER_MODE_GCM
, 0))
267 ws_warning("gcry_cipher_open fail");
271 if (gcry_cipher_setkey(handle
, psk_bin
, AES_KEY_LEN
))
273 ws_warning("gcry_cipher_setkey fail");
277 if (gcry_cipher_setiv(handle
, iv
, sizeof(iv
)))
279 ws_warning("gcry_cipher_setiv fail");
284 payload_len
= tvb_captured_length(next_tvb
);
286 /* For authenticated and encrypted data, the AAD is always 28 bytes and consists of the
287 header data and security tag. */
288 const uint8_t *buf
= tvb_get_ptr(tvb
, 0, SECTAG_LEN_WITH_SC
);
290 memcpy(aad
, header
, ETHHDR_LEN
);
291 memcpy(aad
+ ETHHDR_LEN
, buf
, SECTAG_LEN_WITH_SC
);
293 /* Authenticate with the AAD. */
294 if (gcry_cipher_authenticate(handle
, aad
, AAD_ENCRYPTED_LEN
))
296 ws_warning("gcry_cipher_authenticate fail");
300 tvb_memcpy(next_tvb
, macsec_payload
, 0, payload_len
);
302 /* Attempt to decrypt into the local buffer. */
303 if (gcry_cipher_decrypt(handle
, macsec_payload
, payload_len
, NULL
, 0))
305 ws_warning("gcry_cipher_decrypt fail");
310 /* the frame length for the AAD is the complete frame including ethernet header but without the ICV */
311 unsigned frame_len
= (ETHHDR_LEN
+ tvb_captured_length(tvb
)) - ICV_LEN
;
313 // For authenticated-only data, the aad is the frame minus the ICV
314 // We have to build the AAD since the incoming TVB payload does not have the Ethernet header.
315 payload_len
= frame_len
- ETHHDR_LEN
;
317 // Copy the header we built previously, then the frame data up to the ICV.
318 memcpy(aad
, header
, ETHHDR_LEN
);
319 memcpy((aad
+ ETHHDR_LEN
), tvb_get_ptr(tvb
, 0, payload_len
), payload_len
);
321 /* Authenticate with the AAD. */
322 if (gcry_cipher_authenticate(handle
, aad
, frame_len
))
324 ws_warning("gcry_cipher_authenticate fail");
329 /* Fetch the ICV and use it to verify the decrypted data. */
330 uint8_t icv
[ICV_LEN
] = {0};
331 tvb_memcpy(tvb
, icv
, icv_offset
, icv_length
);
332 if (gcry_cipher_checktag(handle
, icv
, sizeof(icv
)))
334 ws_info("gcry_cipher_checktag fail");
338 /* Everything checks out! */
339 icv_check_success
= PROTO_CHECKSUM_E_GOOD
;
344 gcry_cipher_close(handle
);
346 // Show the original data.
347 call_data_dissector(next_tvb
, pinfo
, tree
);
349 ethertype_data_t ethertype_data
;
351 /* default the next tv_buff to remove ICV */
352 /* lets hand over a buffer without ICV to limit effect of wrong padding calculation */
353 next_tvb
= tvb_new_subset_length(tvb
, data_offset
+ 2, data_length
- 2);
354 ethertype_data
.etype
= tvb_get_ntohs(tvb
, data_offset
);
356 // If the data are ok, attempt to continue dissection.
357 if (PROTO_CHECKSUM_E_GOOD
== icv_check_success
)
362 plain_tvb
= tvb_new_child_real_data(next_tvb
, (uint8_t *)wmem_memdup(pinfo
->pool
, macsec_payload
, payload_len
),
363 payload_len
, payload_len
);
364 ethertype_data
.etype
= tvb_get_ntohs(plain_tvb
, 0);
366 /* lets hand over a buffer without ICV to limit effect of wrong padding calculation */
367 next_tvb
= tvb_new_subset_length(plain_tvb
, 2, payload_len
- 2);
369 /* show the decrypted data and original ethertype */
370 proto_tree_add_item(tree
, hf_macsec_decrypted_data
, plain_tvb
, 0, payload_len
, ENC_NA
);
372 /* add the decrypted data as a data source for the next dissectors */
373 add_new_data_source(pinfo
, plain_tvb
, "Decrypted Data");
375 /* The ethertype is the one from the start of the decrypted data. */
376 proto_tree_add_item(tree
, hf_macsec_etype
, plain_tvb
, 0, 2, ENC_BIG_ENDIAN
);
379 /* lets hand over a buffer without ICV to limit effect of wrong padding calculation */
380 next_tvb
= tvb_new_subset_length(tvb
, data_offset
+ 2, data_length
- 2);
382 /* The ethertype is the original from the unencrypted data. */
383 proto_tree_add_item(tree
, hf_macsec_etype
, tvb
, data_offset
, 2, ENC_BIG_ENDIAN
);
387 /* add the ICV to the sectag subtree */
388 proto_tree_add_item(macsec_tree
, hf_macsec_ICV
, tvb
, icv_offset
, icv_length
, ENC_NA
);
389 proto_tree_set_appendix(macsec_tree
, tvb
, icv_offset
, icv_length
);
391 /* If the frame decoded, or was not encrypted, continue dissection */
392 if ((PROTO_CHECKSUM_E_GOOD
== icv_check_success
) || (false == encrypted
)) {
393 /* help eth padding calculation by subtracting length of the sectag, ethertype, icv, and fcs */
394 int pkt_len_saved
= pinfo
->fd
->pkt_len
;
396 pinfo
->fd
->pkt_len
-= (sectag_length
+ 2 + icv_length
+ fcs_length
);
398 /* continue dissection */
399 ethertype_data
.payload_offset
= 0;
400 ethertype_data
.fh_tree
= macsec_tree
;
401 /* XXX: This could be another trailer, a FCS, or the Ethernet dissector
402 * incorrectly detecting padding if we don't have short_length. */
403 ethertype_data
.trailer_id
= hf_macsec_eth_padding
;
404 ethertype_data
.fcs_len
= 0;
406 call_dissector_with_data(ethertype_handle
, next_tvb
, pinfo
, tree
, ðertype_data
);
408 /* restore original value */
409 pinfo
->fd
->pkt_len
= pkt_len_saved
;
412 /* Set icv_check_success to the correct status */
414 icv_check_success
= PROTO_CHECKSUM_E_UNVERIFIED
;
417 /* If the frame was not verified correctly, append this string to the info line
418 * after dissection completes.
420 if (PROTO_CHECKSUM_E_BAD
== icv_check_success
) {
421 col_append_str(pinfo
->cinfo
, COL_INFO
, " [Authentication fail]");
424 /* add a flag indicating the frame is or is not verified. */
425 macsec_item
= proto_tree_add_uint(macsec_tree
, hf_macsec_ICV_check_success
, tvb
, 0, 0, icv_check_success
);
426 proto_item_set_generated(macsec_item
);
428 /* We called set_actual length if fcs_length !=0, so length is adjusted. */
429 return tvb_captured_length(tvb
);
433 proto_register_macsec(void)
436 static hf_register_info hf
[] = {
438 { "TCI", "macsec.TCI", FT_UINT8
, BASE_HEX
,
439 NULL
, TCI_MASK
, "TAG Control Information", HFILL
}
442 { "VER", "macsec.TCI.V", FT_UINT8
, BASE_HEX
,
443 NULL
, TCI_V_MASK
, "Version", HFILL
}
446 { "ES", "macsec.TCI.ES", FT_BOOLEAN
, 8,
447 TFS(&tfs_set_notset
), TCI_ES_MASK
, "End Station", HFILL
}
450 { "SC", "macsec.TCI.SC", FT_BOOLEAN
, 8,
451 TFS(&tfs_set_notset
), TCI_SC_MASK
, "Secure Channel", HFILL
}
453 { &hf_macsec_TCI_SCB
,
454 { "SCB", "macsec.TCI.SCB", FT_BOOLEAN
, 8,
455 TFS(&tfs_set_notset
), TCI_SCB_MASK
, "Single Copy Broadcast", HFILL
}
458 { "E", "macsec.TCI.E", FT_BOOLEAN
, 8,
459 TFS(&tfs_set_notset
), TCI_E_MASK
, "Encryption", HFILL
}
462 { "C", "macsec.TCI.C", FT_BOOLEAN
, 8,
463 TFS(&tfs_set_notset
), TCI_C_MASK
, "Changed Text", HFILL
}
466 { "AN", "macsec.AN", FT_UINT8
, BASE_HEX
,
467 NULL
, AN_MASK
, "Association Number", HFILL
}
470 { "Short length", "macsec.SL", FT_UINT8
, BASE_DEC
,
471 NULL
, 0, NULL
, HFILL
}
474 { "Packet number", "macsec.PN", FT_UINT32
, BASE_DEC
,
475 NULL
, 0, NULL
, HFILL
}
477 { &hf_macsec_SCI_system_identifier
,
478 { "System Identifier", "macsec.SCI.system_identifier", FT_ETHER
, BASE_NONE
,
479 NULL
, 0, NULL
, HFILL
}
481 { &hf_macsec_SCI_port_identifier
,
482 { "Port Identifier", "macsec.SCI.port_identifier", FT_UINT16
, BASE_DEC
,
483 NULL
, 0, NULL
, HFILL
}
486 { "Ethertype", "macsec.etype", FT_UINT16
, BASE_HEX
,
487 NULL
, 0, NULL
, HFILL
}
489 { &hf_macsec_eth_padding
,
490 { "Padding", "macsec.eth_padding", FT_BYTES
, BASE_NONE
,
491 NULL
, 0, NULL
, HFILL
}
494 { "ICV", "macsec.ICV", FT_BYTES
, BASE_NONE
,
495 NULL
, 0, NULL
, HFILL
}
497 { &hf_macsec_ICV_check_success
,
498 { "Frame authentication status", "macsec.auth_status", FT_UINT8
, BASE_DEC
,
499 NULL
, 0, NULL
, HFILL
}
501 { &hf_macsec_decrypted_data
,
502 { "Decrypted Data", "macsec.decrypted_data", FT_BYTES
, BASE_NONE
,
503 NULL
, 0, NULL
, HFILL
}
507 /* Setup protocol subtree array */
508 static int *ett
[] = {
513 /* Register the protocol name and description */
514 proto_macsec
= proto_register_protocol("802.1AE Security tag", "MACsec", "macsec");
516 /* Required function calls to register the header fields and subtrees used */
517 proto_register_field_array(proto_macsec
, hf
, array_length(hf
));
518 proto_register_subtree_array(ett
, array_length(ett
));
520 /* Register the dissector */
521 macsec_handle
= register_dissector("macsec", dissect_macsec
, proto_macsec
);
523 /* Register the text box to enter the pre-shared key */
524 module
= prefs_register_protocol(proto_macsec
, NULL
);
525 prefs_register_string_preference(module
, "psk", "MACsec Pre-Shared Key",
526 "Pre-Shared AES-GCM-128 Key as a HEX string (16 bytes).",
531 proto_reg_handoff_macsec(void)
533 dissector_add_uint("ethertype", ETHERTYPE_MACSEC
, macsec_handle
);
535 ethertype_handle
= find_dissector("ethertype");
539 * Editor modelines - https://www.wireshark.org/tools/modelines.html
544 * indent-tabs-mode: nil
547 * vi: set shiftwidth=4 tabstop=8 expandtab:
548 * :indentSize=4:tabSize=8:noTabs=true: