/* SmartPGP : JavaCard implementation of OpenPGP card v3 specification https://github.com/ANSSI-FR/SmartPGP Copyright (C) 2016 ANSSI This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package fr.anssi.smartpgp; import javacard.framework.*; import javacard.security.*; import javacardx.apdu.*; import javacardx.crypto.*; public final class SmartPGPApplet extends Applet { private final Persistent data; private final Transients transients; private final Cipher cipher_aes_cbc_nopad; private final RandomData random_data; public SmartPGPApplet() { cipher_aes_cbc_nopad = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); random_data = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); data = new Persistent(); transients = new Transients(); } public static final void install(byte[] buf, short off, byte len) { new SmartPGPApplet().register(); } private final PGPKey currentTagOccurenceToKey() { switch(transients.currentTagOccurrence()) { case 0: return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT]; case 1: return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC]; case 2: return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG]; default: ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return null; } } private final void prepareChainingInput(final byte[] apdubuf) { short tmp; tmp = transients.outputLength(); if(tmp > 0) { Util.arrayFillNonAtomic(transients.buffer, transients.outputStart(), tmp, (byte)0); } transients.setChainingOutput(false); transients.setOutputStart((short)0); transients.setOutputLength((short)0); if(transients.chainingInput()) { if((apdubuf[ISO7816.OFFSET_INS] != transients.chainingInputIns()) || (apdubuf[ISO7816.OFFSET_P1] != transients.chainingInputP1()) || (apdubuf[ISO7816.OFFSET_P2] != transients.chainingInputP2())) { transients.setChainingInput(false); transients.setChainingInputLength((short)0); ISOException.throwIt(Constants.SW_CHAINING_ERROR); return; } if((apdubuf[ISO7816.OFFSET_CLA] & Constants.CLA_MASK_CHAINING) != Constants.CLA_MASK_CHAINING) { transients.setChainingInput(false); } } else { tmp = transients.chainingInputLength(); if(tmp > 0) { Util.arrayFillNonAtomic(transients.buffer, (short)0, tmp, (byte)0); } transients.setChainingInputLength((short)0); if((apdubuf[ISO7816.OFFSET_CLA] & Constants.CLA_MASK_CHAINING) == Constants.CLA_MASK_CHAINING) { transients.setChainingInputIns(apdubuf[ISO7816.OFFSET_INS]); transients.setChainingInputP1(apdubuf[ISO7816.OFFSET_P1]); transients.setChainingInputP2(apdubuf[ISO7816.OFFSET_P2]); transients.setChainingInput(true); } } } private final void receiveData(final APDU apdu) { final byte[] apdubuf = apdu.getBuffer(); short blen = apdu.setIncomingAndReceive(); final short lc = apdu.getIncomingLength(); final short offcdata = apdu.getOffsetCdata(); short off = transients.chainingInputLength(); if((short)(off + lc) > Constants.INTERNAL_BUFFER_MAX_LENGTH) { transients.setChainingInput(false); transients.setChainingInputLength((short)0); ISOException.throwIt(Constants.SW_MEMORY_FAILURE); return; } while(blen > 0) { off = Util.arrayCopyNonAtomic(apdubuf, offcdata, transients.buffer, off, blen); blen = apdu.receiveBytes(offcdata); } transients.setChainingInputLength(off); } private final void sensitiveData() { } private final void assertAdmin() { if(!data.admin_pin.isValidated()) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } private final void assertUserMode81() { if(!data.user_pin.isValidated() || !transients.userPinMode81()) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } private final void assertUserMode82() { if(!data.user_pin.isValidated() || !transients.userPinMode82()) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } private final short writePwStatus(final byte[] buf, short off) { buf[off++] = (byte)(data.user_pin_force_verify_signature ? 0x00 : 0x01); buf[off++] = Constants.USER_PIN_MAX_SIZE; buf[off++] = Constants.USER_PUK_MAX_SIZE; buf[off++] = Constants.ADMIN_PIN_MAX_SIZE; buf[off++] = data.user_pin.getTriesRemaining(); buf[off++] = data.user_puk.getTriesRemaining(); buf[off++] = data.admin_pin.getTriesRemaining(); return off; } private final short writeKeyFingerprints(final byte[] buf, short off) { for(byte i = 0; i < data.pgp_keys.length; ++i) { off = data.pgp_keys[i].fingerprint.write(buf, off); } return off; } private final short writeCaFingerprints(final byte[] buf, short off) { for(byte i = 0; i < data.fingerprints.length; ++i) { off = data.fingerprints[i].write(buf, off); } return off; } private final short writeKeyGenerationDates(final byte[] buf, short off) { for(byte i = 0; i < data.pgp_keys.length; ++i) { off = Util.arrayCopyNonAtomic(data.pgp_keys[i].generation_date, (short)0, buf, off, Constants.GENERATION_DATE_SIZE); } return off; } private final void processSelectData(final short lc, final byte p1, final byte p2) { if((lc < 5) || (lc > 6)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } if((p1 < 0) || (p1 > 3) || (p2 != 0x04)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } final byte[] buf = transients.buffer; if((buf[0] != (byte)0x60) || (buf[1] != (byte)(lc - 2)) || (buf[2] != (byte)0x5C) || (buf[3] != (byte)(lc - 2 - 2))) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); return; } transients.setCurrentTagOccurrence(p1); if(buf[3] == 1) { transients.setCurrentTag(buf[4]); } else if(buf[3] == 2) { transients.setCurrentTag(Util.getShort(buf, (short)4)); } } private final short processGetData(final byte p1, final byte p2) { final short tag = Util.makeShort(p1, p2); short off = 0; short tlen = 0; if(transients.currentTag() == 0) { transients.setCurrentTag(tag); transients.setCurrentTagOccurrence((byte)0); } else if(transients.currentTag() != tag) { transients.setCurrentTagOccurrence((byte)0); } final byte[] buf = transients.buffer; PGPKey k; switch(tag) { case Constants.TAG_AID: off = (short)(off + JCSystem.getAID().getBytes(buf, off)); break; case Constants.TAG_LOGIN: off = Util.arrayCopyNonAtomic(data.login, (short)0, buf, off, data.login_length); break; case Constants.TAG_URL: off = Util.arrayCopyNonAtomic(data.url, (short)0, buf, off, data.url_length); break; case Constants.TAG_PRIVATE_DO_0101: off = Util.arrayCopyNonAtomic(data.do_0101, (short)0, buf, off, data.do_0101_length); break; case Constants.TAG_PRIVATE_DO_0102: off = Util.arrayCopyNonAtomic(data.do_0102, (short)0, buf, off, data.do_0102_length); break; case Constants.TAG_PRIVATE_DO_0103: assertUserMode82(); off = Util.arrayCopyNonAtomic(data.do_0103, (short)0, buf, off, data.do_0103_length); break; case Constants.TAG_PRIVATE_DO_0104: assertAdmin(); off = Util.arrayCopyNonAtomic(data.do_0104, (short)0, buf, off, data.do_0104_length); break; case Constants.TAG_KEY_FINGERPRINTS: off = writeKeyFingerprints(buf, off); break; case Constants.TAG_CA_FINGERPRINTS: off = writeCaFingerprints(buf, off); break; case Constants.TAG_KEY_GENERATION_DATES: off = writeKeyGenerationDates(buf, off); break; case Constants.TAG_HISTORICAL_BYTES_CARD_SERVICE_CARD_CAPABILITIES: off = Util.arrayCopyNonAtomic(Constants.HISTORICAL_BYTES, (short)0, buf, off, (byte)Constants.HISTORICAL_BYTES.length); break; case Constants.TAG_CARDHOLDER_RELATED_DATA: buf[off++] = (byte)0x5B; off = Common.writeLength(buf, off, data.name_length); off = Util.arrayCopyNonAtomic(data.name, (short)0, buf, off, data.name_length); off = Util.setShort(buf, off, (short)0x5f2d); off = Common.writeLength(buf, off, data.lang_length); off = Util.arrayCopyNonAtomic(data.lang, (short)0, buf, off, data.lang_length); off = Util.setShort(buf, off, (short)0x5f35); buf[off++] = (byte)0x01; buf[off++] = data.sex; break; case Constants.TAG_EXTENDED_LENGTH_INFORMATION: off = Util.setShort(buf, off, Constants.TAG_EXTENDED_LENGTH_INFORMATION); off = Common.writeLength(buf, off, (short)8); buf[off++] = (byte)0x02; buf[off++] = (byte)0x02; off = Util.setShort(buf, off, Constants.APDU_MAX_LENGTH); buf[off++] = (byte)0x02; buf[off++] = (byte)0x02; off = Util.setShort(buf, off, Constants.APDU_MAX_LENGTH); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_SIG: buf[off++] = (byte)0xc1; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_DEC: buf[off++] = (byte)0xc2; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_AUT: buf[off++] = (byte)0xc3; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); break; case Constants.TAG_APPLICATION_RELATED_DATA: tlen = (short)(1 + 1 + Constants.EXTENDED_CAPABILITIES.length + 1 + 1 + data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG].attributes_length + 1 + 1 + data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC].attributes_length + 1 + 1 + data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT].attributes_length + 1 + 1 + 7 + 1 + 1 + (3 * Constants.FINGERPRINT_SIZE) + 1 + 1 + (3 * Constants.FINGERPRINT_SIZE) + 1 + 1 + (3 * Constants.GENERATION_DATE_SIZE)); final byte aid_length = JCSystem.getAID().getBytes(buf, off); buf[off++] = (byte)Constants.TAG_APPLICATION_RELATED_DATA; off = Common.writeLength(buf, off, (short)(tlen + 1 + aid_length + 2 + 1 + Constants.HISTORICAL_BYTES.length)); buf[off++] = (byte)Constants.TAG_AID; off = Common.writeLength(buf, off, aid_length); off += JCSystem.getAID().getBytes(buf, off); off = Util.setShort(buf, off, Constants.TAG_HISTORICAL_BYTES_CARD_SERVICE_CARD_CAPABILITIES); off = Common.writeLength(buf, off, (short)Constants.HISTORICAL_BYTES.length); off = Util.arrayCopyNonAtomic(Constants.HISTORICAL_BYTES, (short)0, buf, off, (byte)Constants.HISTORICAL_BYTES.length); buf[off++] = (byte)0x73; off = Common.writeLength(buf, off, tlen); buf[off++] = (byte)0xc0; off = Common.writeLength(buf, off, (short)Constants.EXTENDED_CAPABILITIES.length); off = Util.arrayCopyNonAtomic(Constants.EXTENDED_CAPABILITIES, (short)0, buf, off, (short)Constants.EXTENDED_CAPABILITIES.length); buf[off++] = (byte)0xc1; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); buf[off++] = (byte)0xc2; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); buf[off++] = (byte)0xc3; k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT]; off = Common.writeLength(buf, off, k.attributes_length); off = Util.arrayCopyNonAtomic(k.attributes, (short)0, buf, off, k.attributes_length); buf[off++] = (byte)0xc4; buf[off++] = 7; off = writePwStatus(buf, off); buf[off++] = (byte)0xc5; off = Common.writeLength(buf, off, (short)(3 * Constants.FINGERPRINT_SIZE)); off = writeKeyFingerprints(buf, off); buf[off++] = (byte)0xc6; off = Common.writeLength(buf, off, (short)(3 * Constants.FINGERPRINT_SIZE)); off = writeCaFingerprints(buf, off); buf[off++] = (byte)0xcd; off = Common.writeLength(buf, off, (short)(3 * Constants.GENERATION_DATE_SIZE)); off = writeKeyGenerationDates(buf, off); Common.writeLength(buf, (short)1, (short)(off - 3)); break; case Constants.TAG_PW_STATUS: off = writePwStatus(buf, off); break; case Constants.TAG_SECURITY_SUPPORT_TEMPLATE: buf[off++] = (byte)0x93; buf[off++] = (byte)data.digital_signature_counter.length; off = Util.arrayCopyNonAtomic(data.digital_signature_counter, (short)0, buf, off, (byte)data.digital_signature_counter.length); break; case Constants.TAG_CARDHOLDER_CERTIFICATE: k = currentTagOccurenceToKey(); if(k == null) { ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return 0; } off = Util.arrayCopyNonAtomic(k.certificate, (short)0, buf, off, k.certificate_length); break; case Constants.TAG_KEY_DERIVATION_FUNCTION: off = Util.arrayCopyNonAtomic(data.key_derivation_function, (short)0, buf, off, data.key_derivation_function_length); break; default: ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return 0; } return off; } private final short processGetNextData(final byte p1, final byte p2) { if(Util.makeShort(p1, p2) != Constants.TAG_CARDHOLDER_CERTIFICATE) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return 0; } final PGPKey k = currentTagOccurenceToKey(); if(k == null) { ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return 0; } transients.setCurrentTagOccurrence((byte)(transients.currentTagOccurrence() + 1)); return Util.arrayCopyNonAtomic(k.certificate, (short)0, transients.buffer, (short)0, k.certificate_length); } private final void processVerify(short lc, final byte p1, final byte p2) { sensitiveData(); if(p1 == 0) { if(lc == 0) { byte remaining = 0; switch(p2) { case (byte)0x81: if(data.user_pin.isValidated() && transients.userPinMode81()) { return; } remaining = data.user_pin.getTriesRemaining(); break; case (byte)0x82: if(data.user_pin.isValidated() && transients.userPinMode82()) { return; } remaining = data.user_pin.getTriesRemaining(); break; case (byte)0x83: if(data.admin_pin.isValidated()) { return; } remaining = data.admin_pin.getTriesRemaining(); break; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } remaining = (byte)((byte)0xf & remaining); ISOException.throwIt(Util.makeShort((byte)0x63, (byte)(0xC0 | remaining))); return; } else { switch(p2) { case (byte)0x81: case (byte)0x82: if((lc < Constants.USER_PIN_MIN_SIZE) || (lc > Constants.USER_PIN_MAX_SIZE)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } if(p2 == (byte)0x81) { transients.setUserPinMode81(false); } else { transients.setUserPinMode82(false); } if(!data.user_pin.check(transients.buffer, (short)0, (byte)lc)) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); return; } if(p2 == (byte)0x81) { transients.setUserPinMode81(true); } else { transients.setUserPinMode82(true); } return; case (byte)0x83: if((lc < Constants.ADMIN_PIN_MIN_SIZE) || (lc > Constants.ADMIN_PIN_MAX_SIZE)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } if(!data.admin_pin.check(transients.buffer, (short)0, (byte)lc)) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); return; } return; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } } } else if(p1 == (byte)0xff) { if(lc != 0) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } switch(p2) { case (byte)0x81: transients.setUserPinMode81(false); return; case (byte)0x82: transients.setUserPinMode82(false); return; case (byte)0x83: if(data.admin_pin.isValidated()) { data.admin_pin.reset(); } return; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } } ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } private final void processChangeReferenceData(final short lc, final byte p1, final byte p2) { sensitiveData(); byte off; if(p1 != 0) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } switch(p2) { case (byte)0x81: if((lc < (Constants.USER_PIN_MIN_SIZE + Constants.USER_PIN_MIN_SIZE)) || (lc > (Constants.USER_PIN_MAX_SIZE + Constants.USER_PIN_MAX_SIZE))) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } off = data.user_pin_length; if(!data.user_pin.check(transients.buffer, (short)0, off)) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); return; } transients.setUserPinMode81(false); transients.setUserPinMode82(false); JCSystem.beginTransaction(); data.user_pin_length = (byte)(lc - off); data.user_pin.update(transients.buffer, off, data.user_pin_length); JCSystem.commitTransaction(); data.user_pin.resetAndUnblock(); break; case (byte)0x83: if((lc < (Constants.ADMIN_PIN_MIN_SIZE + Constants.ADMIN_PIN_MIN_SIZE)) || (lc > (Constants.ADMIN_PIN_MAX_SIZE + Constants.ADMIN_PIN_MAX_SIZE))) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } off = data.admin_pin_length; if(!data.admin_pin.check(transients.buffer, (short)0, off)) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); return; } JCSystem.beginTransaction(); data.admin_pin_length = (byte)(lc - off); data.admin_pin.update(transients.buffer, off, data.admin_pin_length); JCSystem.commitTransaction(); data.admin_pin.resetAndUnblock();; break; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } } private final void processResetRetryCounter(final short lc, final byte p1, final byte p2) { sensitiveData(); byte off = 0; if(p2 != (byte)0x81) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } switch(p1) { case (byte)0x00: if((lc < (Constants.USER_PUK_MIN_SIZE + Constants.USER_PIN_MIN_SIZE)) || (lc > (Constants.USER_PUK_MAX_SIZE + Constants.USER_PIN_MAX_SIZE))) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } off = data.user_puk_length; if(!data.user_puk.check(transients.buffer, (short)0, off)) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); return; } transients.setUserPinMode81(false); transients.setUserPinMode82(false); JCSystem.beginTransaction(); data.user_pin_length = (byte)(lc - off); data.user_pin.update(transients.buffer, off, data.user_pin_length); JCSystem.commitTransaction(); data.user_pin.resetAndUnblock(); break; case (byte)0x02: assertAdmin(); if((lc < Constants.USER_PIN_MIN_SIZE) || (lc > Constants.USER_PIN_MAX_SIZE)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } transients.setUserPinMode81(false); transients.setUserPinMode82(false); JCSystem.beginTransaction(); data.user_pin_length = (byte)lc; data.user_pin.update(transients.buffer, (short)0, data.user_pin_length); JCSystem.commitTransaction(); data.user_pin.resetAndUnblock(); break; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } } private final void processPutData(final short lc, final byte p1, final byte p2, final boolean isOdd) { sensitiveData(); final byte[] buf = transients.buffer; PGPKey k = null; if(isOdd) { assertAdmin(); if((p1 != (byte)0x3f) || (p2 != (byte)0xff)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } if(lc < 6) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } if(buf[0] != (byte)0x4D) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); return; } final short len = Common.readLength(buf, (byte)1, (short)(lc - 1)); final short off = Common.skipLength(buf, (byte)1, (short)(lc - 1)); if((short)(off + len) != lc) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } switch(Util.getShort(buf, off)) { case Constants.CRT_SIGNATURE_KEY: k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG]; JCSystem.beginTransaction(); Util.arrayFillNonAtomic(data.digital_signature_counter, (short)0, (byte)data.digital_signature_counter.length, (byte)0); JCSystem.commitTransaction(); break; case Constants.CRT_DECRYPTION_KEY: k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC]; break; case Constants.CRT_AUTHENTICATION_KEY: k = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT]; break; default: ISOException.throwIt(ISO7816.SW_WRONG_DATA); return; } k.importKey(buf, (short)(off + 2), (short)(lc - off - 2)); } else { final short tag = Util.makeShort(p1, p2); if(transients.currentTag() == 0) { transients.setCurrentTag(tag); transients.setCurrentTagOccurrence((byte)0); } else if(transients.currentTag() != tag) { transients.setCurrentTagOccurrence((byte)0); } switch(tag) { case Constants.TAG_NAME: assertAdmin(); if((lc < 0) || (lc > Constants.NAME_MAX_LENGTH)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.name_length > 0) { Util.arrayFillNonAtomic(data.name, (short)0, data.name_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.name, (short)0, lc); data.name_length = (byte)lc; JCSystem.commitTransaction(); break; case Constants.TAG_LOGIN: assertAdmin(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.login_length > 0) { Util.arrayFillNonAtomic(data.login, (short)0, data.login_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.login, (short)0, lc); data.login_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_LANG: assertAdmin(); if((lc < Constants.LANG_MIN_LENGTH) || (lc > Constants.LANG_MAX_LENGTH)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.lang_length > 0) { Util.arrayFillNonAtomic(data.lang, (short)0, data.lang_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.lang, (short)0, lc); data.lang_length = (byte)lc; JCSystem.commitTransaction(); break; case Constants.TAG_SEX: assertAdmin(); if(lc != 1) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } switch(buf[0]) { case Constants.SEX_MALE: case Constants.SEX_FEMALE: case Constants.SEX_NOT_ANNOUNCED: data.sex = buf[0]; break; default: ISOException.throwIt(ISO7816.SW_WRONG_DATA); return; } break; case Constants.TAG_URL: assertAdmin(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.url_length > 0) { Util.arrayFillNonAtomic(data.url, (short)0, data.url_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.url, (short)0, lc); data.url_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_PRIVATE_DO_0101: assertUserMode82(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.do_0101_length > 0) { Util.arrayFillNonAtomic(data.do_0101, (short)0, data.do_0101_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.do_0101, (short)0, lc); data.do_0101_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_PRIVATE_DO_0102: assertAdmin(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.do_0102_length > 0) { Util.arrayFillNonAtomic(data.do_0102, (short)0, data.do_0102_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.do_0102, (short)0, lc); data.do_0102_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_PRIVATE_DO_0103: assertUserMode82(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.do_0103_length > 0) { Util.arrayFillNonAtomic(data.do_0103, (short)0, data.do_0103_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.do_0103, (short)0, lc); data.do_0103_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_PRIVATE_DO_0104: assertAdmin(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.do_0104_length > 0) { Util.arrayFillNonAtomic(data.do_0104, (short)0, data.do_0104_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.do_0104, (short)0, lc); data.do_0104_length = lc; JCSystem.commitTransaction(); break; case Constants.TAG_AES_KEY: assertAdmin(); if((lc != (short)16) && (lc != (short)32)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.aes_key != null) { data.aes_key.clearKey(); } data.aes_key = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, (short)(lc * 8), false); data.aes_key.setKey(buf, (short)0); JCSystem.commitTransaction(); break; case Constants.TAG_CARDHOLDER_CERTIFICATE: assertAdmin(); k = currentTagOccurenceToKey(); if(k == null) { ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return; } k.setCertificate(buf, (short)0, lc); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_SIG: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG].setAttributes(buf, (short)0, lc); JCSystem.beginTransaction(); Util.arrayFillNonAtomic(data.digital_signature_counter, (short)0, (byte)data.digital_signature_counter.length, (byte)0); JCSystem.commitTransaction(); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_DEC: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC].setAttributes(buf, (short)0, lc); break; case Constants.TAG_ALGORITHM_ATTRIBUTES_AUT: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT].setAttributes(buf, (short)0, lc); break; case Constants.TAG_PW_STATUS: assertAdmin(); if(lc != 0x01) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } if((buf[0] != 0x00) && (buf[0] != 0x01)) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); return; } data.user_pin_force_verify_signature = (buf[0] == 0); break; case Constants.TAG_FINGERPRINT_SIG: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG].fingerprint.set(buf, (short)0, lc); break; case Constants.TAG_FINGERPRINT_DEC: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC].fingerprint.set(buf, (short)0, lc); break; case Constants.TAG_FINGERPRINT_AUT: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT].fingerprint.set(buf, (short)0, lc); break; case Constants.TAG_FINGERPRINT_CA: assertAdmin(); data.fingerprints[Persistent.FINGERPRINTS_OFFSET_CA].set(buf, (short)0, lc); break; case Constants.TAG_FINGERPRINT_CB: assertAdmin(); data.fingerprints[Persistent.FINGERPRINTS_OFFSET_CB].set(buf, (short)0, lc); break; case Constants.TAG_FINGERPRINT_CC: assertAdmin(); data.fingerprints[Persistent.FINGERPRINTS_OFFSET_CC].set(buf, (short)0, lc); break; case Constants.TAG_GENERATION_DATE_SIG: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG].setGenerationDate(buf, (short)0, lc); break; case Constants.TAG_GENERATION_DATE_DEC: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC].setGenerationDate(buf, (short)0, lc); break; case Constants.TAG_GENERATION_DATE_AUT: assertAdmin(); data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT].setGenerationDate(buf, (short)0, lc); break; case Constants.TAG_RESETTING_CODE: assertAdmin(); if((lc < Constants.USER_PUK_MIN_SIZE) || (lc > Constants.USER_PUK_MAX_SIZE)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); data.user_puk_length = (byte)lc; data.user_puk.update(buf, (short)0, data.user_puk_length); JCSystem.commitTransaction(); data.user_puk.resetAndUnblock(); break; case Constants.TAG_KEY_DERIVATION_FUNCTION: assertAdmin(); if((lc < 0) || (lc > Constants.specialDoMaxLength())) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return; } JCSystem.beginTransaction(); if(data.key_derivation_function_length > 0) { Util.arrayFillNonAtomic(data.key_derivation_function, (short)0, data.key_derivation_function_length, (byte)0); } Util.arrayCopyNonAtomic(buf, (short)0, data.key_derivation_function, (short)0, lc); data.key_derivation_function_length = (byte)lc; JCSystem.commitTransaction(); break; default: ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND); return; } } } private final short processGenerateAsymmetricKeyPair(final short lc, final byte p1, final byte p2) { final byte[] buf = transients.buffer; if(((p1 != (byte)0x80) && (p1 != (byte)0x81)) || (p2 != 0)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return 0; } if(lc != 2) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return 0; } boolean do_reset = false; PGPKey pkey; switch(Util.makeShort(buf[0], buf[1])) { case Constants.CRT_SIGNATURE_KEY: do_reset = true; pkey = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG]; break; case Constants.CRT_DECRYPTION_KEY: pkey = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC]; break; case Constants.CRT_AUTHENTICATION_KEY: pkey = data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT]; break; default: ISOException.throwIt(ISO7816.SW_WRONG_DATA); return 0; } if(p1 == (byte)0x80) { assertAdmin(); pkey.generate(); if(do_reset) { JCSystem.beginTransaction(); Util.arrayFillNonAtomic(data.digital_signature_counter, (short)0, (byte)data.digital_signature_counter.length, (byte)0); JCSystem.commitTransaction(); } } return pkey.writePublicKeyDo(buf, (short)0); } private final short processPerformSecurityOperation(final short lc, final byte p1, final byte p2) { sensitiveData(); /* PSO : COMPUTE DIGITAL SIGNATURE */ if((p1 == (byte)0x9e) && (p2 == (byte)0x9a)) { assertUserMode81(); if(data.user_pin_force_verify_signature) { transients.setUserPinMode81(false); } byte i = 0; JCSystem.beginTransaction(); while(data.digital_signature_counter[(byte)(data.digital_signature_counter.length - i - 1)] == (byte)0xff) { ++i; } if(i < data.digital_signature_counter.length) { ++data.digital_signature_counter[(byte)(data.digital_signature_counter.length - i - 1)]; if(i > 0) { --i; Util.arrayFillNonAtomic(data.digital_signature_counter, (short)(data.digital_signature_counter.length - i - 1), (byte)(i + 1), (byte)0); } } JCSystem.commitTransaction(); return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_SIG].sign(transients.buffer, lc, false); } /* PSO : DECIPHER */ if((p1 == (byte)0x80) && (p2 == (byte)0x86)) { assertUserMode82(); if(lc <= 1) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return 0; } if(transients.buffer[0] == (byte)0x02) { if(((short)(lc - 1) % Constants.AES_BLOCK_SIZE) != 0) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return 0; } if((data.aes_key == null) || !data.aes_key.isInitialized()) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); return 0; } cipher_aes_cbc_nopad.init(data.aes_key, Cipher.MODE_DECRYPT); final short res = cipher_aes_cbc_nopad.doFinal(transients.buffer, (short)1, (short)(lc - 1), transients.buffer, lc); Util.arrayCopyNonAtomic(transients.buffer, lc, transients.buffer, (short)0, res); Util.arrayFillNonAtomic(transients.buffer, lc, res, (byte)0); return res; } return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_DEC].decipher(transients.buffer, lc); } /* PSO : ENCIPHER */ if((p1 == (byte)0x86) && (p2 == (byte)0x80)) { assertUserMode82(); if((lc <= 0) || ((lc % Constants.AES_BLOCK_SIZE) != 0)) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return 0; } if((data.aes_key == null) || !data.aes_key.isInitialized()) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); return 0; } cipher_aes_cbc_nopad.init(data.aes_key, Cipher.MODE_ENCRYPT); final short res = cipher_aes_cbc_nopad.doFinal(transients.buffer, (short)0, lc, transients.buffer, (short)(lc + 1)); transients.buffer[lc] = (byte)0x02; Util.arrayCopyNonAtomic(transients.buffer, lc, transients.buffer, (short)0, (short)(res + 1)); Util.arrayFillNonAtomic(transients.buffer, (short)(lc + 1), res, (byte)0); return (short)(res + 1); } ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return 0; } private final short processInternalAuthenticate(final short lc, final byte p1, final byte p2) { if(p2 == (byte)0x00) { switch(p1) { case (byte)0x00: sensitiveData(); assertUserMode82(); return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT].sign(transients.buffer, lc, true); } } ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return 0; } private final short processGetChallenge(short le, final byte p1, final byte p2) { if((p1 != (byte)0) || (p2 != (byte)0)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return 0; } if(le < 0) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); return 0; } if(le > Constants.challengeMaxLength()) { le = Constants.challengeMaxLength(); } if(le != 0) { random_data.generateData(transients.buffer, (short)0, le); } return le; } private final void processTerminateDf(final byte p1, final byte p2) { if((p1 != (byte)0) || (p2 != (byte)0)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } if(data.admin_pin.getTriesRemaining() <= 0) { data.isTerminated = true; return; } assertAdmin(); data.isTerminated = true; } private final void processActivateFile(final byte p1, final byte p2) { if(p1 != (byte)0) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } if(data.isTerminated) { switch(p2) { case (byte)0: transients.clear(); data.reset(false); break; default: ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } } } private final void clearConnection() { data.user_pin.reset(); data.user_puk.reset(); data.admin_pin.reset(); transients.clear(); } public final void process(final APDU apdu) { final byte[] apdubuf = apdu.getBuffer(); if(apdu.isISOInterindustryCLA() && selectingApplet()) { clearConnection(); if(data.isTerminated) { ISOException.throwIt(Constants.SW_TERMINATED); } return; } if(data.isTerminated) { if(apdubuf[ISO7816.OFFSET_CLA] != 0) { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); return; } if(apdubuf[ISO7816.OFFSET_INS] == Constants.INS_ACTIVATE_FILE) { processActivateFile(apdubuf[ISO7816.OFFSET_P1], apdubuf[ISO7816.OFFSET_P2]); return; } ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); return; } final byte p1 = apdubuf[ISO7816.OFFSET_P1]; final byte p2 = apdubuf[ISO7816.OFFSET_P2]; short available_le = 0; short sw = (short)0x9000; if(((apdubuf[ISO7816.OFFSET_CLA] & Constants.CLA_MASK_CHAINING) != Constants.CLA_MASK_CHAINING) && (apdubuf[ISO7816.OFFSET_INS] == Constants.INS_GET_RESPONSE)) { if(transients.chainingInput() || !transients.chainingOutput()) { ISOException.throwIt(Constants.SW_CHAINING_ERROR); return; } if((p1 != 0) || (p2 != 0)) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); return; } available_le = transients.outputLength(); } else if((apdubuf[ISO7816.OFFSET_CLA] & Constants.CLA_MASK_CHAINING) == Constants.CLA_MASK_CHAINING) { prepareChainingInput(apdubuf); receiveData(apdu); } else { prepareChainingInput(apdubuf); receiveData(apdu); short lc = transients.chainingInputLength(); try { switch(apdubuf[ISO7816.OFFSET_INS]) { case Constants.INS_SELECT_DATA: processSelectData(lc, p1, p2); break; case Constants.INS_GET_DATA: available_le = processGetData(p1, p2); break; case Constants.INS_GET_NEXT_DATA: available_le = processGetNextData(p1, p2); break; case Constants.INS_VERIFY: processVerify(lc, p1, p2); break; case Constants.INS_CHANGE_REFERENCE_DATA: processChangeReferenceData(lc, p1, p2); break; case Constants.INS_RESET_RETRY_COUNTER: processResetRetryCounter(lc, p1, p2); break; case Constants.INS_PUT_DATA_DA: processPutData(lc, p1, p2, false); break; case Constants.INS_PUT_DATA_DB: processPutData(lc, p1, p2, true); break; case Constants.INS_GENERATE_ASYMMETRIC_KEY_PAIR: available_le = processGenerateAsymmetricKeyPair(lc, p1, p2); break; case Constants.INS_PERFORM_SECURITY_OPERATION: available_le = processPerformSecurityOperation(lc, p1, p2); break; case Constants.INS_INTERNAL_AUTHENTICATE: available_le = processInternalAuthenticate(lc, p1, p2); break; case Constants.INS_GET_CHALLENGE: available_le = processGetChallenge(apdu.setOutgoing(), p1, p2); break; case Constants.INS_TERMINATE_DF: processTerminateDf(p1, p2); break; case Constants.INS_ACTIVATE_FILE: processActivateFile(p1, p2); break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); return; } } catch (ISOException e) { sw = e.getReason(); } transients.setOutputLength(available_le); } if(available_le > 0) { short resp_le = available_le; if(apdu.getCurrentState() != APDU.STATE_OUTGOING) { resp_le = apdu.setOutgoing(); if((resp_le == (short)0) || (available_le < resp_le)) { resp_le = available_le; } } if(resp_le > Constants.APDU_MAX_LENGTH) { resp_le = Constants.APDU_MAX_LENGTH; } short off = transients.outputStart(); Util.arrayCopyNonAtomic(transients.buffer, off, apdubuf, (short)0, resp_le); apdu.setOutgoingLength(resp_le); apdu.sendBytes((short)0, resp_le); Util.arrayFillNonAtomic(transients.buffer, off, resp_le, (byte)0); available_le -= resp_le; off += resp_le; if(available_le > 0) { transients.setChainingOutput(true); transients.setOutputLength(available_le); transients.setOutputStart(off); if(available_le > (short)0x00ff) { available_le = (short)0x00ff; } sw = (short)(ISO7816.SW_BYTES_REMAINING_00 | available_le); } else { transients.setChainingOutput(false); transients.setOutputLength((short)0); transients.setOutputStart((short)0); } } ISOException.throwIt(sw); } }