-
Notifications
You must be signed in to change notification settings - Fork 72
/
IsoApplet.java
1729 lines (1551 loc) · 66.6 KB
/
IsoApplet.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
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* IsoApplet: A Java Card PKI applet aimiing for ISO 7816 compliance.
* Copyright (C) 2014 Philip Wendland ([email protected])
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package xyz.wendland.javacard.pki.isoapplet;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.APDU;
import javacard.framework.SystemException;
import javacard.framework.TransactionException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.framework.OwnerPIN;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.Key;
import javacard.security.RSAPublicKey;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.ECKey;
import javacard.security.ECPublicKey;
import javacard.security.ECPrivateKey;
import javacardx.crypto.Cipher;
import javacardx.apdu.ExtendedLength;
import javacard.security.CryptoException;
import javacard.security.MessageDigest;
import javacard.security.Signature;
import javacard.security.RandomData;
/**
* \brief The IsoApplet class.
*
* This applet has a filesystem and accepts relevant ISO 7816 instructions.
* Access control is forced through a PIN and a PUK. The PUK is optional
* (Set PUK_MUST_BE_SET). Security Operations are being processed directly in
* this class. Only private keys are stored as Key-objects. Only security
* operations with private keys can be performed (decrypt with RSA, sign with RSA,
* sign with ECDSA).
*
* \author Philip Wendland
*/
public class IsoApplet extends Applet implements ExtendedLength {
/* API Version */
public static final byte API_VERSION_MAJOR = (byte) 0x01;
public static final byte API_VERSION_MINOR = (byte) 0x00;
/* Card-specific configuration */
public static final boolean DEF_PRIVATE_KEY_IMPORT_ALLOWED = false;
/* Certain cards (J3H081) break in unexpected ways testing these at
* runtime. Allow them to be forced off so no test is run. */
public static final boolean DEF_TEST_RSA_4096 = true;
public static final boolean DEF_TEST_RSA_PSS = true;
/* ISO constants not in the "ISO7816" interface */
// File system related INS:
public static final byte INS_CREATE_FILE = (byte) 0xE0;
public static final byte INS_UPDATE_BINARY = (byte) 0xD6;
public static final byte INS_READ_BINARY = (byte) 0xB0;
public static final byte INS_DELETE_FILE = (byte) 0xE4;
// Other INS:
public static final byte INS_VERIFY = (byte) 0x20;
public static final byte INS_CHANGE_REFERENCE_DATA = (byte) 0x24;
public static final byte INS_GENERATE_ASYMMETRIC_KEYPAIR = (byte) 0x46;
public static final byte INS_RESET_RETRY_COUNTER = (byte) 0x2C;
public static final byte INS_MANAGE_SECURITY_ENVIRONMENT = (byte) 0x22;
public static final byte INS_PERFORM_SECURITY_OPERATION = (byte) 0x2A;
public static final byte INS_PUT_DATA = (byte) 0xDB;
public static final byte INS_GET_CHALLENGE = (byte) 0x84;
public static final byte INS_GET_DATA = (byte) 0xCA;
// Status words:
public static final short SW_PIN_TRIES_REMAINING = 0x63C0; // See ISO 7816-4 section 7.5.1
public static final short SW_COMMAND_NOT_ALLOWED_GENERAL = 0x6900;
/* PIN, PUK and key realted constants */
// PIN:
private static final byte PIN_MAX_TRIES = 3;
private static final byte PIN_MIN_LENGTH = 4;
private static final byte PIN_MAX_LENGTH = 16;
// PUK:
private static final boolean PUK_MUST_BE_SET = false;
private static final byte PUK_MAX_TRIES = 5;
private static final byte PUK_LENGTH = 16;
// Keys:
private static final short KEY_MAX_COUNT = 16;
private static final byte ALG_GEN_RSA_2048 = (byte) 0xF3;
private static final byte ALG_GEN_RSA_4096 = (byte) 0xF5;
private static final byte ALG_RSA_PAD_PKCS1 = (byte) 0x11;
private static final byte ALG_RSA_PAD_PSS = (byte) 0x12;
private static final byte ALG_GEN_EC = (byte) 0xEC;
private static final byte ALG_ECDSA = (byte) 0x21;
private static final short LENGTH_EC_FP_224 = 224;
private static final short LENGTH_EC_FP_256 = 256;
private static final short LENGTH_EC_FP_320 = 320;
private static final short LENGTH_EC_FP_384 = 384;
private static final short LENGTH_EC_FP_512 = 512;
private static final short LENGTH_EC_FP_521 = 521;
/* Card/Applet lifecycle states */
private static final byte STATE_CREATION = (byte) 0x00; // No restrictions, PUK not set yet.
private static final byte STATE_INITIALISATION = (byte) 0x01; // PUK set, PIN not set yet. PUK may not be changed.
private static final byte STATE_OPERATIONAL_ACTIVATED = (byte) 0x05; // PIN is set, data is secured.
private static final byte STATE_OPERATIONAL_DEACTIVATED = (byte) 0x04; // Applet usage is deactivated. (Unused at the moment.)
private static final byte STATE_TERMINATED = (byte) 0x0C; // Applet usage is terminated. (Unused at the moment.)
private static final byte API_FEATURE_EXT_APDU = (byte) 0x01;
private static final byte API_FEATURE_SECURE_RANDOM = (byte) 0x02;
private static final byte API_FEATURE_ECC = (byte) 0x04;
private static final byte API_FEATURE_RSA_PSS = (byte) 0x08;
private static final byte API_FEATURE_RSA_4096 = (byte) 0x20;
/* The ram buffer is required for request and response data, that is too large for the APDU buffer.
The size of the APDU buffer depends on the card, but must be at least 133 bytes long.
We have to use the ram buffer for outgoing and incoming data larger than 133 bytes,
unless the data is directly read from or written to the file system.
*/
private static final short RAM_BUF_SIZE = (short) 660;
/* Member variables: */
private byte state;
private IsoFileSystem fs = null;
private OwnerPIN pin = null;
private OwnerPIN puk = null;
private byte[] currentAlgorithmRef = null;
private short[] currentPrivateKeyRef = null;
private Key[] keys = null;
private byte[] ram_buf = null;
private Cipher rsaPkcs1Cipher = null;
private RandomData randomData = null;
private byte api_features;
/**
* \brief Installs this applet.
*
* \param bArray
* the array containing installation parameters
* \param bOffset
* the starting offset in bArray
* \param bLength
* the length in bytes of the parameter data in bArray
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
new IsoApplet();
}
/**
* \brief Only this class's install method should create the applet object.
*/
protected IsoApplet() {
api_features = API_FEATURE_EXT_APDU;
pin = new OwnerPIN(PIN_MAX_TRIES, PIN_MAX_LENGTH);
fs = new IsoFileSystem();
ram_buf = JCSystem.makeTransientByteArray(RAM_BUF_SIZE, JCSystem.CLEAR_ON_DESELECT);
currentAlgorithmRef = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
currentPrivateKeyRef = JCSystem.makeTransientShortArray((short)1, JCSystem.CLEAR_ON_DESELECT);
keys = new Key[KEY_MAX_COUNT];
rsaPkcs1Cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
// API features: probe card support for ECDSA
try {
Signature.getInstance(MessageDigest.ALG_NULL, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL, false);
api_features |= API_FEATURE_ECC;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
/* Few Java Cards do not support ECDSA at all.
* We should not throw an exception in this cases
* as this would prevent installation. */
api_features &= ~API_FEATURE_ECC;
} else {
throw e;
}
}
// API features: probe card support for 4096 bit RSA keys
if (DEF_TEST_RSA_4096) {
try {
RSAPrivateCrtKey testKey = (RSAPrivateCrtKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_4096, false);
api_features |= API_FEATURE_RSA_4096;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
api_features &= ~API_FEATURE_RSA_4096;
} else {
throw e;
}
} catch (TransactionException e) {
// J3H081 from javacardsdk.com (FUTAKO) raises this exception instead.
if(e.getReason() == TransactionException.INTERNAL_FAILURE) {
api_features &= ~API_FEATURE_RSA_4096;
} else {
throw e;
}
}
}
/* API features: probe card support for RSA and PSS padding with SHA-1 and all SHA-2 algorithms
* to be used with Signature.signPreComputedHash() */
if (DEF_TEST_RSA_PSS) {
try {
Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1_PSS, false);
Signature.getInstance(Signature.ALG_RSA_SHA_224_PKCS1_PSS, false);
Signature.getInstance(Signature.ALG_RSA_SHA_256_PKCS1_PSS, false);
Signature.getInstance(Signature.ALG_RSA_SHA_384_PKCS1_PSS, false);
Signature.getInstance(Signature.ALG_RSA_SHA_512_PKCS1_PSS, false);
api_features |= API_FEATURE_RSA_PSS;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
/* Certain Java Cards do not support this algorithm.
* We should not throw an exception in this cases
* as this would prevent installation. */
api_features &= ~API_FEATURE_RSA_PSS;
} else {
throw e;
}
}
}
// API features: probe secure random number generation support.
try {
randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
api_features |= API_FEATURE_SECURE_RANDOM;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
randomData = null;
api_features &= ~API_FEATURE_SECURE_RANDOM;
} else {
throw e;
}
}
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
state = STATE_CREATION;
register();
}
/**
* \brief This method is called whenever the applet is being deselected.
*/
public void deselect() {
pin.reset();
if(puk != null) {
puk.reset();
}
fs.setUserAuthenticated(false);
}
/**
* \brief This method is called whenever the applet is being selected.
*/
public boolean select() {
if(state == STATE_CREATION
|| state == STATE_INITIALISATION) {
fs.setUserAuthenticated(true);
} else {
fs.setUserAuthenticated(false);
}
// Reset file selection state
fs.selectFile(null);
return true;
}
/**
* \brief Processes an incoming APDU.
*
* \see APDU.
*
* \param apdu The incoming APDU.
*/
public void process(APDU apdu) {
byte buffer[] = apdu.getBuffer();
byte ins = buffer[ISO7816.OFFSET_INS];
if(selectingApplet()) {
return;
}
// No secure messaging at the moment
if(apdu.isSecureMessagingCLA()) {
ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED);
}
if(isCommandChainingCLA(apdu)) {
ISOException.throwIt(ISO7816.SW_COMMAND_CHAINING_NOT_SUPPORTED);
}
if(apdu.isISOInterindustryCLA()) {
switch (ins) {
case ISO7816.INS_SELECT:
fs.processSelectFile(apdu);
break;
case INS_READ_BINARY:
fs.processReadBinary(apdu);
break;
case INS_GET_DATA:
processGetData(apdu);
break;
case INS_VERIFY:
processVerify(apdu);
break;
case INS_MANAGE_SECURITY_ENVIRONMENT:
processManageSecurityEnvironment(apdu);
break;
case INS_PERFORM_SECURITY_OPERATION:
processPerformSecurityOperation(apdu);
break;
case INS_CREATE_FILE:
fs.processCreateFile(apdu);
break;
case INS_UPDATE_BINARY:
fs.processUpdateBinary(apdu);
break;
case INS_CHANGE_REFERENCE_DATA:
processChangeReferenceData(apdu);
break;
case INS_DELETE_FILE:
fs.processDeleteFile(apdu);
break;
case INS_GENERATE_ASYMMETRIC_KEYPAIR:
processGenerateAsymmetricKeypair(apdu);
break;
case INS_RESET_RETRY_COUNTER:
processResetRetryCounter(apdu);
break;
case INS_PUT_DATA:
processPutData(apdu);
break;
case INS_GET_CHALLENGE:
processGetChallenge(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
} // switch
} else {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
}
/**
* \brief Parse the apdu's CLA byte to determine if the apdu is the first or second-last part of a chain.
*
* The Java Card API version 2.2.2 has a similar method (APDU.isCommandChainingCLA()), but tests have shown
* that some smartcard platform's implementations are wrong (not according to the JC API specification),
* specifically, but not limited to, JCOP 2.4.1 R3.
*
* \param apdu The apdu.
*
* \return true If the apdu is the [1;last[ part of a command chain,
* false if there is no chain or the apdu is the last part of the chain.
*/
static boolean isCommandChainingCLA(APDU apdu) {
byte[] buf = apdu.getBuffer();
return ((byte)(buf[0] & (byte)0x10) == (byte)0x10);
}
/**
* \brief Process the GET DATA apdu (INS = CA)
*
* This APDU can be used to request the following data:
* P1P2 = 0x0101: Applet version and features
*
* \param apdu The apdu to process.
*/
private void processGetData(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte ins = buf[ISO7816.OFFSET_INS];
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
if(ins != (byte) 0xCA) {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
if(p1 == (byte) 0x01 && p2 == (byte) 0x01) {
buf[0] = API_VERSION_MAJOR;
buf[1] = API_VERSION_MINOR;
buf[2] = api_features;
apdu.setOutgoingAndSend((short) 0, (short) 3);
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
}
/**
* \brief Process the VERIFY apdu (INS = 20).
*
* This apdu is used to verify a PIN and authenticate the user. A counter is used
* to limit unsuccessful tries (i.e. brute force attacks).
*
* \param apdu The apdu.
*
* \throw ISOException SW_INCORRECT_P1P2, ISO7816.SW_WRONG_LENGTH, SW_PIN_TRIES_REMAINING.
*/
private void processVerify(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
short offset_cdata;
short lc;
// P1P2 0001 only at the moment. (key-reference 01 = PIN)
if(buf[ISO7816.OFFSET_P1] != 0x00 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// Lc might be 0, in this case the caller checks if verification is required.
if((lc > 0 && (lc < PIN_MIN_LENGTH) || lc > PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Caller asks if verification is needed.
if(lc == 0
&& state != STATE_CREATION
&& state != STATE_INITIALISATION) {
// Verification required, return remaining tries.
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
} else if(lc == 0
&& (state == STATE_CREATION
|| state == STATE_INITIALISATION)) {
// No verification required.
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
// Pad the PIN if not done by caller, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Check the PIN.
if(!pin.check(buf, offset_cdata, PIN_MAX_LENGTH)) {
fs.setUserAuthenticated(false);
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
} else {
fs.setUserAuthenticated(true);
}
}
/**
* \brief Process the CHANGE REFERENCE DATA apdu (INS = 24).
*
* If the state is STATE_CREATION, we can set the PUK without verification.
* The state will advance to STATE_INITIALISATION (i.e. the PUK must be set before the PIN).
* In a "later" state the user must authenticate himself to be able to change the PIN.
*
* \param apdu The apdu.
*
* \throws ISOException SW_INCORRECT_P1P2, ISO7816.SW_WRONG_LENGTH, SW_PIN_TRIES_REMAINING.
*/
private void processChangeReferenceData(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short lc;
short offset_cdata;
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
if(state == STATE_CREATION) {
// We _set_ the PUK or the PIN. If we set the PIN in this state, no PUK will be present on the card, ever.
// Key reference must be 02 (PUK) or 01 (PIN). P1 must be 01 because no verification data should be present in this state.
if(p1 != 0x01 || (p2 != 0x02 && p2 != 0x01) ) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
if(p2 == 0x02) {
// We set the PUK and advance to STATE_INITIALISATION.
// Check length.
if(lc != PUK_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Set PUK
puk = new OwnerPIN(PUK_MAX_TRIES, PUK_LENGTH);
puk.update(buf, offset_cdata, (byte)lc);
puk.resetAndUnblock();
state = STATE_INITIALISATION;
} else if(p2 == 0x01) {
// We are supposed to set the PIN right away - no PUK will be set, ever.
// This might me forbidden because of security policies:
if(PUK_MUST_BE_SET) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
// Check length.
if(lc > PIN_MAX_LENGTH || lc < PIN_MIN_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Pad the PIN upon creation, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set PIN.
pin.update(buf, offset_cdata, PIN_MAX_LENGTH);
pin.resetAndUnblock();
state = STATE_OPERATIONAL_ACTIVATED;
}
} else if(state == STATE_INITIALISATION) {
// We _set_ the PIN (P2=01).
if(buf[ISO7816.OFFSET_P1] != 0x01 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check the PIN length.
if(lc > PIN_MAX_LENGTH || lc < PIN_MIN_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Pad the PIN upon creation, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set PIN.
pin.update(buf, offset_cdata, PIN_MAX_LENGTH);
pin.resetAndUnblock();
state = STATE_OPERATIONAL_ACTIVATED;
} else {
// We _change_ the PIN (P2=01).
// P1 must be 00 as the old PIN must be provided, followed by new PIN without delimitation.
// Both PINs must already padded (otherwise we can not tell when the old PIN ends.)
if(buf[ISO7816.OFFSET_P1] != 0x00 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check PIN lengths: PINs must be padded, i.e. Lc must be 2*PIN_MAX_LENGTH.
if(lc != (short)(2*PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Check the old PIN.
if(!pin.check(buf, offset_cdata, PIN_MAX_LENGTH)) {
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
}
// UPDATE PIN
pin.update(buf, (short) (offset_cdata+PIN_MAX_LENGTH), PIN_MAX_LENGTH);
}// end if(state == STATE_CREATION)
}// end processChangeReferenceData()
/**
* \brief Process the RESET RETRY COUNTER apdu (INS = 2C).
*
* This is used to unblock the PIN with the PUK and set a new PIN value.
*
* \param apdu The RESET RETRY COUNTER apdu.
*
* \throw ISOException SW_COMMAND_NOT_ALLOWED, ISO7816.SW_WRONG_LENGTH, SW_INCORRECT_P1P2,
* SW_PIN_TRIES_REMAINING.
*/
public void processResetRetryCounter(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short lc;
short offset_cdata;
if(state != STATE_OPERATIONAL_ACTIVATED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// Length of data field.
if(lc < (short)(PUK_LENGTH + PIN_MIN_LENGTH)
|| lc > (short)(PUK_LENGTH + PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// We expect the PUK followed by a new PIN.
if(p1 != (byte) 0x00 || p2 != (byte) 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check the PUK.
if(puk == null) {
ISOException.throwIt(SW_PIN_TRIES_REMAINING);
} else if (!puk.check(buf, offset_cdata, PUK_LENGTH)) {
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | puk.getTriesRemaining()));
} else {
// If we're here, the PUK was correct.
// Pad the new PIN, if not done by caller. We don't want any gargabe from the APDU buffer to be part of the new PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PUK_LENGTH + PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set the PIN.
pin.update(buf, (short)(offset_cdata+PUK_LENGTH), PIN_MAX_LENGTH);
pin.resetAndUnblock();
}
}
/**
* \brief Initialize an EC key with the curve parameters from buf.
*
* \param buf The buffer containing the EC curve parameters. It must be TLV with the following format:
* 81 - prime
* 82 - coefficient A
* 83 - coefficient B
* 84 - base point G
* 85 - order
* 87 - cofactor
*
* \param bOff The offset at where the first entry is located.
*
* \param bLen The remaining length of buf.
*
* \param key The EC key to initialize.
*
* \throw NotFoundException Parts of the data needed to fully initialize
* the key were missing.
*
* \throw InvalidArgumentsException The ASN.1 sequence was malformatted.
*/
private void initEcParams(byte[] buf, short bOff, short bLen, ECKey key) throws NotFoundException, InvalidArgumentsException {
short pos = bOff;
short len;
/* Search for the prime */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x81);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setFieldFP(buf, pos, len); // "p"
/* Search for coefficient A */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x82);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setA(buf, pos, len);
/* Search for coefficient B */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x83);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setB(buf, pos, len);
/* Search for base point G */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x84);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setG(buf, pos, len); // G(x,y)
/* Search for order */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x85);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setR(buf, pos, len); // Order of G - "q"
/* Search for cofactor */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x87);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
if(len == 2) {
key.setK(Util.getShort(buf, pos));
} else if(len == 1) {
key.setK(buf[pos]);
} else {
throw InvalidArgumentsException.getInstance();
}
}
/**
* \brief Process the GENERATE ASYMMETRIC KEY PAIR apdu (INS = 46).
*
* A MANAGE SECURITY ENVIRONMENT must have succeeded earlier to set parameters for key
* generation.
*
* \param apdu The apdu.
*
* \throw ISOException SW_WRONG_LENGTH, SW_INCORRECT_P1P2, SW_CONDITIONS_NOT_SATISFIED,
* SW_SECURITY_STATUS_NOT_SATISFIED.
*/
public void processGenerateAsymmetricKeypair(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short privKeyRef = currentPrivateKeyRef[0];
short lc;
KeyPair kp = null;
ECPrivateKey privKey = null;
ECPublicKey pubKey = null;
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
switch(currentAlgorithmRef[0]) {
case ALG_GEN_RSA_2048:
case ALG_GEN_RSA_4096:
if(p1 != (byte) 0x42 || p2 != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Command chaining might be used for ECC, but not for RSA.
if(isCommandChainingCLA(apdu)) {
ISOException.throwIt(ISO7816.SW_COMMAND_CHAINING_NOT_SUPPORTED);
}
try {
short keyLength = currentAlgorithmRef[0] == ALG_GEN_RSA_2048 ? KeyBuilder.LENGTH_RSA_2048 : KeyBuilder.LENGTH_RSA_4096;
kp = new KeyPair(KeyPair.ALG_RSA_CRT, keyLength);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
kp.genKeyPair();
if(keys[privKeyRef] != null) {
keys[privKeyRef].clearKey();
}
keys[privKeyRef] = kp.getPrivate();
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
// Return pubkey. See ISO7816-8 table 3.
try {
sendRSAPublicKey(apdu, ((RSAPublicKey)(kp.getPublic())));
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
} catch (NotEnoughSpaceException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
break;
case ALG_GEN_EC:
if((p1 != (byte) 0x00) || p2 != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
short recvLen = readIncomingDataIntoRam(apdu);
/* Search for prime */
short pos = 0;
try {
pos = UtilTLV.findTag(ram_buf, (short)0, recvLen, (byte) 0x81);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
pos++;
short len = 0;
try {
len = UtilTLV.decodeLengthField(ram_buf, pos);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Try to calculate field length frome prime length.
short field_len = getEcFpFieldLength(len);
// Try to instantiate key objects of that length
try {
privKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, field_len, false);
pubKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, field_len, false);
kp = new KeyPair(pubKey, privKey);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
try {
initEcParams(ram_buf, (short)0, recvLen, pubKey);
initEcParams(ram_buf, (short)0, recvLen, privKey);
} catch (NotFoundException e) {
// Parts of the data needed to initialize the EC keys were missing.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
// Malformatted ASN.1.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
try {
kp.genKeyPair();
} catch (CryptoException e) {
if(e.getReason() == CryptoException.ILLEGAL_VALUE) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
if(keys[privKeyRef] != null) {
keys[privKeyRef].clearKey();
}
keys[privKeyRef] = privKey;
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
// Return pubkey. See ISO7816-8 table 3.
try {
sendECPublicKey(apdu, pubKey);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
} catch (NotEnoughSpaceException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
break;
default:
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
/**
* \brief Read all incoming data into ram_buf.
*
* This is a convenience method if large data has to be accumulated.
* The APDU must be in the INITIAL state, i.e. setIncomingAndReceive()
* must not have been called already.
*
* \return length of the received data
*/
private short readIncomingDataIntoRam(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
short recvLen = apdu.setIncomingAndReceive();
short dataOffset = apdu.getOffsetCdata();
short ramOffset = 0;
while (recvLen > 0) {
ramOffset = Util.arrayCopyNonAtomic(buf, dataOffset, ram_buf, ramOffset, recvLen);
recvLen = apdu.receiveBytes(dataOffset);
}
return apdu.getIncomingLength();
}
/**
* \brief Encode a 2048 bit RSAPublicKey according to ISO7816-8 table 3 and send it as a response,
* using an extended APDU.
*
* \see ISO7816-8 table 3.
*
* \param apdu The apdu to answer. setOutgoing() must not be called already.
*
* \param key The RSAPublicKey to send.
* Can be null for the secound part if there is no support for extended apdus.
*/
private void sendRSAPublicKey(APDU apdu, RSAPublicKey key) throws InvalidArgumentsException, NotEnoughSpaceException {
short pos = 0;
// Interindustry template for nesting one set of public key data objects.
// Len = modulus tag and len (4) + modulus (key size in bytes) + exponent tag and len (2) + exponent (3)
short key_size = (short)(key.getSize() / 8);
short len = (short)(4 + key_size + 2 + 3);
pos += UtilTLV.writeTagAndLen((short)0x7F49, len, ram_buf, pos);
// Modulus
pos += UtilTLV.writeTagAndLen((short)0x81, key_size, ram_buf, pos);
pos += key.getModulus(ram_buf, pos);
// Exponent
pos += UtilTLV.writeTagAndLen((short)0x82, (short)3, ram_buf, pos);
pos += key.getExponent(ram_buf, pos);
apdu.setOutgoing();
apdu.setOutgoingLength(pos);
apdu.sendBytesLong(ram_buf, (short)0, pos);
}
/**
* \brief Encode a ECPublicKey according to ISO7816-8 table 3 and send it as a response,
* using an extended APDU.
*
* \see ISO7816-8 table 3.
*
* \param The apdu to answer. setOutgoing() must not be called already.
*
* \throw InvalidArgumentsException Field length of the EC key provided can not be handled.
*
* \throw NotEnoughSpaceException ram_buf is too small to contain the EC key to send.
*/
private void sendECPublicKey(APDU apdu, ECPublicKey key) throws InvalidArgumentsException, NotEnoughSpaceException {
short pos = 0;
final short field_bytes = (key.getSize()%8 == 0) ? (short)(key.getSize()/8) : (short)(key.getSize()/8+1);
short len, r;
// Return pubkey. See ISO7816-8 table 3.
len = (short)(7 // We have: 7 tags,
+ (key.getSize() >= LENGTH_EC_FP_512 ? 9 : 7) // 7 length fields, of which 2 are 2 byte fields when using >= 512 bit curves,
+ 8 * field_bytes + 4); // 4 * field_len + 2 * 2 field_len + cofactor (2 bytes) + 2 * uncompressed tag
pos += UtilTLV.writeTagAndLen((short)0x7F49, len, ram_buf, pos);
// Prime - "P"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x81, len, ram_buf, pos);
r = key.getField(ram_buf, pos);
if(r < len) {
// If the parameter has fewer bytes than the field length, we fill
// the MSB's with zeroes.
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// First coefficient - "A"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x82, len, ram_buf, pos);
r = key.getA(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Second coefficient - "B"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x83, len, ram_buf, pos);
r = key.getB(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Generator - "PB"
len = (short)(1 + 2 * field_bytes);
pos += UtilTLV.writeTagAndLen((short)0x84, len, ram_buf, pos);
r = key.getG(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Order - "Q"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x85, len, ram_buf, pos);
r = key.getR(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Public key - "PP"
len = (short)(1 + 2 * field_bytes);
pos += UtilTLV.writeTagAndLen((short)0x86, len, ram_buf, pos);
r = key.getW(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Cofactor
len = 2;
pos += UtilTLV.writeTagAndLen((short)0x87, len, ram_buf, pos);