Coverage Report

Created: 2025-08-04 07:15

/src/wireshark/epan/dissectors/packet-iso7816.c
Line
Count
Source (jump to first uncovered line)
1
/* packet-iso7816.c
2
 * Routines for packet dissection of generic ISO 7816 smart card messages
3
 * Copyright 2012-2013 by Martin Kaiser <martin@kaiser.cx>
4
 *
5
 * Wireshark - Network traffic analyzer
6
 * By Gerald Combs <gerald@wireshark.org>
7
 * Copyright 1998 Gerald Combs
8
 *
9
 * SPDX-License-Identifier: GPL-2.0-or-later
10
 */
11
12
/* This dissector supports the command and response apdu structure
13
 * as defined in ISO 7816-4. Detailed dissection of the APDUs defined
14
 * in the ISO 7816 specifications will be added in the future.
15
 *
16
 * The dissection of Answer To Reset (ATR) messages was made a separate
17
 * protocol so that it can be shared easily.
18
 */
19
20
21
#include "config.h"
22
23
#include <epan/packet.h>
24
#include <epan/expert.h>
25
#include <epan/decode_as.h>
26
27
void proto_register_iso7816(void);
28
void proto_reg_handoff_iso7816(void);
29
30
static int proto_iso7816;
31
static int proto_iso7816_atr;
32
33
static dissector_handle_t iso7816_handle;
34
static dissector_handle_t iso7816_atr_handle;
35
36
static wmem_tree_t *transactions;
37
38
static dissector_table_t iso7816_apdu_pld_table;
39
40
static int ett_iso7816;
41
static int ett_iso7816_class;
42
static int ett_iso7816_param;
43
static int ett_iso7816_p1;
44
static int ett_iso7816_p2;
45
static int ett_iso7816_atr;
46
static int ett_iso7816_atr_ta;
47
static int ett_iso7816_atr_td;
48
49
static int hf_iso7816_atr_init_char;
50
static int hf_iso7816_atr_t0;
51
static int hf_iso7816_atr_ta;
52
/* these two fields hold the converted values Fi and Di,
53
   not the binary representations FI and DI */
54
static int hf_iso7816_atr_ta1_fi;
55
static int hf_iso7816_atr_ta1_di;
56
static int hf_iso7816_atr_tb;
57
static int hf_iso7816_atr_tc;
58
static int hf_iso7816_atr_td;
59
static int hf_iso7816_atr_next_ta_present;
60
static int hf_iso7816_atr_next_tb_present;
61
static int hf_iso7816_atr_next_tc_present;
62
static int hf_iso7816_atr_next_td_present;
63
static int hf_iso7816_atr_k;
64
static int hf_iso7816_atr_t;
65
static int hf_iso7816_atr_hist_bytes;
66
static int hf_iso7816_atr_tck;
67
68
static int hf_iso7816_resp_in;
69
static int hf_iso7816_resp_to;
70
static int hf_iso7816_cla;
71
static int hf_iso7816_cla_sm;
72
static int hf_iso7816_cla_channel;
73
static int hf_iso7816_ins;
74
static int hf_iso7816_p1;
75
static int hf_iso7816_p2;
76
static int hf_iso7816_lc;
77
static int hf_iso7816_le;
78
static int hf_iso7816_body;
79
static int hf_iso7816_sw1;
80
static int hf_iso7816_sw2;
81
static int hf_iso7816_sel_file_ctrl;
82
static int hf_iso7816_sel_file_fci_req;
83
static int hf_iso7816_sel_file_occ;
84
static int hf_iso7816_read_rec_ef;
85
static int hf_iso7816_read_rec_usage;
86
static int hf_iso7816_get_resp;
87
static int hf_iso7816_offset_first_byte;
88
static int hf_iso7816_rfu;
89
static int hf_iso7816_application_data;
90
91
static expert_field ei_iso7816_atr_tck_not1;
92
93
0
#define ADDR_INTF "Interface"
94
0
#define ADDR_CARD "Card"
95
96
typedef struct _iso7816_transaction_t {
97
    uint32_t cmd_frame;
98
    uint32_t resp_frame;
99
    uint8_t  cmd_ins;  /* instruction byte in the command apdu */
100
    /* no need to add the channel number,
101
       the response contains no channel number to compare this to
102
       and the spec explicitly prohibits interleaving of command-response
103
       pairs, regardless of logical channels */
104
    dissector_handle_t handle;
105
} iso7816_transaction_t;
106
107
static const value_string iso7816_atr_init_char[] = {
108
    { 0x3B, "Direct convention (A==0, Z==1, MSB==m9)" },
109
    { 0x3F, "Inverse convention (A==1, Z==0, MSB==m2)" },
110
    { 0, NULL }
111
};
112
113
static const value_string iso7816_cla_sm[] = {
114
    { 0x00, "No SM" },
115
    { 0x01, "Proprietary SM" },
116
    { 0x02, "SM, command header not authenticated" },
117
    { 0x03, "SM, command header authenticated" },
118
    { 0, NULL }
119
};
120
121
#define INS_ERASE_BIN      0x0E
122
#define INS_VRFY           0x20
123
#define INS_MANAGE_CHANNEL 0x70
124
0
#define INS_EXT_AUTH       0x82
125
#define INS_GET_CHALLENGE  0x84
126
0
#define INS_SELECT_FILE    0xA4
127
0
#define INS_READ_BIN       0xB0
128
0
#define INS_READ_REC       0xB2
129
0
#define INS_GET_RESP       0xC0
130
#define INS_ENVELOPE       0xC2
131
0
#define INS_GET_DATA       0xCA
132
#define INS_WRITE_BIN      0xD0
133
#define INS_WRITE_REC      0xD2
134
#define INS_UPDATE_BIN     0xD6
135
#define INS_PUT_DATA       0xDA
136
#define INS_UPDATE_REC     0xDC
137
#define INS_APPEND_REC     0xE2
138
/* for our transaction tracking, not defined in the specification */
139
0
#define INS_INVALID        0x00
140
141
static const value_string iso7816_ins[] = {
142
    /* instructions defined in ISO 7816-4 */
143
    { INS_ERASE_BIN,      "Erase binary" },
144
    { INS_VRFY,           "Verify" },
145
    { INS_MANAGE_CHANNEL, "Manage channel" },
146
    { INS_EXT_AUTH,       "External authenticate" },
147
    { INS_GET_CHALLENGE,  "Get challenge" },
148
    { INS_SELECT_FILE,    "Select file" },
149
    { INS_READ_BIN,       "Read binary" },
150
    { INS_READ_REC,       "Read record" },
151
    { INS_GET_RESP,       "Get response" },
152
    { INS_ENVELOPE,       "Envelope" },
153
    { INS_GET_DATA,       "Get data" },
154
    { INS_WRITE_BIN,      "Write binary" },
155
    { INS_WRITE_REC,      "Write record" },
156
    { INS_UPDATE_BIN,     "Update binary" },
157
    { INS_PUT_DATA,       "Put data" },
158
    { INS_UPDATE_REC,     "Update record" },
159
    { INS_APPEND_REC,     "Append record" },
160
    { 0, NULL }
161
};
162
static value_string_ext iso7816_ins_ext = VALUE_STRING_EXT_INIT(iso7816_ins);
163
164
static const value_string iso7816_sel_file_ctrl[] = {
165
    { 0x00, "Select MF, DF or EF" },
166
    { 0x01, "Select child DF" },
167
    { 0x02, "Select EF under current DF" },
168
    { 0x03, "Select parent DF of the current DF" },
169
    { 0x04, "Direct selection by DF name" },
170
    { 0x08, "Selection by path from MF" },
171
    { 0x09, "Selection by path from current DF" },
172
    { 0, NULL }
173
};
174
static value_string_ext ext_iso7816_sel_file_ctrl =
175
    VALUE_STRING_EXT_INIT(iso7816_sel_file_ctrl);
176
177
static const value_string iso7816_sel_file_fci_req[] = {
178
    { 0x00, "Return FCI, optional template" },
179
    { 0x01, "Return FCP template" },
180
    { 0x02, "Return FMD template" },
181
    { 0, NULL }
182
};
183
static value_string_ext ext_iso7816_sel_file_fci_req =
184
    VALUE_STRING_EXT_INIT(iso7816_sel_file_fci_req);
185
186
static const value_string iso7816_sel_file_occ[] = {
187
    { 0x00, "First or only occurrence" },
188
    { 0x01, "Last occurrence" },
189
    { 0x02, "Next occurrence" },
190
    { 0x03, "Previous occurrence" },
191
    { 0, NULL }
192
};
193
static value_string_ext ext_iso7816_sel_file_occ =
194
    VALUE_STRING_EXT_INIT(iso7816_sel_file_occ);
195
196
0
#define READ_REC_USAGE_SINGLE 0x04
197
#define READ_REC_USAGE_START  0x05
198
static const value_string iso7816_read_rec_usage[] = {
199
    { READ_REC_USAGE_SINGLE, "Read record P1" },
200
    { READ_REC_USAGE_START,  "Read all records from P1 up to the last" },
201
    { 0, NULL }
202
};
203
static value_string_ext ext_iso7816_read_rec_usage =
204
    VALUE_STRING_EXT_INIT(iso7816_read_rec_usage);
205
206
static const range_string iso7816_sw1[] = {
207
  { 0x61, 0x61, "Normal processing" },
208
  { 0x62, 0x63, "Warning processing" },
209
  { 0x64, 0x65, "Execution error" },
210
  { 0x67, 0x6F, "Checking error" },
211
  { 0x90, 0x90, "Normal processing" },
212
  { 0,0,  NULL }
213
};
214
215
static const range_string iso7816_class_rvals[] = {
216
    {0x00, 0x0F, "structure and coding according to ISO/IEC 7816" },
217
    {0x10, 0x7F, "reserved for future use" },
218
    {0x80, 0x9F, "structure according to ISO/IEC 7816, coding is proprietary" },
219
    {0xA0, 0xAF, "structure and coding according to ISO/IEC 7816 unless specified otherwise by the application context" },
220
    {0xB0, 0xCF, "structure according to ISO/IEC 7816" },
221
    {0xD0, 0xFE, "proprietary structure and coding" },
222
    {0xFF, 0xFF, "reserved for Protocol Type Selection" },
223
    {0, 0,   NULL}
224
};
225
226
static const value_string unique_or_unused[] = {
227
    { 0, "or unused" },
228
    { 0, NULL }
229
};
230
231
static const value_string unique_max_num_available_bytes[] = {
232
    { 0, "maximum number of available bytes" },
233
    { 0, NULL }
234
};
235
236
static inline
237
uint16_t FI_to_Fi(uint8_t FI)
238
12
{
239
12
    if (FI<=1)
240
2
        return 372;
241
10
    else if (FI<=6)
242
0
        return (FI-1) * 372;
243
10
    else if (FI==9)
244
1
        return 512;
245
9
    else if (FI==10)
246
0
        return 768;
247
9
    else if (FI==11)
248
0
        return 1024;
249
9
    else if (FI==12)
250
8
        return 1536;
251
1
    else if (FI==13)
252
1
        return 2048;
253
254
0
    return 0; /* 0 means RFU (reserved for future use) here */
255
12
}
256
257
static inline
258
uint8_t DI_to_Di(uint8_t DI)
259
12
{
260
12
    if (DI>=1 && DI<=6)
261
2
        return 1 << (DI-1);
262
10
    else if (DI==8)
263
1
        return 12;
264
9
    else if (DI==9)
265
0
        return 20;
266
267
9
    return 0; /* 0 means RFU (reserved for future use) here */
268
12
}
269
270
/* dissect TA(ta_index) */
271
static void
272
dissect_iso7816_atr_ta(tvbuff_t *tvb, int offset, unsigned ta_index,
273
        packet_info *pinfo _U_, proto_tree *tree)
274
79
{
275
79
    uint8_t     ta, FI, DI;
276
79
    uint16_t    Fi;
277
79
    uint8_t     Di;
278
79
    proto_item *ta_it;
279
79
    proto_tree *ta_tree;
280
281
79
    ta = tvb_get_uint8(tvb, offset);
282
79
    ta_it = proto_tree_add_uint_format(tree, hf_iso7816_atr_ta,
283
79
            tvb, offset, 1, ta,
284
79
            "Interface character TA(%d): 0x%02x", ta_index, ta);
285
79
    ta_tree = proto_item_add_subtree(ta_it, ett_iso7816_atr_ta);
286
287
79
    if (ta_index==1) {
288
12
        FI = (tvb_get_uint8(tvb, offset) & 0xF0) >> 4;
289
12
        Fi = FI_to_Fi(FI);
290
12
        if (Fi>0) {
291
12
            proto_tree_add_uint_format(ta_tree, hf_iso7816_atr_ta1_fi,
292
12
                    tvb, offset, 1, Fi,
293
12
                    "Clock rate conversion factor Fi: %d (FI 0x%x)",
294
12
                    Fi, FI);
295
12
        }
296
297
12
        DI = tvb_get_uint8(tvb, offset) & 0x0F;
298
12
        Di = DI_to_Di(DI);
299
12
        if (Di>0) {
300
3
            proto_tree_add_uint_format(ta_tree, hf_iso7816_atr_ta1_di,
301
3
                    tvb, offset, 1, Di,
302
3
                    "Baud rate adjustment factor Di: %d (DI 0x%x)",
303
3
                    Di, DI);
304
3
        }
305
12
    }
306
79
}
307
308
static int
309
dissect_iso7816_atr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
310
17
{
311
17
    int         offset=0;
312
17
    uint8_t     init_char;
313
17
    unsigned    i=0;  /* loop index for TA(i)...TD(i) */
314
17
    proto_item *proto_it;
315
17
    proto_tree *proto_tr;
316
17
    uint8_t     tb, tc, td, k=0;
317
17
    int         tck_len;
318
319
    /* we need at least the initial char TS and the format char T0 */
320
17
    if (tvb_captured_length(tvb) < 2)
321
0
        return 0; /* no ATR sequence */
322
323
17
    init_char = tvb_get_uint8(tvb, offset);
324
17
    if (init_char!=0x3B && init_char!=0x3F)
325
4
        return 0;
326
327
13
    proto_it = proto_tree_add_protocol_format(tree, proto_iso7816_atr,
328
13
                tvb, 0, -1, "ISO 7816 ATR");
329
13
    proto_tr = proto_item_add_subtree(proto_it, ett_iso7816_atr);
330
331
13
    col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, "ATR");
332
333
    /* ISO 7816-4, section 4 indicates that concatenations are big endian */
334
13
    proto_tree_add_item(proto_tr, hf_iso7816_atr_init_char,
335
13
            tvb, offset, 1, ENC_BIG_ENDIAN);
336
13
    offset++;
337
338
107
    do {
339
107
        proto_item *td_it;
340
107
        proto_tree *td_tree;
341
342
        /* for i==0, this is the T0 byte, otherwise it's the TD(i) byte
343
           in each loop, we dissect T0/TD(i) and TA(i+1), TB(i+1), TC(i+1) */
344
107
        td = tvb_get_uint8(tvb, offset);
345
107
        if (i==0) {
346
13
            td_it = proto_tree_add_item(proto_tr, hf_iso7816_atr_t0,
347
13
                    tvb, offset, 1, ENC_BIG_ENDIAN);
348
13
        }
349
94
        else {
350
94
            td_it = proto_tree_add_uint_format(proto_tr, hf_iso7816_atr_td,
351
94
                    tvb, offset, 1, td,
352
94
                    "Interface character TD(%d): 0x%02x", i, td);
353
94
        }
354
107
        td_tree = proto_item_add_subtree(td_it, ett_iso7816_atr_td);
355
356
107
        proto_tree_add_boolean_format(td_tree, hf_iso7816_atr_next_ta_present,
357
107
                tvb, offset, 1, td&0x10,
358
107
                "TA(%d) present: %s", i+1, td&0x10 ? "True" : "False");
359
107
        proto_tree_add_boolean_format(td_tree, hf_iso7816_atr_next_tb_present,
360
107
                tvb, offset, 1, td&0x20,
361
107
                "TB(%d) present: %s", i+1, td&0x20 ? "True" : "False");
362
107
        proto_tree_add_boolean_format(td_tree, hf_iso7816_atr_next_tc_present,
363
107
                tvb, offset, 1, td&0x40,
364
107
                "TC(%d) present: %s", i+1, td&0x40 ? "True" : "False");
365
107
        proto_tree_add_boolean_format(td_tree, hf_iso7816_atr_next_td_present,
366
107
                tvb, offset, 1, td&0x80,
367
107
                "TD(%d) present: %s", i+1, td&0x80 ? "True" : "False");
368
369
107
        col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
370
107
                "TA(%d)=%s TB(%d)=%s TC(%d)=%s TD(%d)=%s",
371
107
                i+1, td&0x10 ? "True" : "False",
372
107
                i+1, td&0x20 ? "True" : "False",
373
107
                i+1, td&0x40 ? "True" : "False",
374
107
                i+1, td&0x80 ? "True" : "False");
375
376
107
        if (i==0) {
377
13
            k = td&0x0F;   /* number of historical bytes */
378
13
            proto_tree_add_item(td_tree, hf_iso7816_atr_k,
379
13
                    tvb, offset, 1, ENC_BIG_ENDIAN);
380
13
        }
381
94
        else {
382
94
            proto_tree_add_item(td_tree, hf_iso7816_atr_t,
383
94
                    tvb, offset, 1, ENC_BIG_ENDIAN);
384
94
        }
385
107
        offset++;
386
387
107
        if (td&0x10) {
388
            /* we read TA(i+1), see comment above */
389
79
            dissect_iso7816_atr_ta(tvb, offset, i+1, pinfo, proto_tr);
390
79
            offset++;
391
79
        }
392
107
        if (td&0x20) {
393
36
            tb = tvb_get_uint8(tvb, offset);
394
36
            proto_tree_add_uint_format(proto_tr, hf_iso7816_atr_tb,
395
36
                    tvb, offset, 1, tb,
396
36
                    "Interface character TB(%d): 0x%02x", i+1, tb);
397
36
            offset++;
398
36
        }
399
107
        if (td&0x40) {
400
43
            tc = tvb_get_uint8(tvb, offset);
401
43
            proto_tree_add_uint_format(proto_tr, hf_iso7816_atr_tc,
402
43
                    tvb, offset, 1, tc,
403
43
                    "Interface character TC(%d): 0x%02x", i+1, tc);
404
43
            offset++;
405
43
        }
406
407
107
        i++;
408
107
    } while (td&0x80);
409
410
13
    if (k>0) {
411
4
        proto_tree_add_item(proto_tr, hf_iso7816_atr_hist_bytes,
412
4
                tvb, offset, k, ENC_NA);
413
4
        offset += k;
414
4
    }
415
416
13
    tck_len = tvb_reported_length_remaining(tvb, offset);
417
    /* tck is either absent or exactly one byte */
418
13
    if (tck_len==1) {
419
0
        proto_tree_add_item(proto_tr, hf_iso7816_atr_tck,
420
0
                tvb, offset, 1, ENC_BIG_ENDIAN);
421
0
        offset++;
422
0
    }
423
13
    else if (tck_len>1) {
424
13
        proto_tree_add_expert(proto_tr, pinfo, &ei_iso7816_atr_tck_not1,
425
13
                tvb, offset, tck_len);
426
13
    }
427
428
13
    proto_item_set_len(proto_it, offset);
429
13
    return offset;
430
17
}
431
432
/* Dissect the class byte. Return 1 if the APDU's structure and coding
433
   adhere to ISO 7816. In this case, we can dissect the rest of the
434
   APDU. Otherwise, return -1. We may then pass the APDU to other
435
   dissectors. */
436
static int
437
dissect_iso7816_class(tvbuff_t *tvb, int offset,
438
        packet_info *pinfo _U_, proto_tree *tree)
439
0
{
440
0
    proto_item *class_item;
441
0
    proto_tree *class_tree;
442
0
    uint8_t     dev_class;
443
444
0
    class_item = proto_tree_add_item(tree, hf_iso7816_cla,
445
0
            tvb, offset, 1, ENC_BIG_ENDIAN);
446
0
    class_tree = proto_item_add_subtree(class_item, ett_iso7816_class);
447
448
0
    dev_class = tvb_get_uint8(tvb, offset);
449
450
0
    if (dev_class>=0x10 && dev_class<=0x7F) {
451
        /* these values are RFU. */
452
0
        return -1;
453
0
    }
454
455
0
    if (dev_class>=0xD0 && dev_class<=0xFE) {
456
        /* proprietary structure and coding */
457
0
        return -1;
458
0
    }
459
460
0
    if (dev_class==0xFF) {
461
        /* reserved for Protocol Type Selection */
462
0
        return -1;
463
0
    }
464
465
    /* If we made it this far, the structrue of the APDU is compliant
466
       with ISO 7816. */
467
468
0
    proto_tree_add_item(class_tree, hf_iso7816_cla_sm,
469
0
            tvb, offset, 1, ENC_BIG_ENDIAN);
470
471
0
    proto_tree_add_item(class_tree, hf_iso7816_cla_channel,
472
0
            tvb, offset, 1, ENC_BIG_ENDIAN);
473
474
0
    if (dev_class>=0x80 && dev_class<=0x9F) {
475
        /* structure according to ISO 7816, coding is proprietary */
476
0
        return -1;
477
0
    }
478
479
0
    if (dev_class>=0xB0 && dev_class<=0xCF) {
480
        /* structure according to ISO 7816 */
481
0
        return -1;
482
0
    }
483
484
    /* both structure and coding according to ISO 7816 */
485
0
    return 1;
486
0
}
487
488
/* dissect the parameters p1 and p2
489
   return number of dissected bytes or -1 for error */
490
static int
491
dissect_iso7816_params(uint8_t ins, tvbuff_t *tvb, int offset,
492
                 packet_info *pinfo _U_, proto_tree *tree)
493
0
{
494
0
    int         offset_start, p1_offset, p2_offset;
495
0
    proto_tree *params_tree;
496
0
    uint8_t     p1, p2;
497
0
    proto_item *p1_it = NULL, *p2_it = NULL;
498
0
    proto_tree *p1_tree = NULL, *p2_tree = NULL;
499
0
    proto_item *p1_p2_it = NULL;
500
0
    uint16_t    P1P2;
501
0
    uint32_t    ef, read_rec_usage;
502
503
0
    offset_start = offset;
504
505
0
    params_tree = proto_tree_add_subtree(tree, tvb, offset_start, 2,
506
0
                                ett_iso7816_param, NULL, "Parameters");
507
508
0
    p1 = tvb_get_uint8(tvb,offset);
509
0
    p1_it = proto_tree_add_item(params_tree, hf_iso7816_p1, tvb,
510
0
            offset, 1, ENC_BIG_ENDIAN);
511
0
    p1_offset = offset;
512
0
    offset++;
513
0
    p2 = tvb_get_uint8(tvb,offset);
514
0
    p2_it = proto_tree_add_item(params_tree, hf_iso7816_p2,
515
0
            tvb, offset, 1, ENC_BIG_ENDIAN);
516
0
    p2_offset = offset;
517
0
    offset++;
518
0
    P1P2 = (p1<<8|p2);
519
520
0
    switch (ins) {
521
0
        case INS_EXT_AUTH:
522
0
            if (p1>0) {
523
0
                proto_item_append_text(p1_it,
524
0
                        " (reference of the algorithm on the card)");
525
0
            }
526
0
            proto_item_append_text(p2_it, " (reference of the secret)");
527
0
            break;
528
0
        case INS_SELECT_FILE:
529
0
            proto_item_append_text(p1_it, " (selection control)");
530
0
            p1_tree = proto_item_add_subtree(p1_it, ett_iso7816_p1);
531
0
            proto_tree_add_item(p1_tree, hf_iso7816_sel_file_ctrl,
532
0
                    tvb, p1_offset, 1, ENC_BIG_ENDIAN);
533
0
            proto_item_append_text(p2_it, " (selection options)");
534
0
            p2_tree = proto_item_add_subtree(p2_it, ett_iso7816_p2);
535
0
            proto_tree_add_item(p2_tree, hf_iso7816_sel_file_fci_req,
536
0
                    tvb, p2_offset, 1, ENC_BIG_ENDIAN);
537
0
            proto_tree_add_item(p2_tree, hf_iso7816_sel_file_occ,
538
0
                    tvb, p2_offset, 1, ENC_BIG_ENDIAN);
539
0
            break;
540
0
        case INS_READ_BIN:
541
0
            if (p1&0x80) {
542
                /* XXX - b5-b1 of P1 == short ef identifier for the selected file */
543
                /* XXX - P2 == offset for the read */
544
0
            }
545
0
            else {
546
0
                p1_p2_it = proto_tree_add_uint(params_tree, hf_iso7816_offset_first_byte,
547
0
                        tvb, offset_start, offset-offset_start, P1P2);
548
0
                col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
549
0
                        "offset %d", P1P2);
550
0
            }
551
0
            break;
552
0
        case INS_READ_REC:
553
0
            proto_item_append_text(p1_it, " (record number)");
554
0
            proto_item_append_text(p2_it, " (reference control)");
555
0
            p2_tree = proto_item_add_subtree(p2_it, ett_iso7816_p2);
556
0
            proto_tree_add_item_ret_uint(p2_tree, hf_iso7816_read_rec_ef,
557
0
                    tvb, p2_offset, 1, ENC_BIG_ENDIAN, &ef);
558
0
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "EF %d", ef);
559
0
            proto_tree_add_item_ret_uint(p2_tree, hf_iso7816_read_rec_usage,
560
0
                    tvb, p2_offset, 1, ENC_BIG_ENDIAN, &read_rec_usage);
561
0
            if (read_rec_usage == READ_REC_USAGE_SINGLE) {
562
0
                col_append_sep_fstr(
563
0
                        pinfo->cinfo, COL_INFO, NULL, "record %d", p1);
564
0
            }
565
0
            break;
566
0
        case INS_GET_RESP:
567
0
            p1_p2_it = proto_tree_add_uint_format(params_tree, hf_iso7816_get_resp,
568
0
                    tvb, offset_start, offset-offset_start, P1P2,
569
0
                    "Both should be 0x00, other values are RFU");
570
0
            break;
571
0
        case INS_GET_DATA:
572
0
            if (P1P2<=0x003F || (0x0300<=P1P2 && P1P2<=0x3FFF)) {
573
0
                p1_p2_it = proto_tree_add_uint(params_tree, hf_iso7816_rfu,
574
0
                        tvb, offset_start, offset-offset_start, P1P2);
575
0
            }
576
0
            else if (0x0100<=P1P2 && P1P2<=0x01FF) {
577
0
                p1_p2_it = proto_tree_add_uint(params_tree, hf_iso7816_application_data,
578
0
                        tvb, offset_start, offset-offset_start, P1P2);
579
0
            }
580
0
            break;
581
0
        default:
582
0
            break;
583
0
    }
584
585
0
    proto_item_set_generated(p1_p2_it);
586
587
0
    return 2;
588
0
}
589
590
static int
591
dissect_iso7816_le(
592
        tvbuff_t *tvb, int offset, packet_info *pinfo _U_, proto_tree *tree)
593
0
{
594
0
    proto_tree_add_item(tree, hf_iso7816_le, tvb, offset, 1, ENC_BIG_ENDIAN);
595
596
0
    return 1;
597
0
}
598
599
600
static int
601
dissect_iso7816_cmd_apdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
602
0
{
603
0
    iso7816_transaction_t *iso7816_trans = NULL;
604
0
    proto_item            *trans_ti = NULL;
605
0
    int                    ret;
606
0
    int                    offset = 0;
607
0
    uint8_t                ins;
608
0
    int                    body_len;
609
0
    uint8_t                lc;
610
611
612
0
    if (PINFO_FD_VISITED(pinfo)) {
613
0
        iso7816_trans = (iso7816_transaction_t *)wmem_tree_lookup32(
614
0
                transactions, pinfo->num);
615
0
        if (iso7816_trans && iso7816_trans->cmd_frame==pinfo->num &&
616
0
                iso7816_trans->resp_frame!=0) {
617
0
            trans_ti = proto_tree_add_uint_format(tree, hf_iso7816_resp_in,
618
0
                           NULL, 0, 0, iso7816_trans->resp_frame,
619
0
                           "Response in frame %d", iso7816_trans->resp_frame);
620
0
            proto_item_set_generated(trans_ti);
621
0
        }
622
0
    }
623
0
    else {
624
0
        if (transactions) {
625
0
            iso7816_trans = wmem_new(wmem_file_scope(), iso7816_transaction_t);
626
0
            iso7816_trans->cmd_frame = pinfo->num;
627
0
            iso7816_trans->resp_frame = 0;
628
0
            iso7816_trans->cmd_ins = INS_INVALID;
629
0
            iso7816_trans->handle = NULL;
630
631
0
            wmem_tree_insert32(transactions,
632
0
                    iso7816_trans->cmd_frame, (void *)iso7816_trans);
633
0
        }
634
0
    }
635
636
0
    ret = dissect_iso7816_class(tvb, offset, pinfo, tree);
637
0
    if (ret == -1) {
638
        /* the class byte says that the remaining APDU is not
639
            in ISO7816 format */
640
641
0
        if (iso7816_trans) {
642
0
            iso7816_trans->handle =
643
0
                dissector_get_payload_handle(iso7816_apdu_pld_table);
644
0
            if (iso7816_trans->handle != NULL) {
645
0
                ret = call_dissector(iso7816_trans->handle, tvb, pinfo, tree);
646
0
                if (ret == 0) {
647
0
                    col_append_sep_str(pinfo->cinfo, COL_INFO, NULL,
648
0
                            "Command APDU using proprietary format");
649
0
                    return 1; /* we only dissected the class byte */
650
0
                }
651
0
            }
652
0
        }
653
654
0
        return ret;
655
0
    }
656
0
    offset += ret;
657
658
0
    ins = tvb_get_uint8(tvb, offset);
659
0
    proto_tree_add_item(tree, hf_iso7816_ins, tvb, offset, 1, ENC_BIG_ENDIAN);
660
0
    col_append_sep_str(pinfo->cinfo, COL_INFO, NULL,
661
0
            val_to_str_ext_const(ins, &iso7816_ins_ext, "Unknown instruction"));
662
0
    offset++;
663
    /* if we just created a new transaction, we can now fill in the cmd id */
664
0
    if (iso7816_trans && iso7816_trans->cmd_ins==INS_INVALID)
665
0
        iso7816_trans->cmd_ins = ins;
666
667
0
    ret = dissect_iso7816_params(ins, tvb, offset, pinfo, tree);
668
0
    if (ret>0)
669
0
        offset += ret;
670
671
    /* for now, we support only short length fields
672
       based on infos from the ATR, we could support extended length fields too */
673
0
    body_len = tvb_reported_length_remaining(tvb, offset);
674
675
    /* nothing to do for body_len==0 */
676
0
    if (body_len==1) {
677
0
        offset += dissect_iso7816_le(tvb, offset, pinfo, tree);
678
0
    }
679
0
    else if (body_len>1) {
680
0
        lc = tvb_get_uint8(tvb, offset);
681
0
        proto_tree_add_item(
682
0
                tree, hf_iso7816_lc, tvb, offset, 1, ENC_BIG_ENDIAN);
683
0
        offset++;
684
0
        if (lc>0) {
685
0
            proto_tree_add_item(tree, hf_iso7816_body, tvb, offset, lc, ENC_NA);
686
0
            offset += lc;
687
0
        }
688
0
        if (tvb_reported_length_remaining(tvb, offset)>0) {
689
0
            offset += dissect_iso7816_le(tvb, offset, pinfo, tree);
690
0
        }
691
0
    }
692
693
0
    return offset;
694
0
}
695
696
static int
697
dissect_iso7816_resp_apdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
698
0
{
699
0
    iso7816_transaction_t *iso7816_trans;
700
0
    proto_item            *trans_ti = NULL;
701
0
    const char            *cmd_ins_str;
702
0
    int                    offset = 0;
703
0
    int                    body_len;
704
705
0
    col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, "Response APDU");
706
707
0
    if (transactions) {
708
        /* receive the largest key that is less than or equal to our frame
709
           number */
710
0
        iso7816_trans = (iso7816_transaction_t *)wmem_tree_lookup32_le(
711
0
                transactions, pinfo->num);
712
0
        if (iso7816_trans) {
713
0
            if (iso7816_trans->resp_frame==0) {
714
                /* there's a pending request, this packet is the response */
715
0
                iso7816_trans->resp_frame = pinfo->num;
716
0
            }
717
718
0
            if (iso7816_trans->resp_frame== pinfo->num) {
719
                /* we found the request that corresponds to our response */
720
0
                cmd_ins_str = val_to_str_const(iso7816_trans->cmd_ins,
721
0
                        iso7816_ins, "Unknown instruction");
722
0
                trans_ti = proto_tree_add_uint_format(tree, hf_iso7816_resp_to,
723
0
                        NULL, 0, 0, iso7816_trans->cmd_frame,
724
0
                        "Response to frame %d (%s)",
725
0
                        iso7816_trans->cmd_frame, cmd_ins_str);
726
0
                proto_item_set_generated(trans_ti);
727
728
0
                col_append_sep_fstr(pinfo->cinfo, COL_INFO, " ",
729
0
                        "(to %s)", cmd_ins_str);
730
0
            }
731
732
0
            if (iso7816_trans->handle != NULL)
733
0
                call_dissector(iso7816_trans->handle, tvb, pinfo, tree);
734
0
        }
735
0
    }
736
737
    /* - 2 bytes SW1, SW2 */
738
0
    body_len = tvb_reported_length_remaining(tvb, offset) - 2;
739
740
0
    if (body_len>0) {
741
0
        proto_tree_add_item(tree, hf_iso7816_body,
742
0
                tvb, offset, body_len, ENC_NA);
743
0
        offset += body_len;
744
0
    }
745
746
0
    if (tvb_reported_length_remaining(tvb, offset) >= 2) {
747
0
        proto_tree_add_item(tree, hf_iso7816_sw1,
748
0
                tvb, offset, 1, ENC_BIG_ENDIAN);
749
0
        offset++;
750
0
        proto_tree_add_item(tree, hf_iso7816_sw2,
751
0
                tvb, offset, 1, ENC_BIG_ENDIAN);
752
0
        offset++;
753
0
    }
754
755
0
    return offset;
756
0
}
757
758
static int
759
dissect_iso7816(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
760
0
{
761
0
    int         offset = 0;
762
0
    proto_item *tree_ti;
763
0
    proto_tree *iso7816_tree;
764
0
    bool        is_atr = false;
765
766
0
    if (pinfo->p2p_dir!=P2P_DIR_SENT && pinfo->p2p_dir!=P2P_DIR_RECV)
767
0
        return 0;
768
769
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "ISO 7816");
770
0
    col_clear(pinfo->cinfo, COL_INFO);
771
772
0
    tree_ti = proto_tree_add_protocol_format(tree, proto_iso7816,
773
0
            tvb, 0, tvb_reported_length(tvb), "ISO 7816");
774
0
    iso7816_tree = proto_item_add_subtree(tree_ti, ett_iso7816);
775
776
    /* per our definition, sent/received is from the perspective of the interface
777
       i.e sent is from interface to card, received is from card to interface */
778
0
    if (pinfo->p2p_dir==P2P_DIR_SENT) {
779
0
        set_address(&pinfo->src, AT_STRINGZ,
780
0
                (int)strlen(ADDR_INTF)+1, ADDR_INTF);
781
0
        set_address(&pinfo->dst, AT_STRINGZ,
782
0
                (int)strlen(ADDR_CARD)+1, ADDR_CARD);
783
0
        proto_item_append_text(tree_ti, " Command APDU");
784
0
        offset = dissect_iso7816_cmd_apdu(tvb, pinfo, iso7816_tree);
785
0
    }
786
0
    else if (pinfo->p2p_dir==P2P_DIR_RECV) {
787
0
        set_address(&pinfo->src, AT_STRINGZ,
788
0
                (int)strlen(ADDR_CARD)+1, ADDR_CARD);
789
0
        set_address(&pinfo->dst, AT_STRINGZ,
790
0
                (int)strlen(ADDR_INTF)+1, ADDR_INTF);
791
792
0
        if (iso7816_atr_handle) {
793
0
            offset = call_dissector_only(iso7816_atr_handle,
794
0
                    tvb, pinfo, iso7816_tree, NULL);
795
0
            if (offset > 0)
796
0
                is_atr = true;
797
0
        }
798
0
        if (!is_atr) {
799
0
            proto_item_append_text(tree_ti, " Response APDU");
800
0
            offset = dissect_iso7816_resp_apdu(tvb, pinfo, iso7816_tree);
801
0
        }
802
0
    }
803
804
0
    return offset;
805
0
}
806
807
void
808
proto_register_iso7816(void)
809
14
{
810
14
    static hf_register_info hf[] = {
811
14
        { &hf_iso7816_atr_init_char,
812
14
            { "Initial character", "iso7816.atr.init_char",
813
14
                FT_UINT8, BASE_HEX, VALS(iso7816_atr_init_char), 0, NULL, HFILL }
814
14
        },
815
14
        { &hf_iso7816_atr_t0,
816
14
            { "Format character T0", "iso7816.atr.t0",
817
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
818
14
        },
819
14
        { &hf_iso7816_atr_ta,
820
14
            { "Interface character TA(i)", "iso7816.atr.ta",
821
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
822
14
        },
823
14
        { &hf_iso7816_atr_ta1_fi,
824
14
            { "Fi", "iso7816.atr.ta1.fi",
825
14
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
826
14
        },
827
14
        { &hf_iso7816_atr_ta1_di,
828
14
            { "Di", "iso7816.atr.ta1.di",
829
14
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
830
14
        },
831
14
        { &hf_iso7816_atr_tb,
832
14
            { "Interface character TB(i)", "iso7816.atr.tb",
833
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
834
14
        },
835
14
        { &hf_iso7816_atr_tc,
836
14
            { "Interface character TC(i)", "iso7816.atr.tc",
837
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
838
14
        },
839
14
        { &hf_iso7816_atr_td,
840
14
            { "Interface character TD(i)", "iso7816.atr.td",
841
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
842
14
        },
843
14
        { &hf_iso7816_atr_next_ta_present,
844
14
            { "TA(i+1) present", "iso7816.atr.next_ta_present",
845
14
                FT_BOOLEAN, 8, NULL, 0x10, NULL, HFILL }
846
14
        },
847
14
        { &hf_iso7816_atr_next_tb_present,
848
14
            { "TB(i+1) present", "iso7816.atr.next_tb_present",
849
14
                FT_BOOLEAN, 8, NULL, 0x20, NULL, HFILL }
850
14
        },
851
14
        { &hf_iso7816_atr_next_tc_present,
852
14
            { "TC(i+1) present", "iso7816.atr.next_tc_present",
853
14
                FT_BOOLEAN, 8, NULL, 0x40, NULL, HFILL }
854
14
        },
855
14
        { &hf_iso7816_atr_next_td_present,
856
14
            { "TD(i+1) present", "iso7816.atr.next_td_present",
857
14
                FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL }
858
14
        },
859
14
        { &hf_iso7816_atr_k,
860
14
            { "Number K of historical bytes", "iso7816.atr.k",
861
14
                FT_UINT8, BASE_DEC, NULL, 0x0F, NULL, HFILL }
862
14
        },
863
14
        { &hf_iso7816_atr_t,
864
14
            { "Protocol reference T", "iso7816.atr.t",
865
14
                FT_UINT8, BASE_HEX, NULL, 0x0F, NULL, HFILL }
866
14
        },
867
14
        { &hf_iso7816_atr_hist_bytes,
868
14
            { "Historical bytes", "iso7816.atr.historical_bytes",
869
14
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
870
14
        },
871
14
        { &hf_iso7816_atr_tck,
872
14
            { "Check character TCK", "iso7816.atr.tck",
873
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
874
14
        },
875
14
        { &hf_iso7816_resp_in,
876
14
            { "Response In", "iso7816.resp_in",
877
14
                FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0,
878
14
                "The response to this command is in this frame", HFILL }
879
14
        },
880
14
        { &hf_iso7816_resp_to,
881
14
            { "Response To", "iso7816.resp_to",
882
14
                FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0,
883
14
                "This is the response to the command in this frame", HFILL }
884
14
        },
885
14
        { &hf_iso7816_cla,
886
14
            { "Class", "iso7816.apdu.cla",
887
14
                FT_UINT8, BASE_HEX|BASE_RANGE_STRING, RVALS(iso7816_class_rvals), 0, NULL , HFILL }
888
14
        },
889
14
        { &hf_iso7816_cla_sm,
890
14
            { "Secure Messaging", "iso7816.apdu.cla.sm",
891
14
                FT_UINT8, BASE_HEX, VALS(iso7816_cla_sm), 0x0C, NULL , HFILL }
892
14
        },
893
14
        { &hf_iso7816_cla_channel,
894
14
            { "Logical channel number", "iso7816.apdu.cla.channel",
895
14
                FT_UINT8, BASE_HEX|BASE_SPECIAL_VALS, VALS(unique_or_unused), 0x03, NULL , HFILL }
896
14
        },
897
14
        { &hf_iso7816_ins,
898
14
            { "Instruction", "iso7816.apdu.ins",
899
14
                FT_UINT8, BASE_HEX | BASE_EXT_STRING, &iso7816_ins_ext, 0, NULL, HFILL }
900
14
        },
901
14
        { &hf_iso7816_p1,
902
14
            { "Parameter 1", "iso7816.apdu.p1",
903
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
904
14
        },
905
14
        { &hf_iso7816_p2,
906
14
            { "Parameter 2", "iso7816.apdu.p2",
907
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
908
14
        },
909
14
        { &hf_iso7816_lc,
910
14
            { "Length field Lc", "iso7816.apdu.lc",
911
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
912
14
        },
913
14
        { &hf_iso7816_le,
914
14
            { "Expected response length Le", "iso7816.apdu.le",
915
14
                FT_UINT8, BASE_HEX|BASE_SPECIAL_VALS, VALS(unique_max_num_available_bytes), 0, NULL, HFILL }
916
14
        },
917
14
        { &hf_iso7816_body,
918
14
            { "APDU Body", "iso7816.apdu.body",
919
14
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
920
14
        },
921
14
        { &hf_iso7816_sw1,
922
14
            { "Status Word SW1", "iso7816.apdu.sw1", FT_UINT8,
923
14
                BASE_RANGE_STRING|BASE_HEX, RVALS(iso7816_sw1), 0, NULL, HFILL }
924
14
        },
925
14
        { &hf_iso7816_sw2,
926
14
            { "Status Word SW2", "iso7816.apdu.sw2",
927
14
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
928
14
        },
929
14
        { &hf_iso7816_sel_file_ctrl,
930
14
            { "Selection control", "iso7816.apdu.select_file.ctrl",
931
14
                FT_UINT8, BASE_HEX | BASE_EXT_STRING,
932
14
                &ext_iso7816_sel_file_ctrl, 0, NULL, HFILL }
933
14
        },
934
14
        { &hf_iso7816_sel_file_fci_req,
935
14
            { "File control information request", "iso7816.apdu.select_file.fci_req",
936
14
                FT_UINT8, BASE_HEX | BASE_EXT_STRING,
937
14
                &ext_iso7816_sel_file_fci_req, 0x0C, NULL, HFILL }
938
14
        },
939
14
        { &hf_iso7816_sel_file_occ,
940
14
            { "Occurrence", "iso7816.apdu.select_file.occurrence",
941
14
                FT_UINT8, BASE_HEX | BASE_EXT_STRING,
942
14
                &ext_iso7816_sel_file_occ, 0x03, NULL, HFILL }
943
14
        },
944
14
        { &hf_iso7816_read_rec_ef,
945
14
            { "Short EF identifier", "iso7816.apdu.read_rec.ef",
946
14
                FT_UINT8, BASE_HEX, NULL, 0xF8, NULL, HFILL }
947
14
        },
948
14
        { &hf_iso7816_read_rec_usage,
949
14
            { "Usage", "iso7816.apdu.read_rec.usage",
950
14
                FT_UINT8, BASE_HEX | BASE_EXT_STRING,
951
14
                &ext_iso7816_read_rec_usage, 0x07, NULL, HFILL }
952
14
        },
953
14
        { &hf_iso7816_offset_first_byte,
954
14
            { "Offset of the first byte to read", "iso7816.offset_first_byte",
955
14
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
956
14
        },
957
14
        { &hf_iso7816_get_resp,
958
14
            { "GetResp", "iso7816.get_resp",
959
14
                FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
960
14
        },
961
14
        { &hf_iso7816_rfu,
962
14
            { "RFU", "iso7816.rfu",
963
14
                FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
964
14
        },
965
14
        { &hf_iso7816_application_data,
966
14
            { "Application data (proprietary coding)", "iso7816.application_data",
967
14
                FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
968
14
        },
969
14
    };
970
14
    static int *ett[] = {
971
14
        &ett_iso7816,
972
14
        &ett_iso7816_class,
973
14
        &ett_iso7816_param,
974
14
        &ett_iso7816_p1,
975
14
        &ett_iso7816_p2,
976
14
        &ett_iso7816_atr,
977
14
        &ett_iso7816_atr_ta,
978
14
        &ett_iso7816_atr_td
979
14
    };
980
981
14
    static ei_register_info ei[] = {
982
14
        { &ei_iso7816_atr_tck_not1, { "iso7816.atr.tck.not1", PI_PROTOCOL, PI_WARN, "TCK byte must either be absent or exactly one byte", EXPFILL }}
983
14
    };
984
985
14
    expert_module_t* expert_iso7816;
986
987
14
    proto_iso7816 = proto_register_protocol("ISO/IEC 7816", "ISO 7816", "iso7816");
988
14
    proto_register_field_array(proto_iso7816, hf, array_length(hf));
989
14
    proto_register_subtree_array(ett, array_length(ett));
990
14
    expert_iso7816 = expert_register_protocol(proto_iso7816);
991
14
    expert_register_field_array(expert_iso7816, ei, array_length(ei));
992
993
14
    iso7816_handle = register_dissector("iso7816", dissect_iso7816, proto_iso7816);
994
995
14
    transactions = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
996
997
14
    proto_iso7816_atr = proto_register_protocol_in_name_only("ISO/IEC 7816-3", "ISO 7816-3", "iso7816.atr", proto_iso7816, FT_PROTOCOL);
998
14
    iso7816_atr_handle = register_dissector("iso7816.atr", dissect_iso7816_atr, proto_iso7816_atr);
999
1000
14
    iso7816_apdu_pld_table =
1001
14
        register_decode_as_next_proto(proto_iso7816,
1002
14
                "iso7816.apdu_payload",
1003
14
                "ISO7816 proprietary APDU dissector", NULL);
1004
14
}
1005
1006
1007
void proto_reg_handoff_iso7816(void)
1008
14
{
1009
14
    dissector_add_for_decode_as("usbccid.subdissector", iso7816_handle);
1010
14
    dissector_add_for_decode_as("iso14443.subdissector", iso7816_handle);
1011
14
}
1012
1013
1014
/*
1015
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1016
 *
1017
 * Local variables:
1018
 * c-basic-offset: 4
1019
 * tab-width: 8
1020
 * indent-tabs-mode: nil
1021
 * End:
1022
 *
1023
 * vi: set shiftwidth=4 tabstop=8 expandtab:
1024
 * :indentSize=4:tabSize=8:noTabs=true:
1025
 */