0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.satsa.pkiapplet;
0028:
0029: import javacard.framework.*;
0030: import javacard.security.*;
0031: import javacardx.crypto.Cipher;
0032:
0033: /**
0034: * Card side application for PKI implementation. Supports subset of
0035: * WIM functionality.
0036: */
0037: public class PKIApplet extends Applet {
0038:
0039: /** Constant that is used to avoid '(short) x' notation. */
0040: static final byte x0 = 0;
0041: /** Constant that is used to avoid '(short) x' notation. */
0042: static final byte x1 = 1;
0043: /** Constant that is used to avoid '(short) x' notation. */
0044: static final byte x2 = 2;
0045: /** Constant that is used to avoid '(short) x' notation. */
0046: static final byte x3 = 3;
0047: /** Constant that is used to avoid '(short) x' notation. */
0048: static final byte x4 = 4;
0049: /** Constant that is used to avoid '(short) x' notation. */
0050: static final byte x5 = 5;
0051: /** Constant that is used to avoid '(short) x' notation. */
0052: static final byte x6 = 6;
0053: /** Constant that is used to avoid '(short) x' notation. */
0054: static final byte x8 = 8;
0055:
0056: /** INS byte for command APDU. */
0057: static final byte INS_VERIFY = (byte) 0x20;
0058: /** INS byte for command APDU. */
0059: static final byte INS_SELECT = (byte) 0xa4;
0060: /** INS byte for command APDU. */
0061: static final byte INS_READ = (byte) 0xb0;
0062: /** INS byte for command APDU. */
0063: static final byte INS_UPDATE = (byte) 0xd6;
0064: /** INS byte for command APDU. */
0065: static final byte INS_MSE = (byte) 0x22;
0066: /** INS byte for command APDU. */
0067: static final byte INS_PSO = (byte) 0x2a;
0068:
0069: /** INS byte for command APDU. */
0070: static final byte INS_NEW = (byte) 0xBC;
0071:
0072: /** DigestInfo structure size for RSA signature. */
0073: static final short digestLength = 35;
0074: /** Temporaru buffer for RSA signature. */
0075: static byte[] signBuffer = new byte[digestLength];
0076:
0077: /** If false, applet always report that PINs are validated. */
0078: static boolean verifyPINs = true;
0079:
0080: /** If false, key generation is disabled. */
0081: static boolean supportKeyGeneration = true;
0082:
0083: /** PIN identifiers. */
0084: byte[] PIN_REFs;
0085: /** PIN objects. */
0086: OwnerPIN[] PINs;
0087: /** Private keys. */
0088: PrivateKey[] keys;
0089: /** Root DF for entire file structure. */
0090: DFile top;
0091: /**
0092: * Root DF for WIM application. All relative paths start from
0093: * here. */
0094: DFile base;
0095: /** Currently selected file. */
0096: File current;
0097: /** Flag that indicates that SE is restored. */
0098: boolean isSERestored;
0099: /** Flag that indicates that private key path was set properly. */
0100: boolean isKeyFileSet;
0101: /** Private key number for signature generation. */
0102: short keyNum;
0103: /** Cipher object. */
0104: Cipher cipher;
0105: /** MessageDigest object for key hash calculation. */
0106: MessageDigest digest;
0107: /** Number of unused keys. */
0108: static short unusedKeys = 0;
0109:
0110: /** Constructor. */
0111: PKIApplet() {
0112:
0113: if (Data.PINs == null) {
0114: CardRuntimeException.throwIt(x0);
0115: }
0116: cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
0117: if (supportKeyGeneration) {
0118: digest = MessageDigest.getInstance(MessageDigest.ALG_SHA,
0119: false);
0120: }
0121: register();
0122: }
0123:
0124: /**
0125: * To create an instance of the Applet subclass, the JCRE will call
0126: * this static method first.
0127: * @param bArray the array containing installation parameters
0128: * @param bOffset the starting offset in bArray
0129: * @param bLength the length in bytes of the parameter data in bArray
0130: */
0131: public static void install(byte[] bArray, short bOffset,
0132: byte bLength) {
0133: new PKIApplet();
0134: }
0135:
0136: /**
0137: * Called by the JCRE to inform this applet that it has been
0138: * selected. When invoked first time initialises the file system.
0139: * @return true
0140: */
0141: public boolean select() {
0142:
0143: if (Data.PINs != null) {
0144: init();
0145: }
0146: current = base;
0147: isSERestored = false;
0148: return true;
0149: }
0150:
0151: /**
0152: * Called by the JCRE to inform this applet that it has been
0153: * deselected. When invoked PIN-G should be reset.
0154: */
0155: public void deselect() {
0156: if (PINs != null) {
0157: PINs[0].reset();
0158: }
0159: }
0160:
0161: /**
0162: * Initialises the WIM data structures.
0163: */
0164: void init() {
0165:
0166: Parser.init(Data.PINs);
0167: short cnt = Parser.getByte();
0168:
0169: PIN_REFs = new byte[(short) (cnt + Data.freePINSlots)];
0170: PINs = new OwnerPIN[(short) (cnt + Data.freePINSlots)];
0171:
0172: for (short i = 0; i < cnt; i++) {
0173:
0174: PIN_REFs[i] = Parser.getByte();
0175: byte len = Parser.getByte();
0176: PINs[i] = new OwnerPIN(x3, x8);
0177: PINs[i].update(Data.PINs, Parser.offset, len);
0178: Parser.skip(len);
0179: }
0180:
0181: Parser.init(Data.PrivateKeys);
0182: cnt = Parser.getByte();
0183: keys = new PrivateKey[(short) (cnt + Data.freeKeySlots)];
0184: short keyPos = 0;
0185: // calculate start of first key in a file
0186: short privKeyStart = (short) (Data.PrKDFOffset
0187: + Data.newPrivKeyOffset - Data.privKeyRecordSize * cnt);
0188: short pubKeyStart = (short) (Data.PuKDFOffset
0189: + Data.newPubKeyOffset - Data.pubKeyRecordSize * cnt);
0190: for (short i = 0; i < cnt; i++) {
0191: PrivateKey key = new PrivateKey(this );
0192: if (key.value != null) {
0193: keys[keyPos++] = key;
0194: } else {
0195: // This key is not supported by card
0196: byte[] files = Data.Files;
0197: short tail = (short) (cnt + Data.freeKeySlots - keyPos - 1);
0198: if (tail > 0) {
0199: // Shift up private keys
0200: short privOffset = (short) (privKeyStart + Data.privKeyRecordSize
0201: * keyPos);
0202: Util
0203: .arrayCopyNonAtomic(
0204: files,
0205: (short) (privOffset + Data.privKeyRecordSize),
0206: files,
0207: privOffset,
0208: (short) (Data.privKeyRecordSize * tail));
0209: privOffset = (short) ((privKeyStart + Data.privKeyRecordSize
0210: * (keyPos + tail)));
0211:
0212: // Shift up public keys
0213: short pubOffset = (short) (pubKeyStart + Data.pubKeyRecordSize
0214: * keyPos);
0215: Util
0216: .arrayCopyNonAtomic(
0217: files,
0218: (short) (pubOffset + Data.pubKeyRecordSize),
0219: files,
0220: pubOffset,
0221: (short) (Data.pubKeyRecordSize * tail));
0222: pubOffset = (short) ((pubKeyStart + Data.pubKeyRecordSize
0223: * (keyPos + tail)));
0224: }
0225: }
0226: }
0227: Data.newPrivKeyOffset = (short) (privKeyStart
0228: - Data.PrKDFOffset + keyPos * Data.privKeyRecordSize);
0229: Data.newPubKeyOffset = (short) (pubKeyStart - Data.PuKDFOffset + keyPos
0230: * Data.pubKeyRecordSize);
0231:
0232: unusedKeys = (short) (keys.length - Data.freeKeySlots - keyPos);
0233: Parser.init(Data.Files);
0234: top = (DFile) readFile(null);
0235:
0236: if (base == null) {
0237: ISOException.throwIt((short) 0x9001);
0238: }
0239:
0240: Data.PINs = null;
0241: Data.PrivateKeys = null;
0242:
0243: // IMPL_NOTE: debug check - remove
0244: if (Parser.offset != Data.Files.length) {
0245: ISOException.throwIt((short) 0x9001);
0246: }
0247: }
0248:
0249: /**
0250: * Creates new file object.
0251: * @param parent parent DF for this file
0252: * @return the new file object
0253: */
0254: File readFile(DFile parent) {
0255:
0256: short id = Parser.getShort();
0257: short type = Parser.getByte();
0258: short length = Parser.getShort();
0259:
0260: if ((type & File.DIR) == 0) {
0261:
0262: EFile f;
0263: if ((type & File.EMPTY) == 0) {
0264: f = new EFile(parent, id, type, Parser.offset, length,
0265: Data.Files);
0266: Parser.skip(length);
0267: } else {
0268: type &= ~File.EMPTY;
0269: byte[] data = new byte[length];
0270: short dlen = Parser.getShort();
0271: Util.arrayCopyNonAtomic(Data.Files, Parser.offset,
0272: data, (short) 0, dlen);
0273: f = new EFile(parent, id, type, (short) 0, length, data);
0274: Parser.skip(dlen);
0275: }
0276: return f;
0277: }
0278:
0279: DFile f = new DFile(parent, id, type);
0280:
0281: File[] files = new File[length];
0282: for (short i = 0; i < length; i++) {
0283: files[i] = readFile(f);
0284: }
0285:
0286: f.files = files;
0287:
0288: if (type == File.WIM) {
0289: base = f;
0290: }
0291:
0292: return f;
0293: }
0294:
0295: /**
0296: * Main entry point.
0297: * @param apdu command APDU
0298: */
0299: public void process(APDU apdu) {
0300:
0301: byte[] data = apdu.getBuffer();
0302: byte CLA = (byte) (data[ISO7816.OFFSET_CLA] & 0xF0);
0303: byte INS = data[ISO7816.OFFSET_INS];
0304:
0305: if (CLA == 0 && INS == (byte) (0xA4)
0306: && data[ISO7816.OFFSET_P1] == 4) {
0307: return;
0308: }
0309:
0310: if (CLA != (byte) 0x80) {
0311: ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
0312: }
0313: switch (INS) {
0314:
0315: case INS_SELECT:
0316: selectFile(apdu);
0317: return;
0318:
0319: case INS_READ:
0320: read(apdu);
0321: return;
0322:
0323: case INS_UPDATE:
0324: update(apdu);
0325: return;
0326:
0327: case INS_VERIFY:
0328: verify(apdu);
0329: return;
0330:
0331: case INS_MSE:
0332: manageSE(apdu);
0333: return;
0334:
0335: case INS_PSO:
0336: sign(apdu);
0337: return;
0338:
0339: case INS_NEW:
0340: if (supportKeyGeneration) {
0341: newKey(apdu);
0342: return;
0343: }
0344: break;
0345: }
0346:
0347: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
0348: }
0349:
0350: /**
0351: * Handles SELECT FILE APDU.
0352: * @param apdu command APDU
0353: */
0354: void selectFile(APDU apdu) {
0355:
0356: byte[] data = apdu.getBuffer();
0357:
0358: if (Util.getShort(data, x2) != 0) {
0359: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
0360: }
0361:
0362: checkDataSize(x2, apdu);
0363:
0364: File f = select(Util.getShort(data, x5));
0365:
0366: if (f == null) {
0367: ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
0368: }
0369:
0370: current = f;
0371:
0372: if (current.isDF()) {
0373:
0374: Util.setShort(data, x0, (short) 0x6f00);
0375: apdu.setOutgoingAndSend(x0, x2);
0376: } else {
0377:
0378: Util.setShort(data, x0, (short) 0x6f04);
0379: Util.setShort(data, x2, (short) 0x8002);
0380: Util.setShort(data, x4, ((EFile) current).length);
0381: apdu.setOutgoingAndSend(x0, x6);
0382: }
0383: }
0384:
0385: /**
0386: * Selects the file specified by file identifier.
0387: * @param id file identifier
0388: * @return selected file
0389: */
0390: File select(short id) {
0391:
0392: DFile f;
0393: if (current.isDF()) {
0394: f = (DFile) current;
0395: } else {
0396: f = current.parent;
0397: }
0398:
0399: File x = f.getFile(id);
0400: if (x != null) {
0401: return x;
0402: }
0403:
0404: f = f.parent;
0405:
0406: if (f == null) {
0407: return null;
0408: }
0409:
0410: return f.getFile(id);
0411: }
0412:
0413: /**
0414: * Handles READ BINARY APDU.
0415: * @param apdu command APDU
0416: */
0417: void read(APDU apdu) {
0418:
0419: if (current.isDF()) {
0420: ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
0421: }
0422:
0423: EFile f = (EFile) current;
0424:
0425: byte[] data = apdu.getBuffer();
0426:
0427: short offset = Util.getShort(data, x2);
0428: if (offset < 0 || offset > f.length) {
0429: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
0430: }
0431:
0432: short len = (short) (data[x4] & 0xff);
0433: if ((short) (offset + len) > f.length) {
0434: ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
0435: }
0436:
0437: apdu.setOutgoing();
0438: apdu.setOutgoingLength(len);
0439: apdu.sendBytesLong(f.data, (short) (f.offset + offset), len);
0440: }
0441:
0442: /**
0443: * Handles UPDATE BINARY apdu.
0444: * @param apdu command APDU
0445: */
0446: void update(APDU apdu) {
0447:
0448: if (current.isDF()) {
0449: ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
0450: }
0451:
0452: EFile f = (EFile) current;
0453:
0454: if (!(f.type == File.UPDATE && isValidated(PINs[0]))) {
0455: ISOException
0456: .throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
0457: }
0458:
0459: byte[] data = apdu.getBuffer();
0460:
0461: short offset = Util.getShort(data, x2);
0462: if (offset < 0) {
0463: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
0464: }
0465:
0466: short len = (short) (data[x4] & 0xff);
0467:
0468: if ((short) (offset + len) > f.length) {
0469: ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
0470: }
0471:
0472: short l = apdu.setIncomingAndReceive();
0473: short off = 5;
0474:
0475: while (l > 0) {
0476: Util.arrayCopyNonAtomic(data, off, f.data,
0477: (short) (f.offset + offset), l);
0478: offset += l;
0479: l = apdu.receiveBytes(x0);
0480: off = 0;
0481: }
0482: }
0483:
0484: /**
0485: * Handles PIN related APDUs.
0486: * @param apdu command APDU
0487: */
0488: void verify(APDU apdu) {
0489:
0490: if (current.type != File.PIN) {
0491: ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
0492: }
0493:
0494: byte[] data = apdu.getBuffer();
0495:
0496: short pin_num = -1;
0497: for (short i = 0; i < (short) (PIN_REFs.length - Data.freePINSlots); i++) {
0498: if (PIN_REFs[i] == data[x3]) {
0499: pin_num = i;
0500: break;
0501: }
0502: }
0503:
0504: if (pin_num == -1 || data[x2] != 0) {
0505: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
0506: }
0507:
0508: OwnerPIN pin = PINs[pin_num];
0509:
0510: if (data[x4] == 0) {
0511:
0512: if (isValidated(pin)) {
0513: return; // SW = 0x9000
0514: }
0515:
0516: if (pin.getTriesRemaining() == 0) {
0517: ISOException.throwIt(ISO7816.SW_FILE_INVALID);
0518: }
0519:
0520: ISOException.throwIt((short) (0x6300 | pin
0521: .getTriesRemaining()));
0522: }
0523:
0524: short len = apdu.setIncomingAndReceive();
0525: if (len > 8) { // too long, set to 0 to update PIN
0526: len = 0;
0527: }
0528: pin.check(data, x5, (byte) len);
0529:
0530: if (isValidated(pin)) {
0531: return; // SW = 0x9000
0532: }
0533:
0534: ISOException.throwIt((short) 0x6300);
0535: }
0536:
0537: /**
0538: * Hanldes MANAGE SECURITY ENVIRONMENT APDUs.
0539: * @param apdu command APDU
0540: */
0541: void manageSE(APDU apdu) {
0542:
0543: byte[] data = apdu.getBuffer();
0544:
0545: if (data[x2] == (byte) 0xf3) {
0546:
0547: if (data[x3] != Data.WIM_GENERIC_RSA_ID) {
0548: ISOException.throwIt((short) 0x6600);
0549: }
0550:
0551: isSERestored = true;
0552: isKeyFileSet = false;
0553: keyNum = -1;
0554: return;
0555: }
0556:
0557: if (!isSERestored || Util.getShort(data, x2) != (short) 0x41b6) {
0558: ISOException.throwIt((short) 0x6600);
0559: }
0560:
0561: File keyFile = current;
0562:
0563: short len = apdu.setIncomingAndReceive();
0564: short index = 5;
0565: len += index;
0566:
0567: try {
0568: while (index < len) {
0569:
0570: byte tag = data[index++];
0571: byte l = data[index++];
0572:
0573: if (l <= 0 || l > 32) {
0574: ISOException.throwIt((short) 0x6600);
0575: }
0576:
0577: if (tag == (byte) 0x84) { // private key reference
0578:
0579: if (l != 1) {
0580: ISOException.throwIt((short) 0x6600);
0581: }
0582:
0583: for (short i = 0; i < (short) (keys.length
0584: - Data.freeKeySlots - unusedKeys); i++) {
0585:
0586: if (data[index] == keys[i].id) {
0587: keyNum = i;
0588: break;
0589: }
0590: }
0591: } else if (tag == (byte) 0x81) { // private key DF path
0592: keyFile = getFile(data, index, l);
0593: } else {
0594: ISOException.throwIt((short) 0x6A80); // invalid tag
0595: }
0596:
0597: // path (id, relative or complete)
0598: index += l;
0599: }
0600: } catch (ArrayIndexOutOfBoundsException e) {
0601: keyNum = -1;
0602: }
0603:
0604: if (keyNum == -1 || keyFile == null
0605: || keyFile.type != File.PrivateKeyFile) {
0606: ISOException.throwIt((short) 0x6600);
0607: }
0608:
0609: EFile f = (EFile) keyFile;
0610: isKeyFileSet = (f.data[f.offset] == keys[keyNum].id);
0611:
0612: if (!isKeyFileSet) {
0613: ISOException.throwIt((short) 0x6600);
0614: }
0615: }
0616:
0617: /**
0618: * Allocates new key and, if necessary, PIN.
0619: * @param apdu the command APDU. APDU data contains key type
0620: * (byte, 0 - authenticatiuon, 1 - non-repudiation), key lenght in
0621: * bits (2 byte value). For non-repudiation key after that goes
0622: * initial PIN value (8 bytes) and label (32 bytes). If p1 = 1
0623: * the command just verifies that a new key can be generated.
0624: */
0625: void newKey(APDU apdu) {
0626:
0627: apdu.setIncomingAndReceive();
0628: byte[] data = apdu.getBuffer();
0629:
0630: boolean nonRepudiation = (data[x5] == 1);
0631:
0632: if (Data.freeKeySlots == 0
0633: || (nonRepudiation && Data.freePINSlots == 0)) {
0634: ISOException.throwIt((short) 0x9001);
0635: }
0636:
0637: short keyLen = Util.getShort(data, x6);
0638:
0639: if (keyLen > (short) ((data.length - 1) * 8)) {
0640: ISOException.throwIt((short) 0x9001);
0641: }
0642:
0643: // check if keyLen & RSA algorithm are supported by card
0644: if (!Pairs.tryKeyPair(keyLen)) {
0645: ISOException.throwIt((short) 0x9001);
0646: }
0647:
0648: if (data[x2] == 1) {
0649: Util.setShort(data, x0, (short) 0x1234);
0650: Util.setShort(data, x2, (short) 0x4321);
0651: apdu.setOutgoingAndSend(x0, x4);
0652: return;
0653: }
0654:
0655: /*
0656: * IMPL_NOTE: For testing purposes return existing key instead of new one
0657: for (short i = 0; i < (short)(keys.length - Data.freeKeySlots - unusedKeys); i++) {
0658: if (keys[i].keyLen == keyLen
0659: && keys[i].nonRepudiation == nonRepudiation) {
0660: data[0] = (byte)keys[i].id;
0661: apdu.setOutgoingAndSend(x0, x1);
0662: return;
0663: }
0664: }
0665: */
0666:
0667: try {
0668: short pinIndex = 0;
0669:
0670: if (nonRepudiation) {
0671: // new PIN must be allocated
0672: pinIndex = (short) (PINs.length - Data.freePINSlots);
0673:
0674: OwnerPIN pin = new OwnerPIN((byte) 3, (byte) 8);
0675: pin.update(data, x8, (byte) 8);
0676: pin.check(data, x8, (byte) 8);
0677: PINs[pinIndex] = pin;
0678: PIN_REFs[pinIndex] = Data.newPINRef;
0679:
0680: Util
0681: .arrayCopy(
0682: data,
0683: (short) 16,
0684: Data.Files,
0685: (short) (Data.AODFOffset
0686: + Data.newPINOffset + Data.PINLabelOffset),
0687: (short) 32);
0688: }
0689:
0690: KeyPair p = Pairs.getKeyPair(keyLen);
0691: p.genKeyPair();
0692:
0693: PrivateKey key = new PrivateKey(this , Data.newKeyID,
0694: PINs[pinIndex], nonRepudiation, keyLen,
0695: (RSAPrivateKey) p.getPrivate());
0696:
0697: RSAPublicKey pk = (RSAPublicKey) p.getPublic();
0698:
0699: EFile f = (EFile) base.getFile(Data.newFileID);
0700: f.data = encodePublicKey(pk, data);
0701: f.offset = 0;
0702: f.length = (short) (f.data.length);
0703:
0704: byte[] hash = getKeyHash(pk, data);
0705: byte[] files = Data.Files;
0706:
0707: short offset = (short) (Data.PuKDFOffset + Data.newPubKeyOffset);
0708: Util.arrayCopy(hash, x0, files,
0709: (short) (offset + Data.pubHashOffset), (short) 20);
0710:
0711: Util.setShort(files,
0712: (short) (offset + Data.pubKeyLengthOffset), keyLen);
0713:
0714: offset = (short) (Data.PrKDFOffset + Data.newPrivKeyOffset);
0715:
0716: files[(short) (offset + Data.privPINIDOffset)] = nonRepudiation ? Data.newPINID
0717: : Data.PIN_G_ID;
0718:
0719: Util.arrayCopy(hash, x0, files,
0720: (short) (offset + Data.privHashOffset), (short) 20);
0721:
0722: Util
0723: .setShort(
0724: files,
0725: (short) (offset + Data.privKeyLengthOffset),
0726: keyLen);
0727: offset += Data.privUsageOffset;
0728: if (nonRepudiation) {
0729: files[offset++] = 6;
0730: files[offset++] = 0;
0731: files[offset++] = 0x40;
0732: } else {
0733: files[offset++] = 7;
0734: files[offset++] = 0x20;
0735: files[offset++] = 0;
0736: }
0737:
0738: data[0] = Data.newKeyID;
0739: apdu.setOutgoingAndSend(x0, x1);
0740:
0741: JCSystem.beginTransaction();
0742:
0743: if (nonRepudiation) {
0744: Data.freePINSlots--;
0745: Data.newPINID++;
0746: Data.newPINRef++;
0747: files[(short) (Data.AODFOffset + Data.newPINOffset)] = 0x30;
0748: Data.newPINOffset += Data.PINRecordSize;
0749: }
0750:
0751: keys[(short) (keys.length - Data.freeKeySlots - unusedKeys)] = key;
0752: Data.freeKeySlots--;
0753: Data.newFileID += 2;
0754: Data.newKeyID++;
0755: files[(short) (Data.PrKDFOffset + Data.newPrivKeyOffset)] = 0x30;
0756: files[(short) (Data.PuKDFOffset + Data.newPubKeyOffset)] = 0x30;
0757: Data.newPrivKeyOffset += Data.privKeyRecordSize;
0758: Data.newPubKeyOffset += Data.pubKeyRecordSize;
0759:
0760: JCSystem.commitTransaction();
0761: } catch (ISOException ie) {
0762: throw ie;
0763: } catch (Exception e) {
0764: ISOException.throwIt((short) 0x9001);
0765: }
0766: }
0767:
0768: /**
0769: * Generates DER encoded RSA public key.
0770: * @param pk the key
0771: * @param data temporary data buffer
0772: * @return DER encoded RSA public key
0773: */
0774: static byte[] encodePublicKey(RSAPublicKey pk, byte[] data) {
0775:
0776: short modulusLength = pk.getModulus(data, (short) 0);
0777: boolean padModulus = ((data[0] & 0x80) != 0);
0778:
0779: if (padModulus) {
0780: modulusLength++;
0781: }
0782:
0783: short size = getDERSize(modulusLength);
0784:
0785: short exponentLength = pk.getExponent(data, (short) 0);
0786: boolean padExponent = ((data[0] & 0x80) != 0);
0787:
0788: if (padExponent) {
0789: exponentLength++;
0790: }
0791:
0792: size += getDERSize(exponentLength);
0793:
0794: byte[] der = new byte[getDERSize(size)];
0795:
0796: // generate public key record
0797:
0798: short offset = 0;
0799: der[offset++] = 0x30;
0800: offset = putLength(der, offset, size);
0801: der[offset++] = 2;
0802: offset = putLength(der, offset, modulusLength);
0803: if (padModulus) {
0804: offset++;
0805: }
0806: offset += pk.getModulus(der, offset);
0807:
0808: der[offset++] = 2;
0809: offset = putLength(der, offset, exponentLength);
0810: if (padExponent) {
0811: offset++;
0812: }
0813: pk.getExponent(der, offset);
0814:
0815: return der;
0816: }
0817:
0818: /**
0819: * Calculates identifier for public RSA key.
0820: * @param pk the key
0821: * @param data temporary data buffer
0822: * @return the identifier
0823: */
0824: byte[] getKeyHash(RSAPublicKey pk, byte[] data) {
0825:
0826: short offset = 1;
0827: short len = pk.getModulus(data, offset);
0828:
0829: if ((data[1] & 0x80) != 0) {
0830: offset--;
0831: len++;
0832: data[offset] = 0;
0833: }
0834: byte[] hash = new byte[(short) 20];
0835: digest.doFinal(data, offset, len, hash, x0);
0836: return hash;
0837: }
0838:
0839: /**
0840: * Returns the size of DER object for given value size.
0841: * @param i the value size
0842: * @return the size of DER object
0843: */
0844: static short getDERSize(short i) {
0845:
0846: if (i < 128) {
0847: return (short) (i + 2);
0848: }
0849: return (short) (i + 3);
0850: }
0851:
0852: /**
0853: * Places encoded length of DER object into the buffer.
0854: * @param data the buffer
0855: * @param offset offset in the buffer where the length must be
0856: * placed
0857: * @param length the length to be placed
0858: * @return the new offset
0859: */
0860: static short putLength(byte[] data, short offset, short length) {
0861: if (length >= 128) {
0862: data[offset++] = (byte) 0x81;
0863: }
0864: data[offset++] = (byte) length;
0865: return offset;
0866: }
0867:
0868: /**
0869: * Returns file object specified by path in the buffer.
0870: * @param data the buffer
0871: * @param index path offset
0872: * @param l path length
0873: * @return file object or null if not found
0874: */
0875: File getFile(byte[] data, short index, short l) {
0876:
0877: // path must contain even number of bytes
0878: if (l < 2 || l % 2 != 0) {
0879: return null;
0880: }
0881: l = (short) (l / 2);
0882:
0883: short id = Util.getShort(data, index);
0884:
0885: File x = null;
0886:
0887: if (l == 1) {
0888: x = base;
0889: } else {
0890:
0891: if (id == top.id) {
0892: x = top;
0893: } else {
0894: if (id == (short) 0x3fff) {
0895: x = base;
0896: } else {
0897: return null;
0898: }
0899: }
0900: index += 2;
0901: l--;
0902: }
0903:
0904: while (l != 0) {
0905:
0906: if (!x.isDF()) {
0907: return null;
0908: }
0909: File f = ((DFile) x).getFile(Util.getShort(data, index));
0910: if (f == null || f.parent != x) {
0911: return null;
0912: }
0913: x = f;
0914: index += 2;
0915: l--;
0916: }
0917:
0918: return x;
0919: }
0920:
0921: /**
0922: * Handles PSO-CDS APDU command.
0923: * @param apdu command APDU
0924: */
0925: void sign(APDU apdu) {
0926:
0927: byte[] data = apdu.getBuffer();
0928:
0929: if (Util.getShort(data, x2) != (short) 0x9e9a) {
0930: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
0931: }
0932:
0933: if (!isSERestored || keyNum == -1 || !isKeyFileSet) {
0934: ISOException.throwIt((short) 0x6600);
0935: }
0936:
0937: checkDataSize(digestLength, apdu);
0938:
0939: if (!keys[keyNum].checkAccess()) {
0940: ISOException
0941: .throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
0942: }
0943:
0944: cipher.init(keys[keyNum].value, Cipher.MODE_ENCRYPT);
0945: Util.arrayCopyNonAtomic(data, x5, signBuffer, x0, digestLength);
0946: short len = cipher.doFinal(signBuffer, x0, digestLength, data,
0947: x0);
0948:
0949: short expected = (short) (keys[keyNum].value.getSize() >> 3);
0950: if (len != expected) {
0951: Util.arrayFillNonAtomic(data, x0, (short) (expected - len),
0952: x0);
0953: cipher.doFinal(signBuffer, x0, digestLength, data,
0954: (short) (expected - len));
0955: }
0956:
0957: apdu.setOutgoingAndSend(x0, expected);
0958: }
0959:
0960: /**
0961: * Verifies that APDU contains correct number of data bytes.
0962: * @param expectedSize expected data size
0963: * @param apdu APDU object
0964: */
0965: private void checkDataSize(short expectedSize, APDU apdu) {
0966: if (expectedSize != apdu.setIncomingAndReceive()) {
0967: ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
0968: }
0969: }
0970:
0971: /**
0972: * Verifies that PIN is validated.
0973: * @param pin the PIN
0974: * @return true if the PIN is validated or PIN verification is disabled.
0975: */
0976: public boolean isValidated(OwnerPIN pin) {
0977: return verifyPINs ? pin.isValidated() : true;
0978: }
0979: }
0980:
0981: /**
0982: * This class represents private key.
0983: */
0984: class PrivateKey {
0985: /** Owner applet. */
0986: PKIApplet applet;
0987: /** Key identifier. */
0988: short id;
0989: /** PIN object that protects this key. */
0990: OwnerPIN PIN;
0991: /** True if its non-repudiation key. */
0992: boolean nonRepudiation;
0993: /** Length of the key. */
0994: short keyLen;
0995: /** The key object. */
0996: RSAPrivateKey value;
0997:
0998: /**
0999: * Constructs new key object using hardcoded data.
1000: * @param app The applet object
1001: */
1002: PrivateKey(PKIApplet app) {
1003: applet = app;
1004: id = Parser.getByte();
1005: PIN = applet.PINs[Parser.getByte()];
1006: nonRepudiation = (Parser.getByte() == 0);
1007: keyLen = Parser.getShort();
1008: boolean notSupported = false;
1009: try {
1010: value = (RSAPrivateKey) KeyBuilder.buildKey(
1011: KeyBuilder.TYPE_RSA_PRIVATE, keyLen, false);
1012: } catch (CryptoException e) {
1013: notSupported = true;
1014: }
1015: if (value == null) {
1016: notSupported = true;
1017: }
1018: if (notSupported) {
1019: value = null;
1020: short len = Parser.getShort();
1021: Parser.skip(len);
1022: len = Parser.getShort();
1023: Parser.skip(len);
1024: } else {
1025: short len = Parser.getShort();
1026: value.setModulus(Data.PrivateKeys, Parser.offset, len);
1027: Parser.skip(len);
1028: len = Parser.getShort();
1029: value.setExponent(Data.PrivateKeys, Parser.offset, len);
1030: Parser.skip(len);
1031: }
1032: }
1033:
1034: /**
1035: * Constructs new key object.
1036: * @param app the applet object
1037: * @param id key identifier
1038: * @param PIN PIN object that protects this key
1039: * @param nonRepudiation is it non-repudiation key?
1040: * @param keyLen length of key
1041: * @param value the key object
1042: */
1043: PrivateKey(PKIApplet app, short id, OwnerPIN PIN,
1044: boolean nonRepudiation, short keyLen, RSAPrivateKey value) {
1045: this .applet = app;
1046: this .id = id;
1047: this .PIN = PIN;
1048: this .nonRepudiation = nonRepudiation;
1049: this .keyLen = keyLen;
1050: this .value = value;
1051: }
1052:
1053: /**
1054: * Verifies that user has access to this key.
1055: * @return true if PIN is validated
1056: */
1057: boolean checkAccess() {
1058:
1059: boolean result = applet.isValidated(PIN);
1060: if (nonRepudiation) {
1061: PIN.reset();
1062: }
1063: return result;
1064: }
1065: }
1066:
1067: /**
1068: * This class represents private/public key pairs pool.
1069: */
1070: class Pairs {
1071: /**
1072: * Singleton instance reference.
1073: */
1074: private static Pairs instance = null;
1075: /**
1076: * Pairs in the pool.
1077: */
1078: private KeyPair[] pairs;
1079: /**
1080: * Key lengths of pairs stored in pool.
1081: * Initial value is -1.
1082: */
1083: private short[] lengths;
1084: /**
1085: * Default number of slots in the pool.
1086: */
1087: private static final short defaultSize = 10;
1088: /**
1089: * Number of slots in the pool.
1090: */
1091: private short poolSize;
1092:
1093: /**
1094: * Constructs new pool using given size.
1095: * @param poolSize Number of slots in the pool
1096: */
1097: private Pairs(short poolSize) {
1098: this .poolSize = poolSize;
1099: pairs = new KeyPair[poolSize];
1100: lengths = new short[poolSize];
1101: for (short i = 0; i < poolSize; i++) {
1102: lengths[i] = (short) -1;
1103: }
1104: }
1105:
1106: /**
1107: * Constructs new pool using default size.
1108: */
1109: private Pairs() {
1110: this (defaultSize);
1111: }
1112:
1113: /**
1114: * Finds a KeyPair object with keys of given length.
1115: * If needed object does not exists in the pool the method
1116: * tries to create it.
1117: *
1118: * @param length Length of keys.
1119: * @return Found KeyPair object or null in case of error.
1120: */
1121: private KeyPair findPair(short length) {
1122: for (short i = 0; i < poolSize; i++) {
1123: if (lengths[i] == length) {
1124: return pairs[i];
1125: }
1126: }
1127: for (short i = 0; i < instance.poolSize; i++) {
1128: if (lengths[i] == (short) -1) {
1129: try {
1130: pairs[i] = new KeyPair(KeyPair.ALG_RSA, length);
1131: } catch (CryptoException e) {
1132: return null;
1133: }
1134: lengths[i] = length;
1135: return pairs[i];
1136: }
1137: }
1138: return null;
1139: }
1140:
1141: /**
1142: * Gets a KeyPair object with keys of given length.
1143: * @param length Length of keys.
1144: * @return Found KeyPair object.
1145: * @exception ISOException if no suitable pairs found or
1146: * algorithm or length not valid.
1147: */
1148: public static KeyPair getKeyPair(short length) {
1149: if (instance == null) {
1150: instance = new Pairs();
1151: }
1152: KeyPair pair = instance.findPair(length);
1153: if (pair == null) {
1154: ISOException.throwIt((short) 0x9001);
1155: }
1156: return pair;
1157: }
1158:
1159: /**
1160: * Checks if a suitable KeyPair object can be received from the pool.
1161: * @param length Length of keys.
1162: * @return true if a KeyPair object is available,
1163: * false in other case.
1164: * @exception ISOException if no suitable pairs found or
1165: * algorithm or length are not valid.
1166: */
1167: public static boolean tryKeyPair(short length) {
1168: if (instance == null) {
1169: instance = new Pairs();
1170: }
1171: return instance.findPair(length) != null;
1172: }
1173: }
|