Initial commit
parents
Showing
.gitignore
0 → 100644
.idea/.gitignore
0 → 100644
.idea/compiler.xml
0 → 100644
.idea/gradle.xml
0 → 100644
.idea/jarRepositories.xml
0 → 100644
.idea/misc.xml
0 → 100644
build.gradle
0 → 100644
File added
gradlew
0 → 100644
gradlew.bat
0 → 100644
libs/gp221/README.TXT
0 → 100644
libs/gp221/gp221.jar
0 → 100644
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
sdks/jc304_kit/bin/cref.bat
0 → 100644
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
sdks/jc304_kit/document.css
0 → 100644
This source diff could not be displayed because it is too large. You can view the blob instead.
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
sdks/jc304_kit/lib/tools.jar
0 → 100644
File added
3.42 KB
1.68 KB
1.24 KB
91 Bytes
1.89 KB
2.01 KB
93 Bytes
settings.gradle
0 → 100644
| package josh.passport; | ||
| /** | ||
| * A very rough, zero extra memory use, TLV array scanner. | ||
| * | ||
| * @author Cees-Bart Breunesse <[email protected]> | ||
| * @author Wojciech Mostowski <[email protected]> | ||
| * | ||
| */ | ||
| public class BERTLVScanner { | ||
| /** Universal tag class. */ | ||
| public static final short UNIVERSAL_CLASS = 0; | ||
| /** Application tag class. */ | ||
| public static final short APPLICATION_CLASS = 1; | ||
| /** Context specific tag class. */ | ||
| public static final short CONTEXT_SPECIFIC_CLASS = 2; | ||
| /** Private tag class. */ | ||
| public static final short PRIVATE_CLASS = 3; | ||
| // Tag data | ||
| static short tag; | ||
| static short tagClass; | ||
| static boolean isPrimitive; | ||
| // Offset and length for the value | ||
| static short valueOffset; | ||
| static short valueLength; | ||
| private BERTLVScanner() { } | ||
| public static short readTag(byte[] in, short offset) { | ||
| short in_p = offset; | ||
| short b = (short) (in[in_p] & 0xff); | ||
| while (b == 0 || b == 0xff) { | ||
| in_p++; | ||
| b = in[in_p]; /* skip 00 and FF */ | ||
| } | ||
| switch (b & 0xC0) { | ||
| case 0: | ||
| tagClass = UNIVERSAL_CLASS; | ||
| break; | ||
| case 0x40: | ||
| tagClass = APPLICATION_CLASS; | ||
| break; | ||
| case 0x80: | ||
| tagClass = CONTEXT_SPECIFIC_CLASS; | ||
| break; | ||
| case 0xC0: | ||
| tagClass = PRIVATE_CLASS; | ||
| break; | ||
| } | ||
| switch (b & 0x20) { | ||
| case 0: | ||
| isPrimitive = true; | ||
| break; | ||
| case 0x20: | ||
| isPrimitive = false; | ||
| break; | ||
| } | ||
| switch (b & 0x1F) { | ||
| case 0x1F: | ||
| tag = b; | ||
| in_p++; | ||
| b = in[in_p]; | ||
| while ((b & 0x80) == 0x80) { | ||
| tag <<= 8; | ||
| tag |= (b & 0x7F); | ||
| in_p++; | ||
| b = in[in_p]; | ||
| } | ||
| tag <<= 8; | ||
| tag |= (b & 0x7F); | ||
| break; | ||
| default: | ||
| tag = b; | ||
| break; | ||
| } | ||
| in_p++; | ||
| return in_p; | ||
| } | ||
| public static short readLength(byte[] in, short offset) { | ||
| short in_p = offset; | ||
| short b = (short) (in[offset] & 0xff); | ||
| if ((b & 0x80) == 0) { | ||
| /* short form */ | ||
| valueLength = b; | ||
| } else { | ||
| /* long form */ | ||
| short count = (short) (b & 0x7F); | ||
| valueLength = 0; | ||
| for (short i = 0; i < count; i++) { | ||
| in_p++; | ||
| b = (short) (in[in_p] & 0xff); | ||
| valueLength <<= 8; | ||
| valueLength += b; | ||
| } | ||
| } | ||
| valueOffset = (short) (in_p + 1); | ||
| return valueOffset; | ||
| } | ||
| public static short skipValue() { | ||
| return (short) (valueOffset + valueLength); | ||
| } | ||
| } |
| /* | ||
| * passportapplet - A reference implementation of the MRTD standards. | ||
| * | ||
| * Copyright (C) 2006 SoS group, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id$ | ||
| */ | ||
| package josh.passport; | ||
| import javacard.framework.ISO7816; | ||
| import javacard.framework.ISOException; | ||
| import javacard.framework.JCSystem; | ||
| import javacard.framework.Util; | ||
| import javacard.security.CryptoException; | ||
| import javacard.security.DESKey; | ||
| import javacard.security.Signature; | ||
| import javacardx.crypto.Cipher; | ||
| /** | ||
| * This class is a hack. It (probably) implements | ||
| * => encrypt/decrypt of ALG_DES_CBC_NOPAD using ALG_DES_CBC_ISO9797_M2 | ||
| * This is because ALG_DES_CBC_NOPAD and ALG_DES_MAC8_ISO9797_1_M2_ALG3 do not | ||
| * exist on CREF. | ||
| * | ||
| * @author Cees-Bart Breunesse <[email protected]> | ||
| * @author Ronny Wichers Schreur <[email protected]> | ||
| * | ||
| * @version $Revision$ | ||
| */ | ||
| public class CREFPassportCrypto extends JCOP41PassportCrypto implements ISO7816 { | ||
| private byte padding; | ||
| protected void init() { | ||
| ciph = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false); | ||
| sig = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, | ||
| false); | ||
| } | ||
| CREFPassportCrypto(KeyStore keyStore) { | ||
| super(keyStore); | ||
| tempSpace_decryptDES = JCSystem.makeTransientByteArray((short) 16, | ||
| JCSystem.CLEAR_ON_RESET); | ||
| tempSpace_doMacFinal = JCSystem.makeTransientByteArray((short) 24, | ||
| JCSystem.CLEAR_ON_RESET); | ||
| } | ||
| private short decryptDESusingDESCBCM2(DESKey key, byte[] in, | ||
| short in_offset, byte[] out, short out_offset, short length) { | ||
| if ((ciph.getAlgorithm() != Cipher.ALG_DES_CBC_ISO9797_M2) | ||
| || ((short) (length + out_offset + 16) > (short) (out.length)) | ||
| || ((short) (length + in_offset) > (short) in.length)) | ||
| ISOException.throwIt((short) 0x6d69); | ||
| ciph.init(key, Cipher.MODE_ENCRYPT); | ||
| ciph.doFinal(ZERO, | ||
| (short) 0, | ||
| (short) 8, | ||
| tempSpace_decryptDES, | ||
| (short) 0); | ||
| ciph.init(key, Cipher.MODE_DECRYPT); | ||
| short written = ciph.update(in, in_offset, length, out, out_offset); | ||
| written += ciph.doFinal(tempSpace_decryptDES, | ||
| (short) 0, | ||
| (short) (16), | ||
| out, | ||
| (short) (out_offset + written)); | ||
| return (short)(written - 8); // FIXME: hack, compensate for padding | ||
| } | ||
| private static byte[] tempSpace_decryptDES; | ||
| private static final byte[] ZERO = { 0, 0, 0, 0, 0, 0, 0, 0 }; | ||
| private DESKey k; | ||
| private byte[] tempSpace_doMacFinal; | ||
| private void decryptInit(DESKey k) { | ||
| this.k = k; | ||
| } | ||
| private void encryptInit(DESKey k) { | ||
| this.k = k; | ||
| } | ||
| public void decryptInit() { | ||
| k=keyStore.getCryptKey(); | ||
| } | ||
| public short decrypt(byte[] ctext, short ctext_offset, short ctext_len, | ||
| byte[] ptext, short ptext_offset) { | ||
| CryptoException.throwIt((short)0x6d66); | ||
| return 0; | ||
| } | ||
| public short encrypt(byte[] ctext, short ctext_offset, short ctext_len, | ||
| byte[] ptext, short ptext_offset) { | ||
| CryptoException.throwIt((short)0x6d66); | ||
| return 0; | ||
| } | ||
| public short decryptFinal(byte[] ctext, short ctext_offset, short ctext_len, | ||
| byte[] ptext, short ptext_offset) { | ||
| return decryptDESusingDESCBCM2(k, ctext, ctext_offset, ptext, ptext_offset, ctext_len); | ||
| } | ||
| public short encryptInit(byte padding, byte[] plainText, short plaintextOffset, short plaintextLength) { | ||
| return encryptInit(keyStore.getCryptKey(), padding, plainText, plaintextOffset, plaintextLength); | ||
| } | ||
| private short encryptInit(DESKey k, byte padding, byte[] plainText, short plaintextOffset, short plaintextLength) { | ||
| this.k = k; | ||
| this.padding = padding; | ||
| return plaintextLength; | ||
| } | ||
| public short encryptFinal(byte[] ptext, short ptext_offset, short ptext_len, | ||
| byte[] ctext, short ctext_offset) { | ||
| ciph.init(k, Cipher.MODE_ENCRYPT); | ||
| short len = ciph.doFinal(ptext, ptext_offset, ptext_len, ctext, ctext_offset); | ||
| if(padding == PAD_INPUT) { | ||
| // ALG_DES_CBC_ISO9797_M2 does padding | ||
| return len; | ||
| } | ||
| else if (padding == DONT_PAD_INPUT) { | ||
| return (short)(len - 8); // FIXME: hack | ||
| } | ||
| return 0; | ||
| } | ||
| public void createMacFinal(byte[] msg, short msg_offset, short msg_len, | ||
| byte[] mac, short mac_offset) { | ||
| DESKey kA = keyStore.getMacKey(KeyStore.KEY_A); | ||
| DESKey kB = keyStore.getMacKey(KeyStore.KEY_B); | ||
| // updateMac(msg, msg_offset, msg_len); | ||
| sig.sign(msg, msg_offset, msg_len, mac, mac_offset); | ||
| decryptInit(kB); | ||
| short tempmac_offset = 0; | ||
| //macCiphECB.init(kB, Cipher.MODE_DECRYPT); | ||
| decryptFinal(mac, mac_offset, (short)8, tempSpace_doMacFinal, tempmac_offset ); | ||
| //macCiphECB.doFinal(mac, mac_offset, (short)8, mac, mac_offset); | ||
| encryptInit(kA); | ||
| //macCiphECB.init(kA, Cipher.MODE_ENCRYPT); | ||
| encryptFinal(tempSpace_doMacFinal, tempmac_offset, (short)8, tempSpace_doMacFinal, tempmac_offset); | ||
| //macCiphECB.doFinal(mac, mac_offset, (short)8, mac, mac_offset); | ||
| Util.arrayCopyNonAtomic(tempSpace_doMacFinal, tempmac_offset, mac, mac_offset, (short)8); | ||
| } | ||
| } |
| package josh.passport; | ||
| import javacard.framework.ISO7816; | ||
| import javacard.framework.ISOException; | ||
| import javacard.framework.JCSystem; | ||
| import javacard.framework.Util; | ||
| import javacard.security.KeyBuilder; | ||
| import javacard.security.RSAPublicKey; | ||
| import javacard.security.Signature; | ||
| /** | ||
| * Encapsulation class for a card verifiable certificates according to EAC 1.11. | ||
| * | ||
| * @author Wojciech Mostowski <[email protected]> | ||
| * | ||
| */ | ||
| public class CVCertificate { | ||
| private static final byte ROLE_DV_DOMESTIC = (byte)0x80; | ||
| private static final byte ROLE_DV_FOREIGN = (byte)0x40; | ||
| private static final byte ACCESS_DG3 = 0x01; | ||
| private static final byte ACCESS_DG4 = 0x02; | ||
| private static final byte CAR_TAG = 0x42; | ||
| /** | ||
| * Offsets to where the particular data (offsets & lengths) of the current | ||
| * certificate is (temporarily) stored in the data array | ||
| */ | ||
| static final short OFFSET_PUB_KEY_MODULUS_OFFSET = 0; | ||
| static final short OFFSET_PUB_KEY_MODULUS_LENGTH = 1; | ||
| static final short OFFSET_PUB_KEY_EXPONENT_OFFSET = 2; | ||
| static final short OFFSET_PUB_KEY_EXPONENT_LENGTH = 3; | ||
| static final short OFFSET_SUB_ID_OFFSET = 4; | ||
| static final short OFFSET_SUB_ID_LENGTH = 5; | ||
| static final short OFFSET_AUTHORIZATION_OFFSET = 6; | ||
| static final short OFFSET_EFF_DATE_OFFSET = 7; | ||
| static final short OFFSET_EXP_DATE_OFFSET = 8; | ||
| static final short OFFSET_SIGNATURE_OFFSET = 9; | ||
| static final short OFFSET_SIGNATURE_LENGTH = 10; | ||
| static final short OFFSET_BODY_LENGTH = 11; | ||
| /** Different tags to parse */ | ||
| private static final short TAG_CERT_BODY = 0x7F4E; | ||
| private static final short TAG_CERT_VERSION = 0x5F29; | ||
| private static final short TAG_AUTH_ID = 0x42; | ||
| private static final short TAG_PUB_KEY = 0x7F49; | ||
| private static final short TAG_OID = 0x06; | ||
| private static final short TAG_MODULUS = 0x81; | ||
| private static final short TAG_EXPONENT = 0x82; | ||
| private static final short TAG_SUBJECT_ID = 0x5F20; | ||
| private static final short TAG_SUBJECT_AUTH = 0x7F4C; | ||
| private static final short TAG_AUTHORIZATION = 0x53; | ||
| private static final short TAG_EFF_DATE = 0x5F25; | ||
| private static final short TAG_EXP_DATE = 0x5F24; | ||
| private static final short TAG_SIGNATURE = 0x5F37; | ||
| /** The ASN1 OID of the only algorithm our certificates support */ | ||
| private static final byte[] RSA_SHA1_OID = { 0x04, 0x00, | ||
| 0x7F, 0x00, 0x07, 0x02, 0x02, 0x02, 0x01, 0x01 }; | ||
| /** The EAC OID, see EAC 1.11, D.2.1.3 */ | ||
| private static final byte[] EAC_OID = { 0x04, 0x00, | ||
| 0x7F, 0x00, 0x07, 0x03, 0x01, 0x02, 0x01 }; | ||
| short[] data; | ||
| Object[] source; | ||
| RSAPublicKey currentCertPublicKey; | ||
| byte[] currentCertSubjectId; | ||
| byte[] effectiveCertAuthorization; | ||
| byte[] currentCertEffDate; | ||
| byte[] currentCertExpDate; | ||
| byte[] accessFlag; | ||
| byte[] currentDate = { 0x00, 0x09, 0x00, 0x01, 0x00, 0x01 }; // 2009-01-01 | ||
| Signature signature; | ||
| byte[] currentCertNum; | ||
| byte[] cert1HolderReference; | ||
| byte[] cert1PublicKeyData; | ||
| byte cert1Authorization; | ||
| byte[] cert1EffDate; | ||
| byte[] cert1ExpDate; | ||
| byte[] cert2HolderReference; | ||
| byte[] cert2PublicKeyData; | ||
| byte cert2Authorization; | ||
| byte[] cert2EffDate; | ||
| byte[] cert2ExpDate; | ||
| byte[] cvcaFileReference; | ||
| CVCertificate() { | ||
| data = JCSystem.makeTransientShortArray( | ||
| (short) (OFFSET_BODY_LENGTH + 1), JCSystem.CLEAR_ON_DESELECT); | ||
| effectiveCertAuthorization = JCSystem.makeTransientByteArray((short)1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| currentCertNum = JCSystem.makeTransientByteArray((short)1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| accessFlag = JCSystem.makeTransientByteArray((short)1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| currentCertEffDate = JCSystem.makeTransientByteArray((short)6, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| currentCertExpDate = JCSystem.makeTransientByteArray((short)6, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| source = JCSystem.makeTransientObjectArray((short) 1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| currentCertSubjectId = JCSystem.makeTransientByteArray((short) 17, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| currentCertPublicKey = (RSAPublicKey) KeyBuilder.buildKey( | ||
| KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_1024, false); | ||
| signature = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false); | ||
| } | ||
| /** | ||
| * Checks if the provided subject id matches the current one, if any, or selects one of the root ones. | ||
| * | ||
| * @param data | ||
| * byte[] with the subject id | ||
| * @param offset | ||
| * offset to data | ||
| * @param length | ||
| * length of the data | ||
| * @return true if the current subject match or it was possible to select one of the root ones | ||
| */ | ||
| boolean selectSubjectId(byte[] data, short offset, short length) { | ||
| if(currentCertSubjectId[0] == 0) { | ||
| if(cert1HolderReference != null && cert1HolderReference[0] == (byte)length) { | ||
| if(Util.arrayCompare(cert1HolderReference, (short)1, data, offset, length) == 0) { | ||
| setupCurrentKey(cert1HolderReference, cert1PublicKeyData, cert1Authorization, cert1EffDate, cert1ExpDate); | ||
| currentCertNum[0] = 1; | ||
| return true; | ||
| } | ||
| }else if(cert2HolderReference != null && cert2HolderReference[0] == (byte)length) { | ||
| if(Util.arrayCompare(cert2HolderReference, (short)1, data, offset, length) == 0) { | ||
| setupCurrentKey(cert2HolderReference, cert2PublicKeyData, cert2Authorization, cert2EffDate, cert2ExpDate); | ||
| currentCertNum[0] = 2; | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| return length == currentCertSubjectId[0] && Util.arrayCompare(currentCertSubjectId, (short) 1, data, offset, | ||
| length) == 0; | ||
| } | ||
| // Sets up the current certificate data from the certificate contained in one | ||
| // of the cvca certificate stored in this object | ||
| private void setupCurrentKey(byte[] certHolderReference, byte[] certPublicKeyData, byte certAuthorization, byte[] certEffDate, byte[] certExpDate) { | ||
| Util.arrayCopyNonAtomic(certHolderReference, (short)0, currentCertSubjectId, (short)0, (short)certHolderReference.length); | ||
| currentCertPublicKey.setExponent(certPublicKeyData, (short)0, (short)3); | ||
| currentCertPublicKey.setModulus(certPublicKeyData, (short)3, (short)128); | ||
| effectiveCertAuthorization[0] = certAuthorization; | ||
| Util.arrayCopyNonAtomic(certEffDate, (short)0, currentCertEffDate, (short)0, (short)6); | ||
| Util.arrayCopyNonAtomic(certExpDate, (short)0, currentCertExpDate, (short)0, (short)6); | ||
| } | ||
| // Sets up the current certificate data from the certificate contained in source and data. | ||
| private void setupCurrentKeyFromCurrentCertificate() { | ||
| byte[] certData = (byte[])source[0]; | ||
| short certHolderReferenceOffset = data[OFFSET_SUB_ID_OFFSET]; | ||
| short certHolderReferenceLength = data[OFFSET_SUB_ID_LENGTH]; | ||
| short pubKeyExpOffset = data[OFFSET_PUB_KEY_EXPONENT_OFFSET]; | ||
| short pubKeyExpLength = data[OFFSET_PUB_KEY_EXPONENT_LENGTH]; | ||
| short pubKeyModOffset = data[OFFSET_PUB_KEY_MODULUS_OFFSET]; | ||
| short pubKeyModLength = data[OFFSET_PUB_KEY_MODULUS_LENGTH]; | ||
| short authorizationOffset = data[OFFSET_AUTHORIZATION_OFFSET]; | ||
| short effDateOffset = data[OFFSET_EFF_DATE_OFFSET]; | ||
| short expDateOffset = data[OFFSET_EXP_DATE_OFFSET]; | ||
| Util.arrayCopyNonAtomic(certData, certHolderReferenceOffset, currentCertSubjectId, (short)1, certHolderReferenceLength); | ||
| currentCertSubjectId[0] = (byte)certHolderReferenceLength; | ||
| currentCertPublicKey.setExponent(certData, pubKeyExpOffset, pubKeyExpLength); | ||
| currentCertPublicKey.setModulus(certData, pubKeyModOffset, pubKeyModLength); | ||
| effectiveCertAuthorization[0] &= certData[authorizationOffset]; | ||
| Util.arrayCopyNonAtomic(certData, effDateOffset, currentCertEffDate, (short)0, (short)6); | ||
| Util.arrayCopyNonAtomic(certData, expDateOffset, currentCertExpDate, (short)0, (short)6); | ||
| } | ||
| /** | ||
| * Cleans up the current certificate information. | ||
| * | ||
| */ | ||
| void clear() { | ||
| for (short i = 0; i < data.length; i++) { | ||
| data[i] = 0; | ||
| } | ||
| Util.arrayFillNonAtomic(effectiveCertAuthorization, (short) 0, (short) 1, | ||
| (byte) 0); | ||
| Util.arrayFillNonAtomic(currentCertSubjectId, (short) 0, (short) 17, | ||
| (byte) 0); | ||
| Util.arrayFillNonAtomic(currentCertEffDate, (short) 0, (short) 6, | ||
| (byte) 0); | ||
| Util.arrayFillNonAtomic(currentCertExpDate, (short) 0, (short) 6, | ||
| (byte) 0); | ||
| Util.arrayFillNonAtomic(currentCertNum, (short) 0, (short) 1, | ||
| (byte) 0); | ||
| Util.arrayFillNonAtomic(accessFlag, (short) 0, (short) 1, | ||
| (byte) 0); | ||
| currentCertPublicKey.clearKey(); | ||
| source[0] = null; | ||
| } | ||
| /** | ||
| * Verify the current certificate (ie. the data in source) using the current | ||
| * state of certificate verification data (publicKey, subject id, etc.) The | ||
| * verification procedure is described in EAC 1.11 spec in various places. | ||
| * | ||
| * @return true if certificate verification succeeds | ||
| */ | ||
| boolean verify() { | ||
| byte[] certData = (byte[])source[0]; | ||
| short bodyLength =data[OFFSET_BODY_LENGTH]; | ||
| short sigOffset =data[OFFSET_SIGNATURE_OFFSET]; | ||
| short sigLength =data[OFFSET_SIGNATURE_LENGTH]; | ||
| // check the actual signature | ||
| signature.init(currentCertPublicKey, Signature.MODE_VERIFY); | ||
| signature.update(certData, (short) 0, | ||
| bodyLength); | ||
| boolean result = signature.verify(certData, bodyLength, | ||
| (short) 0, certData, sigOffset, sigLength); | ||
| // check dates | ||
| result = (compareDate((byte[]) source[0], data[OFFSET_EXP_DATE_OFFSET], | ||
| currentDate, (short) 0) > 0) | ||
| && result; | ||
| short subjectIdOffset = data[OFFSET_SUB_ID_OFFSET]; | ||
| short subjectIdLength = data[OFFSET_SUB_ID_LENGTH]; | ||
| if((cert1HolderReference != null && (byte)subjectIdLength == cert1HolderReference[0] && | ||
| Util.arrayCompare(cert1HolderReference, (short)1, certData, subjectIdOffset, subjectIdLength) == 0) | ||
| || | ||
| (cert2HolderReference != null && (byte)subjectIdLength == cert2HolderReference[0] && | ||
| Util.arrayCompare(cert2HolderReference, (short)1, certData, subjectIdOffset, subjectIdLength) == 0)){ | ||
| result = false; | ||
| } | ||
| if (result) { | ||
| boolean preDomestic = (byte)(effectiveCertAuthorization[0] & ROLE_DV_DOMESTIC) == ROLE_DV_DOMESTIC; | ||
| setupCurrentKeyFromCurrentCertificate(); | ||
| boolean bit1 = (byte)(effectiveCertAuthorization[0] & ROLE_DV_DOMESTIC) == ROLE_DV_DOMESTIC; | ||
| boolean bit2 = (byte)(effectiveCertAuthorization[0] & ROLE_DV_FOREIGN) == ROLE_DV_FOREIGN; | ||
| boolean setTime = bit1 || bit2 || preDomestic; | ||
| boolean setCert = bit1 && bit2; | ||
| boolean grantAccess = !bit1 && !bit2; | ||
| if(setTime && compareDate(currentDate, (short)0, currentCertEffDate, (short)0) >= 0) { | ||
| setTime = false; | ||
| } | ||
| if(setCert || setTime) { | ||
| byte num = currentCertNum[0]; | ||
| byte[] certHolderReference = num == 1 ? cert1HolderReference : cert2HolderReference; | ||
| byte[] certPublicKeyData = num == 1 ? cert1PublicKeyData : cert2PublicKeyData; | ||
| byte[] certEffDate = num == 1 ? cert1EffDate : cert2EffDate; | ||
| byte[] certExpDate = num == 1 ? cert1ExpDate : cert2ExpDate; | ||
| JCSystem.beginTransaction(); | ||
| if(setCert) { | ||
| if(num == 1) { | ||
| cert1Authorization = effectiveCertAuthorization[0]; | ||
| }else{ | ||
| cert2Authorization = effectiveCertAuthorization[0]; | ||
| } | ||
| Util.arrayCopy(currentCertSubjectId, (short)0, certHolderReference, (short)0, (short)17); | ||
| Util.arrayCopy(currentCertEffDate, (short)0, certEffDate, (short)0, (short)6); | ||
| Util.arrayCopy(currentCertExpDate, (short)0, certExpDate, (short)0, (short)6); | ||
| currentCertPublicKey.getExponent(certPublicKeyData, (short)0); | ||
| currentCertPublicKey.getModulus(certPublicKeyData, (short)3); | ||
| short index = 0; | ||
| if(cert1HolderReference != null) { | ||
| index = setupCVCA(index, cert1HolderReference); | ||
| } | ||
| if(cert2HolderReference != null) { | ||
| index = setupCVCA(index, cert2HolderReference); | ||
| } | ||
| while(index < 36) cvcaFileReference[index++] = 0; | ||
| } | ||
| if(setTime) { | ||
| Util.arrayCopy(currentCertEffDate, (short)0, currentDate, (short)0, (short)6); | ||
| } | ||
| JCSystem.commitTransaction(); | ||
| } | ||
| if(setCert) { | ||
| clear(); | ||
| } | ||
| if(grantAccess) { | ||
| accessFlag[0] = effectiveCertAuthorization[0]; | ||
| // FIXME: clear() ? | ||
| } | ||
| } else { | ||
| clear(); | ||
| } | ||
| return result; | ||
| } | ||
| // Updates the cvcaFile contents with the new CVCA reference | ||
| private short setupCVCA(short index, byte[] reference) { | ||
| short len = reference[0]; | ||
| cvcaFileReference[index++] = CAR_TAG; | ||
| cvcaFileReference[index++] = (byte)len; | ||
| Util.arrayCopy(reference, (short)1, cvcaFileReference, index, len); | ||
| index += len; | ||
| return index; | ||
| } | ||
| /** | ||
| * Sets the root certificate data stored in this object from the data recoreded in | ||
| * <code>source</code> and <code>data</code>. This is only used during applet | ||
| * personalisation. | ||
| * | ||
| * @param num certificate number, 1 or 2. | ||
| */ | ||
| void setRootCertificate(byte[] in, short num) { | ||
| if((num == 1 && cert1HolderReference != null) || (num == 2 && cert2HolderReference != null) || (num != 1 && num != 2)) { | ||
| return; | ||
| } | ||
| short certHolderReferenceOffset = data[OFFSET_SUB_ID_OFFSET]; | ||
| short certHolderReferenceLength = data[OFFSET_SUB_ID_LENGTH]; | ||
| short pubKeyExpOffset = data[OFFSET_PUB_KEY_EXPONENT_OFFSET]; | ||
| short pubKeyExpLength = data[OFFSET_PUB_KEY_EXPONENT_LENGTH]; | ||
| short pubKeyModOffset = data[OFFSET_PUB_KEY_MODULUS_OFFSET]; | ||
| short pubKeyModLength = data[OFFSET_PUB_KEY_MODULUS_LENGTH]; | ||
| short authorizationOffset = data[OFFSET_AUTHORIZATION_OFFSET]; | ||
| short effDateOffset = data[OFFSET_EFF_DATE_OFFSET]; | ||
| short expDateOffset = data[OFFSET_EXP_DATE_OFFSET]; | ||
| byte[] holderReference = new byte[17]; | ||
| Util.arrayCopyNonAtomic(in, certHolderReferenceOffset, holderReference, (short)1, certHolderReferenceLength); | ||
| holderReference[0] = (byte)certHolderReferenceLength; | ||
| byte[] certPubKeyData = new byte[(short)(pubKeyExpLength + pubKeyModLength)]; | ||
| Util.arrayCopyNonAtomic(in, pubKeyExpOffset, certPubKeyData, (short)0, pubKeyExpLength); | ||
| Util.arrayCopyNonAtomic(in, pubKeyModOffset, certPubKeyData, pubKeyExpLength, pubKeyModLength); | ||
| byte certAuthorization = in[authorizationOffset]; | ||
| byte[] certEffDate = new byte[6]; | ||
| Util.arrayCopyNonAtomic(in, effDateOffset, certEffDate, (short)0, (short)6); | ||
| byte[] certExpDate = new byte[6]; | ||
| Util.arrayCopyNonAtomic(in, expDateOffset, certExpDate, (short)0, (short)6); | ||
| if(num == 1) { | ||
| cert1HolderReference = holderReference; | ||
| cert1PublicKeyData = certPubKeyData; | ||
| cert1Authorization = certAuthorization; | ||
| cert1EffDate = certEffDate; | ||
| cert1ExpDate = certExpDate; | ||
| }else { | ||
| cert2HolderReference = holderReference; | ||
| cert2PublicKeyData = certPubKeyData; | ||
| cert2Authorization = certAuthorization; | ||
| cert2EffDate = certEffDate; | ||
| cert2ExpDate = certExpDate; | ||
| } | ||
| clear(); | ||
| } | ||
| /** | ||
| * Parse the current certificate. The data in source/in is analyzed and | ||
| * offsets and lengths of particular elements of the certificate are stored | ||
| * in the <code>data</code> array. For the root certificate (root == true) we do not | ||
| * parse the signature (we have chosen not to provide it). The format of the | ||
| * certificate is described in EAC spec version 1.11 App A & C. | ||
| * | ||
| * @param in | ||
| * the array with the certificate to be parsed | ||
| * @param offset | ||
| * offset to in | ||
| * @param length | ||
| * length of the data | ||
| * @param root | ||
| * whether we are parsing a root certificate (no signature) | ||
| */ | ||
| void parseCertificate(byte[] in, short offset, short length, boolean root) { | ||
| try { | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_CERT_BODY) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_CERT_VERSION | ||
| || BERTLVScanner.valueLength != (short) 1 | ||
| || in[offset] != (byte) 0x00) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_AUTH_ID) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| BERTLVScanner.readLength(in, offset); | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_PUB_KEY) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_OID | ||
| || BERTLVScanner.valueLength != (short) RSA_SHA1_OID.length | ||
| || Util.arrayCompare(in, offset, RSA_SHA1_OID, (short) 0, | ||
| (short) RSA_SHA1_OID.length) != 0) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_MODULUS) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| data[OFFSET_PUB_KEY_MODULUS_OFFSET] = BERTLVScanner.readLength(in, | ||
| offset); | ||
| data[OFFSET_PUB_KEY_MODULUS_LENGTH] = BERTLVScanner.valueLength; | ||
| offset = BERTLVScanner.skipValue(); | ||
| if (in[data[OFFSET_PUB_KEY_MODULUS_OFFSET]] == (byte) 0x00) { | ||
| data[OFFSET_PUB_KEY_MODULUS_OFFSET]++; | ||
| data[OFFSET_PUB_KEY_MODULUS_LENGTH]--; | ||
| } | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_EXPONENT) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| data[OFFSET_PUB_KEY_EXPONENT_OFFSET] = BERTLVScanner.readLength(in, | ||
| offset); | ||
| data[OFFSET_PUB_KEY_EXPONENT_LENGTH] = BERTLVScanner.valueLength; | ||
| offset = BERTLVScanner.skipValue(); | ||
| if (in[data[OFFSET_PUB_KEY_EXPONENT_OFFSET]] == (byte) 0x00) { | ||
| data[OFFSET_PUB_KEY_EXPONENT_OFFSET]++; | ||
| data[OFFSET_PUB_KEY_EXPONENT_LENGTH]--; | ||
| } | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_SUBJECT_ID) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| data[OFFSET_SUB_ID_OFFSET] = BERTLVScanner.readLength(in, offset); | ||
| data[OFFSET_SUB_ID_LENGTH] = BERTLVScanner.valueLength; | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_SUBJECT_AUTH | ||
| || BERTLVScanner.valueLength != (short) 14) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| offset = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_OID | ||
| || BERTLVScanner.valueLength != (short)EAC_OID.length | ||
| || Util.arrayCompare(in, offset, EAC_OID, | ||
| (short) 0, (short)EAC_OID.length) != 0) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| data[OFFSET_AUTHORIZATION_OFFSET] = BERTLVScanner.readLength(in, | ||
| offset); | ||
| if (BERTLVScanner.tag != TAG_AUTHORIZATION | ||
| || BERTLVScanner.valueLength != (short) 1) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| data[OFFSET_EFF_DATE_OFFSET] = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_EFF_DATE | ||
| || BERTLVScanner.valueLength != (short) 6) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| data[OFFSET_EXP_DATE_OFFSET] = BERTLVScanner.readLength(in, offset); | ||
| if (BERTLVScanner.tag != TAG_EXP_DATE | ||
| || BERTLVScanner.valueLength != (short) 6) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| offset = BERTLVScanner.skipValue(); | ||
| data[OFFSET_BODY_LENGTH] = offset; | ||
| if (!root) { | ||
| offset = BERTLVScanner.readTag(in, offset); | ||
| if (BERTLVScanner.tag != TAG_SIGNATURE) { | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| data[OFFSET_SIGNATURE_OFFSET] = BERTLVScanner.readLength(in, | ||
| offset); | ||
| data[OFFSET_SIGNATURE_LENGTH] = BERTLVScanner.valueLength; | ||
| source[0] = in; | ||
| } | ||
| } catch (Exception e){ | ||
| clear(); | ||
| ISOException.throwIt(ISO7816.SW_WRONG_DATA); | ||
| } | ||
| } | ||
| /** | ||
| * Compares two dates. | ||
| * | ||
| * @param date1 | ||
| * the first date | ||
| * @param offset1 | ||
| * offset to the first date | ||
| * @param date2 | ||
| * the second date | ||
| * @param offset2 | ||
| * offset to the second date | ||
| * @return -1 if the first date is before the second, 1 if it is after, 0 if | ||
| * the same | ||
| */ | ||
| private byte compareDate(byte[] date1, short offset1, byte[] date2, | ||
| short offset2) { | ||
| short year1 = (short) ((short) (date1[offset1] * 10) + date1[(short) (offset1 + 1)]); | ||
| short year2 = (short) ((short) (date2[offset2] * 10) + date2[(short) (offset2 + 1)]); | ||
| short month1 = (short) ((short) (date1[(short) (offset1 + 2)] * 10) + date1[(short) (offset1 + 3)]); | ||
| short month2 = (short) ((short) (date2[(short) (offset2 + 2)] * 10) + date2[(short) (offset2 + 3)]); | ||
| short day1 = (short) ((short) (date1[(short) (offset1 + 4)] * 10) + date1[(short) (offset1 + 5)]); | ||
| short day2 = (short) ((short) (date2[(short) (offset2 + 4)] * 10) + date2[(short) (offset2 + 5)]); | ||
| if (year1 < year2) { | ||
| return -1; | ||
| } else if (year1 > year2) { | ||
| return 1; | ||
| } | ||
| if (month1 < month2) { | ||
| return -1; | ||
| } else if (month1 > month2) { | ||
| return 1; | ||
| } | ||
| if (day1 < day2) { | ||
| return -1; | ||
| } else if (day1 > day2) { | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } | ||
| boolean isDG3Accessible() { | ||
| return (byte)(accessFlag[0] & ACCESS_DG3) == ACCESS_DG3; | ||
| } | ||
| boolean isDG4Accessible() { | ||
| return (byte)(accessFlag[0] & ACCESS_DG4) == ACCESS_DG4; | ||
| } | ||
| } |
| /* | ||
| * JMRTD - A Java API for accessing machine readable travel documents. | ||
| * | ||
| * Copyright (C) 2006 SoS group, ICIS, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id: ISO7816.java 80 2006-07-20 07:34:25Z martijno $ | ||
| */ | ||
| package josh.passport; | ||
| /** | ||
| * Constants interface for Evil applets. | ||
| * | ||
| * @author Ronny Wichers Schreur ([email protected]) | ||
| * | ||
| * @version $Revision: 0 $ | ||
| */ | ||
| public interface EvilInterface | ||
| { | ||
| static final short INTERFACE_VERSION_NUMBER = 0x0000; | ||
| // evil class byte | ||
| static final byte CLA_EVIL = (byte) 0xE6; | ||
| // back door instructions | ||
| static final byte INS_OPEN_BACKDOOR = 0x66; | ||
| // p0, p1 == 0, le = length (access code), data = access code | ||
| // returns interface version number upon success | ||
| // static final byte[] ACCESS_CODE = {(byte) 0xAC, (byte) 0xCE, (byte) 0x55}; | ||
| static final byte INS_CLOSE_BACKDOOR = 0x67; | ||
| // p0, p1, le ignored | ||
| } | ||
| \ No newline at end of file |
| /* | ||
| * passportapplet - A reference implementation of the MRTD standards. | ||
| * | ||
| * Copyright (C) 2006 SoS group, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id$ | ||
| */ | ||
| package josh.passport; | ||
| import javacard.framework.ISO7816; | ||
| import javacard.framework.ISOException; | ||
| import javacard.framework.Util; | ||
| /** | ||
| * FileSystem. | ||
| * | ||
| * @author Martijn Oostdijk ([email protected]) | ||
| * @author Cees-Bart Breunesse ([email protected]) | ||
| * | ||
| * @version $Revision$ | ||
| */ | ||
| public class FileSystem { | ||
| static final short EF_DG1_FID = (short) 0x0101; | ||
| static final short EF_DG2_FID = (short) 0x0102; | ||
| static final short EF_DG3_FID = (short) 0x0103; | ||
| static final short EF_DG4_FID = (short) 0x0104; | ||
| static final short EF_DG5_FID = (short) 0x0105; | ||
| static final short EF_DG6_FID = (short) 0x0106; | ||
| static final short EF_DG7_FID = (short) 0x0107; | ||
| static final short EF_DG8_FID = (short) 0x0108; | ||
| static final short EF_DG9_FID = (short) 0x0109; | ||
| static final short EF_DG10_FID = (short) 0x010A; | ||
| static final short EF_DG11_FID = (short) 0x010B; | ||
| static final short EF_DG12_FID = (short) 0x010C; | ||
| static final short EF_DG13_FID = (short) 0x010D; | ||
| static final short EF_DG14_FID = (short) 0x010E; | ||
| static final short EF_DG15_FID = (short) 0x010F; | ||
| static final short EF_SOD_FID = (short) 0x011D; | ||
| static final short EF_COM_FID = (short) 0x011E; | ||
| static final short EF_CVCA_FID = (short) 0x011C; | ||
| static final short SOS_LOG_FID = (short) 0xdead; | ||
| private static final short EF_DG1_INDEX = (short) 0; | ||
| private static final short EF_DG2_INDEX = (short) 1; | ||
| private static final short EF_DG3_INDEX = (short) 2; | ||
| private static final short EF_DG4_INDEX = (short) 3; | ||
| private static final short EF_DG5_INDEX = (short) 4; | ||
| private static final short EF_DG6_INDEX = (short) 5; | ||
| private static final short EF_DG7_INDEX = (short) 6; | ||
| private static final short EF_DG8_INDEX = (short) 7; | ||
| private static final short EF_DG9_INDEX = (short) 8; | ||
| private static final short EF_DG10_INDEX = (short) 9; | ||
| private static final short EF_DG11_INDEX = (short) 10; | ||
| private static final short EF_DG12_INDEX = (short) 11; | ||
| private static final short EF_DG13_INDEX = (short) 12; | ||
| private static final short EF_DG14_INDEX = (short) 13; | ||
| private static final short EF_DG15_INDEX = (short) 14; | ||
| private static final short EF_SOD_INDEX = (short) 15; | ||
| private static final short EF_COM_INDEX = (short) 16; | ||
| private static final short EF_CVCA_INDEX = (short) 17; | ||
| private static final short SOS_LOG_INDEX = (short) 18; | ||
| private Object[] files; | ||
| private short[] fileSizes; | ||
| public FileSystem() { | ||
| short size = (short) (SOS_LOG_INDEX + 1); | ||
| files = new Object[size]; | ||
| fileSizes = new short[size]; | ||
| } | ||
| public void createFile(short fid, short size) { | ||
| createFile(fid, size, null); | ||
| } | ||
| public void createFile(short fid, short size, CVCertificate certObject) { | ||
| short idx = getFileIndex(fid); | ||
| // first create determines maximum file size | ||
| if (files[idx] == null) | ||
| files[idx] = new byte[size]; | ||
| if (certObject != null) { | ||
| certObject.cvcaFileReference = (byte[]) files[idx]; | ||
| } | ||
| if (((byte[]) files[idx]).length < size) | ||
| ISOException.throwIt(ISO7816.SW_FILE_FULL); | ||
| fileSizes[idx] = size; | ||
| } | ||
| public void writeData(short fid, short file_offset, byte[] data, | ||
| short data_offset, short length) { | ||
| byte[] file = getFile(fid); | ||
| short fileSize = getFileSize(fid); | ||
| if (file == null) { | ||
| ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); | ||
| } | ||
| if (fileSize < (short) (file_offset + length)) | ||
| ISOException.throwIt(ISO7816.SW_FILE_FULL); | ||
| Util.arrayCopy(data, data_offset, getFile(fid), file_offset, length); | ||
| } | ||
| public byte[] getFile(short fid) { | ||
| short idx = getFileIndex(fid); | ||
| if (idx == -1) { | ||
| return null; | ||
| } | ||
| return (byte[]) files[idx]; | ||
| } | ||
| public short getFileSize(short fid) { | ||
| short idx = getFileIndex(fid); | ||
| if (idx == -1) { | ||
| return -1; | ||
| } | ||
| return fileSizes[idx]; | ||
| } | ||
| private static short getFileIndex(short fid) throws ISOException { | ||
| if ((fid == EF_DG3_FID && !PassportApplet.certificate.isDG3Accessible()) | ||
| || (fid == EF_DG4_FID && !PassportApplet.certificate | ||
| .isDG4Accessible())) { | ||
| ISOException | ||
| .throwIt(PassportApplet.SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| switch (fid) { | ||
| case EF_DG1_FID: | ||
| return EF_DG1_INDEX; | ||
| case EF_DG2_FID: | ||
| return EF_DG2_INDEX; | ||
| case EF_DG3_FID: | ||
| return EF_DG3_INDEX; | ||
| case EF_DG4_FID: | ||
| return EF_DG4_INDEX; | ||
| case EF_DG5_FID: | ||
| return EF_DG5_INDEX; | ||
| case EF_DG6_FID: | ||
| return EF_DG6_INDEX; | ||
| case EF_DG7_FID: | ||
| return EF_DG7_INDEX; | ||
| case EF_DG8_FID: | ||
| return EF_DG8_INDEX; | ||
| case EF_DG9_FID: | ||
| return EF_DG9_INDEX; | ||
| case EF_DG10_FID: | ||
| return EF_DG10_INDEX; | ||
| case EF_DG11_FID: | ||
| return EF_DG11_INDEX; | ||
| case EF_DG12_FID: | ||
| return EF_DG12_INDEX; | ||
| case EF_DG13_FID: | ||
| return EF_DG13_INDEX; | ||
| case EF_DG14_FID: | ||
| return EF_DG14_INDEX; | ||
| case EF_DG15_FID: | ||
| return EF_DG15_INDEX; | ||
| case EF_SOD_FID: | ||
| return EF_SOD_INDEX; | ||
| case EF_COM_FID: | ||
| return EF_COM_INDEX; | ||
| case EF_CVCA_FID: | ||
| return EF_CVCA_INDEX; | ||
| case SOS_LOG_FID: | ||
| return SOS_LOG_INDEX; | ||
| default: | ||
| return -1; | ||
| } | ||
| } | ||
| } |
| /* | ||
| * passportapplet - A reference implementation of the MRTD standards. | ||
| * | ||
| * Copyright (C) 2006 SoS group, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id$ | ||
| */ | ||
| package josh.passport; | ||
| import javacard.framework.APDU; | ||
| import javacard.framework.Applet; | ||
| import javacard.framework.CardRuntimeException; | ||
| import javacard.framework.ISO7816; | ||
| import javacard.framework.ISOException; | ||
| import javacard.framework.JCSystem; | ||
| import javacard.framework.Util; | ||
| import javacard.security.RandomData; | ||
| import javacard.security.Signature; | ||
| import javacardx.crypto.Cipher; | ||
| // API for setATRHistBytes - requires Global Platform API gp211.jar | ||
| // Comment out the following line if API not available. | ||
| // import org.globalplatform.GPSystem; | ||
| /** | ||
| * PassportApplet | ||
| * | ||
| * @author ceesb ([email protected]) | ||
| * @author woj ([email protected]) | ||
| * @author martijno ([email protected]) | ||
| * | ||
| * @version $Revision$ | ||
| */ | ||
| public class PassportApplet extends Applet implements ISO7816 { | ||
| static byte volatileState[]; | ||
| static byte persistentState; | ||
| /* values for volatile state */ | ||
| static final byte CHALLENGED = 1; | ||
| static final byte MUTUAL_AUTHENTICATED = 2; // ie BAC | ||
| static final byte FILE_SELECTED = 4; | ||
| static final byte CHIP_AUTHENTICATED = 0x10; | ||
| static final byte TERMINAL_AUTHENTICATED = 0x20; | ||
| /* values for persistent state */ | ||
| static final byte HAS_MUTUALAUTHENTICATION_KEYS = 1; | ||
| static final byte HAS_EXPONENT = 2; | ||
| static final byte LOCKED = 4; | ||
| static final byte HAS_MODULUS = 8; | ||
| static final byte HAS_EC_KEY = 0x10; | ||
| static final byte HAS_CVCERTIFICATE = 0x20; | ||
| static final byte CHAIN_CLA = 0x10; | ||
| /* for authentication */ | ||
| static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82; | ||
| static final byte INS_GET_CHALLENGE = (byte) 0x84; | ||
| static final byte CLA_PROTECTED_APDU = 0x0c; | ||
| static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88; | ||
| /* for EAC */ | ||
| static final byte INS_PSO = (byte) 0x2A; | ||
| static final byte INS_MSE = (byte) 0x22; | ||
| static final byte P2_VERIFYCERT = (byte) 0xBE; | ||
| static final byte P1_SETFORCOMPUTATION = (byte) 0x41; | ||
| static final byte P1_SETFORVERIFICATION = (byte) 0x81; | ||
| static final byte P2_KAT = (byte) 0xA6; | ||
| static final byte P2_DST = (byte) 0xB6; | ||
| static final byte P2_AT = (byte) 0xA4; | ||
| /* for reading */ | ||
| static final byte INS_SELECT_FILE = (byte) 0xA4; | ||
| static final byte INS_READ_BINARY = (byte) 0xB0; | ||
| /* for writing */ | ||
| static final byte INS_UPDATE_BINARY = (byte) 0xd6; | ||
| static final byte INS_CREATE_FILE = (byte) 0xe0; | ||
| static final byte INS_PUT_DATA = (byte) 0xda; | ||
| static final short KEY_LENGTH = 16; | ||
| static final short KEYMATERIAL_LENGTH = 16; | ||
| static final short RND_LENGTH = 8; | ||
| static final short MAC_LENGTH = 8; | ||
| private static final byte PRIVMODULUS_TAG = 0x60; | ||
| private static final byte PRIVEXPONENT_TAG = 0x61; | ||
| private static final byte MRZ_TAG = 0x62; | ||
| private static final byte ECPRIVATEKEY_TAG = 0x63; | ||
| private static final byte CVCERTIFICATE_TAG = 0x64; | ||
| /* status words */ | ||
| private static final short SW_OK = (short) 0x9000; | ||
| private static final short SW_REFERENCE_DATA_NOT_FOUND = (short) 0x6A88; | ||
| static final short SW_INTERNAL_ERROR = (short) 0x6d66; | ||
| private byte[] rnd; | ||
| private byte[] ssc; | ||
| private byte[] documentNumber; | ||
| private FileSystem fileSystem; | ||
| private RandomData randomData; | ||
| private short selectedFile; | ||
| private PassportCrypto passportCrypto; | ||
| private PassportInit passportInit; | ||
| static CVCertificate certificate; | ||
| KeyStore keyStore; | ||
| private byte[] lastINS; | ||
| private short[] chainingOffset; | ||
| private byte[] chainingTmp; | ||
| // This is as long we suspect a card verifiable certifcate could be | ||
| private static final short CHAINING_BUFFER_LENGTH = 400; | ||
| // public ATRGlobal atrGlobal; | ||
| /** | ||
| * Creates a new passport applet. | ||
| */ | ||
| public PassportApplet(byte mode) { | ||
| fileSystem = new FileSystem(); | ||
| randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); | ||
| certificate = new CVCertificate(); | ||
| //// | ||
| keyStore = new KeyStore(mode); | ||
| // if(true) | ||
| // return; | ||
| switch (mode) { | ||
| case PassportCrypto.CREF_MODE: | ||
| passportCrypto = new CREFPassportCrypto(keyStore); | ||
| break; | ||
| case PassportCrypto.PERFECTWORLD_MODE: | ||
| passportCrypto = new PassportCrypto(keyStore); | ||
| break; | ||
| case PassportCrypto.JCOP41_MODE: | ||
| passportCrypto = new JCOP41PassportCrypto(keyStore); | ||
| break; | ||
| } | ||
| passportInit = new PassportInit(passportCrypto); | ||
| //// | ||
| rnd = JCSystem.makeTransientByteArray(RND_LENGTH, | ||
| JCSystem.CLEAR_ON_RESET); | ||
| ssc = JCSystem | ||
| .makeTransientByteArray((byte) 8, JCSystem.CLEAR_ON_RESET); | ||
| volatileState = JCSystem.makeTransientByteArray((byte) 1, | ||
| JCSystem.CLEAR_ON_RESET); | ||
| lastINS = JCSystem.makeTransientByteArray((short) 1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| chainingOffset = JCSystem.makeTransientShortArray((short) 1, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| chainingTmp = JCSystem.makeTransientByteArray(CHAINING_BUFFER_LENGTH, | ||
| JCSystem.CLEAR_ON_DESELECT); | ||
| } | ||
| /** | ||
| * Installs an instance of the applet. The default crypto mode is now | ||
| * PERFECTWORLD_MODE as the new JCOP41 cards support all required crypto. | ||
| * | ||
| * @param buffer | ||
| * @param offset | ||
| * @param length | ||
| * @see javacard.framework.Applet#install(byte[], short, byte) | ||
| */ | ||
| public static void install(byte[] buffer, short offset, byte length) { | ||
| // (new PassportApplet(PassportCrypto.JCOP41_MODE)).register(); | ||
| // new PassportApplet(PassportCrypto.PERFECTWORLD_MODE).register(); | ||
| new PassportApplet(PassportCrypto.CREF_MODE).register(); | ||
| } | ||
| /** | ||
| * Processes incoming APDUs. | ||
| * | ||
| * @param apdu | ||
| * @see javacard.framework.Applet#process(javacard.framework.APDU) | ||
| */ | ||
| public void process(APDU apdu) { | ||
| byte[] buffer = apdu.getBuffer(); | ||
| byte cla = buffer[OFFSET_CLA]; | ||
| byte ins = buffer[OFFSET_INS]; | ||
| short sw1sw2 = SW_OK; | ||
| boolean protectedApdu = (byte)(cla & CLA_PROTECTED_APDU) == CLA_PROTECTED_APDU; | ||
| short responseLength = 0; | ||
| short le = 0; | ||
| if (lastINS[0] != ins) { | ||
| chainingOffset[0] = 0; | ||
| } | ||
| lastINS[0] = ins; | ||
| /* Ignore APDU that selects this applet... */ | ||
| if (selectingApplet()) { | ||
| // Set ATR Historical Bytes (ATS). | ||
| // Requires gp211 API. Comment out the following line if API not | ||
| // available. | ||
| // org.globalplatform.GPSystem.setATRHistBytes(ATRGlobal.ATR_HIST, | ||
| // (short) 0x00, ATRGlobal.ATR_HIST_LEN); | ||
| return; | ||
| } | ||
| if (protectedApdu & hasMutuallyAuthenticated()) { | ||
| try { | ||
| le = passportCrypto.unwrapCommandAPDU(ssc, apdu); | ||
| } catch (CardRuntimeException e) { | ||
| sw1sw2 = e.getReason(); | ||
| } | ||
| } else if (protectedApdu) { | ||
| ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED); | ||
| } | ||
| if (sw1sw2 == SW_OK) { | ||
| try { | ||
| responseLength = processAPDU(apdu, cla, ins, protectedApdu, le); | ||
| } catch (CardRuntimeException e) { | ||
| sw1sw2 = e.getReason(); | ||
| } | ||
| } | ||
| if (protectedApdu && hasMutuallyAuthenticated()) { | ||
| responseLength = passportCrypto.wrapResponseAPDU(ssc, apdu, passportCrypto | ||
| .getApduBufferOffset(responseLength), responseLength, | ||
| sw1sw2); | ||
| } | ||
| if (responseLength > 0) { | ||
| if (apdu.getCurrentState() != APDU.STATE_OUTGOING) | ||
| apdu.setOutgoing(); | ||
| if (apdu.getCurrentState() != APDU.STATE_OUTGOING_LENGTH_KNOWN) | ||
| apdu.setOutgoingLength(responseLength); | ||
| apdu.sendBytes((short) 0, responseLength); | ||
| } | ||
| if (sw1sw2 != SW_OK) { | ||
| ISOException.throwIt(sw1sw2); | ||
| } | ||
| } | ||
| /** | ||
| * Processes incoming APDUs, excluding Secure Messaging. | ||
| * | ||
| * This method assumes SM protection has been removed from the | ||
| * incoming APDU, and does not add SM protection to the response. | ||
| * Handling of Secure Messaging is done in process(APDU apdu) | ||
| * | ||
| * @param protectedApdu true if Secure Messaging is used | ||
| * @return length of the response APDU | ||
| */ | ||
| public short processAPDU(APDU apdu, byte cla, byte ins, | ||
| boolean protectedApdu, short le) { | ||
| short responseLength = 0; | ||
| switch (ins) { | ||
| case INS_GET_CHALLENGE: | ||
| responseLength = processGetChallenge(apdu, protectedApdu, le); | ||
| break; | ||
| case INS_EXTERNAL_AUTHENTICATE: | ||
| responseLength = processMutualAuthenticate(apdu, protectedApdu); | ||
| break; | ||
| case INS_PSO: | ||
| if (!protectedApdu) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| responseLength = processPSO(apdu); | ||
| break; | ||
| case INS_MSE: | ||
| if (!protectedApdu) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| responseLength = processMSE(apdu); | ||
| break; | ||
| case INS_INTERNAL_AUTHENTICATE: | ||
| responseLength = processInternalAuthenticate(apdu, protectedApdu); | ||
| break; | ||
| case INS_SELECT_FILE: | ||
| processSelectFile(apdu); | ||
| break; | ||
| case INS_READ_BINARY: | ||
| responseLength = processReadBinary(apdu, le, protectedApdu); | ||
| break; | ||
| case INS_UPDATE_BINARY: | ||
| processUpdateBinary(apdu); | ||
| break; | ||
| case INS_CREATE_FILE: | ||
| processCreateFile(apdu); | ||
| break; | ||
| case INS_PUT_DATA: | ||
| processPutData(apdu); | ||
| break; | ||
| default: | ||
| ISOException.throwIt(SW_INS_NOT_SUPPORTED); | ||
| break; | ||
| } | ||
| return responseLength; | ||
| } | ||
| private short processPSO(APDU apdu) { | ||
| byte[] buffer = apdu.getBuffer(); | ||
| if (!hasChipAuthenticated() || hasTerminalAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| if (buffer[OFFSET_P1] != (byte) 0x00 | ||
| && buffer[OFFSET_P2] != P2_VERIFYCERT) { | ||
| ISOException.throwIt(SW_INCORRECT_P1P2); | ||
| } | ||
| if (certificate.currentCertSubjectId[0] == 0) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| short lc = (short) (buffer[OFFSET_LC] & 0xFF); | ||
| if (chainingOffset[0] > (short) (CHAINING_BUFFER_LENGTH - lc)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| chainingOffset[0] = Util.arrayCopyNonAtomic(buffer, OFFSET_CDATA, | ||
| chainingTmp, chainingOffset[0], lc); | ||
| if (((byte) (buffer[OFFSET_CLA] & CHAIN_CLA) == CHAIN_CLA)) { | ||
| return (short) 0; | ||
| } | ||
| short chainingTmpLength = chainingOffset[0]; | ||
| chainingOffset[0] = (short) 0; | ||
| certificate.parseCertificate(chainingTmp, (short) 0, chainingTmpLength, | ||
| false); | ||
| if (!certificate.verify()) { | ||
| ISOException.throwIt((short) 0x6300); | ||
| } | ||
| return (short) 0; | ||
| } | ||
| private short processMSE(APDU apdu) { | ||
| byte[] buffer = apdu.getBuffer(); | ||
| byte p1 = (byte) (buffer[OFFSET_P1] & 0xff); | ||
| byte p2 = (byte) (buffer[OFFSET_P2] & 0xff); | ||
| short lc = (short) (buffer[OFFSET_LC] & 0xff); | ||
| short buffer_p = OFFSET_CDATA; | ||
| if (!hasEACKey() || !hasEACCertificate()) { | ||
| ISOException.throwIt(SW_INS_NOT_SUPPORTED); | ||
| } | ||
| if (!hasMutuallyAuthenticated() || hasTerminalAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| if (p1 == P1_SETFORCOMPUTATION && p2 == P2_KAT) { | ||
| short lastOffset = (short) (lc + OFFSET_CDATA); | ||
| if (buffer_p > (short) (lastOffset - 2)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| if (buffer[buffer_p++] != (byte) 0x91) { | ||
| ISOException.throwIt(SW_WRONG_DATA); | ||
| } | ||
| short pubKeyLen = (short) (buffer[buffer_p++] & 0xFF); | ||
| short pubKeyOffset = buffer_p; | ||
| if (pubKeyOffset > (short) (lastOffset - pubKeyLen)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| buffer_p += pubKeyLen; | ||
| short keyIdOffset = 0; | ||
| short keyIdLength = 0; | ||
| if (buffer_p != lastOffset) { | ||
| if (buffer_p > (short) (lastOffset - 2)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| if (buffer[buffer_p++] != (byte) 0x84) { | ||
| ISOException.throwIt(SW_WRONG_DATA); | ||
| } | ||
| keyIdLength = (short) (buffer[buffer_p++] & 0xFF); | ||
| keyIdOffset = buffer_p; | ||
| if (keyIdOffset != (short) (lastOffset - keyIdLength)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| // ignore the key id, we don't use it for now | ||
| } | ||
| if (!passportCrypto.authenticateChip(buffer, pubKeyOffset, pubKeyLen)) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| volatileState[0] |= CHIP_AUTHENTICATED; | ||
| return 0; | ||
| } else if (p1 == P1_SETFORVERIFICATION && (p2 == P2_DST || p2 == P2_AT)) { | ||
| if (!hasChipAuthenticated() || hasTerminalAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| short lastOffset = (short) (lc + OFFSET_CDATA); | ||
| if (buffer_p > (short) (lastOffset - 2)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| if (buffer[buffer_p++] != (byte) 0x83) { | ||
| ISOException.throwIt(SW_WRONG_DATA); | ||
| } | ||
| short subIdLen = (short) (buffer[buffer_p++] & 0xFF); | ||
| if (buffer_p != (short) (lastOffset - subIdLen)) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| if (!certificate.selectSubjectId(buffer, buffer_p, subIdLen)) { | ||
| ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND); | ||
| } | ||
| } else { | ||
| ISOException.throwIt(SW_INCORRECT_P1P2); | ||
| } | ||
| return 0; | ||
| } | ||
| private void processPutData(APDU apdu) { | ||
| if (isLocked()) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short buffer_p = (short) (OFFSET_CDATA & 0xff); | ||
| short lc = (short) (buffer[OFFSET_LC] & 0xff); | ||
| short p1 = (short) (buffer[OFFSET_P1] & 0xff); | ||
| short p2 = (short) (buffer[OFFSET_P2] & 0xff); | ||
| // sanity check | ||
| if (buffer.length < (short) (buffer_p + lc)) { | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| if (p1 == 0xde && p2 == 0xad) { | ||
| persistentState |= LOCKED; | ||
| } else if (p1 == 0 && p2 == PRIVMODULUS_TAG) { | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == | ||
| // PRIVMODULUS_TAG | ||
| buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length == | ||
| // 00 | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04 | ||
| short modOffset = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short modLength = BERTLVScanner.valueLength; | ||
| if (buffer[modOffset] == 0) { | ||
| modLength--; | ||
| modOffset++; | ||
| } | ||
| keyStore.rsaPrivateKey.setModulus(buffer, modOffset, modLength); | ||
| persistentState |= HAS_MODULUS; | ||
| } else if (p1 == 0 && p2 == PRIVEXPONENT_TAG) { | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == | ||
| // PRIVEXP_TAG | ||
| buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length == | ||
| // 00 | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04 | ||
| short expOffset = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short expLength = BERTLVScanner.valueLength; | ||
| // leading zero | ||
| if (buffer[expOffset] == 0) { | ||
| expLength--; | ||
| expOffset++; | ||
| } | ||
| keyStore.rsaPrivateKey.setExponent(buffer, expOffset, expLength); | ||
| persistentState |= HAS_EXPONENT; | ||
| } else if (p1 == 0 && p2 == MRZ_TAG) { | ||
| // data is BERTLV object with three objects; docNr, dataOfBirth, | ||
| // dateOfExpiry | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); | ||
| buffer_p = BERTLVScanner.readLength(buffer, buffer_p); | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); | ||
| short docNrOffset = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short docNrLength = BERTLVScanner.valueLength; | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); | ||
| short dobOffset = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short dobLength = BERTLVScanner.valueLength; | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); | ||
| short doeOffset = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short doeLength = BERTLVScanner.valueLength; | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| documentNumber = new byte[(short)(docNrLength+1)]; | ||
| Util.arrayCopyNonAtomic(buffer, docNrOffset, documentNumber, | ||
| (short) 0, docNrLength); | ||
| documentNumber[docNrLength] = PassportInit.checkDigit(documentNumber,(short)0, docNrLength); | ||
| short keySeed_offset = passportInit.computeKeySeed(buffer, docNrOffset, | ||
| docNrLength, dobOffset, dobLength, doeOffset, doeLength); | ||
| short macKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH); | ||
| short encKey_p = (short) (keySeed_offset + KEYMATERIAL_LENGTH + KEY_LENGTH); | ||
| passportCrypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE, | ||
| macKey_p); | ||
| passportCrypto.deriveKey(buffer, keySeed_offset, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE, | ||
| encKey_p); | ||
| keyStore.setMutualAuthenticationKeys(buffer, macKey_p, buffer, | ||
| encKey_p); | ||
| persistentState |= HAS_MUTUALAUTHENTICATION_KEYS; | ||
| } else if (p1 == 0 && p2 == ECPRIVATEKEY_TAG) { | ||
| short finish = (short) (buffer_p + lc); | ||
| while (buffer_p < finish) { | ||
| buffer_p = BERTLVScanner.readTag(buffer, buffer_p); | ||
| buffer_p = BERTLVScanner.readLength(buffer, buffer_p); | ||
| short len = BERTLVScanner.valueLength; | ||
| switch (BERTLVScanner.tag) { | ||
| case (short) 0x81: | ||
| if (len == (short) 6) { | ||
| short e1 = Util.getShort(buffer, buffer_p); | ||
| short e2 = Util | ||
| .getShort(buffer, (short) (buffer_p + 2)); | ||
| short e3 = Util | ||
| .getShort(buffer, (short) (buffer_p + 4)); | ||
| keyStore.ecPrivateKey.setFieldF2M(e1, e2, e3); | ||
| keyStore.ecPublicKey.setFieldF2M(e1, e2, e3); | ||
| } else { | ||
| keyStore.ecPrivateKey.setFieldF2M(Util.getShort(buffer, | ||
| buffer_p)); | ||
| keyStore.ecPublicKey.setFieldF2M(Util.getShort(buffer, | ||
| buffer_p)); | ||
| } | ||
| break; | ||
| case (short) 0x82: | ||
| keyStore.ecPrivateKey.setA(buffer, buffer_p, len); | ||
| keyStore.ecPublicKey.setA(buffer, buffer_p, len); | ||
| break; | ||
| case (short) 0x83: | ||
| keyStore.ecPrivateKey.setB(buffer, buffer_p, len); | ||
| keyStore.ecPublicKey.setB(buffer, buffer_p, len); | ||
| break; | ||
| case (short) 0x84: | ||
| keyStore.ecPrivateKey.setG(buffer, buffer_p, len); | ||
| keyStore.ecPublicKey.setG(buffer, buffer_p, len); | ||
| break; | ||
| case (short) 0x85: | ||
| keyStore.ecPrivateKey.setR(buffer, buffer_p, len); | ||
| keyStore.ecPublicKey.setR(buffer, buffer_p, len); | ||
| break; | ||
| case (short) 0x86: | ||
| if (len == (short) 20) { | ||
| buffer_p--; | ||
| len++; | ||
| buffer[buffer_p] = 0x00; | ||
| } | ||
| keyStore.ecPrivateKey.setS(buffer, buffer_p, len); | ||
| break; | ||
| case (short) 0x87: | ||
| // This is the k, ignore it | ||
| // short k = Util.getShort(buffer, buffer_p); | ||
| break; | ||
| default: | ||
| ISOException.throwIt(SW_WRONG_DATA); | ||
| break; | ||
| } | ||
| buffer_p = BERTLVScanner.skipValue(); | ||
| } | ||
| if (keyStore.ecPrivateKey.isInitialized()) { | ||
| persistentState |= HAS_EC_KEY; | ||
| } else { | ||
| ISOException.throwIt(SW_WRONG_DATA); | ||
| } | ||
| } else if (p2 == CVCERTIFICATE_TAG) { | ||
| if ((byte) (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE) { | ||
| // We already have the certificate initialized | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| certificate.parseCertificate(buffer, buffer_p, lc, true); | ||
| certificate.setRootCertificate(buffer, p1); | ||
| persistentState |= HAS_CVCERTIFICATE; | ||
| } else { | ||
| ISOException.throwIt(SW_INCORRECT_P1P2); | ||
| } | ||
| } | ||
| /** | ||
| * Processes INTERNAL_AUTHENTICATE apdus, ie Active Authentication (AA). | ||
| * Receives a random and signs it. | ||
| * | ||
| * @param apdu | ||
| * @param protectedApdu true if Secure Messaging was used | ||
| * @return | ||
| */ | ||
| private short processInternalAuthenticate(APDU apdu, boolean protectedApdu) { | ||
| if (!hasInternalAuthenticationKeys() || !hasMutuallyAuthenticated()) { | ||
| ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| short buffer_p = (short) (OFFSET_CDATA & 0xff); | ||
| short hdr_offset = 0; | ||
| if (protectedApdu) { | ||
| hdr_offset = passportCrypto.getApduBufferOffset((short) 128); | ||
| } | ||
| short hdr_len = 1; | ||
| short m1_len = 106; // whatever | ||
| short m1_offset = (short) (hdr_offset + hdr_len); | ||
| short m2_len = 8; | ||
| short m2_offset = (short) (m1_offset + m1_len); | ||
| // we will write the hash over m2 | ||
| short m1m2hash_offset = (short) (m1_offset + m1_len); | ||
| short m1m2hash_len = 20; | ||
| short trailer_offset = (short) (m1m2hash_offset + m1m2hash_len); | ||
| short trailer_len = 1; | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF); | ||
| if (bytesLeft != m2_len) | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| // put m2 in place | ||
| Util.arrayCopyNonAtomic(buffer, buffer_p, buffer, m2_offset, m2_len); | ||
| // write some random data of m1_len | ||
| // randomData.generateData(buffer, m1_offset, m1_length); | ||
| for (short i = m1_offset; i < (short) (m1_offset + m1_len); i++) { | ||
| buffer[i] = 0; | ||
| } | ||
| // calculate SHA1 hash over m1 and m2 | ||
| passportCrypto.shaDigest.doFinal(buffer, m1_offset, (short) (m1_len + m2_len), buffer, | ||
| m1m2hash_offset); | ||
| passportCrypto.shaDigest.reset(); | ||
| // write trailer | ||
| buffer[trailer_offset] = (byte) 0xbc; | ||
| // write header | ||
| buffer[hdr_offset] = (byte) 0x6a; | ||
| // encrypt the whole buffer with our AA private key | ||
| short plaintext_len = (short) (hdr_len + m1_len + m1m2hash_len + trailer_len); | ||
| // sanity check | ||
| if (plaintext_len != 128) { | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| passportCrypto.rsaCiph.init(keyStore.rsaPrivateKey, Cipher.MODE_ENCRYPT); | ||
| short ciphertext_len = passportCrypto.rsaCiph.doFinal(buffer, hdr_offset, | ||
| plaintext_len, buffer, hdr_offset); | ||
| // sanity check | ||
| if (ciphertext_len != 128) { | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| return ciphertext_len; | ||
| } | ||
| /** | ||
| * Processes incoming GET_CHALLENGE APDUs, as part of BAC or EAC. | ||
| * | ||
| * Generates random 8 bytes, sends back result and stores result in rnd. | ||
| * A GET_CHALLENGE APDU can be sent as part of BAC, or as part of | ||
| * EAC (more specifically, Terminal Authentication (TA). | ||
| * | ||
| * @param apdu | ||
| * is used for sending (8 bytes) only | ||
| * @param protectedApdu true if Secure Messaging was used | ||
| */ | ||
| private short processGetChallenge(APDU apdu, boolean protectedApdu, short le) { | ||
| if (protectedApdu) { | ||
| // we're doing TA | ||
| if (!hasChipAuthenticated() | ||
| || certificate.cert1HolderReference[0] == (byte)0 | ||
| || hasTerminalAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| } else { | ||
| // we're doing BAC | ||
| if (!hasMutualAuthenticationKeys() || hasMutuallyAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| if (!protectedApdu) { | ||
| le = apdu.setOutgoing(); | ||
| } | ||
| // For the BAP challenge the length should be 8, for the EAP challenge | ||
| // we guess other lenghts are fine too? | ||
| if (le != 8) { | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| } | ||
| randomData.generateData(rnd, (short) 0, le); | ||
| short bufferOffset = protectedApdu ? passportCrypto.getApduBufferOffset(le) | ||
| : (short) 0; | ||
| Util.arrayCopyNonAtomic(rnd, (short) 0, buffer, bufferOffset, le); | ||
| volatileState[0] |= CHALLENGED; | ||
| return le; | ||
| } | ||
| /** | ||
| * Processes incoming EXTERNAL_AUTHENTICATE APDUs, as part of BAC or EAC. | ||
| * | ||
| * An EXTERNAL_AUTHENTICATE can be the last step of BAC, or the last | ||
| * step of Terminal Authentication (TA) when doing EAC. | ||
| * | ||
| * @param apdu | ||
| * the APDU | ||
| * @param protectedApdu true if Secure Messaging was used | ||
| * @return length of response APDU | ||
| */ | ||
| private short processMutualAuthenticate(APDU apdu, boolean protectedApdu) { | ||
| if (protectedApdu) { | ||
| // we're doing EAC | ||
| if (!hasChipAuthenticated() || !isChallenged() | ||
| || certificate.currentCertSubjectId[0] == (byte)0 | ||
| || hasTerminalAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short buffer_p = OFFSET_CDATA; | ||
| short lc = (short) (buffer[OFFSET_LC] & 0xFF); | ||
| setNoChallenged(); | ||
| if (!passportCrypto.eacVerifySignature(certificate.currentCertPublicKey, rnd, | ||
| documentNumber, buffer, buffer_p, lc)) { | ||
| certificate.clear(); | ||
| ISOException.throwIt((short) 0x6300); | ||
| } | ||
| certificate.clear(); | ||
| volatileState[0] |= TERMINAL_AUTHENTICATED; | ||
| return 0; | ||
| } else { | ||
| // we're doing BAC | ||
| if (!isChallenged() || hasMutuallyAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF); | ||
| short e_ifd_length = RND_LENGTH + RND_LENGTH + KEYMATERIAL_LENGTH; | ||
| // incoming message is e_ifd || m_ifd | ||
| // where e_ifd == E_KENC(rnd_ifd || rnd_icc || k_ifd) | ||
| if (bytesLeft != (short) (e_ifd_length + MAC_LENGTH)) | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| short e_ifd_p = OFFSET_CDATA; | ||
| short m_ifd_p = (short) (e_ifd_p + e_ifd_length); | ||
| if (apdu.getCurrentState() == APDU.STATE_INITIAL) { | ||
| apdu.setIncomingAndReceive(); | ||
| } | ||
| if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) { | ||
| // need all data in one APDU. | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| // buffer[OFFSET_CDATA ... +40] consists of e_ifd || m_ifd | ||
| // verify checksum m_ifd of cryptogram e_ifd | ||
| passportCrypto.initMac(Signature.MODE_VERIFY); | ||
| if (!passportCrypto.verifyMacFinal(buffer, e_ifd_p, e_ifd_length, buffer, | ||
| m_ifd_p)) | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| // decrypt e_ifd into buffer[0] where buffer = rnd.ifd || rnd.icc || | ||
| // k.ifd | ||
| passportCrypto.decryptInit(); | ||
| short plaintext_len = passportCrypto.decryptFinal(buffer, e_ifd_p, | ||
| e_ifd_length, buffer, (short) 0); | ||
| if (plaintext_len != e_ifd_length) // sanity check | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| short rnd_ifd_p = 0; | ||
| short rnd_icc_p = RND_LENGTH; | ||
| short k_ifd_p = (short) (rnd_icc_p + RND_LENGTH); | ||
| /* | ||
| * we use apdu buffer for writing intermediate data in buffer with | ||
| * following pointers | ||
| */ | ||
| short k_icc_p = (short) (k_ifd_p + KEYMATERIAL_LENGTH); | ||
| short keySeed_p = (short) (k_icc_p + KEYMATERIAL_LENGTH); | ||
| short keys_p = (short) (keySeed_p + KEYMATERIAL_LENGTH); | ||
| // verify that rnd.icc equals value generated in getChallenge | ||
| if (Util.arrayCompare(buffer, rnd_icc_p, rnd, (short) 0, RND_LENGTH) != 0) | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| // generate keying material k.icc | ||
| randomData.generateData(buffer, k_icc_p, KEYMATERIAL_LENGTH); | ||
| // calculate keySeed for session keys by xorring k_ifd and k_icc | ||
| PassportUtil.xor(buffer, k_ifd_p, buffer, k_icc_p, buffer, keySeed_p, | ||
| KEYMATERIAL_LENGTH); | ||
| // calculate session keys | ||
| passportCrypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.MAC_MODE, keys_p); | ||
| short macKey_p = keys_p; | ||
| keys_p += KEY_LENGTH; | ||
| passportCrypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, PassportCrypto.ENC_MODE, keys_p); | ||
| short encKey_p = keys_p; | ||
| keys_p += KEY_LENGTH; | ||
| keyStore.setSecureMessagingKeys(buffer, macKey_p, buffer, encKey_p); | ||
| // compute ssc | ||
| PassportCrypto.computeSSC(buffer, rnd_icc_p, buffer, rnd_ifd_p, ssc); | ||
| // create response in buffer where response = rnd.icc || rnd.ifd || | ||
| // k.icc | ||
| PassportUtil.swap(buffer, rnd_icc_p, rnd_ifd_p, RND_LENGTH); | ||
| Util.arrayCopyNonAtomic(buffer, k_icc_p, buffer, (short) (2 * RND_LENGTH), | ||
| KEYMATERIAL_LENGTH); | ||
| // make buffer encrypted using k_enc | ||
| passportCrypto.encryptInit(); | ||
| short ciphertext_len = passportCrypto.encryptFinal(buffer, (short) 0, | ||
| (short) (2 * RND_LENGTH + KEYMATERIAL_LENGTH), buffer, | ||
| (short) 0); | ||
| // create m_icc which is a checksum of response | ||
| passportCrypto.initMac(Signature.MODE_SIGN); | ||
| passportCrypto.createMacFinal(buffer, (short) 0, ciphertext_len, buffer, | ||
| ciphertext_len); | ||
| setNoChallenged(); | ||
| volatileState[0] |= MUTUAL_AUTHENTICATED; | ||
| return (short) (ciphertext_len + MAC_LENGTH); | ||
| } | ||
| } | ||
| /** | ||
| * Processes incoming SELECT_FILE APDUs. | ||
| * | ||
| * @param apdu | ||
| * where the first 2 data bytes encode the file to select. | ||
| */ | ||
| private void processSelectFile(APDU apdu) { | ||
| if (isLocked() & !hasMutuallyAuthenticated()) { | ||
| ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short lc = (short) (buffer[OFFSET_LC] & 0x00FF); | ||
| if (lc != 2) | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| if (apdu.getCurrentState() == APDU.STATE_INITIAL) { | ||
| apdu.setIncomingAndReceive(); | ||
| } | ||
| if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) { | ||
| // need all data in one APDU. | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| short fid = Util.getShort(buffer, OFFSET_CDATA); | ||
| if (fileSystem.getFile(fid) != null) { | ||
| selectedFile = fid; | ||
| volatileState[0] |= FILE_SELECTED; | ||
| return; | ||
| } | ||
| setNoFileSelected(); | ||
| ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); | ||
| } | ||
| /** | ||
| * Processes incoming READ_BINARY APDUs. Returns data of the currently | ||
| * selected file. | ||
| * | ||
| * @param apdu | ||
| * where the offset is carried in header bytes p1 and p2. | ||
| * @param le | ||
| * expected length by terminal | ||
| * @return length of the response APDU | ||
| */ | ||
| private short processReadBinary(APDU apdu, short le, boolean protectedApdu) { | ||
| if (!hasMutuallyAuthenticated()) { | ||
| ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); | ||
| } | ||
| if (!hasFileSelected()) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| byte p1 = buffer[OFFSET_P1]; | ||
| byte p2 = buffer[OFFSET_P2]; | ||
| short offset = Util.makeShort(p1, p2); | ||
| byte[] file = fileSystem.getFile(selectedFile); | ||
| if (file == null) { | ||
| ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); | ||
| } | ||
| short len; | ||
| short fileSize = fileSystem.getFileSize(selectedFile); | ||
| len = PassportUtil.min((short) (buffer.length - 37), | ||
| (short) (fileSize - offset)); | ||
| // FIXME: 37 magic | ||
| len = PassportUtil.min(len, (short) buffer.length); | ||
| len = PassportUtil.min(le, len); | ||
| short bufferOffset = 0; | ||
| if (protectedApdu) { | ||
| bufferOffset = passportCrypto.getApduBufferOffset(len); | ||
| } | ||
| Util.arrayCopyNonAtomic(file, offset, buffer, bufferOffset, len); | ||
| return len; | ||
| } | ||
| /** | ||
| * Processes and UPDATE_BINARY apdu. Writes data in the currently selected | ||
| * file. | ||
| * | ||
| * @param apdu | ||
| * carries the offset where to write date in header bytes p1 and | ||
| * p2. | ||
| */ | ||
| private void processUpdateBinary(APDU apdu) { | ||
| if (!hasFileSelected() || isLocked()) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| byte p1 = buffer[OFFSET_P1]; | ||
| byte p2 = buffer[OFFSET_P2]; | ||
| short offset = Util.makeShort(p1, p2); | ||
| short readCount = (short) (buffer[ISO7816.OFFSET_LC] & 0xff); | ||
| readCount = apdu.setIncomingAndReceive(); | ||
| while (readCount > 0) { | ||
| fileSystem.writeData(selectedFile, offset, buffer, OFFSET_CDATA, | ||
| readCount); | ||
| offset += readCount; | ||
| readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA); | ||
| } | ||
| } | ||
| /** | ||
| * Processes and CREATE_FILE apdu. | ||
| * | ||
| * This functionality is only partly implemented. Only non-directories | ||
| * (files) can be created, all options for CREATE_FILE are ignored. | ||
| * | ||
| * @param apdu | ||
| * containing 6 bytes: 0x64 || (1 byte) || size (2) || fid (2) | ||
| */ | ||
| private void processCreateFile(APDU apdu) { | ||
| if (isLocked()) { | ||
| ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| byte[] buffer = apdu.getBuffer(); | ||
| short lc = (short) (buffer[OFFSET_LC] & 0xff); | ||
| if (apdu.getCurrentState() == APDU.STATE_INITIAL) { | ||
| apdu.setIncomingAndReceive(); | ||
| } | ||
| if (apdu.getCurrentState() != APDU.STATE_FULL_INCOMING) { | ||
| // need all data in one APDU. | ||
| ISOException.throwIt(SW_INTERNAL_ERROR); | ||
| } | ||
| if (lc < (short) 6 || (buffer[OFFSET_CDATA + 1] & 0xff) < 4) | ||
| ISOException.throwIt(SW_WRONG_LENGTH); | ||
| if (buffer[OFFSET_CDATA] != 0x63) | ||
| ISOException.throwIt(SW_DATA_INVALID); | ||
| short size = Util.makeShort(buffer[(short) (OFFSET_CDATA + 2)], | ||
| buffer[(short) (OFFSET_CDATA + 3)]); | ||
| short fid = Util.makeShort(buffer[(short) (OFFSET_CDATA + 4)], | ||
| buffer[(short) (OFFSET_CDATA + 5)]); | ||
| if(fid == FileSystem.EF_CVCA_FID) { | ||
| fileSystem.createFile(fid, size, certificate); | ||
| }else{ | ||
| fileSystem.createFile(fid, size); | ||
| } | ||
| } | ||
| public static boolean hasInternalAuthenticationKeys() { | ||
| return (persistentState & (HAS_EXPONENT | HAS_MODULUS)) == (HAS_EXPONENT | HAS_MODULUS); | ||
| } | ||
| public static boolean hasMutualAuthenticationKeys() { | ||
| return (persistentState & HAS_MUTUALAUTHENTICATION_KEYS) == HAS_MUTUALAUTHENTICATION_KEYS; | ||
| } | ||
| public static boolean hasEACKey() { | ||
| return (persistentState & HAS_EC_KEY) == HAS_EC_KEY; | ||
| } | ||
| public static boolean hasEACCertificate() { | ||
| return (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE; | ||
| } | ||
| public static void setNoFileSelected() { | ||
| if (hasFileSelected()) { | ||
| volatileState[0] ^= FILE_SELECTED; | ||
| } | ||
| } | ||
| public static void setNoChallenged() { | ||
| if ((volatileState[0] & CHALLENGED) == CHALLENGED) { | ||
| volatileState[0] ^= CHALLENGED; | ||
| } | ||
| } | ||
| public static boolean hasFileSelected() { | ||
| return (volatileState[0] & FILE_SELECTED) == FILE_SELECTED; | ||
| } | ||
| public static boolean isLocked() { | ||
| return (persistentState & LOCKED) == LOCKED; | ||
| } | ||
| public static boolean isChallenged() { | ||
| return (volatileState[0] & CHALLENGED) == CHALLENGED; | ||
| } | ||
| /** Has BAC been completed? */ | ||
| public static boolean hasMutuallyAuthenticated() { | ||
| return (volatileState[0] & MUTUAL_AUTHENTICATED) == MUTUAL_AUTHENTICATED; | ||
| } | ||
| public static boolean hasChipAuthenticated() { | ||
| return (volatileState[0] & CHIP_AUTHENTICATED) == CHIP_AUTHENTICATED; | ||
| } | ||
| public static boolean hasTerminalAuthenticated() { | ||
| return (volatileState[0] & TERMINAL_AUTHENTICATED) == TERMINAL_AUTHENTICATED; | ||
| } | ||
| } |
| /* | ||
| * passportapplet - A reference implementation of the MRTD standards. | ||
| * | ||
| * Copyright (C) 2006 SoS group, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id$ | ||
| */ | ||
| package josh.passport; | ||
| import javacard.framework.ISOException; | ||
| import javacard.framework.Util; | ||
| /******************************************************************************* | ||
| * Contains methods to initialize a fresh passport. | ||
| * | ||
| * @author Cees-Bart Breunesse ([email protected]) | ||
| * @author Engelbert Hubbers ([email protected]) | ||
| * @author Martijn Oostdijk ([email protected]) | ||
| * | ||
| */ | ||
| public class PassportInit { | ||
| private PassportCrypto crypto; | ||
| private static byte[] weights = { 7, 3, 1 }; | ||
| PassportInit(PassportCrypto crypto) { | ||
| this.crypto = crypto; | ||
| } | ||
| /** | ||
| * Looks up the numerical value for MRZ characters. In order to be able to | ||
| * compute check digits. | ||
| * | ||
| * @param ch | ||
| * a character from the MRZ. | ||
| * @return the numerical value of the character. | ||
| */ | ||
| private static byte decodeMRZDigit(byte ch) { | ||
| switch (ch) { | ||
| case '<': | ||
| case '0': | ||
| return 0; | ||
| case '1': | ||
| return 1; | ||
| case '2': | ||
| return 2; | ||
| case '3': | ||
| return 3; | ||
| case '4': | ||
| return 4; | ||
| case '5': | ||
| return 5; | ||
| case '6': | ||
| return 6; | ||
| case '7': | ||
| return 7; | ||
| case '8': | ||
| return 8; | ||
| case '9': | ||
| return 9; | ||
| case 'a': | ||
| case 'A': | ||
| return 10; | ||
| case 'b': | ||
| case 'B': | ||
| return 11; | ||
| case 'c': | ||
| case 'C': | ||
| return 12; | ||
| case 'd': | ||
| case 'D': | ||
| return 13; | ||
| case 'e': | ||
| case 'E': | ||
| return 14; | ||
| case 'f': | ||
| case 'F': | ||
| return 15; | ||
| case 'g': | ||
| case 'G': | ||
| return 16; | ||
| case 'h': | ||
| case 'H': | ||
| return 17; | ||
| case 'i': | ||
| case 'I': | ||
| return 18; | ||
| case 'j': | ||
| case 'J': | ||
| return 19; | ||
| case 'k': | ||
| case 'K': | ||
| return 20; | ||
| case 'l': | ||
| case 'L': | ||
| return 21; | ||
| case 'm': | ||
| case 'M': | ||
| return 22; | ||
| case 'n': | ||
| case 'N': | ||
| return 23; | ||
| case 'o': | ||
| case 'O': | ||
| return 24; | ||
| case 'p': | ||
| case 'P': | ||
| return 25; | ||
| case 'q': | ||
| case 'Q': | ||
| return 26; | ||
| case 'r': | ||
| case 'R': | ||
| return 27; | ||
| case 's': | ||
| case 'S': | ||
| return 28; | ||
| case 't': | ||
| case 'T': | ||
| return 29; | ||
| case 'u': | ||
| case 'U': | ||
| return 30; | ||
| case 'v': | ||
| case 'V': | ||
| return 31; | ||
| case 'w': | ||
| case 'W': | ||
| return 32; | ||
| case 'x': | ||
| case 'X': | ||
| return 33; | ||
| case 'y': | ||
| case 'Y': | ||
| return 34; | ||
| case 'z': | ||
| case 'Z': | ||
| return 35; | ||
| default: | ||
| throw new ISOException((short) 0x6d04); | ||
| } | ||
| } | ||
| /** | ||
| * Computes the 7-3-1 check digit for part of the MRZ. | ||
| * | ||
| * @param chars | ||
| * a part of the MRZ. | ||
| * @return the resulting check digit. | ||
| */ | ||
| static byte checkDigit(byte[] chars, short offset, short length) { | ||
| byte result = 0; | ||
| for (short i = 0; i < length; i++) { | ||
| result = (byte) ((short) ((result + weights[i % 3] | ||
| * decodeMRZDigit(chars[(short) (offset + i)]))) % 10); | ||
| } | ||
| return (byte) (result + 0x30); // return as character | ||
| } | ||
| public static short DOCNR_LEN = 9; | ||
| public static short DOB_LEN = 6; | ||
| public static short DOE_LEN = 6; | ||
| /** | ||
| * Computes the static key seed, based on information from the MRZ. | ||
| * | ||
| * @param buffer | ||
| * containing docNr || dateOfBirth || dateOfExpiry | ||
| * @param offset | ||
| * pointing to docNr | ||
| * @returns offset in buffer pointing to keySeed. | ||
| */ | ||
| public short computeKeySeed( | ||
| byte[] buffer, | ||
| short docNr_p, | ||
| short docNr_length, | ||
| short dateOfBirth_p, | ||
| short dateOfBirth_length, | ||
| short dateOfExpiry_p, | ||
| short dateOfExpiry_length) { | ||
| // sanity check: data is ordered | ||
| if(!((docNr_p < dateOfBirth_p) & (dateOfBirth_p < dateOfExpiry_p))) { | ||
| ISOException.throwIt((short)0x6d66); | ||
| } | ||
| // sanity check: no overlap | ||
| if(((short)(docNr_p + docNr_length) > dateOfBirth_p) || | ||
| ((short)(dateOfBirth_p + dateOfBirth_length) > dateOfExpiry_p)) { | ||
| ISOException.throwIt((short)0x6d66); | ||
| } | ||
| short buffer_p = 0; | ||
| Util.arrayCopyNonAtomic(buffer, docNr_p, buffer, buffer_p, docNr_length); | ||
| short offset = buffer_p; | ||
| buffer_p += docNr_length; | ||
| buffer[buffer_p] = checkDigit(buffer, offset, docNr_length); | ||
| buffer_p++; | ||
| Util.arrayCopyNonAtomic(buffer, dateOfBirth_p, buffer, buffer_p, dateOfBirth_length); | ||
| offset = buffer_p; | ||
| buffer_p += dateOfBirth_length; | ||
| buffer[buffer_p] = checkDigit(buffer, offset, dateOfBirth_length); | ||
| buffer_p++; | ||
| Util.arrayCopyNonAtomic(buffer, dateOfExpiry_p, buffer, buffer_p, dateOfExpiry_length); | ||
| offset = buffer_p; | ||
| buffer_p += dateOfExpiry_length; | ||
| buffer[buffer_p] = checkDigit(buffer, offset, dateOfExpiry_length); | ||
| buffer_p++; | ||
| crypto.createHash(buffer, | ||
| (short)0, | ||
| buffer_p, | ||
| buffer, | ||
| (short)0); | ||
| return 0; | ||
| } | ||
| /** | ||
| * Computes the static key seed, based on information from the MRZ. | ||
| * | ||
| * @param buffer | ||
| * containing docNr || dateOfBirth || dateOfExpiry | ||
| * @param offset | ||
| * pointing to docNr | ||
| * @returns offset in buffer pointing to keySeed. | ||
| */ | ||
| public short computeKeySeed(byte[] buffer, short offset) { | ||
| // sanity checks (80 for hash, 3 for checkdigits) | ||
| if (buffer.length < (short) (offset + DOCNR_LEN + DOB_LEN + DOE_LEN | ||
| + 80 + 3)) { | ||
| ISOException.throwIt((short) 0x6d66); | ||
| } | ||
| short start_offset = offset; | ||
| // offset must initially point to docNr | ||
| offset += DOCNR_LEN; | ||
| short len = (short) (DOB_LEN + DOE_LEN); | ||
| // make room for checkdigit after docNr | ||
| Util.arrayCopyNonAtomic(buffer, offset, buffer, (short) (offset + 1), len); | ||
| buffer[offset] = checkDigit(buffer, | ||
| (short) (offset - DOCNR_LEN), | ||
| DOCNR_LEN); | ||
| offset += (short) (1 + DOB_LEN); | ||
| len -= DOB_LEN; | ||
| // make room for checkdigit after dateOfBirth | ||
| Util.arrayCopyNonAtomic(buffer, offset, buffer, (short) (offset + 1), len); | ||
| buffer[offset] = checkDigit(buffer, (short) (offset - DOB_LEN), DOB_LEN); | ||
| offset += (short) (1 + DOE_LEN); | ||
| buffer[offset] = checkDigit(buffer, (short) (offset - DOE_LEN), DOE_LEN); | ||
| offset++; | ||
| crypto.createHash(buffer, | ||
| start_offset, | ||
| (short) (offset - start_offset), | ||
| buffer, | ||
| offset); | ||
| return offset; | ||
| } | ||
| } |
| /* | ||
| * passportapplet - A reference implementation of the MRTD standards. | ||
| * | ||
| * Copyright (C) 2006 SoS group, Radboud University | ||
| * | ||
| * This library is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2.1 of the License, or (at your option) any later version. | ||
| * | ||
| * This library 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 | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public | ||
| * License along with this library; if not, write to the Free Software | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| * | ||
| * $Id$ | ||
| */ | ||
| package josh.passport; | ||
| import javacard.framework.ISO7816; | ||
| /** | ||
| * Generic helpers for the Passport. | ||
| * | ||
| * Contains some commented debug code. | ||
| * | ||
| * @author Cees-Bart Breunese ([email protected]) | ||
| * | ||
| */ | ||
| public class PassportUtil implements ISO7816 { | ||
| /** | ||
| * Counts the number of set bits in a byte | ||
| * | ||
| * @param b byte to be counted | ||
| * @return 0 when number of bits in b is even | ||
| */ | ||
| public static byte evenBits(byte b) { | ||
| short count = 0; | ||
| for (short i = 0; i < 8; i++) { | ||
| count += (b >>> i) & 0x1; | ||
| } | ||
| return (byte) (count % 2); | ||
| } | ||
| /** | ||
| * Calculates the xor of byte arrays in1 and in2 into out. | ||
| * | ||
| * Arrays may be the same, but regions may not overlap. | ||
| * | ||
| * | ||
| * @param in1 input array | ||
| * @param in1_o offset of input array | ||
| * @param in2 input array | ||
| * @param in2_o offset of inputarray | ||
| * @param out output array | ||
| * @param out_o offset of output array | ||
| * @param len length of xor | ||
| */ | ||
| public static void xor(byte[] in1, short in1_o, byte[] in2, | ||
| short in2_o, byte[] out, short out_o, short len) { | ||
| for(short s=0; s < len; s++) { | ||
| out[(short)(out_o + s)] = (byte)(in1[(short)(in1_o + s)] ^ in2[(short)(in2_o + s)]); | ||
| } | ||
| } | ||
| /** | ||
| * Swaps two non-overlapping segments of the same length in the same byte array | ||
| * in place. | ||
| * | ||
| * @param buffer a byte array | ||
| * @param offset1 offset to first byte array | ||
| * @param offset2 offset to the second byte array | ||
| * @param len length of the segments | ||
| */ | ||
| public static void swap(byte[] buffer, short offset1, short offset2, short len) { | ||
| byte byte1, byte2; | ||
| for(short i=0; i<len; i++) { | ||
| byte1 = buffer[(short)(offset1 + i)]; | ||
| byte2 = buffer[(short)(offset2 + i)]; | ||
| buffer[(short)(offset1 + i)] = byte2; | ||
| buffer[(short)(offset2 + i)] = byte1; | ||
| } | ||
| } | ||
| /** | ||
| * Returns the sign bit of a short as a short. | ||
| * @param a a short value | ||
| * @return the sign bit of a as a short | ||
| */ | ||
| public static short sign(short a) { | ||
| return (byte)((a >>> (short)15) & 1); | ||
| } | ||
| /** | ||
| * Returns the smallest unsigned short argument. | ||
| * | ||
| * @param a a short | ||
| * @param b another short | ||
| * @return smallest unsigned value a or b. | ||
| */ | ||
| public static short min(short a, short b) { | ||
| if(sign(a) == sign(b)) | ||
| return (a < b ? a : b); | ||
| else if(sign(a) == 1) | ||
| return b; | ||
| else | ||
| return a; | ||
| } | ||
| // public static void throwShort(short s) { | ||
| // ISOException.throwIt((short)(0x6d00 | (s & 0xff))); | ||
| // } | ||
| // | ||
| // public static void returnDESKey(DESKey k, APDU apdu) { | ||
| // byte[] b = new byte[(short)(k.getSize()/8)]; | ||
| // | ||
| // k.getKey(b, (short) 0); | ||
| // returnBuffer(b, apdu); | ||
| // } | ||
| /*** | ||
| * Pads an input buffer with max 8 and min 1 byte padding (0x80 followed by optional zeros) | ||
| * relative to the offset and length given. Always pad with at least a 0x80 byte. | ||
| * | ||
| * See 6.2.3.1 in ISO7816-4 | ||
| * | ||
| * @param buffer array to pad | ||
| * @param offset to data | ||
| * @param length of data | ||
| * @return new length, with padding, of data | ||
| * | ||
| */ | ||
| public static short pad(byte[] buffer, short offset, short len) { | ||
| short padbytes = (short)(lengthWithPadding(len) - len); | ||
| for(short i=0; i<padbytes; i++) { | ||
| buffer[(short)(offset+len+i)] = (i == 0 ? (byte)0x80 : 0x00); | ||
| } | ||
| return (short)(len + padbytes); | ||
| } | ||
| public static short lengthWithPadding(short inputLength) { | ||
| return (short)((((short)(inputLength + 8)) / 8) * 8); | ||
| } | ||
| // public static void pad(APDU aapdu, short pad_len) { | ||
| // byte[] apdu = aapdu.getBuffer(); | ||
| // short apdu_len=0; | ||
| // short lc = (short)(apdu[ISO7816.OFFSET_LC] & 0xff); | ||
| // try { | ||
| // apdu_len = (short)(ISO7816.OFFSET_CDATA + lc); | ||
| // } catch(NullPointerException e) { | ||
| // ISOException.throwIt((short)0x6d66); | ||
| // } | ||
| // | ||
| // if(apdu_len < apdu.length) | ||
| // if(pad_len < CREFPassportCrypto.PAD_DATA.length) | ||
| // Util.arrayCopy(CREFPassportCrypto.PAD_DATA, (short)0, apdu, | ||
| // apdu_len, | ||
| // pad_len); | ||
| // } | ||
| // public static void returnBuffer(byte[] b, APDU apdu) { | ||
| // returnBuffer(b, (short)b.length, apdu); | ||
| // } | ||
| // | ||
| // public static void returnBuffer(byte[] b, short length, APDU apdu) { | ||
| // returnBuffer(b, (short)0, length, apdu); | ||
| // } | ||
| // public static void returnBuffer(byte[] b, short offset, short length, APDU aapdu) { | ||
| // byte[] apdu = aapdu.getBuffer(); | ||
| // short le = aapdu.setOutgoing(); //(short)(apdu[(short)(apdu.length-2)] & 0xff); | ||
| // | ||
| // if (le >= b.length) { | ||
| // aapdu.setOutgoingLength(le); | ||
| // if(le > b.length) { | ||
| // pad(aapdu, (short)(le - (short)b.length)); | ||
| // } | ||
| // } | ||
| // else | ||
| // { | ||
| // ISOException.throwIt(SW_WRONG_LENGTH); | ||
| // } | ||
| // | ||
| // aapdu.setOutgoingLength(length); | ||
| // Util.arrayCopy(b, offset, apdu, (short)0, (short) length); | ||
| // aapdu.sendBytes((short) 0,length); | ||
| // | ||
| // } | ||
| // public static void returnByte(byte b, APDU apdu) { | ||
| // byte[] buffer = apdu.getBuffer(); | ||
| // short le = apdu.setOutgoing(); | ||
| // if (le != 1) { | ||
| // ISOException.throwIt(SW_WRONG_LENGTH); | ||
| // } | ||
| // | ||
| // apdu.setOutgoingLength((short) 1); | ||
| // buffer[0] = b; | ||
| // apdu.sendBytes((short) 0, (short) 1); | ||
| // } | ||
| /*** | ||
| * Computes the actual length of a data block as byte value, without the padding. | ||
| * | ||
| * @param apdu containing data | ||
| * @param offset to data | ||
| * @param length of data | ||
| * @return new length of data, without padding | ||
| */ | ||
| public static byte calcLcFromPaddedData(byte[] apdu, short offset, short length) { | ||
| for(short i=(short)(length - 1) ; i>=0; i--) | ||
| if(apdu[(short)(offset + i)] != 0) | ||
| if((apdu[(short)(offset + i)] & 0xff)!= 0x80) | ||
| // not padded | ||
| return (byte)(length & 0xff); | ||
| else | ||
| return (byte)(i & 0xff); | ||
| return 0; | ||
| } | ||
| } |