-
Notifications
You must be signed in to change notification settings - Fork 34
/
ChainBuffer.java
531 lines (432 loc) · 19.4 KB
/
ChainBuffer.java
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
/******************************************************************************
* MIT License
*
* Project: OpenFIPS201
* Copyright: (c) 2017 Commonwealth of Australia
* Author: Kim O'Sullivan - Makina (kim@makina.com.au)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
package com.makina.security.openfips201;
import javacard.framework.APDU;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
/**
* ChainBuffer supports reading and writing of buffers larger than a single APDU frame. It takes
* away the responsibility of dealing with the actual read and write operations, state management
* and transaction management from the APDU processing functions. Instead each function that needs
* to support chained reads or writes simply calls this method with a buffer to act on and
* ChainBuffer will do the rest.
*/
final class ChainBuffer {
// The chain context is inactive and buffer does not point to anything
static final short STATE_NONE = (short) 0x00;
// The chain context is reading (supporting multiple GET RESPONSE commands)
static final short STATE_OUTGOING = (short) 0x01;
// The chain context is writing (supporting chained commands of whatever INS started it)
static final short STATE_INCOMING_OBJECT = (short) 0x02;
// The chain context is writing (supporting chained commands of whatever INS started it)
static final short STATE_INCOMING_APDU = (short) 0x03;
// The chain state
private static final short CONTEXT_STATE = (short) 0;
// The current offset in the data buffer
private static final short CONTEXT_OFFSET = (short) 1;
// The initial offset in the data buffer that was supplied by the caller
private static final short CONTEXT_INITIAL = (short) 2;
// The total length of the data buffer
private static final short CONTEXT_LENGTH = (short) 3;
// The number of remaining bytes to write or read in the buffer
private static final short CONTEXT_REMAINING = (short) 4;
// Indicates whether the buffer should be wiped on completion of the chain
private static final short CONTEXT_CLEAR_ON_COMPLETE = (short) 5;
// Indicates whether the chain is operating inside a transaction
private static final short CONTEXT_TRANSACTION = (short) 6;
// The APDU header used for tracking incoming data
// NOTE: It's cheaper to use 4 shorts in this array than to allocate a separate 4 bytes, as the
// minimum allocation size is 32 bytes anyway
private static final short CONTEXT_APDU_CLASS = (short) 8;
private static final short CONTEXT_APDU_P1P2 = (short) 9;
// Total length of the context transient object
private static final short LENGTH_CONTEXT = (short) 10;
// APDU constants
private static final byte CLA_CHAINING = (byte) 0x10;
private static final byte INS_GET_RESPONSE = (byte) 0xC0;
// A pointer to our read/write data buffer
private final Object[] dataPtr;
// Holds transient context information about the current chain
private final short[] context;
ChainBuffer() {
dataPtr = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_DESELECT);
context = JCSystem.makeTransientShortArray(LENGTH_CONTEXT, JCSystem.CLEAR_ON_DESELECT);
reset();
}
/** Resets the ChainBuffer, aborting any outstanding transaction */
private void resetAbort() {
// Have we been asked to conduct this in a transaction?
if ((short) 0 != context[CONTEXT_TRANSACTION]) {
JCSystem.abortTransaction();
}
// Perform a normal reset
reset();
}
/** Resets the ChainBuffer, committing any outstanding transaction */
private void resetCommit() {
// Have we been asked to conduct this in a transaction?
if ((short) 0 != context[CONTEXT_TRANSACTION]) {
JCSystem.commitTransaction();
}
// Perform a normal reset
reset();
}
/** Resets the ChainBuffer and clears any internal buffer and state tracking values */
void reset() {
// Have we been asked to clear the buffer?
if (dataPtr[0] != null && context[CONTEXT_CLEAR_ON_COMPLETE] != (short) 0) {
Util.arrayFillNonAtomic(
(byte[]) dataPtr[0], context[CONTEXT_INITIAL], context[CONTEXT_LENGTH], (byte) 0x00);
}
// Burn them... Burn them all
dataPtr[0] = null;
context[CONTEXT_STATE] = STATE_NONE;
context[CONTEXT_OFFSET] = (short) 0;
context[CONTEXT_INITIAL] = (short) 0;
context[CONTEXT_REMAINING] = (short) 0;
context[CONTEXT_LENGTH] = (short) 0;
context[CONTEXT_CLEAR_ON_COMPLETE] = (short) 0;
context[CONTEXT_APDU_CLASS] = (short) 0;
context[CONTEXT_APDU_P1P2] = (short) 0;
context[CONTEXT_TRANSACTION] = (short) 0;
}
/**
* Configures the ChainBuffer class to process a stream of outgoing data which will be retrieved
* by subsequent GET RESPONSE commands
*
* @param buffer the buffer to read data from
* @param offset The starting offset of the data to read from
* @param length The total number of bytes to read
* @param clearOnCompletion If true, the buffer will be wiped when the chain operation ends
*/
void setOutgoing(byte[] buffer, short offset, short length, boolean clearOnCompletion) {
reset();
dataPtr[0] = buffer;
context[CONTEXT_STATE] = STATE_OUTGOING;
context[CONTEXT_OFFSET] = offset;
context[CONTEXT_INITIAL] = offset;
context[CONTEXT_REMAINING] = length;
context[CONTEXT_LENGTH] = length;
context[CONTEXT_CLEAR_ON_COMPLETE] = clearOnCompletion ? (short) 1 : (short) 0;
}
/**
* Configures the ChainBuffer class to process a stream of incoming data directly to an object
*
* @param destination The buffer to write data to
* @param offset The starting offset of the data to write to
* @param length The length to expect to be written
* @param atomic If true, this operation will be conducted inside a transaction
*/
void setIncomingObject(byte[] destination, short offset, short length, boolean atomic) {
reset();
dataPtr[0] = destination;
context[CONTEXT_STATE] = STATE_INCOMING_OBJECT;
context[CONTEXT_OFFSET] = offset;
context[CONTEXT_REMAINING] = length;
context[CONTEXT_LENGTH] = length;
if (atomic) {
JCSystem.beginTransaction();
context[CONTEXT_TRANSACTION] = (short) 1;
}
}
/**
* Configures the ChainBuffer class to process a large incoming APDU
*
* @param apdu The first incoming APDU buffer
* @param inOffset The starting offset of initial APDU
* @param inLength The length of the initial APDU
* @param outBuffer The destination buffer for the large APDU CDATA content
* @param outOffset The offset to start writing in the destination buffer
* @return The number of bytes in the command data if complete, otherwise zero to indicate there
* is more to come NOTE: The destination will contain only the command data of the APDU, not
* the header.
*/
short processIncomingAPDU(
byte[] apdu, short inOffset, short inLength, byte[] outBuffer, short outOffset)
throws ISOException {
//
// STATE VALIDATION
//
// Make sure that we are not in the middle of some other outstanding transaction
if (context[CONTEXT_STATE] != STATE_NONE && context[CONTEXT_STATE] != STATE_INCOMING_APDU) {
// We have been called in the middle of another operation! call resetAbort in case there is
// some outstanding transaction
resetAbort();
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
//
// CASE 1 - A single-frame APDU (CLA_CHAINING == FALSE and STATE == STATE_NONE)
//
if ((apdu[ISO7816.OFFSET_CLA] & CLA_CHAINING) == 0 && context[CONTEXT_STATE] == STATE_NONE) {
// Just copy the buffer to the destination and we are done
try {
Util.arrayCopyNonAtomic(apdu, inOffset, outBuffer, outOffset, inLength);
} catch (Exception ex) {
// Buffer overrun
reset();
ISOException.throwIt(ISO7816.SW_FILE_FULL);
}
// We're done! No state needs to change, just return the value of the LC byte (length of
// command data)
return inLength;
}
//
// CASE 2 - The start of an incoming APDU chain (CLA_CHAINING == TRUE and STATE == STATE_NONE)
//
else if ((apdu[ISO7816.OFFSET_CLA] & CLA_CHAINING) != 0
&& context[CONTEXT_STATE] == STATE_NONE) {
// Set up the internal state
context[CONTEXT_STATE] = STATE_INCOMING_APDU;
context[CONTEXT_INITIAL] = inOffset;
context[CONTEXT_APDU_CLASS] = Util.getShort(apdu, ISO7816.OFFSET_CLA);
context[CONTEXT_APDU_P1P2] = Util.getShort(apdu, ISO7816.OFFSET_P1);
// Write the first section of data
try {
Util.arrayCopyNonAtomic(apdu, inOffset, outBuffer, outOffset, inLength);
} catch (Exception ex) {
// Buffer overrun
reset();
ISOException.throwIt(ISO7816.SW_FILE_FULL);
}
context[CONTEXT_LENGTH] = inLength;
// Done, return 0 so the caller knows we're not finished!
return (short) 0;
}
//
// CASE 3 - The middle of an incoming APDU chain (CLA_CHAINING == TRUE and STATE ==
// STATE_INCOMING_APDU)
//
else if ((apdu[ISO7816.OFFSET_CLA] & CLA_CHAINING) != 0
&& context[CONTEXT_STATE] == STATE_INCOMING_APDU) {
// Validate that we are chaining for the correct command
if (context[CONTEXT_APDU_CLASS] != Util.getShort(apdu, ISO7816.OFFSET_CLA)
|| context[CONTEXT_APDU_P1P2] != Util.getShort(apdu, ISO7816.OFFSET_P1)) {
reset();
// NOTE regarding Config.FEATURE_STRICT_APDU_CHAINING:
// If we have gotten here, then checkIncomingAPDU was not called. Since we are
// already past the point of allowing the applet to switch to other commands,
// so we always fail. Use checkIncomingAPDU() and this will never be reached.
ISOException.throwIt(ISO7816.SW_LAST_COMMAND_EXPECTED);
}
// Calculate the outOffset by adding the amount of data we have already written
outOffset += context[CONTEXT_LENGTH];
// Write the next section of data
try {
Util.arrayCopyNonAtomic(apdu, inOffset, outBuffer, outOffset, inLength);
} catch (Exception ex) {
// Buffer overrun
reset();
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Update the internal state
context[CONTEXT_LENGTH] += inLength;
// Done, return 0 so the caller knows we're not finished!
return (short) 0;
}
//
// CASE 4 - The end of an incoming APDU chain (CLA_CHAINING == FALSE and STATE ==
// STATE_INCOMING_APDU)
//
else if ((apdu[ISO7816.OFFSET_CLA] & CLA_CHAINING) == 0
&& context[CONTEXT_STATE] == STATE_INCOMING_APDU) {
// Validate that we are chaining for the correct command
// NOTE: We have to mask off the chaining bit before comparing
final short CLA_MASK = ~(short) 0x1000;
if ((context[CONTEXT_APDU_CLASS] & CLA_MASK) != Util.getShort(apdu, ISO7816.OFFSET_CLA)
|| context[CONTEXT_APDU_P1P2] != Util.getShort(apdu, ISO7816.OFFSET_P1)) {
reset();
// NOTE regarding Config.FEATURE_STRICT_APDU_CHAINING:
// If we have gotten here, then checkIncomingAPDU was not called. Since we are
// already past the point of allowing the applet to switch to other commands,
// so we always fail. Use checkIncomingAPDU() and this will never be reached.
ISOException.throwIt(ISO7816.SW_LAST_COMMAND_EXPECTED);
}
// Calculate the outOffset by adding the amount of data we have already written
outOffset += context[CONTEXT_LENGTH];
// Write the final section of data
try {
Util.arrayCopyNonAtomic(apdu, inOffset, outBuffer, outOffset, inLength);
} catch (Exception ex) {
// Buffer overrun
reset();
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Calculate our final length
inLength += context[CONTEXT_LENGTH];
// Reset our internal state
reset();
// Done, return the total length!
return inLength;
}
//
// Unexpected state
//
// Should never reach this state, throw back SW_UNKNOWN to flag we have a bug
reset();
ISOException.throwIt(ISO7816.SW_UNKNOWN);
return (short) 0; // Keep the compiler happy
}
/**
* Starts or continues processing of an incoming data stream, which will be written directly to a
* buffer
*
* @param buffer The incoming APDU buffer
* @param offset The starting offset to read from
* @param length The length of the data to read
*/
void processIncomingObject(byte[] buffer, short offset, short length) throws ISOException {
// Check if we have anything to do
if (context[CONTEXT_STATE] != STATE_INCOMING_OBJECT) return;
// This method presumes that setIncomingAndReceive() was previously called if required
final short CLA_MASK = ~(short) 0x1000;
// If we have not written anything, this must be the first command so set the APDU header
if (context[CONTEXT_LENGTH] == context[CONTEXT_REMAINING]) {
context[CONTEXT_APDU_CLASS] = (short) (Util.getShort(buffer, ISO7816.OFFSET_CLA) & CLA_MASK);
context[CONTEXT_APDU_P1P2] = Util.getShort(buffer, ISO7816.OFFSET_P1);
}
// Validate that we are chaining for the correct command
else if (context[CONTEXT_APDU_CLASS]
!= (short) (Util.getShort(buffer, ISO7816.OFFSET_CLA) & CLA_MASK)
|| context[CONTEXT_APDU_P1P2] != Util.getShort(buffer, ISO7816.OFFSET_P1)) {
// Abort the data object write
resetAbort();
// Ignore this and let the applet handle as a new APDU
return;
}
// Check if we are chaining or not (we don't use the in-built APDU.isCommandChainingCLA() call
// because it doesn't always work!
if ((buffer[ISO7816.OFFSET_CLA] & CLA_CHAINING) != 0) {
//
// CASE 0: If the chaining bit is SET, we are writing the first or an intermediary frame
// and we must not write up to or over the total expected length
//
// No data to write? Nothing to do.. What a waste of everyone's time
if (length == 0) return;
if (length >= context[CONTEXT_REMAINING]) {
resetAbort();
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Write to the data buffer and update our context
if ((short) 0 != context[CONTEXT_TRANSACTION]) {
Util.arrayCopy(buffer, offset, (byte[]) dataPtr[0], context[CONTEXT_OFFSET], length);
} else {
Util.arrayCopyNonAtomic(
buffer, offset, (byte[]) dataPtr[0], context[CONTEXT_OFFSET], length);
}
context[CONTEXT_OFFSET] += length;
context[CONTEXT_REMAINING] -= length;
} else {
//
// CASE 1: If the chaining bit is NOT SET, we must be writing either the last or the only
// frame
// and we must write exactly up to the length of the data buffer
//
if (length == 0) {
resetAbort();
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Must be exactly the # of bytes remaining
if (length != context[CONTEXT_REMAINING]) {
resetAbort();
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Write to the data buffer and update our context
Util.arrayCopy(buffer, offset, (byte[]) dataPtr[0], context[CONTEXT_OFFSET], length);
// Clear our context as the chain is now complete
resetCommit();
}
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
/**
* Starts or continues processing for an outgoing buffer being transmitted to the host
*
* @param apdu The current APDU buffer to transmit with
*/
void processOutgoing(APDU apdu) throws ISOException {
//
// NOTE:
// In previous implementations, this command was intended to be executed every time process()
// was called and it would decide if it did anything or not. This has now changed to be only
// executed when a GET RESPONSE is explicitly requested.
//
// The implication of this is that instead of silently leaving, this will throw an exception
// if it is in the wrong state because it indicates the user requested more data intentionally
// in the wrong state.
//
// The NIST SP-33 reference database uses ID One PIV 2.4 cards and this implementation
// returns 9000 if you try to issue a GET RESPONSE when there was nothing to get. Yubikey
// however returns SW_WRONG_DATA. I believe Yubikey is handling it the more correct way and
// so we will raise an error unless someone out there provides a compelling reason not to.
//
// Check if we are in the correct state
if (context[CONTEXT_STATE] != STATE_OUTGOING) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
byte[] apduBuffer = apdu.getBuffer();
// CASE 0 - If the remaining data is EQUAL TO the total data, ignore the INS
// CASE 1 - If the remaining data is LESS THAN the total data, look for a GET RESPONSE command
// (clear if it isn't)
if (apduBuffer[ISO7816.OFFSET_INS] != INS_GET_RESPONSE
&& context[CONTEXT_REMAINING] != context[CONTEXT_LENGTH]) {
// Clear the apdu buffer
reset();
// Ignore this and let the applet handle this as a new command
return;
}
//
// Transmit the next frame up to a maximum of 'LE' bytes
//
short le = apdu.setOutgoing();
//
// !! HACK !!
// Most applets completely ignore this value and so interface developers have gotten lazy about
// whether or not they include an LE byte when they expect response data.
// This treats the absence of an LE byte (case 3) as if it were a case 4 byte with LE == '00',
// which maps to 256 bytes.
//
if (le == 0) le = 256;
short length = (context[CONTEXT_REMAINING] > le) ? le : context[CONTEXT_REMAINING];
apdu.setOutgoingLength(length);
apdu.sendBytesLong((byte[]) dataPtr[0], context[CONTEXT_OFFSET], length);
context[CONTEXT_REMAINING] -= length;
context[CONTEXT_OFFSET] += length;
// If we have nothing left to send, clear our context and return 9000
if (context[CONTEXT_REMAINING] == 0) {
reset();
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
// Otherwise, notify the caller we have xx remaining bytes (up to 255)
else {
short sw2 =
(context[CONTEXT_REMAINING] > (short) 0x00FF)
? (short) 0x00FF
: context[CONTEXT_REMAINING];
ISOException.throwIt((short) (ISO7816.SW_BYTES_REMAINING_00 | sw2));
}
}
}