-
Notifications
You must be signed in to change notification settings - Fork 3
/
apdu.go
388 lines (314 loc) · 12.4 KB
/
apdu.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// Package apdu implements parsing and conversion of Application Protocol Data Units (APDU) which is the communication format between a card and off-card applications. The format of the APDU is defined in ISO specification 7816-4.
// The package has support for extended length APDUs as well.
package apdu
import (
"encoding/binary"
"encoding/hex"
"strings"
"github.com/pkg/errors"
)
const (
// OffsetCla defines the offset to the Cla byte of a Capdu.
OffsetCla int = 0
// OffsetIns defines the offset to the Ins byte of a Capdu.
OffsetIns int = 1
// OffsetP1 defines the offset to the P1 byte of a Capdu.
OffsetP1 int = 2
// OffsetP2 defines the offset to the P2 byte of a Capdu.
OffsetP2 int = 3
// OffsetLcStandard defines the offset to the LC byte of a standard length Capdu.
OffsetLcStandard int = 4
// OffsetLcExtended defines the offset to the LC byte of an extended length Capdu.
OffsetLcExtended int = 5
// OffsetCdataStandard defines the offset to the beginning of the data field of a standard length Capdu.
OffsetCdataStandard int = 5
// OffsetCdataExtended defines the offset to the beginning of the data field of an extended length Capdu.
OffsetCdataExtended int = 7
// MaxLenCommandDataStandard defines the maximum command data length of a standard length Capdu.
MaxLenCommandDataStandard int = 255
// MaxLenResponseDataStandard defines the maximum response data length of a standard length RAPDU.
MaxLenResponseDataStandard int = 256
// MaxLenCommandDataExtended defines the maximum command data length of an extended length Capdu.
MaxLenCommandDataExtended int = 65535
// MaxLenResponseDataExtended defines the maximum response data length of an extended length RAPDU.
MaxLenResponseDataExtended int = 65536
// LenHeader defines the length of the header of an APDU.
LenHeader int = 4
// LenLCStandard defines the length of the LC of a standard length APDU.
LenLCStandard int = 1
// LenLCExtended defines the length of the LC of an extended length APDU.
LenLCExtended int = 3
// LenResponseTrailer defines the length of the trailer of a Response APDU.
LenResponseTrailer int = 2
packageTag string = "skythen/apdu"
)
// Capdu is a Command APDU.
type Capdu struct {
Cla byte // Cla is the class byte.
Ins byte // Ins is the instruction byte.
P1 byte // P1 is the p1 byte.
P2 byte // P2 is the p2 byte.
Data []byte // Data is the data field.
Ne int // Ne is the total number of expected response data byte (not LE encoded).
}
// ParseCapdu parses a Command APDU and returns a Capdu.
func ParseCapdu(c []byte) (*Capdu, error) {
if len(c) < LenHeader || len(c) > 65544 {
return nil, errors.Errorf("%s: invalid length - Capdu must consist of at least 4 byte and maximum of 65544 byte, got %d", packageTag, len(c))
}
// CASE 1 command: only HEADER
if len(c) == LenHeader {
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2]}, nil
}
// check for zero byte
if c[OffsetLcStandard] == 0x00 {
// check for extended length Capdu
if len(c[OffsetLcExtended:]) > 0 {
// EXTENDED CASE 2 command: HEADER | LE
// in this case no LC is present, but the two byte LE with leading zero byte
if len(c) == LenHeader+LenLCExtended {
ne := 0
le := int(binary.BigEndian.Uint16(c[OffsetLcExtended:]))
if le == 0x00 {
ne = MaxLenResponseDataExtended
} else {
ne = le
}
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Ne: ne}, nil
}
bodyLen := len(c) - LenHeader
lc := int(binary.BigEndian.Uint16(c[OffsetLcExtended : OffsetLcExtended+2]))
if lc != bodyLen-LenLCExtended && lc != bodyLen-LenLCExtended-2 {
return nil, errors.Errorf("%s: invalid LC value - LC indicates data length %d", packageTag, lc)
}
data := c[OffsetCdataExtended : OffsetCdataExtended+lc]
// EXTENDED CASE 3 command: HEADER | LC | DATA
if len(c) == LenHeader+LenLCExtended+len(data) {
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: data, Ne: 0}, nil
}
// EXTENDED CASE 4 command: HEADER | LC | DATA | LE
ne := 0
le := int(binary.BigEndian.Uint16(c[len(c)-2:]))
if le == 0x00 {
ne = MaxLenResponseDataExtended
} else {
ne = le
}
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: data, Ne: ne}, nil
}
}
ne := 0
// STANDARD CASE 2 command: HEADER | LE
if len(c) == LenHeader+LenLCStandard {
// in this case, no LC is present
ne = int(c[OffsetLcStandard])
if ne == 0 {
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: nil, Ne: MaxLenResponseDataStandard}, nil
}
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: nil, Ne: ne}, nil
}
bodyLen := len(c) - LenHeader
// check if lc indicates valid length
lc := int(c[OffsetLcStandard])
if lc != bodyLen-LenLCStandard && lc != bodyLen-LenLCStandard-1 {
return nil, errors.Errorf("%s: invalid Lc value - Lc indicates length %d", packageTag, lc)
}
data := c[OffsetCdataStandard : OffsetCdataStandard+lc]
// STANDARD CASE 3 command: HEADER | LC | DATA
if len(c) == LenHeader+LenLCStandard+len(data) {
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: data}, nil
}
// STANDARD CASE 4 command: HEADER | LC | DATA | LE
if le := int(c[len(c)-1]); le == 0 {
ne = MaxLenResponseDataStandard
} else {
ne = le
}
return &Capdu{Cla: c[OffsetCla], Ins: c[OffsetIns], P1: c[OffsetP1], P2: c[OffsetP2], Data: data, Ne: ne}, nil
}
// ParseCapduHexString decodes the hex-string representation of a Command APDU, calls ParseCapdu and returns a Capdu.
func ParseCapduHexString(s string) (*Capdu, error) {
if len(s)%2 != 0 {
return nil, errors.Errorf("%s: uneven number of hex characters", packageTag)
}
if len(s) < 8 || len(s) > 131088 {
return nil, errors.Errorf("%s: invalid length of hex string - a Capdu must consist of at least 4 byte and maximum of 65544 byte, got %d", packageTag, len(s)/2)
}
b, err := hex.DecodeString(s)
if err != nil {
return nil, errors.Wrapf(err, "%s: hex conversion error", packageTag)
}
return ParseCapdu(b)
}
// Bytes returns the byte representation of the Capdu.
func (c *Capdu) Bytes() ([]byte, error) {
dataLen := len(c.Data)
if dataLen > MaxLenCommandDataExtended {
return nil, errors.Errorf("%s: len of Capdu.Data %d exceeds maximum allowed length of %d",
packageTag, len(c.Data), MaxLenCommandDataExtended)
}
if c.Ne > MaxLenResponseDataExtended {
return nil, errors.Errorf("%s: ne %d exceeds maximum allowed length of %d",
packageTag, len(c.Data), MaxLenResponseDataExtended)
}
ca := c.determineCase()
switch ca {
case 1:
return []byte{c.Cla, c.Ins, c.P1, c.P2}, nil
case 2:
// CASE 2: HEADER | LE
if c.Ne > MaxLenResponseDataStandard {
le := make([]byte, LenLCExtended) // first byte is zero byte, so LE length is equal to LC length
if c.Ne == MaxLenResponseDataExtended {
le[1] = 0x00
le[2] = 0x00
} else {
le[1] = (byte)((c.Ne >> 8) & 0xFF)
le[2] = (byte)(c.Ne & 0xFF)
}
result := make([]byte, 0, LenHeader+LenLCExtended)
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2}...)
result = append(result, le...)
return result, nil
}
//standard format
result := make([]byte, 0, LenHeader+LenLCStandard)
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2}...)
if c.Ne == MaxLenResponseDataStandard {
result = append(result, 0x00)
} else {
result = append(result, byte(c.Ne))
}
return result, nil
case 3:
// CASE 3: HEADER | LC | DATA
if len(c.Data) > MaxLenCommandDataStandard {
// extended length format
lc := make([]byte, LenLCExtended)
lc[1] = (byte)((dataLen >> 8) & 0xFF)
lc[2] = (byte)(dataLen & 0xFF)
result := make([]byte, 0, LenHeader+LenLCExtended+dataLen)
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2}...)
result = append(result, lc...)
result = append(result, c.Data...)
return result, nil
}
//standard format
result := make([]byte, 0, LenHeader+1+dataLen)
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2, byte(dataLen)}...)
result = append(result, c.Data...)
return result, nil
}
// CASE 4: HEADER | LC | DATA | LE
if c.Ne > MaxLenResponseDataStandard || len(c.Data) > MaxLenCommandDataStandard {
// extended length format
lc := make([]byte, LenLCExtended) // first byte is zero byte
lc[1] = (byte)((dataLen >> 8) & 0xFF)
lc[2] = (byte)(dataLen & 0xFF)
le := make([]byte, 2)
if c.Ne == MaxLenResponseDataExtended {
le[0] = 0x00
le[1] = 0x00
} else {
le[0] = (byte)((c.Ne >> 8) & 0xFF)
le[1] = (byte)(c.Ne & 0xFF)
}
result := make([]byte, 0, LenHeader+LenLCExtended+dataLen+len(le))
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2}...)
result = append(result, lc...)
result = append(result, c.Data...)
result = append(result, le...)
return result, nil
}
//standard format
result := make([]byte, 0, LenHeader+LenLCStandard+dataLen+1)
result = append(result, []byte{c.Cla, c.Ins, c.P1, c.P2, byte(dataLen)}...)
result = append(result, c.Data...)
result = append(result, byte(c.Ne))
return result, nil
}
func (c *Capdu) determineCase() int {
if len(c.Data) == 0 && c.Ne == 0 {
return 1
}
if len(c.Data) == 0 && c.Ne > 0 {
return 2
}
if len(c.Data) != 0 && c.Ne == 0 {
return 3
}
return 4
}
// String calls Bytes and returns the hex encoded string representation of the Capdu.
func (c *Capdu) String() (string, error) {
b, err := c.Bytes()
if err != nil {
return "", err
}
return strings.ToUpper(hex.EncodeToString(b)), nil
}
// IsExtendedLength returns true if the Capdu has extended length (len of Data > 65535 or Ne > 65536), else false.
func (c *Capdu) IsExtendedLength() bool {
return c.Ne > MaxLenResponseDataStandard || len(c.Data) > MaxLenCommandDataStandard
}
// Rapdu is a Response APDU.
type Rapdu struct {
Data []byte // Data is the data field.
SW1 byte // SW1 is the first byte of a status word.
SW2 byte // SW2 is the second byte of a status word.
}
// ParseRapdu parses a Response APDU and returns a Rapdu.
func ParseRapdu(b []byte) (*Rapdu, error) {
if len(b) < LenResponseTrailer || len(b) > 65538 {
return nil, errors.Errorf("%s: invalid length - a RAPDU must consist of at least 2 byte and maximum of 65538 byte, got %d", packageTag, len(b))
}
if len(b) == LenResponseTrailer {
return &Rapdu{SW1: b[0], SW2: b[1]}, nil
}
return &Rapdu{Data: b[:len(b)-LenResponseTrailer], SW1: b[len(b)-2], SW2: b[len(b)-1]}, nil
}
// ParseRapduHexString decodes the hex-string representation of a Response APDU, calls ParseRapdu and returns a Rapdu.
func ParseRapduHexString(s string) (*Rapdu, error) {
if len(s)%2 != 0 {
return nil, errors.Errorf("%s: uneven number of hex characters", packageTag)
}
if len(s) < 4 || len(s) > 131076 {
return nil, errors.Errorf("%s: invalid length of hex string - a RAPDU must consist of at least 2 byte and maximum of 65538 byte, got %d", packageTag, len(s)/2)
}
tmp, err := hex.DecodeString(s)
if err != nil {
return nil, errors.Wrapf(err, "%s: hex conversion error", packageTag)
}
return ParseRapdu(tmp)
}
// Bytes returns the byte representation of the RAPDU.
func (r *Rapdu) Bytes() ([]byte, error) {
if len(r.Data) > MaxLenResponseDataExtended {
return nil, errors.Errorf("%s: len of Rapdu.Data %d exceeds maximum allowed length of %d",
packageTag, len(r.Data), MaxLenResponseDataExtended)
}
b := make([]byte, 0, len(r.Data)+2)
b = append(b, r.Data...)
b = append(b, []byte{r.SW1, r.SW2}...)
return b, nil
}
// String calls Bytes and returns the hex encoded string representation of the RAPDU.
func (r *Rapdu) String() (string, error) {
b, err := r.Bytes()
if err != nil {
return "", err
}
return strings.ToUpper(hex.EncodeToString(b)), nil
}
// IsSuccess returns true if the RAPDU indicates the successful execution of a command ('0x61xx' or '0x9000'), otherwise false.
func (r *Rapdu) IsSuccess() bool {
return r.SW1 == 0x61 || r.SW1 == 0x90 && r.SW2 == 0x00
}
// IsWarning returns true if the RAPDU indicates the execution of a command with a warning ('0x62xx' or '0x63xx'), otherwise false.
func (r *Rapdu) IsWarning() bool {
return r.SW1 == 0x62 || r.SW1 == 0x63
}
// IsError returns true if the RAPDU indicates an error during the execution of a command ('0x64xx', '0x65xx' or from '0x67xx' to 0x6Fxx'), otherwise false.
func (r *Rapdu) IsError() bool {
return (r.SW1 == 0x64 || r.SW1 == 0x65) || (r.SW1 >= 0x67 && r.SW1 <= 0x6F)
}