summaryrefslogtreecommitdiff
path: root/smcc/src/main/java
diff options
context:
space:
mode:
authormcentner <mcentner@8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4>2010-01-26 16:27:27 +0000
committermcentner <mcentner@8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4>2010-01-26 16:27:27 +0000
commit84cd553cc40d9850fcd865887219d907693af0e6 (patch)
tree2d1d9054f42845ce951c9b2c2239178c803443d3 /smcc/src/main/java
parent667af128d0adfeee2aa4748ab58411c91bc4905f (diff)
parent7a5310b43849124095d97af3103c4fdaeeacbbbb (diff)
downloadmocca-84cd553cc40d9850fcd865887219d907693af0e6.tar.gz
mocca-84cd553cc40d9850fcd865887219d907693af0e6.tar.bz2
mocca-84cd553cc40d9850fcd865887219d907693af0e6.zip
git-svn-id: https://joinup.ec.europa.eu/svn/mocca/branches/mocca-1.2.11-sha2@602 8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4
Diffstat (limited to 'smcc/src/main/java')
-rw-r--r--smcc/src/main/java/META-INF/MANIFEST.MF3
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java805
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java121
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/BELPICCard.java284
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/CancelledException.java39
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/CardNotSupportedException.java62
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ChangeReferenceDataAPDUSpec.java95
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ExclSignatureCardProxy.java110
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/Exclusive.java28
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/FileNotFoundException.java38
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ITCard.java298
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/LockedException.java38
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/LogCardChannel.java129
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/NewReferenceDataAPDUSpec.java60
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/NotActivatedException.java44
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/PINConfirmationException.java26
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/PINFormatException.java26
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/PINMgmtSignatureCard.java44
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/PINOperationAbortedException.java45
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/PINSpec.java239
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ResetRetryCounterAPDUSpec.java38
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java888
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/SWCard.java396
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/SecurityStatusNotSatisfiedException.java38
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/SignatureCard.java125
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java65
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java401
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/TimeoutException.java39
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/VerificationFailedException.java65
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/VerifyAPDUSpec.java200
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/conf/SMCCConfiguration.java45
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINGUI.java36
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINProvider.java48
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINGUI.java42
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINProvider.java51
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/reader/CardReader.java92
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/reader/DefaultCardReader.java106
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/reader/PinpadCardReader.java703
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/reader/ReaderFactory.java125
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/util/ISO7816Utils.java368
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/util/SMCCHelper.java150
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java204
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/util/TransparentFileInputStream.java194
43 files changed, 6953 insertions, 0 deletions
diff --git a/smcc/src/main/java/META-INF/MANIFEST.MF b/smcc/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..5e949512
--- /dev/null
+++ b/smcc/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java b/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java
new file mode 100644
index 00000000..9b3b88ed
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java
@@ -0,0 +1,805 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.util.ISO7816Utils;
+import at.gv.egiz.smcc.util.SMCCHelper;
+import at.gv.egiz.smcc.util.TransparentFileInputStream;
+
+public class ACOSCard extends AbstractSignatureCard implements PINMgmtSignatureCard {
+
+ private static Log log = LogFactory.getLog(ACOSCard.class);
+
+ public static final byte[] AID_DEC = new byte[] { (byte) 0xA0, (byte) 0x00,
+ (byte) 0x00, (byte) 0x01, (byte) 0x18, (byte) 0x45, (byte) 0x4E };
+
+ public static final byte[] DF_DEC = new byte[] { (byte) 0xdf, (byte) 0x71 };
+
+ public static final byte[] AID_SIG = new byte[] { (byte) 0xA0, (byte) 0x00,
+ (byte) 0x00, (byte) 0x01, (byte) 0x18, (byte) 0x45, (byte) 0x43 };
+
+ public static final byte[] DF_SIG = new byte[] { (byte) 0xdf, (byte) 0x70 };
+
+ public static final byte[] EF_C_CH_EKEY = new byte[] { (byte) 0xc0,
+ (byte) 0x01 };
+
+ public static final int EF_C_CH_EKEY_MAX_SIZE = 2000;
+
+ public static final byte[] EF_C_CH_DS = new byte[] { (byte) 0xc0, (byte) 0x02 };
+
+ public static final int EF_C_CH_DS_MAX_SIZE = 2000;
+
+ public static final byte[] EF_PK_CH_EKEY = new byte[] { (byte) 0xb0,
+ (byte) 0x01 };
+
+ public static final byte[] EF_INFOBOX = new byte[] { (byte) 0xc0, (byte) 0x02 };
+
+ public static final byte[] EF_INFO = new byte[] { (byte) 0xd0, (byte) 0x02 };
+
+ public static final int EF_INFOBOX_MAX_SIZE = 1500;
+
+ public static final byte KID_PIN_SIG = (byte) 0x81;
+
+ public static final byte KID_PUK_SIG = (byte) 0x83;
+
+ public static final byte KID_PIN_DEC = (byte) 0x81;
+
+ public static final byte KID_PUK_DEC = (byte) 0x82;
+
+ public static final byte KID_PIN_INF = (byte) 0x83;
+
+ public static final byte KID_PUK_INF = (byte) 0x84;
+
+ public static final byte[] DST_SIG = new byte[] { (byte) 0x84, (byte) 0x01, // tag
+ // ,
+ // length
+ // (
+ // key
+ // ID
+ // )
+ (byte) 0x88, // SK.CH.SIGN
+ (byte) 0x80, (byte) 0x01, // tag, length (algorithm ID)
+ (byte) 0x14 // ECDSA
+ };
+
+ public static final byte[] AT_DEC = new byte[] { (byte) 0x84, (byte) 0x01, // tag
+ // ,
+ // length
+ // (
+ // key
+ // ID
+ // )
+ (byte) 0x88, // SK.CH.EKEY
+ (byte) 0x80, (byte) 0x01, // tag, length (algorithm ID)
+ (byte) 0x01 // RSA // TODO: Not verified yet
+ };
+
+ private static final PINSpec DEC_PIN_SPEC = new PINSpec(0, 8, "[0-9]",
+ "at/gv/egiz/smcc/ACOSCard", "dec.pin", KID_PIN_DEC, AID_DEC);
+
+ private static final PINSpec SIG_PIN_SPEC = new PINSpec(0, 8, "[0-9]",
+ "at/gv/egiz/smcc/ACOSCard", "sig.pin", KID_PIN_SIG, AID_SIG);
+
+ private static final PINSpec INF_PIN_SPEC = new PINSpec(0, 8, "[0-9]",
+ "at/gv/egiz/smcc/ACOSCard", "inf.pin", KID_PIN_INF, AID_DEC);
+
+ static {
+ if (SignatureCardFactory.ENFORCE_RECOMMENDED_PIN_LENGTH) {
+ DEC_PIN_SPEC.setRecLength(4);
+ SIG_PIN_SPEC.setRecLength(6);
+ INF_PIN_SPEC.setRecLength(4);
+ }
+ }
+
+ /**
+ * The version of the card's digital signature application.
+ */
+ protected int appVersion = -1;
+
+ public ACOSCard() {
+ super("at/gv/egiz/smcc/ACOSCard");
+ }
+
+ @Override
+ public void init(Card card, CardTerminal cardTerminal) {
+ super.init(card, cardTerminal);
+
+ // determine application version
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_SIG);
+ // SELECT file
+ execSELECT_FID(channel, EF_INFO);
+ // READ BINARY
+ TransparentFileInputStream is = ISO7816Utils.openTransparentFileInputStream(channel, 8);
+ appVersion = is.read();
+ log.info("a-sign premium application version = " + appVersion);
+ } catch (FileNotFoundException e) {
+ appVersion = 1;
+ log.info("a-sign premium application version = " + appVersion);
+ } catch (SignatureCardException e) {
+ log.warn(e);
+ appVersion = 0;
+ } catch (IOException e) {
+ log.warn(e);
+ appVersion = 0;
+ } catch (CardException e) {
+ log.warn(e);
+ appVersion = 0;
+ }
+
+ pinSpecs.add(DEC_PIN_SPEC);
+ pinSpecs.add(SIG_PIN_SPEC);
+ if (appVersion < 2) {
+ pinSpecs.add(INF_PIN_SPEC);
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException, InterruptedException {
+
+ byte[] aid;
+ byte[] fid;
+ if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ aid = AID_SIG;
+ fid = EF_C_CH_DS;
+ } else if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) {
+ aid = AID_DEC;
+ fid = EF_C_CH_EKEY;
+ } else {
+ throw new IllegalArgumentException("Keybox " + keyboxName
+ + " not supported.");
+ }
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, aid);
+ // SELECT file
+ byte[] fcx = execSELECT_FID(channel, fid);
+ int maxSize = -1;
+ if (getAppVersion() < 2) {
+ maxSize = ISO7816Utils.getLengthFromFCx(fcx);
+ log.debug("Size of selected file = " + maxSize);
+ }
+ // READ BINARY
+ byte[] certificate = ISO7816Utils.readTransparentFileTLV(channel, maxSize, (byte) 0x30);
+ if (certificate == null) {
+ throw new NotActivatedException();
+ }
+ return certificate;
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Failed to get certificate.", e);
+ throw new SignatureCardException(e);
+ }
+
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getInfobox(String infobox, PINGUI provider, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ if ("IdentityLink".equals(infobox)) {
+ if (getAppVersion() < 2) {
+ return getIdentityLinkV1(provider, domainId);
+ } else {
+ return getIdentityLinkV2(provider, domainId);
+ }
+ } else {
+ throw new IllegalArgumentException("Infobox '" + infobox
+ + "' not supported.");
+ }
+
+ }
+
+ protected byte[] getIdentityLinkV1(PINGUI provider, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_DEC);
+ // SELECT file
+ byte[] fcx = execSELECT_FID(channel, EF_INFOBOX);
+ int maxSize = ISO7816Utils.getLengthFromFCx(fcx);
+ log.debug("Size of selected file = " + maxSize);
+ // READ BINARY
+ while(true) {
+ try {
+ return ISO7816Utils.readTransparentFileTLV(channel, maxSize, (byte) 0x30);
+ } catch (SecurityStatusNotSatisfiedException e) {
+ verifyPINLoop(channel, INF_PIN_SPEC, provider);
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Faild to get infobox.", e);
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ protected byte[] getIdentityLinkV2(PINGUI provider, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_DEC);
+ // SELECT file
+ execSELECT_FID(channel, EF_INFOBOX);
+
+ // READ BINARY
+ TransparentFileInputStream is = ISO7816Utils.openTransparentFileInputStream(channel, -1);
+
+ int b = is.read();
+ if (b == 0x00) {
+ return null;
+ }
+ if (b != 0x41 || is.read() != 0x49 || is.read() != 0x4b) {
+ String msg = "Infobox structure invalid.";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ b = is.read();
+ if (b != 0x01) {
+ String msg = "Infobox structure v" + b + " not supported.";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ while ((b = is.read()) != 0x01 && b != 00) {
+ is.read(); // modifiers
+ is.skip(is.read() + (is.read() << 8)); // length
+ }
+
+ if (b != 0x01) {
+ return null;
+ }
+
+ int modifiers = is.read();
+ int length = is.read() + (is.read() << 8);
+
+ byte[] bytes;
+ byte[] key = null;
+
+ switch (modifiers) {
+ case 0x00:
+ bytes = new byte[length];
+ break;
+ case 0x01:
+ key = new byte[is.read() + (is.read() << 8)];
+ is.read(key);
+ bytes = new byte[length - key.length - 2];
+ break;
+ default:
+ String msg = "Compressed infobox structure not yet supported.";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ is.read(bytes);
+
+ if (key == null) {
+ return bytes;
+ }
+
+ execMSE(channel, 0x41, 0xb8, new byte[] {
+ (byte) 0x84, (byte) 0x01, (byte) 0x88, (byte) 0x80, (byte) 0x01,
+ (byte) 0x02 });
+
+
+ byte[] plainKey = null;
+
+ while (true) {
+ try {
+ plainKey = execPSO_DECIPHER(channel, key);
+ break;
+ } catch(SecurityStatusNotSatisfiedException e) {
+ verifyPINLoop(channel, DEC_PIN_SPEC, provider);
+ }
+ }
+
+ try {
+ Cipher cipher = Cipher
+ .getInstance("DESede/CBC/PKCS5Padding");
+ byte[] iv = new byte[8];
+ Arrays.fill(iv, (byte) 0x00);
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ AlgorithmParameters parameters = AlgorithmParameters
+ .getInstance("DESede");
+ parameters.init(ivParameterSpec);
+
+ DESedeKeySpec keySpec = new DESedeKeySpec(plainKey);
+ SecretKeyFactory keyFactory = SecretKeyFactory
+ .getInstance("DESede");
+ SecretKey secretKey = keyFactory.generateSecret(keySpec);
+
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, parameters);
+
+ return cipher.doFinal(bytes);
+
+ } catch (GeneralSecurityException e) {
+ String msg = "Failed to decrypt infobox.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Faild to get infobox.", e);
+ throw new SignatureCardException(e);
+ } catch (IOException e) {
+ if (e.getCause() instanceof SignatureCardException) {
+ throw (SignatureCardException) e.getCause();
+ } else {
+ throw new SignatureCardException(e);
+ }
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName,
+ PINGUI provider, String alg) throws SignatureCardException, InterruptedException, IOException {
+
+ ByteArrayOutputStream dst = new ByteArrayOutputStream();
+ // key ID
+ dst.write(new byte[]{(byte) 0x84, (byte) 0x01, (byte) 0x88});
+ // algorithm ID
+ dst.write(new byte[]{(byte) 0x80, (byte) 0x01});
+
+ MessageDigest md;
+ try {
+ if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)
+ && (alg == null || "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1".equals(alg))) {
+ dst.write((byte) 0x14); // SHA-1/ECC
+ md = MessageDigest.getInstance("SHA-1");
+ } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)
+ && (alg == null || "http://www.w3.org/2000/09/xmldsig#rsa-sha1".equals(alg))) {
+ dst.write((byte) 0x12); // SHA-1 with padding according to PKCS#1 block type 01
+ md = MessageDigest.getInstance("SHA-1");
+ } else if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)
+ && appVersion >= 2
+ && "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256".equals(alg)) {
+ dst.write((byte) 0x44); // SHA-256/ECC
+ md = MessageDigest.getInstance("SHA256");
+ } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)
+ && appVersion >= 2
+ && "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256".equals(alg)) {
+ dst.write((byte) 0x41); // SHA-256 with padding according to PKCS#1
+ md = MessageDigest.getInstance("SHA256");
+ } else {
+ throw new SignatureCardException("Card does not support signature algorithm " + alg + ".");
+ }
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Failed to get MessageDigest.", e);
+ throw new SignatureCardException(e);
+ }
+
+ byte[] digest = new byte[md.getDigestLength()];
+ for (int l; (l = input.read(digest)) != -1;) {
+ md.update(digest, 0, l);
+ }
+ digest = md.digest();
+
+ try {
+
+ CardChannel channel = getCardChannel();
+
+ if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) {
+
+ PINSpec spec = SIG_PIN_SPEC;
+
+ // SELECT application
+ execSELECT_AID(channel, AID_SIG);
+ // MANAGE SECURITY ENVIRONMENT : SET DST
+ execMSE(channel, 0x41, 0xb6, dst.toByteArray());
+ // VERIFY
+ verifyPINLoop(channel, spec, provider);
+ // PERFORM SECURITY OPERATION : HASH
+ execPSO_HASH(channel, digest);
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATRE
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel);
+
+ } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) {
+
+ PINSpec spec = DEC_PIN_SPEC;
+
+ // SELECT application
+ execSELECT_AID(channel, AID_DEC);
+ // MANAGE SECURITY ENVIRONMENT : SET AT
+ execMSE(channel, 0x41, 0xa4, AT_DEC);
+
+ while (true) {
+ try {
+ // INTERNAL AUTHENTICATE
+ return execINTERNAL_AUTHENTICATE(channel, digest);
+ } catch (SecurityStatusNotSatisfiedException e) {
+ verifyPINLoop(channel, spec, provider);
+ }
+ }
+
+ } else {
+ throw new IllegalArgumentException("KeyboxName '" + keyboxName
+ + "' not supported.");
+ }
+
+ } catch (CardException e) {
+ log.warn(e);
+ throw new SignatureCardException("Failed to access card.", e);
+ }
+
+ }
+
+ public int getAppVersion() {
+ return appVersion;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#verifyPIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.PINProvider)
+ */
+ @Override
+ public void verifyPIN(PINSpec pinSpec, PINGUI pinProvider)
+ throws LockedException, NotActivatedException, CancelledException,
+ TimeoutException, SignatureCardException, InterruptedException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ // SELECT application
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ // VERIFY
+ verifyPINLoop(channel, pinSpec, pinProvider);
+ } catch (CardException e) {
+ log.info("Failed to verify PIN.", e);
+ throw new SignatureCardException("Failed to verify PIN.", e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#changePIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.ChangePINProvider)
+ */
+ @Override
+ public void changePIN(PINSpec pinSpec, ModifyPINGUI pinProvider)
+ throws LockedException, NotActivatedException, CancelledException,
+ TimeoutException, SignatureCardException, InterruptedException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ // SELECT application
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ // CHANGE REFERENCE DATA
+ changePINLoop(channel, pinSpec, pinProvider);
+ } catch (CardException e) {
+ log.info("Failed to change PIN.", e);
+ throw new SignatureCardException("Failed to change PIN.", e);
+ }
+
+ }
+
+ @Override
+ public void activatePIN(PINSpec pinSpec, ModifyPINGUI pinGUI)
+ throws CancelledException, SignatureCardException, CancelledException,
+ TimeoutException, InterruptedException {
+ log.error("ACTIVATE PIN not supported by ACOS");
+ throw new SignatureCardException("PIN activation not supported by this card.");
+ }
+
+ @Override
+ public void unblockPIN(PINSpec pinSpec, ModifyPINGUI pinGUI)
+ throws CancelledException, SignatureCardException, InterruptedException {
+ throw new SignatureCardException("Unblock PIN not supported.");
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.PINMgmtSignatureCard#getPINSpecs()
+ */
+ @Override
+ public List<PINSpec> getPINSpecs() {
+ if (getAppVersion() < 2) {
+ return Arrays.asList(new PINSpec[] {DEC_PIN_SPEC, SIG_PIN_SPEC, INF_PIN_SPEC});
+ } else {
+ return Arrays.asList(new PINSpec[] {DEC_PIN_SPEC, SIG_PIN_SPEC});
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.PINMgmtSignatureCard#getPINStatus(at.gv.egiz.smcc.PINSpec)
+ */
+ @Override
+ public PIN_STATE getPINState(PINSpec pinSpec) throws SignatureCardException {
+ return PIN_STATE.UNKNOWN;
+ }
+
+ @Override
+ public String toString() {
+ return "a-sign premium (version " + getAppVersion() + ")";
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // PROTECTED METHODS (assume exclusive card access)
+ ////////////////////////////////////////////////////////////////////////
+
+ protected void verifyPINLoop(CardChannel channel, PINSpec spec, PINGUI provider)
+ throws InterruptedException, CardException, SignatureCardException {
+
+ int retries = -1;
+ do {
+ retries = verifyPIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected void changePINLoop(CardChannel channel, PINSpec spec, ModifyPINGUI provider)
+ throws InterruptedException, CardException, SignatureCardException {
+
+ int retries = -1;
+ do {
+ retries = changePIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected int verifyPIN(CardChannel channel, PINSpec pinSpec,
+ PINGUI provider, int retries) throws InterruptedException, CardException, SignatureCardException {
+
+ VerifyAPDUSpec apduSpec = new VerifyAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x20, (byte) 0x00, pinSpec.getKID(), (byte) 0x08,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 },
+ 0, VerifyAPDUSpec.PIN_FORMAT_ASCII, 8);
+
+ ResponseAPDU resp = reader.verify(channel, apduSpec, provider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ }
+ if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ }
+
+ switch (resp.getSW()) {
+ case 0x6983:
+ // authentication method blocked
+ throw new LockedException();
+
+ default:
+ String msg = "VERIFY failed. SW=" + Integer.toHexString(resp.getSW());
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ }
+
+ protected int changePIN(CardChannel channel, PINSpec pinSpec,
+ ModifyPINGUI pinProvider, int retries) throws CancelledException, InterruptedException, CardException, SignatureCardException {
+
+ ChangeReferenceDataAPDUSpec apduSpec = new ChangeReferenceDataAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x24, (byte) 0x00, pinSpec.getKID(), (byte) 0x10,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ 0, VerifyAPDUSpec.PIN_FORMAT_ASCII, 8);
+
+
+
+ ResponseAPDU resp = reader.modify(channel, apduSpec, pinProvider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ }
+ if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ }
+
+ switch (resp.getSW()) {
+ case 0x6983:
+ // authentication method blocked
+ throw new LockedException();
+
+ default:
+ String msg = "CHANGE REFERENCE DATA failed. SW=" + Integer.toHexString(resp.getSW());
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ }
+
+ protected byte[] execSELECT_AID(CardChannel channel, byte[] aid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found AID="
+ + SMCCHelper.toString(aid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application AID="
+ + SMCCHelper.toString(aid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+ }
+
+ protected byte[] execSELECT_FID(CardChannel channel, byte[] fid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x00, 0x00, fid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+
+ }
+
+ protected void execMSE(CardChannel channel, int p1,
+ int p2, byte[] data) throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x22, p1, p2, data));
+
+ if (resp.getSW() != 0x9000) {
+ String msg = "MSE failed: SW="
+ + Integer.toHexString(resp.getSW());
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ }
+
+ protected byte[] execPSO_DECIPHER(CardChannel channel, byte [] cipher) throws CardException, SignatureCardException {
+
+ byte[] data = new byte[cipher.length + 1];
+ data[0] = 0x00;
+ System.arraycopy(cipher, 0, data, 1, cipher.length);
+ ResponseAPDU resp = channel.transmit(new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, 256));
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ } else if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException(
+ "PSO - DECIPHER failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+
+ return resp.getData();
+
+ }
+
+ protected void execPSO_HASH(CardChannel channel, byte[] hash) throws CardException, SignatureCardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x90, 0x81, hash));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("PSO - HASH failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+
+ }
+
+ protected byte[] execPSO_COMPUTE_DIGITAL_SIGNATURE(CardChannel channel) throws CardException,
+ SignatureCardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, 256));
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ }
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException(
+ "PSO - COMPUTE DIGITAL SIGNATRE failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ } else {
+ return resp.getData();
+ }
+
+ }
+
+ protected byte[] execINTERNAL_AUTHENTICATE(CardChannel channel, byte[] hash) throws CardException,
+ SignatureCardException {
+
+ byte[] digestInfo = new byte[] { (byte) 0x30, (byte) 0x21, (byte) 0x30,
+ (byte) 0x09, (byte) 0x06, (byte) 0x05, (byte) 0x2B, (byte) 0x0E,
+ (byte) 0x03, (byte) 0x02, (byte) 0x1A, (byte) 0x05, (byte) 0x00,
+ (byte) 0x04 };
+
+ byte[] data = new byte[digestInfo.length + hash.length + 1];
+
+ System.arraycopy(digestInfo, 0, data, 0, digestInfo.length);
+ data[digestInfo.length] = (byte) hash.length;
+ System.arraycopy(hash, 0, data, digestInfo.length + 1, hash.length);
+
+ ResponseAPDU resp = channel.transmit(new CommandAPDU(0x00, 0x88, 0x10, 0x00, data, 256));
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ } else if (resp.getSW() == 0x6983) {
+ throw new LockedException();
+ } else if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("INTERNAL AUTHENTICATE failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ } else {
+ return resp.getData();
+ }
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java b/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java
new file mode 100644
index 00000000..fcb94fc6
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java
@@ -0,0 +1,121 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.reader.CardReader;
+import at.gv.egiz.smcc.reader.ReaderFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public abstract class AbstractSignatureCard implements SignatureCard {
+
+ private static Log log = LogFactory.getLog(AbstractSignatureCard.class);
+
+ protected List<PINSpec> pinSpecs = new ArrayList<PINSpec>();
+
+ private ResourceBundle i18n;
+ private String resourceBundleName;
+
+ private Locale locale = Locale.getDefault();
+
+ private Card card_;
+
+ protected CardReader reader;
+
+ protected AbstractSignatureCard(String resourceBundleName) {
+ this.resourceBundleName = resourceBundleName;
+ }
+
+ protected String toString(byte[] b) {
+ StringBuffer sb = new StringBuffer();
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ }
+ for (int i = 1; i < b.length; i++) {
+ sb.append(':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public void init(Card card, CardTerminal cardTerminal) {
+ this.card_ = card;
+ this.reader = ReaderFactory.getReader(card, cardTerminal);
+ }
+
+ @Override
+ public Card getCard() {
+ return card_;
+ }
+
+ protected CardChannel getCardChannel() {
+ return new LogCardChannel(card_.getBasicChannel());
+ }
+
+ @Override
+ public void setLocale(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("Locale must not be set to null");
+ }
+ this.locale = locale;
+ }
+
+ protected ResourceBundle getResourceBundle() {
+ if (i18n == null) {
+ i18n = ResourceBundle.getBundle(resourceBundleName, locale);
+ }
+ return i18n;
+ }
+
+ @Override
+ public void disconnect(boolean reset) {
+ log.debug("Disconnect called");
+ if (card_ != null) {
+ try {
+ card_.disconnect(reset);
+ } catch (Exception e) {
+ log.info("Error while resetting card", e);
+ }
+ }
+ }
+
+ @Override
+ public void reset() throws SignatureCardException {
+ try {
+ log.debug("Disconnect and reset smart card.");
+ card_.disconnect(true);
+ log.debug("Reconnect smart card.");
+ card_ = reader.connect();
+ } catch (CardException e) {
+ throw new SignatureCardException("Failed to reset card.", e);
+ }
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/BELPICCard.java b/smcc/src/main/java/at/gv/egiz/smcc/BELPICCard.java
new file mode 100644
index 00000000..41358bb5
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/BELPICCard.java
@@ -0,0 +1,284 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.util.ISO7816Utils;
+import at.gv.egiz.smcc.util.SMCCHelper;
+
+public class BELPICCard extends AbstractSignatureCard implements SignatureCard {
+
+ /**
+ * Logging facility.
+ */
+ private static Log log = LogFactory.getLog(BELPICCard.class);
+
+ public static final byte[] MF = new byte[] { (byte) 0x3F, (byte) 0x00 };
+
+ public static final byte[] DF_BELPIC = new byte[] { (byte) 0xDF,
+ (byte) 0x00 };
+
+ public static final byte[] DF_ID = new byte[] { (byte) 0xDF, (byte) 0x01 };
+
+ public static final byte[] SIGN_CERT = new byte[] { (byte) 0x50,
+ (byte) 0x39 };
+
+// public static final byte MSE_SET_ALGO_REF = (byte) 0x02;
+
+// public static final byte MSE_SET_PRIV_KEY_REF = (byte) 0x83;
+
+ public static final int SIGNATURE_LENGTH = (int) 0x80;
+
+ public static final byte KID = (byte) 0x01;
+
+ public static final int READ_BUFFER_LENGTH = 256;
+
+ public static final int PINSPEC_SS = 0;
+
+ private static final PINSpec SS_PIN_SPEC =
+ new PINSpec(4, 12, "[0-9]",
+ "at/gv/egiz/smcc/BELPICCard", "sig.pin", KID, DF_BELPIC);
+
+ /**
+ * Creates a new instance.
+ */
+ public BELPICCard() {
+ super("at/gv/egiz/smcc/BelpicCard");
+ pinSpecs.add(SS_PIN_SPEC);
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException {
+
+ if (keyboxName != KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ throw new IllegalArgumentException("Keybox " + keyboxName
+ + " not supported");
+ }
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT MF
+ execSELECT_FID(channel, MF);
+ // SELECT application
+ execSELECT_FID(channel, DF_BELPIC);
+ // SELECT file
+ execSELECT_FID(channel, SIGN_CERT);
+ // READ BINARY
+ byte[] certificate = ISO7816Utils.readTransparentFileTLV(channel, -1, (byte) 0x30);
+ if (certificate == null) {
+ throw new NotActivatedException();
+ }
+ return certificate;
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Failed to get certificate.", e);
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getInfobox(String infobox, PINGUI provider, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ throw new IllegalArgumentException("Infobox '" + infobox
+ + "' not supported.");
+ }
+
+ @Override
+ @Exclusive
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName,
+ PINGUI provider, String alg) throws SignatureCardException, InterruptedException, IOException {
+
+ if (KeyboxName.SECURE_SIGNATURE_KEYPAIR != keyboxName) {
+ throw new SignatureCardException("Card does not support key " + keyboxName + ".");
+ }
+ if (!"http://www.w3.org/2000/09/xmldsig#rsa-sha1".equals(alg)) {
+ throw new SignatureCardException("Card does not support algorithm " + alg + ".");
+ }
+
+ byte[] dst = new byte[] { (byte) 0x04, // number of following
+ // bytes
+ (byte) 0x80, // tag for algorithm reference
+ (byte) 0x02, // algorithm reference
+ (byte) 0x84, // tag for private key reference
+ (byte) 0x83 // private key reference
+ };
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Failed to get MessageDigest.", e);
+ throw new SignatureCardException(e);
+ }
+ // calculate message digest
+ byte[] digest = new byte[md.getDigestLength()];
+ for (int l; (l = input.read(digest)) != -1;) {
+ md.update(digest, 0, l);
+ }
+ digest = md.digest();
+
+ try {
+
+ CardChannel channel = getCardChannel();
+
+ // SELECT MF
+ execSELECT_FID(channel, MF);
+ // VERIFY
+ execMSE(channel, 0x41, 0xb6, dst);
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATURE
+ verifyPINLoop(channel, SS_PIN_SPEC, provider);
+ // MANAGE SECURITY ENVIRONMENT : SET DST
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel, digest);
+
+ } catch (CardException e) {
+ log.warn(e);
+ throw new SignatureCardException("Failed to access card.", e);
+ }
+
+ }
+
+ public String toString() {
+ return "Belpic Card";
+ }
+
+ protected void verifyPINLoop(CardChannel channel, PINSpec spec,
+ PINGUI provider) throws LockedException, NotActivatedException,
+ SignatureCardException, InterruptedException, CardException {
+
+ int retries = -1; //verifyPIN(channel, spec, null, -1);
+ do {
+ retries = verifyPIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected int verifyPIN(CardChannel channel, PINSpec pinSpec,
+ PINGUI provider, int retries) throws SignatureCardException,
+ LockedException, NotActivatedException, InterruptedException,
+ CardException {
+
+ VerifyAPDUSpec apduSpec = new VerifyAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x20, (byte) 0x00, pinSpec.getKID(), (byte) 0x08,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4);
+
+ ResponseAPDU resp = reader.verify(channel, apduSpec, provider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ }
+ if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ }
+
+ switch (resp.getSW()) {
+ case 0x6983:
+ // authentication method blocked
+ throw new LockedException();
+ case 0x6984:
+ // reference data not usable
+ throw new NotActivatedException();
+ case 0x6985:
+ // conditions of use not satisfied
+ throw new NotActivatedException();
+
+ default:
+ String msg = "VERIFY failed. SW=" + Integer.toHexString(resp.getSW());
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ }
+
+ protected byte[] execSELECT_FID(CardChannel channel, byte[] fid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x02, 0x0C, fid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+ }
+
+ protected void execMSE(CardChannel channel, int p1, int p2, byte[] data)
+ throws CardException, SignatureCardException {
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x22, p1, p2, data, 256));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("MSE:SET failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ protected byte[] execPSO_COMPUTE_DIGITAL_SIGNATURE(CardChannel channel, byte[] hash)
+ throws CardException, SignatureCardException {
+ ResponseAPDU resp;
+ resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, hash, 256));
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ } else if (resp.getSW() == 0x6983) {
+ throw new LockedException();
+ } else if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException(
+ "PSO: COMPUTE DIGITAL SIGNATRE failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ } else {
+ return resp.getData();
+ }
+ }
+
+
+
+
+} \ No newline at end of file
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/CancelledException.java b/smcc/src/main/java/at/gv/egiz/smcc/CancelledException.java
new file mode 100644
index 00000000..347d74c9
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/CancelledException.java
@@ -0,0 +1,39 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class CancelledException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public CancelledException() {
+ super();
+ }
+
+ public CancelledException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public CancelledException(String message) {
+ super(message);
+ }
+
+ public CancelledException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/CardNotSupportedException.java b/smcc/src/main/java/at/gv/egiz/smcc/CardNotSupportedException.java
new file mode 100644
index 00000000..1cde093d
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/CardNotSupportedException.java
@@ -0,0 +1,62 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class CardNotSupportedException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new instance of this <code>CardNotSupportedException</code>.
+ *
+ */
+ public CardNotSupportedException() {
+ super();
+ }
+
+ /**
+ * Creates a new instance of this <code>CardNotSupportedException</code>.
+ *
+ * @param message
+ * @param cause
+ */
+ public CardNotSupportedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new instance of this <code>CardNotSupportedException</code>.
+ *
+ * @param message
+ */
+ public CardNotSupportedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new instance of this <code>CardNotSupportedException</code>.
+ *
+ * @param cause
+ */
+ public CardNotSupportedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ChangeReferenceDataAPDUSpec.java b/smcc/src/main/java/at/gv/egiz/smcc/ChangeReferenceDataAPDUSpec.java
new file mode 100644
index 00000000..0b10d88f
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ChangeReferenceDataAPDUSpec.java
@@ -0,0 +1,95 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class ChangeReferenceDataAPDUSpec extends VerifyAPDUSpec {
+
+ /**
+ * The offset for the insertion of the old PIN. (Default: 0)
+ */
+ protected int pinInsertionOffsetOld = 0;
+
+ /**
+ * The offset for the insertion of the new PIN. (Default:
+ * {@link VerifyAPDUSpec#pinLength} + 1})
+ */
+ protected int pinInsertionOffsetNew = pinLength;
+
+ public ChangeReferenceDataAPDUSpec(byte[] apdu, int pinPosition, int pinFormat, int pinLength) {
+ super(apdu, pinPosition, pinFormat, pinLength);
+ }
+
+ /**
+ * @param apdu
+ * @param pinPosition
+ * @param pinFormat
+ * @param pinLength
+ * @param pinLengthSize
+ * @param pinLengthPos
+ */
+ public ChangeReferenceDataAPDUSpec(byte[] apdu, int pinPosition,
+ int pinFormat, int pinLength, int pinLengthSize, int pinLengthPos) {
+ super(apdu, pinPosition, pinFormat, pinLength, pinLengthSize, pinLengthPos);
+ }
+
+ /**
+ * @param apdu
+ * @param pinPosition
+ * @param pinFormat
+ * @param pinLength
+ * @param pinLengthSize
+ * @param pinLengthPos
+ * @param pinInsertionOffsetNew
+ */
+ public ChangeReferenceDataAPDUSpec(byte[] apdu, int pinPosition,
+ int pinFormat, int pinLength, int pinLengthSize, int pinLengthPos,
+ int pinInsertionOffsetNew) {
+ super(apdu, pinPosition, pinFormat, pinLength, pinLengthSize, pinLengthPos);
+ this.pinInsertionOffsetNew = pinInsertionOffsetNew;
+ }
+
+ /**
+ * @return the pinInsertionOffsetOld
+ */
+ public int getPinInsertionOffsetOld() {
+ return pinInsertionOffsetOld;
+ }
+
+ /**
+ * @param pinInsertionOffsetOld the pinInsertionOffsetOld to set
+ */
+ public void setPinInsertionOffsetOld(int pinInsertionOffsetOld) {
+ this.pinInsertionOffsetOld = pinInsertionOffsetOld;
+ }
+
+ /**
+ * @return the pinInsertionOffsetNew
+ */
+ public int getPinInsertionOffsetNew() {
+ return pinInsertionOffsetNew;
+ }
+
+ /**
+ * @param pinInsertionOffsetNew the pinInsertionOffsetNew to set
+ */
+ public void setPinInsertionOffsetNew(int pinInsertionOffsetNew) {
+ this.pinInsertionOffsetNew = pinInsertionOffsetNew;
+ }
+
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ExclSignatureCardProxy.java b/smcc/src/main/java/at/gv/egiz/smcc/ExclSignatureCardProxy.java
new file mode 100644
index 00000000..bfbd0063
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ExclSignatureCardProxy.java
@@ -0,0 +1,110 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class ExclSignatureCardProxy implements InvocationHandler {
+
+ private static Log log = LogFactory.getLog(ExclSignatureCardProxy.class);
+
+ private static final Method init;
+
+ static {
+ try {
+ init = SignatureCard.class.getMethod("init", new Class<?>[] { Card.class,
+ CardTerminal.class });
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private SignatureCard signatureCard;
+
+ public ExclSignatureCardProxy(SignatureCard signatureCard) {
+ this.signatureCard = signatureCard;
+ }
+
+ public static SignatureCard newInstance(SignatureCard signatureCard) {
+ ArrayList<Class<?>> proxyInterfaces = new ArrayList<Class<?>>();
+ proxyInterfaces.add(SignatureCard.class);
+ if (PINMgmtSignatureCard.class.isAssignableFrom(signatureCard.getClass())) {
+ proxyInterfaces.add(PINMgmtSignatureCard.class);
+ }
+ ClassLoader loader = signatureCard.getClass().getClassLoader();
+ return (SignatureCard) Proxy.newProxyInstance(loader, proxyInterfaces
+ .toArray(new Class[proxyInterfaces.size()]),
+ new ExclSignatureCardProxy(signatureCard));
+ }
+
+ public static PINMgmtSignatureCard newInstance(PINMgmtSignatureCard signatureCard) {
+ return null;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+
+ Card card = null;
+
+ Method target = signatureCard.getClass().getMethod(method.getName(),
+ method.getParameterTypes());
+
+ if (target.isAnnotationPresent(Exclusive.class)) {
+ card = (Card) ((method.equals(init))
+ ? args[0]
+ : signatureCard.getCard());
+ }
+
+ if (card != null) {
+ try {
+ log.trace("Invoking method " + method.getName() + "() with exclusive access.");
+ card.beginExclusive();
+ } catch (CardException e) {
+ log.info("Failed to get exclusive access to signature card "
+ + signatureCard.toString() + ".");
+ throw new SignatureCardException(e);
+ }
+ }
+
+ try {
+ return method.invoke(signatureCard, args);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ } finally {
+ if (card != null) {
+ card.endExclusive();
+ }
+ }
+
+
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/Exclusive.java b/smcc/src/main/java/at/gv/egiz/smcc/Exclusive.java
new file mode 100644
index 00000000..b796b045
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/Exclusive.java
@@ -0,0 +1,28 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Exclusive {
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/FileNotFoundException.java b/smcc/src/main/java/at/gv/egiz/smcc/FileNotFoundException.java
new file mode 100644
index 00000000..f96611c2
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/FileNotFoundException.java
@@ -0,0 +1,38 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class FileNotFoundException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public FileNotFoundException() {
+ }
+
+ public FileNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FileNotFoundException(String message) {
+ super(message);
+ }
+
+ public FileNotFoundException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ITCard.java b/smcc/src/main/java/at/gv/egiz/smcc/ITCard.java
new file mode 100644
index 00000000..64389190
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ITCard.java
@@ -0,0 +1,298 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.util.ISO7816Utils;
+import at.gv.egiz.smcc.util.SMCCHelper;
+
+public class ITCard extends AbstractSignatureCard {
+
+ /**
+ * Logging facility.
+ */
+ private static Log log = LogFactory.getLog(STARCOSCard.class);
+
+ public static final byte[] MF = new byte[] { (byte) 0x3F, (byte) 0x00 };
+
+ public static final byte[] DF1 = new byte[] { (byte) 0x11, (byte) 0x00 };
+
+ public static final byte[] EF_C_Carta = new byte[] { (byte) 0x11, (byte) 0x01 };
+
+ private static final PINSpec SS_PIN_SPEC =
+ new PINSpec(5, 8, "[0-9]",
+ "at/gv/egiz/smcc/ITCard", "sig.pin", (byte) 0x10,
+ new byte[] { (byte) 0x11, (byte) 0x00 });
+
+ /**
+ * Creates a new instance.
+ */
+ public ITCard() {
+ super("at/gv/egiz/smcc/ITCard");
+ pinSpecs.add(SS_PIN_SPEC);
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException, InterruptedException {
+
+ if (keyboxName != KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ throw new IllegalArgumentException("Keybox " + keyboxName
+ + " not supported");
+ }
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT MF
+ execSELECT_FID(channel, MF);
+ // SELECT application
+ execSELECT_FID(channel, DF1);
+ // SELECT EF_C_Carta
+ byte[] fcx = execSELECT_FID(channel, EF_C_Carta);
+ int maxsize = ISO7816Utils.getLengthFromFCx(fcx);
+ // READ BINARY
+ byte[] certificate = ISO7816Utils.readTransparentFileTLV(channel, maxsize, (byte) 0x30);
+ if (certificate == null) {
+ throw new NotActivatedException();
+ }
+ return certificate;
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Failed to get certificate.", e);
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getInfobox(String infobox, PINGUI provider, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ throw new IllegalArgumentException("Infobox '" + infobox
+ + "' not supported.");
+ }
+
+ @Override
+ @Exclusive
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName,
+ PINGUI provider, String alg) throws SignatureCardException,
+ InterruptedException, IOException {
+
+ if (KeyboxName.SECURE_SIGNATURE_KEYPAIR != keyboxName) {
+ throw new SignatureCardException("Card does not support key " + keyboxName + ".");
+ }
+ if (!"http://www.w3.org/2000/09/xmldsig#rsa-sha1".equals(alg)) {
+ throw new SignatureCardException("Card does not support algorithm " + alg + ".");
+ }
+
+ byte[] dst = new byte[] {
+ (byte) 0x83, // tag for algorithm reference
+ (byte) 0x01, // algorithm reference
+ (byte) 0x01 // private key reference
+ };
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Failed to get MessageDigest.", e);
+ throw new SignatureCardException(e);
+ }
+ // calculate message digest
+ byte[] digest = new byte[md.getDigestLength()];
+ for (int l; (l = input.read(digest)) != -1;) {
+ md.update(digest, 0, l);
+ }
+ digest = md.digest();
+
+ try {
+
+ CardChannel channel = getCardChannel();
+
+ // SELECT MF
+ execSELECT_FID(channel, MF);
+ // VERIFY
+ verifyPINLoop(channel, SS_PIN_SPEC, provider);
+ // MANAGE SECURITY ENVIRONMENT : RESTORE SE
+ execMSE(channel, 0xF3, 0x03, null);
+ // MANAGE SECURITY ENVIRONMENT : SET DST
+ execMSE(channel, 0xF1, 0xB8, dst);
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATURE
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel, digest);
+
+ } catch (CardException e) {
+ log.warn(e);
+ throw new SignatureCardException("Failed to access card.", e);
+ }
+
+ }
+
+ protected void verifyPINLoop(CardChannel channel, PINSpec spec,
+ PINGUI provider) throws LockedException, NotActivatedException,
+ SignatureCardException, InterruptedException, CardException {
+
+ int retries = -1;
+ do {
+ retries = verifyPIN(channel, spec, provider, retries);
+ } while (retries >= -1);
+ }
+
+ protected int verifyPIN(CardChannel channel, PINSpec pinSpec,
+ PINGUI provider, int retries) throws SignatureCardException,
+ LockedException, NotActivatedException, InterruptedException,
+ CardException {
+
+ VerifyAPDUSpec apduSpec = new VerifyAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x20, (byte) 0x00, pinSpec.getKID(), (byte) 0x08,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 0, VerifyAPDUSpec.PIN_FORMAT_ASCII, 8);
+
+ ResponseAPDU resp = reader.verify(channel, apduSpec, provider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -2;
+ }
+ if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ }
+
+ switch (resp.getSW()) {
+ case 0x6300:
+ // incorrect PIN, number of retries not provided
+ return -1;
+ case 0x6983:
+ // authentication method blocked
+ throw new LockedException();
+ case 0x6984:
+ // reference data not usable
+ throw new NotActivatedException();
+ case 0x6985:
+ // conditions of use not satisfied
+ throw new NotActivatedException();
+
+ default:
+ String msg = "VERIFY failed. SW=" + Integer.toHexString(resp.getSW());
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ }
+
+ protected byte[] execSELECT_FID(CardChannel channel, byte[] fid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x00, 0x00, fid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+ }
+
+ protected void execMSE(CardChannel channel, int p1, int p2, byte[] data)
+ throws CardException, SignatureCardException {
+
+ ResponseAPDU resp;
+ if (data == null) {
+ resp = channel.transmit(new CommandAPDU(0x00, 0x22, p1, p2));
+ } else {
+ resp = channel.transmit(new CommandAPDU(0x00, 0x22, p1, p2, data));
+ }
+
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("MSE:SET failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ protected byte[] execPSO_COMPUTE_DIGITAL_SIGNATURE(CardChannel channel,
+ byte[] hash) throws CardException, SignatureCardException {
+
+ byte[] oid = new byte[] { (byte) 0x30, (byte) 0x21, (byte) 0x30,
+ (byte) 0x09, (byte) 0x06, (byte) 0x05, (byte) 0x2b,
+ (byte) 0x0e, (byte) 0x03, (byte) 0x02, (byte) 0x1a,
+ (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x14 };
+
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+
+ try {
+ // header
+ data.write(new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x01 });
+ // padding
+ for (int i = 0, len = 125 - hash.length - oid.length; i < len; i++) {
+ data.write((byte) 0xFF);
+ }
+ data.write((byte) 0x00);
+ // oid
+ data.write(oid);
+ // hash
+ data.write(hash);
+ } catch (IOException e) {
+ throw new SignatureCardException(e);
+ }
+
+ ResponseAPDU resp = channel
+ .transmit(new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data.toByteArray(), 0x81));
+
+
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ } else if (resp.getSW() == 0x6983) {
+ throw new LockedException();
+ } else if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException(
+ "PSO: COMPUTE DIGITAL SIGNATRE failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ } else {
+ return resp.getData();
+ }
+}
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/LockedException.java b/smcc/src/main/java/at/gv/egiz/smcc/LockedException.java
new file mode 100644
index 00000000..e00322a0
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/LockedException.java
@@ -0,0 +1,38 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class LockedException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public LockedException() {
+ }
+
+ public LockedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public LockedException(String message) {
+ super(message);
+ }
+
+ public LockedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/LogCardChannel.java b/smcc/src/main/java/at/gv/egiz/smcc/LogCardChannel.java
new file mode 100644
index 00000000..3fc80fa1
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/LogCardChannel.java
@@ -0,0 +1,129 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import java.nio.ByteBuffer;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class LogCardChannel extends CardChannel {
+
+ protected static Log log = LogFactory.getLog(LogCardChannel.class);
+
+ private CardChannel channel;
+
+ public LogCardChannel(CardChannel channel) {
+ if (channel == null) {
+ throw new NullPointerException();
+ }
+ this.channel = channel;
+ }
+
+ @Override
+ public void close() throws CardException {
+ channel.close();
+ }
+
+ @Override
+ public Card getCard() {
+ return channel.getCard();
+ }
+
+ @Override
+ public int getChannelNumber() {
+ return channel.getChannelNumber();
+ }
+
+ @Override
+ public ResponseAPDU transmit(CommandAPDU command) throws CardException {
+ if (log.isTraceEnabled()) {
+ switch (command.getINS()) {
+ case 0x20: // VERIFY
+ case 0x21: // VERIFY
+ case 0x24: { // CHANGE REFERENCE DATA
+ // Don't log possibly sensitive command data
+ StringBuilder sb = new StringBuilder();
+ sb.append(command);
+ sb.append('\n');
+ byte[] c = new byte[4];
+ c[0] = (byte) command.getCLA();
+ c[1] = (byte) command.getINS();
+ c[2] = (byte) command.getP1();
+ c[3] = (byte) command.getP2();
+ sb.append(toString(c));
+ if (command.getNc() > 0) {
+ sb.append(':');
+ sb.append(toString(new byte[] {(byte) command.getNc()}));
+ for (int i = 0; i < command.getNc(); i++) {
+ sb.append(":XX");
+ }
+ }
+ if (command.getNe() > 0) {
+ sb.append(':');
+ sb.append(toString(new byte[] {(byte) command.getNe()}));
+ }
+ log.trace(sb.toString());
+ }; break;
+
+ default:
+ log.trace(command + "\n" + toString(command.getBytes()));
+ }
+ long t0 = System.currentTimeMillis();
+ ResponseAPDU response = channel.transmit(command);
+ long t1 = System.currentTimeMillis();
+ log.trace(response + " [" + (t1 - t0) + "ms]\n" + toString(response.getBytes()));
+ return response;
+ } else {
+ return channel.transmit(command);
+ }
+ }
+
+ @Override
+ public int transmit(ByteBuffer command, ByteBuffer response) throws CardException {
+ if (log.isTraceEnabled()) {
+ long t0 = System.currentTimeMillis();
+ int l = channel.transmit(command, response);
+ long t1 = System.currentTimeMillis();
+ log.trace("[" + (t1 - t0) + "ms]");
+ return l;
+ } else {
+ return channel.transmit(command, response);
+ }
+ }
+
+ private String toString(byte[] b) {
+ StringBuffer sb = new StringBuffer();
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ }
+ for (int i = 1; i < b.length; i++) {
+ sb.append(':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/NewReferenceDataAPDUSpec.java b/smcc/src/main/java/at/gv/egiz/smcc/NewReferenceDataAPDUSpec.java
new file mode 100644
index 00000000..2eadaf26
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/NewReferenceDataAPDUSpec.java
@@ -0,0 +1,60 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class NewReferenceDataAPDUSpec extends VerifyAPDUSpec {
+
+ /**
+ * The offset for the insertion of the new PIN. (Default:
+ * {@link VerifyAPDUSpec#pinLength} + 1})
+ */
+ protected int pinInsertionOffsetNew = 0;
+
+ public NewReferenceDataAPDUSpec(byte[] apdu, int pinPosition, int pinFormat, int pinLength) {
+ super(apdu, pinPosition, pinFormat, pinLength);
+ }
+
+ /**
+ * @param apdu
+ * @param pinPosition
+ * @param pinFormat
+ * @param pinLength
+ * @param pinLengthSize
+ * @param pinLengthPos
+ */
+ public NewReferenceDataAPDUSpec(byte[] apdu, int pinPosition,
+ int pinFormat, int pinLength, int pinLengthSize, int pinLengthPos) {
+ super(apdu, pinPosition, pinFormat, pinLength, pinLengthSize, pinLengthPos);
+ }
+
+ /**
+ * @return the pinInsertionOffsetNew
+ */
+ public int getPinInsertionOffsetNew() {
+ return pinInsertionOffsetNew;
+ }
+
+ /**
+ * @param pinInsertionOffsetNew the pinInsertionOffsetNew to set
+ */
+ public void setPinInsertionOffsetNew(int pinInsertionOffsetNew) {
+ this.pinInsertionOffsetNew = pinInsertionOffsetNew;
+ }
+
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/NotActivatedException.java b/smcc/src/main/java/at/gv/egiz/smcc/NotActivatedException.java
new file mode 100644
index 00000000..9181fc5f
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/NotActivatedException.java
@@ -0,0 +1,44 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+/**
+ * This exception is thrown upon a call to a function that
+ * has not been activated (e.g. not yet activated citizen card).
+ */
+public class NotActivatedException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NotActivatedException() {
+ super();
+ }
+
+ public NotActivatedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NotActivatedException(String message) {
+ super(message);
+ }
+
+ public NotActivatedException(Throwable cause) {
+ super(cause);
+ }
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/PINConfirmationException.java b/smcc/src/main/java/at/gv/egiz/smcc/PINConfirmationException.java
new file mode 100644
index 00000000..24dfa53c
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/PINConfirmationException.java
@@ -0,0 +1,26 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+/**
+ * TODO check whether card readers distinguish specific reason (pin too short?)
+ * and add getters/setters
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class PINConfirmationException extends SignatureCardException {
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/PINFormatException.java b/smcc/src/main/java/at/gv/egiz/smcc/PINFormatException.java
new file mode 100644
index 00000000..721c63e2
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/PINFormatException.java
@@ -0,0 +1,26 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+/**
+ * TODO check whether card readers distinguish specific reason (pin too short?)
+ * and add getters/setters
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class PINFormatException extends SignatureCardException {
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/PINMgmtSignatureCard.java b/smcc/src/main/java/at/gv/egiz/smcc/PINMgmtSignatureCard.java
new file mode 100644
index 00000000..5091c10f
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/PINMgmtSignatureCard.java
@@ -0,0 +1,44 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.util.List;
+
+public interface PINMgmtSignatureCard extends SignatureCard {
+
+ public enum PIN_STATE {UNKNOWN, ACTIV, NOT_ACTIV, BLOCKED};
+
+ public List<PINSpec> getPINSpecs();
+
+ public PIN_STATE getPINState(PINSpec pinSpec) throws SignatureCardException;
+
+ public void verifyPIN(PINSpec pinSpec, PINGUI pinGUI)
+ throws LockedException, NotActivatedException, CancelledException, SignatureCardException, InterruptedException;
+
+ public void changePIN(PINSpec pinSpec, ModifyPINGUI changePINGUI)
+ throws LockedException, NotActivatedException, CancelledException, PINFormatException, SignatureCardException, InterruptedException;
+
+ public void activatePIN(PINSpec pinSpec, ModifyPINGUI activatePINGUI)
+ throws CancelledException, SignatureCardException, InterruptedException;
+
+ public void unblockPIN(PINSpec pinSpec, ModifyPINGUI pukGUI)
+ throws CancelledException, SignatureCardException, InterruptedException;
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/PINOperationAbortedException.java b/smcc/src/main/java/at/gv/egiz/smcc/PINOperationAbortedException.java
new file mode 100644
index 00000000..51e4904e
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/PINOperationAbortedException.java
@@ -0,0 +1,45 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+/**
+ * TODO check whether card readers distinguish specific reason (pin too short?)
+ * and add getters/setters
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class PINOperationAbortedException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public PINOperationAbortedException() {
+ super();
+ }
+
+ public PINOperationAbortedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PINOperationAbortedException(String message) {
+ super(message);
+ }
+
+ public PINOperationAbortedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/PINSpec.java b/smcc/src/main/java/at/gv/egiz/smcc/PINSpec.java
new file mode 100644
index 00000000..f68edbed
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/PINSpec.java
@@ -0,0 +1,239 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author mcentner
+ */
+public class PINSpec {
+
+ /**
+ * The minimum PIN length.
+ */
+ protected int minLength = 0;
+
+ /**
+ * The maximum PIN length or -1 if not specified.
+ */
+ protected int maxLength = -1;
+
+ /**
+ * The recommended PIN length or -1 if not specified.
+ */
+ protected int recLength = -1;
+
+ /**
+ * The regular expression pattern of a single PIN digit or character.
+ */
+ protected String rexepPattern;
+
+ /**
+ * The name of the corresponding resource bundle.
+ */
+ protected String resourceBundleName;
+
+ /**
+ * The key of the PIN name in the resource bundle.
+ */
+ protected String nameKey;
+
+ /**
+ * The name of the PIN.
+ */
+ protected String name;
+
+ /**
+ * The key id to be used in VERIFY or CHANGE REFERENCE DATA APDUs.
+ */
+ protected byte kid;
+
+ /**
+ * The context AID of the key id.
+ */
+ protected byte[] context_aid;
+
+ /**
+ * Creates a new instance of this PINSpec with the given lengths, regular
+ * expression pattern, the ResourceBundle name and key to lookup the PIN name
+ * and the KID and AID.
+ *
+ * @param minLenght the minimum length of the PIN
+ * @param maxLength the maximum length of the PIN, or -1 if there is no maximum length
+ * @param rexepPattern the regular expression pattern of a single PIN digit or character
+ * @param resourceBundleName the name of a ResourceBundle for this PIN
+ * @param resourceKey the key to look up the (localized) name of this PIN
+ * @param kid the key id of the PIN
+ * @param contextAID the AID the KID is valid in
+ */
+ public PINSpec(int minLenght, int maxLength, String rexepPattern,
+ String resourceBundleName, String resourceKey, byte kid, byte[] contextAID) {
+
+ this.minLength = minLenght;
+ this.maxLength = maxLength;
+ this.rexepPattern = rexepPattern;
+ this.resourceBundleName = resourceBundleName;
+ this.nameKey = resourceKey + ".name";
+ this.kid = kid;
+ this.context_aid = contextAID;
+ }
+
+ /**
+ * Creates a new instance of this PINSpec with the given lengths, regular
+ * expression pattern, the name of the PIN and the KID and AID.
+ *
+ * @param minLenght the minimum length of the PIN
+ * @param maxLength the maximum length of the PIN, or -1 if there is no maximum length
+ * @param rexepPattern the regular expression pattern of a single PIN digit or character
+ * @param name the name of the PIN
+ * @param kid the key id of the PIN
+ * @param contextAID the AID the KID is valid in
+ */
+ public PINSpec(int minLenght, int maxLength, String rexepPattern,
+ String name, byte kid, byte[] contextAID) {
+
+ this.minLength = minLenght;
+ this.maxLength = maxLength;
+ this.rexepPattern = rexepPattern;
+ this.name = name;
+ this.kid = kid;
+ this.context_aid = contextAID;
+ }
+
+ /**
+ * This method sets the recommended PIN length.
+ *
+ * @param recLength the recommended PIN length
+ */
+ public void setRecLength(int recLength) {
+ this.recLength = recLength;
+ }
+
+ /**
+ * @return the localized (using the default locale) name of the PIN, or the
+ * name set by
+ * {@link #PINSpec(int, int, String, String, byte, byte[])}.
+ */
+ public String getLocalizedName() {
+ if (name != null) {
+ return name;
+ } else if (resourceBundleName != null){
+ try {
+ return ResourceBundle.getBundle(resourceBundleName).getString(nameKey);
+ } catch (MissingResourceException e) {
+ }
+ }
+ return nameKey;
+ }
+
+ /**
+ * @param locale the locale for which the name should be returned
+ * @return the localized name of the PIN, or the name set by
+ * {@link #PINSpec(int, int, String, String, byte, byte[])}
+ */
+ public String getLocalizedName(Locale locale) {
+ if (name != null) {
+ return name;
+ } else if (resourceBundleName != null) {
+ try {
+ return ResourceBundle.getBundle(resourceBundleName, locale).getString(nameKey);
+ } catch (MissingResourceException e) {
+ }
+ }
+ return nameKey;
+ }
+
+ /**
+ * @return the recommended PIN length if specified and
+ * <code>recommended</code> is <code>true</code>, or
+ * <code>minLength</code>-<code>maxLength</code>
+ */
+ public String getLocalizedLength() {
+
+ if (recLength > 0) {
+ return "" + recLength;
+ } else if (maxLength == minLength) {
+ return "" + minLength;
+ } else if (maxLength > minLength) {
+ return minLength + "-" + maxLength;
+ } else {
+ return minLength + "+";
+ }
+
+ }
+
+ /**
+ * @return the minimum length of the PIN
+ */
+ public int getMinLength() {
+ return minLength;
+ }
+
+ /**
+ * @return the maximum length of the PIN, or -1 if not specified.
+ */
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ /**
+ * @return the minimum length of the PIN
+ */
+ public int getRecMinLength() {
+ return (recLength >= minLength) ? recLength : minLength;
+ }
+
+ /**
+ * @return the maximum length of the PIN
+ */
+ public int getRecMaxLength() {
+ return (recLength >= minLength) ? recLength : maxLength;
+ }
+
+ /**
+ * @return the recommended length of the PIN, or -1 if not specified
+ */
+ public int getRecLength() {
+ return recLength;
+ }
+
+ /**
+ * @return the regular expression pattern of one single digit or character
+ */
+ public String getRexepPattern() {
+ return rexepPattern;
+ }
+
+ /**
+ * @return the key id of the PIN
+ */
+ public byte getKID() {
+ return kid;
+ }
+
+ /**
+ * @return the AID the KID is valid in, or <code>null</code> if KID is global
+ */
+ public byte[] getContextAID() {
+ return context_aid;
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ResetRetryCounterAPDUSpec.java b/smcc/src/main/java/at/gv/egiz/smcc/ResetRetryCounterAPDUSpec.java
new file mode 100644
index 00000000..7e71eb7e
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ResetRetryCounterAPDUSpec.java
@@ -0,0 +1,38 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class ResetRetryCounterAPDUSpec extends ChangeReferenceDataAPDUSpec {
+
+ /**
+ * @param apdu
+ * @param pukPosition
+ * @param pukFormat
+ * @param pukLength
+ * @param pukLengthSize
+ * @param pukLengthPos
+ * @param pinInsertionOffsetNew
+ */
+ public ResetRetryCounterAPDUSpec(byte[] apdu, int pukPosition,
+ int pukFormat, int pukLength, int pukLengthSize, int pukLengthPos,
+ int pinInsertionOffsetNew) {
+ super(apdu, pukPosition, pukFormat, pukLength, pukLengthSize, pukLengthPos);
+ this.pinInsertionOffsetNew = pinInsertionOffsetNew;
+ }
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java b/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java
new file mode 100644
index 00000000..b876847f
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java
@@ -0,0 +1,888 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.util.ISO7816Utils;
+import at.gv.egiz.smcc.util.SMCCHelper;
+
+public class STARCOSCard extends AbstractSignatureCard implements PINMgmtSignatureCard {
+
+ /**
+ * Logging facility.
+ */
+ private static Log log = LogFactory.getLog(STARCOSCard.class);
+
+ public static final byte[] MF = new byte[] { (byte) 0x3F, (byte) 0x00 };
+
+ public static final byte[] EF_VERSION = new byte[] { (byte) 0x00, (byte) 0x32 };
+
+ /**
+ * Application ID <em>SV-Personendaten</em>.
+ */
+ public static final byte[] AID_SV_PERSONENDATEN = new byte[] {
+ (byte) 0xD0, (byte) 0x40, (byte) 0x00, (byte) 0x00,
+ (byte) 0x17, (byte) 0x01, (byte) 0x01, (byte) 0x01
+ };
+
+ /**
+ * File ID <em>Grunddaten</em> ({@link #AID_SV_PERSONENDATEN}).
+ */
+ public static final byte[] FID_GRUNDDATEN = new byte[] {
+ (byte) 0xEF, (byte) 0x01
+ };
+
+ /**
+ * File ID <em>EHIC</em> ({@link #AID_SV_PERSONENDATEN}).
+ */
+ public static final byte[] FID_EHIC = new byte[] {
+ (byte) 0xEF, (byte) 0x02
+ };
+
+ /**
+ * File ID <em>Status</em> ({@link #AID_SV_PERSONENDATEN}).
+ */
+ public static final byte[] FID_SV_PERSONENBINDUNG = new byte[] {
+ (byte) 0xEF, (byte) 0x03
+ };
+
+ /**
+ * File ID <em>Status</em> ({@link #AID_SV_PERSONENDATEN}).
+ */
+ public static final byte[] FID_STATUS = new byte[] {
+ (byte) 0xEF, (byte) 0x04
+ };
+
+ public static final byte[] AID_INFOBOX = new byte[] { (byte) 0xd0,
+ (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00,
+ (byte) 0x18, (byte) 0x01 };
+
+ public static final byte[] EF_INFOBOX = new byte[] { (byte) 0xef, (byte) 0x01 };
+
+ public static final byte[] AID_SVSIG_CERT = new byte[] { (byte) 0xd0,
+ (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00,
+ (byte) 0x10, (byte) 0x01 };
+
+ public static final byte[] EF_SVSIG_CERT_CA = new byte[] { (byte) 0x2f,
+ (byte) 0x01 };
+
+ public static final byte[] EF_SVSIG_CERT = new byte[] { (byte) 0x2f,
+ (byte) 0x02 };
+
+ // Sichere Signatur (SS)
+
+ public static final byte[] AID_DF_SS = new byte[] { (byte) 0xd0, (byte) 0x40,
+ (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x12,
+ (byte) 0x01 };
+
+ public static final byte[] EF_C_X509_CH_DS = new byte[] { (byte) 0xc0,
+ (byte) 0x00 };
+
+ public static final byte[] EF_C_X509_CA_CS_DS = new byte[] { (byte) 0xc6,
+ (byte) 0x08 };
+
+ public static final byte KID_PIN_SS = (byte) 0x81;
+
+ // Gewöhnliche Signatur (GS)
+
+ public static final byte[] AID_DF_GS = new byte[] { (byte) 0xd0, (byte) 0x40,
+ (byte) 0x00, (byte) 0x00, (byte) 0x17, (byte) 0x00, (byte) 0x13,
+ (byte) 0x01 };
+
+ public static final byte[] EF_C_X509_CH_AUT = new byte[] { (byte) 0x2f,
+ (byte) 0x01 };
+
+ public static final byte[] EF_C_X509_CA_CS = new byte[] { (byte) 0x2f,
+ (byte) 0x02 };
+
+ public static final byte KID_PIN_CARD = (byte) 0x01;
+
+ private static final PINSpec CARD_PIN_SPEC =
+ new PINSpec(4, 12, "[0-9]",
+ "at/gv/egiz/smcc/STARCOSCard", "card.pin", KID_PIN_CARD, null);
+
+ private static final PINSpec SS_PIN_SPEC =
+ new PINSpec(6, 12, "[0-9]",
+ "at/gv/egiz/smcc/STARCOSCard", "sig.pin", KID_PIN_SS, AID_DF_SS);
+
+ static {
+ if (SignatureCardFactory.ENFORCE_RECOMMENDED_PIN_LENGTH) {
+ CARD_PIN_SPEC.setRecLength(4);
+ SS_PIN_SPEC.setRecLength(6);
+ }
+ }
+
+ protected double version = 1.1;
+
+ /**
+ * Creates a new instance.
+ */
+ public STARCOSCard() {
+ super("at/gv/egiz/smcc/STARCOSCard");
+ pinSpecs.add(CARD_PIN_SPEC);
+ pinSpecs.add(SS_PIN_SPEC);
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#init(javax.smartcardio.Card, javax.smartcardio.CardTerminal)
+ */
+ @Override
+ public void init(Card card, CardTerminal cardTerminal) {
+ super.init(card, cardTerminal);
+
+ // determine application version
+ CardChannel channel = getCardChannel();
+ try {
+ // SELECT MF
+ execSELECT_MF(channel);
+ // SELECT EF_VERSION
+ execSELECT_FID(channel, EF_VERSION);
+ // READ BINARY
+ byte[] ver = ISO7816Utils.readRecord(channel, 1);
+ if (ver[0] == (byte) 0xa5 && ver[2] == (byte) 0x53) {
+ version = (0x0F & ver[4]) + (0xF0 & ver[5])/160.0 + (0x0F & ver[5])/100.0;
+ String generation = (version < 1.2) ? "<= G2" : "G3";
+ log.info("e-card version=" + version + " (" + generation + ")");
+ }
+ } catch (CardException e) {
+ log.warn(e);
+ } catch (SignatureCardException e) {
+ log.warn(e);
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException, InterruptedException {
+
+ byte[] aid;
+ byte[] fid;
+ if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ aid = AID_DF_SS;
+ fid = EF_C_X509_CH_DS;
+ } else if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) {
+ aid = AID_DF_GS;
+ fid = EF_C_X509_CH_AUT;
+ } else {
+ throw new IllegalArgumentException("Keybox " + keyboxName
+ + " not supported.");
+ }
+
+ try {
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, aid);
+ // SELECT file
+ execSELECT_FID(channel, fid);
+ // READ BINARY
+ byte[] certificate = ISO7816Utils.readTransparentFileTLV(channel, -1, (byte) 0x30);
+ if (certificate == null) {
+ throw new NotActivatedException();
+ }
+ return certificate;
+ } catch (FileNotFoundException e) {
+ throw new NotActivatedException();
+ } catch (CardException e) {
+ log.info("Failed to get certificate.", e);
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ @Override
+ @Exclusive
+ public byte[] getInfobox(String infobox, PINGUI pinGUI, String domainId)
+ throws SignatureCardException, InterruptedException {
+
+ try {
+ if ("IdentityLink".equals(infobox)) {
+
+ PINSpec spec = CARD_PIN_SPEC;
+
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_INFOBOX);
+ // SELECT file
+ execSELECT_FID(channel, EF_INFOBOX);
+
+ while (true) {
+ try {
+ return ISO7816Utils.readTransparentFileTLV(channel, -1, (byte) 0x30);
+ } catch (SecurityStatusNotSatisfiedException e) {
+ verifyPINLoop(channel, spec, pinGUI);
+ }
+ }
+
+ } else if ("Status".equals(infobox)) {
+
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_SV_PERSONENDATEN);
+ // SELECT file
+ execSELECT_FID(channel, FID_STATUS);
+ // READ RECORDS
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try {
+ for (int record = 1; record <= 5; record++) {
+ byte[] rb = ISO7816Utils.readRecord(channel, record);
+ bytes.write(rb);
+ }
+ } catch (IOException e) {
+ throw new SignatureCardException("Failed to read infobox '" + infobox
+ + "'.", e);
+ }
+ return bytes.toByteArray();
+
+ } else {
+
+ byte[] fid;
+
+ if ("EHIC".equals(infobox)) {
+ fid = FID_EHIC;
+ } else if ("Grunddaten".equals(infobox)) {
+ fid = FID_GRUNDDATEN;
+ } else if ("SV-Personenbindung".equals(infobox)) {
+ fid = FID_SV_PERSONENBINDUNG;
+ } else {
+ throw new IllegalArgumentException("Infobox '" + infobox
+ + "' not supported.");
+ }
+
+ CardChannel channel = getCardChannel();
+ // SELECT application
+ execSELECT_AID(channel, AID_SV_PERSONENDATEN);
+ // SELECT file
+ execSELECT_FID(channel, fid);
+ // READ BINARY
+ return ISO7816Utils.readTransparentFileTLV(channel, -1, (byte) 0x30);
+
+ }
+
+ } catch (CardException e) {
+ log.warn(e);
+ throw new SignatureCardException("Failed to access card.", e);
+ }
+ }
+
+ @Override
+ @Exclusive
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName,
+ PINGUI provider, String alg) throws SignatureCardException, InterruptedException, IOException {
+
+ ByteArrayOutputStream dst = new ByteArrayOutputStream();
+ byte[] ht = null;
+
+ MessageDigest md = null;
+ try {
+ if (alg == null || "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1".equals(alg)) {
+ // local key ID '02' version '00'
+ dst.write(new byte[] {(byte) 0x84, (byte) 0x03, (byte) 0x80, (byte) 0x02, (byte) 0x00});
+ if (version < 1.2) {
+ // algorithm ID ECDSA with SHA-1
+ dst.write(new byte[] {(byte) 0x89, (byte) 0x03, (byte) 0x13, (byte) 0x35, (byte) 0x10});
+ } else {
+ // portable algorithm reference
+ dst.write(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x04});
+ // hash template
+ ht = new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x10};
+ }
+ md = MessageDigest.getInstance("SHA-1");
+ } else if (version >= 1.2 && "http://www.w3.org/2000/09/xmldsig#rsa-sha1".equals(alg)) {
+ // local key ID '03' version '00'
+ dst.write(new byte[] {(byte) 0x84, (byte) 0x03, (byte) 0x80, (byte) 0x03, (byte) 0x00});
+ // portable algorithm reference
+ dst.write(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x02});
+ // hash template
+ ht = new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x10};
+ md = MessageDigest.getInstance("SHA-1");
+ } else if (version >= 1.2 && "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256".equals(alg)) {
+ // local key ID '02' version '00'
+ dst.write(new byte[] {(byte) 0x84, (byte) 0x03, (byte) 0x80, (byte) 0x02, (byte) 0x00});
+ // portable algorithm reference
+ dst.write(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x04});
+ // hash template
+ ht = new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x40};
+ md = MessageDigest.getInstance("SHA256");
+ } else if (version >= 1.2 && "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256".equals(alg)) {
+ // local key ID '03' version '00'
+ dst.write(new byte[] {(byte) 0x84, (byte) 0x03, (byte) 0x80, (byte) 0x03, (byte) 0x00});
+ // portable algorithm reference
+ dst.write(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x02});
+ // hash template
+ ht = new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x40};
+ md = MessageDigest.getInstance("SHA256");
+ } else {
+ throw new SignatureCardException("e-card version " + version + " does not support signature algorithm " + alg + ".");
+ }
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Failed to get MessageDigest.", e);
+ throw new SignatureCardException(e);
+ }
+
+ // calculate message digest
+ byte[] digest = new byte[md.getDigestLength()];
+ for (int l; (l = input.read(digest)) != -1;) {
+ md.update(digest, 0, l);
+ }
+ digest = md.digest();
+
+ try {
+
+ CardChannel channel = getCardChannel();
+
+ if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) {
+
+ PINSpec spec = SS_PIN_SPEC;
+
+ // SELECT MF
+ execSELECT_MF(channel);
+ // SELECT application
+ execSELECT_AID(channel, AID_DF_SS);
+ // VERIFY
+ verifyPINLoop(channel, spec, provider);
+ // MANAGE SECURITY ENVIRONMENT : SET DST
+ execMSE(channel, 0x41, 0xb6, dst.toByteArray());
+ if (version < 1.2) {
+ // PERFORM SECURITY OPERATION : HASH
+ execPSO_HASH(channel, digest);
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATURE
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel, null);
+ } else {
+ if (ht != null) {
+ // PERFORM SECURITY OPERATION : SET HT
+ execMSE(channel, 0x41, 0xaa, ht);
+ }
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATURE
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel, digest);
+ }
+
+
+ } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) {
+
+ PINSpec spec = CARD_PIN_SPEC;
+
+ // SELECT application
+ execSELECT_AID(channel, AID_DF_GS);
+ // MANAGE SECURITY ENVIRONMENT : SET DST
+ execMSE(channel, 0x41, 0xb6, dst.toByteArray());
+ if (version >= 1.2 && ht != null) {
+ // PERFORM SECURITY OPERATION : SET HT
+ execMSE(channel, 0x41, 0xaa, ht);
+ }
+ // PERFORM SECURITY OPERATION : HASH
+ execPSO_HASH(channel, digest);
+ while (true) {
+ try {
+ // PERFORM SECURITY OPERATION : COMPUTE DIGITAL SIGNATURE
+ return execPSO_COMPUTE_DIGITAL_SIGNATURE(channel, null);
+ } catch (SecurityStatusNotSatisfiedException e) {
+ verifyPINLoop(channel, spec, provider);
+ }
+ }
+
+ } else {
+ throw new IllegalArgumentException("KeyboxName '" + keyboxName
+ + "' not supported.");
+ }
+
+ } catch (CardException e) {
+ log.warn(e);
+ throw new SignatureCardException("Failed to access card.", e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#verifyPIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.PINProvider)
+ */
+ @Override
+ @Exclusive
+ public void verifyPIN(PINSpec pinSpec, PINGUI pinProvider)
+ throws LockedException, NotActivatedException, CancelledException,
+ TimeoutException, SignatureCardException, InterruptedException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ if (pinSpec.getContextAID() != null) {
+ // SELECT application
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ }
+ verifyPINLoop(channel, pinSpec, pinProvider);
+ } catch (CardException e) {
+ log.info("Failed to verify PIN.", e);
+ throw new SignatureCardException("Failed to verify PIN.", e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#changePIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.ChangePINProvider)
+ */
+ @Override
+ @Exclusive
+ public void changePIN(PINSpec pinSpec, ModifyPINGUI pinGUI)
+ throws LockedException, NotActivatedException, CancelledException,
+ TimeoutException, SignatureCardException, InterruptedException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ if (pinSpec.getContextAID() != null) {
+ // SELECT application
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ }
+ changePINLoop(channel, pinSpec, pinGUI);
+ } catch (CardException e) {
+ log.info("Failed to change PIN.", e);
+ throw new SignatureCardException("Failed to change PIN.", e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.AbstractSignatureCard#activatePIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.PINProvider)
+ */
+ @Override
+ @Exclusive
+ public void activatePIN(PINSpec pinSpec, ModifyPINGUI activatePINGUI)
+ throws CancelledException, SignatureCardException, CancelledException,
+ TimeoutException, InterruptedException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ if (pinSpec.getContextAID() != null) {
+ // SELECT application
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ }
+ activatePIN(channel, pinSpec, activatePINGUI);
+ } catch (CardException e) {
+ log.info("Failed to activate PIN.", e);
+ throw new SignatureCardException("Failed to activate PIN.", e);
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.PINMgmtSignatureCard#unblockPIN(at.gv.egiz.smcc.PINSpec, at.gv.egiz.smcc.PINProvider)
+ */
+ @Override
+ public void unblockPIN(PINSpec pinSpec, ModifyPINGUI pukProvider)
+ throws CancelledException, SignatureCardException, InterruptedException {
+ CardChannel channel = getCardChannel();
+
+ try {
+ unblockPINLoop(channel, pinSpec, pukProvider);
+ } catch (CardException e) {
+ log.info("Failed to activate PIN.", e);
+ throw new SignatureCardException("Failed to activate PIN.", e);
+ }
+ }
+
+ @Override
+ public void reset() throws SignatureCardException {
+ try {
+ super.reset();
+ log.debug("select MF (e-card workaround)");
+ CardChannel channel = getCardChannel();
+ ResponseAPDU resp = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x00, 0x0C));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("Failed to select MF after RESET: SW=" + Integer.toHexString(resp.getSW()) + ".");
+ }
+ } catch (CardException ex) {
+ log.error("Failed to select MF after RESET: " + ex.getMessage(), ex);
+ throw new SignatureCardException("Failed to select MF after RESET");
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.PINMgmtSignatureCard#getPINSpecs()
+ */
+ @Override
+ public List<PINSpec> getPINSpecs() {
+ return Arrays.asList(new PINSpec[] {CARD_PIN_SPEC, SS_PIN_SPEC});
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.smcc.PINMgmtSignatureCard#getPINStatus(at.gv.egiz.smcc.PINSpec)
+ */
+ @Override
+ public PIN_STATE getPINState(PINSpec pinSpec) throws SignatureCardException {
+
+ CardChannel channel = getCardChannel();
+
+ try {
+ if (pinSpec.getContextAID() != null) {
+ // SELECT AID
+ execSELECT_AID(channel, pinSpec.getContextAID());
+ }
+ verifyPIN(channel, pinSpec, null, 0);
+ return PIN_STATE.ACTIV;
+ } catch (InterruptedException e) {
+ return PIN_STATE.UNKNOWN;
+ } catch (LockedException e) {
+ return PIN_STATE.BLOCKED;
+ } catch (NotActivatedException e) {
+ return PIN_STATE.NOT_ACTIV;
+ } catch (CardException e) {
+ log.error("Failed to get PIN status.", e);
+ throw new SignatureCardException("Failed to get PIN status.", e);
+ }
+
+ }
+
+ public String toString() {
+ return "e-card";
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // PROTECTED METHODS (assume exclusive card access)
+ ////////////////////////////////////////////////////////////////////////
+
+ protected void verifyPINLoop(CardChannel channel, PINSpec spec, PINGUI provider)
+ throws LockedException, NotActivatedException, SignatureCardException,
+ InterruptedException, CardException {
+
+ int retries = verifyPIN(channel, spec, null, -1);
+ do {
+ retries = verifyPIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected void changePINLoop(CardChannel channel, PINSpec spec, ModifyPINGUI provider)
+ throws LockedException, NotActivatedException, SignatureCardException,
+ InterruptedException, CardException {
+
+ int retries = verifyPIN(channel, spec, null, -1);
+ do {
+ retries = changePIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected void unblockPINLoop(CardChannel channel, PINSpec spec, ModifyPINGUI provider)
+ throws LockedException, NotActivatedException, SignatureCardException,
+ InterruptedException, CardException {
+
+ //TODO get PUK retry counter from EF FID 0036 in MF
+ int retries = -1;
+ do {
+ retries = unblockPIN(channel, spec, provider, retries);
+ } while (retries > 0);
+ }
+
+ protected int verifyPIN(CardChannel channel, PINSpec pinSpec,
+ PINGUI provider, int retries) throws SignatureCardException,
+ LockedException, NotActivatedException, InterruptedException,
+ CardException {
+
+ VerifyAPDUSpec apduSpec = new VerifyAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x20, (byte) 0x00, pinSpec.getKID(), (byte) 0x08,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4);
+
+ ResponseAPDU resp;
+ if (provider != null) {
+ resp = reader.verify(channel, apduSpec, provider, pinSpec, retries);
+ } else {
+ resp = channel.transmit(new CommandAPDU(0x00, 0x20, 0x00, pinSpec.getKID()));
+ }
+
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ } else if (resp.getSW() == 0x6983 || resp.getSW() == 0x63c0) {
+ // authentication method blocked (0x63c0 returned by 'short' VERIFY)
+ throw new LockedException();
+ } else if (resp.getSW() == 0x6984 || resp.getSW() == 0x6985) {
+ // reference data not usable; conditions of use not satisfied
+ throw new NotActivatedException();
+ } else if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ } else if (version >= 1.2 && resp.getSW() == 0x6400) {
+ String msg = "VERIFY failed, card not activated. SW=0x6400";
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ String msg = "VERIFY failed. SW=" + Integer.toHexString(resp.getSW());
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ }
+ }
+
+ protected int changePIN(CardChannel channel, PINSpec pinSpec,
+ ModifyPINGUI pinProvider, int retries) throws CancelledException,
+ InterruptedException, CardException, SignatureCardException {
+
+ ChangeReferenceDataAPDUSpec apduSpec = new ChangeReferenceDataAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x24, (byte) 0x00, pinSpec.getKID(), (byte) 0x10,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4, 8);
+
+ ResponseAPDU resp = reader.modify(channel, apduSpec, pinProvider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ } else if (resp.getSW() == 0x6983) {
+ // authentication method blocked
+ throw new LockedException();
+ } else if (resp.getSW() == 0x6984) {
+ throw new NotActivatedException();
+ } else if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ } else {
+ String msg = "CHANGE REFERENCE DATA failed. SW=" + Integer.toHexString(resp.getSW());
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ }
+ }
+
+ protected int activatePIN(CardChannel channel, PINSpec pinSpec,
+ ModifyPINGUI provider) throws SignatureCardException,
+ InterruptedException, CardException {
+
+ ResponseAPDU resp;
+ if (version < 1.2) {
+ NewReferenceDataAPDUSpec apduSpec = new NewReferenceDataAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x24, (byte) 0x01, pinSpec.getKID(), (byte) 0x08,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4);
+
+ resp = reader.modify(channel, apduSpec, provider, pinSpec);
+ } else {
+ NewReferenceDataAPDUSpec apduSpec = new NewReferenceDataAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x24, (byte) 0x00, pinSpec.getKID(), (byte) 0x10,
+ (byte) 0x26, (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4);
+ apduSpec.setPinInsertionOffsetNew(8);
+ resp = reader.modify(channel, apduSpec, provider, pinSpec);
+ }
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ } else {
+ String msg = "CHANGE REFERENCE DATA failed. SW=" + Integer.toHexString(resp.getSW());
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ }
+ }
+
+ protected int unblockPIN(CardChannel channel, PINSpec pinSpec,
+ ModifyPINGUI provider, int retries) throws SignatureCardException,
+ InterruptedException, CardException {
+
+ if (version < 1.2) {
+ // would return 0x6982 (Security status not satisfied)
+ throw new SignatureCardException("RESET RETRY COUNTER is not supported by this card.");
+ }
+
+ ResetRetryCounterAPDUSpec apduSpec = new ResetRetryCounterAPDUSpec(
+ new byte[] {
+ (byte) 0x00, (byte) 0x2c, (byte) 0x00, pinSpec.getKID(), (byte) 0x10,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x20, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4, 8);
+
+ ResponseAPDU resp = reader.modify(channel, apduSpec, provider, pinSpec, retries);
+
+ if (resp.getSW() == 0x9000) {
+ return -1;
+ } else if (resp.getSW() == 0x6983) {
+ // PUK blocked
+ throw new LockedException();
+ } else if (resp.getSW() == 0x6984) {
+ throw new NotActivatedException();
+ } else if (resp.getSW() >> 4 == 0x63c) {
+ return 0x0f & resp.getSW();
+ } else {
+ String msg = "RESET RETRY COUNTER failed. SW=" + Integer.toHexString(resp.getSW());
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ }
+ }
+
+ protected void execSELECT_MF(CardChannel channel) throws CardException, SignatureCardException {
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x00, 0x0C));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("Failed to select MF: SW="
+ + Integer.toHexString(resp.getSW()) + ".");
+ }
+ }
+
+ protected byte[] execSELECT_AID(CardChannel channel, byte[] aid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found AID="
+ + SMCCHelper.toString(aid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application AID="
+ + SMCCHelper.toString(aid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+ }
+
+ protected byte[] execSELECT_FID(CardChannel channel, byte[] fid)
+ throws SignatureCardException, CardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xA4, 0x02, 0x04, fid, 256));
+
+ if (resp.getSW() == 0x6A82) {
+ String msg = "File or application not found FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.info(msg);
+ throw new FileNotFoundException(msg);
+ } else if (resp.getSW() != 0x9000) {
+ String msg = "Failed to select application FID="
+ + SMCCHelper.toString(fid) + " SW="
+ + Integer.toHexString(resp.getSW()) + ".";
+ log.error(msg);
+ throw new SignatureCardException(msg);
+ } else {
+ return resp.getBytes();
+ }
+
+ }
+
+ protected void execMSE(CardChannel channel, int p1, int p2, byte[] data)
+ throws CardException, SignatureCardException {
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x22, p1, p2, data));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("MSE:SET failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ protected void execPSO_HASH(CardChannel channel, byte[] hash) throws CardException, SignatureCardException {
+ byte[] data = new byte[hash.length + 2];
+ data[0] = (byte) 0x90; // tag
+ data[1] = (byte) (hash.length); // length
+ System.arraycopy(hash, 0, data, 2, hash.length);
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x90, 0xA0, data));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("PSO:HASH failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+ }
+
+ protected void execPSO_HASH(CardChannel channel, InputStream input)
+ throws SignatureCardException, CardException {
+ ResponseAPDU resp;
+ int blockSize = 64;
+ byte[] b = new byte[blockSize];
+ try {
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ // initialize
+ data.write((byte) 0x90);
+ data.write((byte) 0x00);
+ resp = channel.transmit(
+ new CommandAPDU(0x10, 0x2A, 0x90, 0xA0, data.toByteArray()));
+ data.reset();
+ for (int l; (l = input.read(b)) != -1;) {
+ data.write((byte) 0x80);
+ data.write(l);
+ data.write(b, 0, l);
+ resp = channel.transmit(
+ new CommandAPDU((l == blockSize) ? 0x10 : 0x00, 0x2A, 0x90, 0xA0, data.toByteArray()));
+ if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException("PSO:HASH failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+ data.reset();
+ }
+ } catch (IOException e) {
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ protected byte[] execPSO_COMPUTE_DIGITAL_SIGNATURE(CardChannel channel, byte[] hash)
+ throws CardException, SignatureCardException {
+ ResponseAPDU resp;
+ if (hash != null) {
+ resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, hash, 256));
+ } else {
+ resp = channel.transmit(
+ new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, 256));
+ }
+ if (resp.getSW() == 0x6982) {
+ throw new SecurityStatusNotSatisfiedException();
+ } else if (resp.getSW() == 0x6983) {
+ throw new LockedException();
+ } else if (resp.getSW() != 0x9000) {
+ throw new SignatureCardException(
+ "PSO: COMPUTE DIGITAL SIGNATRE failed: SW="
+ + Integer.toHexString(resp.getSW()));
+ } else {
+ return resp.getData();
+ }
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java b/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java
new file mode 100644
index 00000000..73c7faa8
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java
@@ -0,0 +1,396 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardTerminal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+
+/**
+ *
+ * @author mcentner
+ */
+public class SWCard implements SignatureCard {
+
+ private static final String BKU_USER_DIR = ".mocca";
+
+ private static final String SWCARD_DIR = "smcc";
+
+ private static final String KEYSTORE_CERTIFIED_KEYPAIR = "certified.p12";
+
+ private static final String KEYSTORE_PASSWORD_CERTIFIED_KEYPAIR = "certified.pwd";
+
+ private static final String CERTIFICATE_CERTIFIED_KEYPAIR = "certified.cer";
+
+ private static final String KEYSTORE_SECURE_KEYPAIR = "secure.p12";
+
+ private static final String KEYSTORE_PASSWORD_SECURE_KEYPAIR = "secure.pwd";
+
+ private static final String CERTIFICATE_SECURE_KEYPAIR = "secure.cer";
+
+ private static String swCardDir;
+
+ private static Log log = LogFactory.getLog(SWCard.class);
+
+ private KeyStore certifiedKeyStore;
+
+ private char[] certifiedKeyStorePassword;
+
+ private KeyStore secureKeyStore;
+
+ private char[] secureKeyStorePassword;
+
+ private Certificate certifiedCertificate;
+
+ private Certificate secureCertificate;
+
+ static {
+ String userHome = System.getProperty("user.home");
+ String fs = System.getProperty("file.separator");
+ swCardDir = userHome + fs + BKU_USER_DIR + fs + SWCARD_DIR;
+ }
+
+ /**
+ * @return the swCardDir
+ */
+ public static String getSwCardDir() {
+ return swCardDir;
+ }
+
+ /**
+ * @param swCardDir the swCardDir to set
+ */
+ public static void setSwCardDir(String swCardDir) {
+ SWCard.swCardDir = swCardDir;
+ }
+
+ public void init(Card card, CardTerminal cardTerminal) {
+ }
+
+ @Override
+ public Card getCard() {
+ return null;
+ }
+
+ private String getFileName(String fileName) {
+ String fs = System.getProperty("file.separator");
+ return swCardDir + fs + fileName;
+ }
+
+ private Certificate loadCertificate(String certificateFileName) throws SignatureCardException {
+
+ final String certificateType = "x509";
+ CertificateFactory factory;
+ try {
+ factory = CertificateFactory.getInstance(certificateType);
+ } catch (CertificateException e) {
+ String msg = "Failed to get CertificateFactory instance for type '" + certificateType + "'.";
+ log.error(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ // try to load Certificate file
+ String fileName = getFileName(certificateFileName);
+ log.info("Trying to load Certificate from file '" + fileName + "'.");
+
+ FileInputStream certificateFile;
+ try {
+ certificateFile = new FileInputStream(fileName);
+ } catch (FileNotFoundException e) {
+ String msg = "Certificate file '" + fileName + "' not found.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ Certificate certificate;
+ try {
+ certificate = factory.generateCertificate(certificateFile);
+ } catch (CertificateException e) {
+ String msg = "Failed to load Certificate from file '" + fileName + "'.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ return certificate;
+
+ }
+
+ private KeyStore loadKeyStore(String keyStoreFileName, char[] password) throws SignatureCardException {
+
+ final String keyStoreType = "pkcs12";
+ KeyStore keyStore;
+ try {
+ keyStore = KeyStore.getInstance(keyStoreType);
+ } catch (KeyStoreException e) {
+ String msg = "Failed to get KeyStore instance for KeyStore type '" + keyStoreType + "'.";
+ log.error(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ // try to load KeyStore file
+ String fileName = getFileName(keyStoreFileName);
+ log.info("Trying to load KeyStore from file '" + fileName + "'.");
+
+ FileInputStream keyStoreFile;
+ try {
+ keyStoreFile = new FileInputStream(fileName);
+ } catch (FileNotFoundException e) {
+ String msg = "KeyStore file '"+ fileName + "' not found.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ try {
+ keyStore.load(keyStoreFile, password);
+ } catch (Exception e) {
+ String msg = "Failed to load KeyStore from file '" + fileName + "'.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ return keyStore;
+
+ }
+
+ private char[] loadKeyStorePassword(String passwordFileName) throws SignatureCardException {
+
+ String fileName = getFileName(passwordFileName);
+ FileInputStream keyStorePasswordFile;
+ try {
+ keyStorePasswordFile = new FileInputStream(fileName);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+
+ try {
+ InputStreamReader reader = new InputStreamReader(keyStorePasswordFile, Charset.forName("UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ char b[] = new char[16];
+ for (int l; (l = reader.read(b)) != -1;) {
+ sb.append(b, 0, l);
+ }
+ return sb.toString().toCharArray();
+ } catch (IOException e) {
+ throw new SignatureCardException("Failed to read file '" + passwordFileName + "'.");
+ }
+
+ }
+
+ private KeyStore getKeyStore(KeyboxName keyboxName, char[] password) throws SignatureCardException {
+
+ if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) {
+ if (certifiedKeyStore == null) {
+ certifiedKeyStore = loadKeyStore(KEYSTORE_CERTIFIED_KEYPAIR, password);
+ }
+ return certifiedKeyStore;
+ } else if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ if (secureKeyStore == null) {
+ secureKeyStore = loadKeyStore(KEYSTORE_SECURE_KEYPAIR, password);
+ }
+ return secureKeyStore;
+ } else {
+ throw new SignatureCardException("Keybox of type '" + keyboxName + "' not supported.");
+ }
+
+ }
+
+ private char[] getPassword(KeyboxName keyboxName) throws SignatureCardException {
+
+ if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) {
+ if (certifiedKeyStorePassword == null) {
+ certifiedKeyStorePassword = loadKeyStorePassword(KEYSTORE_PASSWORD_CERTIFIED_KEYPAIR);
+ }
+ return certifiedKeyStorePassword;
+ } else if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ if (secureKeyStorePassword == null) {
+ secureKeyStorePassword = loadKeyStorePassword(KEYSTORE_PASSWORD_SECURE_KEYPAIR);
+ }
+ return secureKeyStorePassword;
+ } else {
+ throw new SignatureCardException("Keybox of type '" + keyboxName + "' not supported.");
+ }
+
+ }
+
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException {
+
+ try {
+ if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) {
+ if (certifiedCertificate == null) {
+ certifiedCertificate = loadCertificate(CERTIFICATE_CERTIFIED_KEYPAIR);
+ }
+ return certifiedCertificate.getEncoded();
+ } else if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) {
+ if (secureCertificate == null) {
+ secureCertificate = loadCertificate(CERTIFICATE_SECURE_KEYPAIR);
+ }
+ return secureCertificate.getEncoded();
+ } else {
+ throw new SignatureCardException("Keybox of type '" + keyboxName + "' not supported.");
+ }
+ } catch (CertificateEncodingException e) {
+ throw new SignatureCardException("Failed to get encoded Certificate.", e);
+ }
+
+
+ }
+
+ public byte[] getInfobox(String infobox, PINGUI provider, String domainId) throws SignatureCardException {
+
+ String fileName = getFileName(infobox + ".ibx");
+ FileInputStream file;
+ try {
+ file = new FileInputStream(fileName);
+ } catch (FileNotFoundException e) {
+ String msg = "Infobox '" + infobox + "' not found.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try {
+ byte[] b = new byte[512];
+ for(int l; (l = file.read(b)) != -1;) {
+ bytes.write(b, 0, l);
+ }
+ file.close();
+ } catch (IOException e) {
+ String msg = "Failed to read infobox '" + infobox + "'.";
+ log.error(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ return bytes.toByteArray();
+
+ }
+
+ @Override
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName, PINGUI provider, String alg) throws SignatureCardException, InterruptedException, IOException {
+
+ // KeyStore password
+ char[] password = getPassword(keyboxName);
+
+ if (password == null) {
+
+ PINSpec pinSpec = new PINSpec(0, -1, ".", "KeyStore-Password", (byte) 0x01, null);
+
+ password = provider.providePIN(pinSpec, -1);
+
+ if (password == null) {
+ return null;
+ }
+
+ }
+
+ KeyStore keyStore = getKeyStore(keyboxName, password);
+
+ PrivateKey privateKey = null;
+
+ try {
+ for (Enumeration<String> aliases = keyStore.aliases(); aliases
+ .hasMoreElements() && privateKey == null;) {
+ String alias = aliases.nextElement();
+ log.debug("Found alias '" + alias + "' in keystore");
+ if (keyStore.isKeyEntry(alias)) {
+ Key key = null;
+ while (key == null) {
+ try {
+ key = keyStore.getKey(alias, password);
+ } catch (UnrecoverableKeyException e) {
+ log.info("Failed to get Key from KeyStore. Wrong password?", e);
+ }
+ }
+ privateKey = (PrivateKey) key;
+ }
+ }
+ } catch (Exception e) {
+ String msg = "Failed to get certificate from KeyStore.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ if (privateKey == null) {
+ String msg = "No private key found in KeyStore.";
+ log.info(msg);
+ throw new SignatureCardException(msg);
+ }
+
+ String algorithm = privateKey.getAlgorithm();
+ algorithm = "SHA1with" + algorithm;
+ try {
+ Signature signature = Signature.getInstance(algorithm);
+ signature.initSign(privateKey);
+ int l;
+ for (byte[] b = new byte[20]; (l = input.read(b)) != -1;) {
+ signature.update(b, 0, l);
+ }
+ return signature.sign();
+ } catch (NoSuchAlgorithmException e) {
+ String msg = "Algorithm + '" + algorithm + "' not supported for signing.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ } catch (SignatureException e) {
+ String msg = "Signing faild.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ } catch (InvalidKeyException e) {
+ String msg = "Key not valid for algorithm + '" + algorithm + "'.";
+ log.info(msg, e);
+ throw new SignatureCardException(msg, e);
+ }
+
+ }
+
+ @Override
+ public void setLocale(Locale locale) {
+ }
+
+ @Override
+ public void disconnect(boolean reset) {
+ }
+
+ @Override
+ public void reset() throws SignatureCardException {
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SecurityStatusNotSatisfiedException.java b/smcc/src/main/java/at/gv/egiz/smcc/SecurityStatusNotSatisfiedException.java
new file mode 100644
index 00000000..bf0af76c
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/SecurityStatusNotSatisfiedException.java
@@ -0,0 +1,38 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class SecurityStatusNotSatisfiedException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public SecurityStatusNotSatisfiedException() {
+ }
+
+ public SecurityStatusNotSatisfiedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SecurityStatusNotSatisfiedException(String message) {
+ super(message);
+ }
+
+ public SecurityStatusNotSatisfiedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCard.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCard.java
new file mode 100644
index 00000000..fa589b84
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCard.java
@@ -0,0 +1,125 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package at.gv.egiz.smcc;
+
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardTerminal;
+
+public interface SignatureCard {
+
+ public static class KeyboxName {
+
+ public static KeyboxName SECURE_SIGNATURE_KEYPAIR = new KeyboxName(
+ "SecureSignatureKeypair");
+ public static KeyboxName CERITIFIED_KEYPAIR = new KeyboxName(
+ "CertifiedKeypair");
+
+ private String keyboxName_;
+
+ private KeyboxName(String keyboxName_) {
+ this.keyboxName_ = keyboxName_;
+ }
+
+ public static KeyboxName getKeyboxName(String keyBox) {
+ if (SECURE_SIGNATURE_KEYPAIR.equals(keyBox)) {
+ return SECURE_SIGNATURE_KEYPAIR;
+ } else if (CERITIFIED_KEYPAIR.equals(keyBox)) {
+ return CERITIFIED_KEYPAIR;
+ } else {
+ return new KeyboxName(keyBox);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof String) {
+ return obj.equals(keyboxName_);
+ }
+ if (obj instanceof KeyboxName) {
+ return ((KeyboxName) obj).keyboxName_.equals(keyboxName_);
+ } else {
+ return super.equals(obj);
+ }
+ }
+
+ public String getKeyboxName() {
+ return keyboxName_;
+ }
+
+ @Override
+ public String toString() {
+ return keyboxName_;
+ }
+
+ }
+
+ public void init(Card card, CardTerminal cardTerminal);
+
+ public Card getCard();
+
+ public byte[] getCertificate(KeyboxName keyboxName)
+ throws SignatureCardException, InterruptedException;
+
+ public void disconnect(boolean reset);
+
+ /**
+ * Performs a reset of the card.
+ *
+ * @throws SignatureCardException if reset fails.
+ */
+ public void reset() throws SignatureCardException;
+
+ /**
+ *
+ * @param infobox
+ * @param provider
+ * @param domainId may be null.
+ * @return
+ * @throws SignatureCardException
+ * @throws InterruptedException if applet is destroyed while in pin dialog
+ */
+ public byte[] getInfobox(String infobox, PINGUI pinGUI, String domainId)
+ throws SignatureCardException, InterruptedException;
+
+ /**
+ *
+ * @param input
+ * @param keyboxName
+ * @param provider
+ * @param alg TODO
+ * @return
+ * @throws at.gv.egiz.smcc.SignatureCardException
+ * @throws java.lang.InterruptedException if applet is destroyed while in pin dialog
+ * @throws IOException
+ */
+ public byte[] createSignature(InputStream input, KeyboxName keyboxName,
+ PINGUI pinGUI, String alg) throws SignatureCardException, InterruptedException, IOException;
+
+ /**
+ * Sets the local for evtl. required callbacks (e.g. PINSpec)
+ * @param locale must not be null;
+ */
+ public void setLocale(Locale locale);
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java
new file mode 100644
index 00000000..48b4646a
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java
@@ -0,0 +1,65 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package at.gv.egiz.smcc;
+
+public class SignatureCardException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new instance of this <code>SignatureCardException</code>.
+ *
+ */
+ public SignatureCardException() {
+ super();
+ }
+
+ /**
+ * Creates a new instance of this <code>SignatureCardException</code>.
+ *
+ * @param message
+ * @param cause
+ */
+ public SignatureCardException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new instance of this <code>SignatureCardException</code>.
+ *
+ * @param message
+ */
+ public SignatureCardException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new instance of this <code>SignatureCardException</code>.
+ *
+ * @param cause
+ */
+ public SignatureCardException(Throwable cause) {
+ super(cause);
+ }
+
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java
new file mode 100644
index 00000000..9165a7d8
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java
@@ -0,0 +1,401 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package at.gv.egiz.smcc;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.smartcardio.ATR;
+import javax.smartcardio.Card;
+import javax.smartcardio.CardTerminal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A factory for creating {@link SignatureCard}s from {@link Card}s.
+ */
+public class SignatureCardFactory {
+
+ public static boolean ENFORCE_RECOMMENDED_PIN_LENGTH = false;
+
+ /**
+ * This class represents a supported smart card.
+ */
+ private class SupportedCard {
+
+ /**
+ * The ATR pattern.
+ */
+ private byte[] atrPattern;
+
+ /**
+ * The ATR mask.
+ */
+ private byte[] atrMask;
+
+ /**
+ * The implementation class.
+ */
+ private String impl;
+
+ /**
+ * Creates a new SupportedCard instance with the given ATR pattern and mask
+ * und the corresponding implementation class.
+ *
+ * @param atrPattern
+ * the ATR pattern
+ * @param atrMask
+ * the ATR mask
+ * @param implementationClass
+ * the name of the implementation class
+ *
+ * @throws NullPointerException
+ * if <code>atrPattern</code> or <code>atrMask</code> is
+ * <code>null</code>.
+ * @throws IllegalArgumentException
+ * if the lengths of <code>atrPattern</code> and
+ * <code>atrMask</code> of not equal.
+ */
+ public SupportedCard(byte[] atrPattern, byte[] atrMask, String implementationClass) {
+ if (atrPattern.length != atrMask.length) {
+ throw new IllegalArgumentException("Length of 'atr' and 'mask' must be equal.");
+ }
+ this.atrPattern = atrPattern;
+ this.atrMask = atrMask;
+ this.impl = implementationClass;
+ }
+
+ /**
+ * Returns true if the given ATR matches the ATR pattern and mask this
+ * SupportedCard object.
+ *
+ * @param atr
+ * the ATR
+ *
+ * @return <code>true</code> if the given ATR matches the ATR pattern and
+ * mask of this SupportedCard object, or <code>false</code>
+ * otherwise.
+ */
+ public boolean matches(ATR atr) {
+
+ byte[] bytes = atr.getBytes();
+ if (bytes == null) {
+ return false;
+ }
+ if (bytes.length < atrMask.length) {
+ // we cannot test for equal length here, as we get ATRs with
+ // additional bytes on systems using PCSClite (e.g. linux and OS X) sometimes
+ return false;
+ }
+
+ int l = Math.min(atrMask.length, bytes.length);
+ for (int i = 0; i < l; i++) {
+ if ((bytes[i] & atrMask[i]) != atrPattern[i]) {
+ return false;
+ }
+ }
+ return true;
+
+ }
+
+ /**
+ * @return the corresponding implementation class.
+ */
+ public String getImplementationClassName() {
+ return impl;
+ }
+
+ }
+
+ /**
+ * Logging facility.
+ */
+ private static Log log = LogFactory.getLog(SignatureCardFactory.class);
+
+ /**
+ * The instance to be returned by {@link #getInstance()}.
+ */
+ private static SignatureCardFactory instance;
+
+ /**
+ * The list of supported smart cards.
+ */
+ private List<SupportedCard> supportedCards;
+
+ /**
+ * @return an instance of this SignatureCardFactory.
+ */
+ public static synchronized SignatureCardFactory getInstance() {
+ if (instance == null) {
+ instance = new SignatureCardFactory();
+ }
+ return instance;
+ }
+
+ /**
+ * Private constructor.
+ */
+ private SignatureCardFactory() {
+
+ supportedCards = new ArrayList<SupportedCard>();
+
+ // e-card
+ supportedCards.add(new SupportedCard(
+ // ATR (3b:bd:18:00:81:31:fe:45:80:51:02:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0x3b, (byte) 0xbd, (byte) 0x18, (byte) 0x00, (byte) 0x81, (byte) 0x31, (byte) 0xfe, (byte) 0x45,
+ (byte) 0x80, (byte) 0x51, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ "at.gv.egiz.smcc.STARCOSCard"));
+
+ // e-card G3
+ supportedCards.add(new SupportedCard(
+ // ATR (3b:dd:96:ff:81:b1:fe:45:1f:03:80:31:b0:52:02:03:64:04:1b:b4:22:81:05:18)
+ new byte[] {
+ (byte) 0x3b, (byte) 0xdd, (byte) 0x96, (byte) 0xff, (byte) 0x81, (byte) 0xb1, (byte) 0xfe, (byte) 0x45,
+ (byte) 0x1f, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ // mask (
+ new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ "at.gv.egiz.smcc.STARCOSCard"));
+
+ // a-sign premium (EPA)
+ supportedCards.add(new SupportedCard(
+ // ATR (3b:bf:11:00:81:31:fe:45:45:50:41:00:00:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0x3b, (byte) 0xbf, (byte) 0x11, (byte) 0x00, (byte) 0x81, (byte) 0x31, (byte) 0xfe, (byte) 0x45,
+ (byte) 0x45, (byte) 0x50, (byte) 0x41, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:00:00:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ "at.gv.egiz.smcc.ACOSCard"));
+
+ // a-sign premium (MCA)
+ supportedCards.add(new SupportedCard(
+ // ATR (3b:bf:11:00:81:31:fe:45:45:50:41:00:00:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0x3b, (byte) 0xbf, (byte) 0x11, (byte) 0x00, (byte) 0x81, (byte) 0x31, (byte) 0xfe, (byte) 0x45,
+ (byte) 0x4D, (byte) 0x43, (byte) 0x41, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:00:00:00:00:00:00:00:00:00:00:00:00:00)
+ new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ },
+ "at.gv.egiz.smcc.ACOSCard"));
+
+ // BELPIC
+ supportedCards.add(new SupportedCard(
+ // ATR (3b:98:13:40:0A:A5:03:01:01:01:AD:13:11)
+ new byte[] { (byte) 0x3b, (byte) 0x98, (byte) 0x13,
+ (byte) 0x40, (byte) 0x0a, (byte) 0xa5, (byte) 0x03,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xad,
+ (byte) 0x13, (byte) 0x11 },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff },
+ "at.gv.egiz.smcc.BELPICCard"));
+ supportedCards.add(new SupportedCard(
+ // ATR [3b:98:_94_:40:_ff_:a5:03:01:01:01:ad:13:_10_]
+ new byte[] { (byte) 0x3b, (byte) 0x98, (byte) 0x94,
+ (byte) 0x40, (byte) 0xff, (byte) 0xa5, (byte) 0x03,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xad,
+ (byte) 0x13, (byte) 0x10 },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff },
+ "at.gv.egiz.smcc.BELPICCard"));
+ supportedCards.add(new SupportedCard(
+ // ATR [3b:98:_94_:40:0a:a5:03:01:01:01:ad:13:_10_]
+ new byte[] { (byte) 0x3b, (byte) 0x98, (byte) 0x94,
+ (byte) 0x40, (byte) 0x0a, (byte) 0xa5, (byte) 0x03,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xad,
+ (byte) 0x13, (byte) 0x10 },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff },
+ "at.gv.egiz.smcc.BELPICCard"));
+ supportedCards.add(new SupportedCard(
+ // ATR [3b:98:_95_:40:0a:a5:_07_:01:01:01:ad:13:_20_]
+ new byte[] { (byte) 0x3b, (byte) 0x98, (byte) 0x95,
+ (byte) 0x40, (byte) 0x0a, (byte) 0xa5, (byte) 0x07,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0xad,
+ (byte) 0x13, (byte) 0x20 },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff },
+ "at.gv.egiz.smcc.BELPICCard"));
+
+ // ITCards
+ supportedCards.add(new SupportedCard(
+ // ATR =
+ // [3b:ff:18:00:ff:81:31:fe:55:00:6b:02:09:02:00:01:11:01:43:4e:53:11:31:80:8e]
+ new byte[] { (byte) 0x3b, (byte) 0xff, (byte) 0x18,
+ (byte) 0x00, (byte) 0xff, (byte) 0x81, (byte) 0x31,
+ (byte) 0xfe, (byte) 0x55, (byte) 0x00, (byte) 0x6b,
+ (byte) 0x02, (byte) 0x09 /*
+ * , (byte) 0x02, (byte) 0x00,
+ * (byte) 0x01, (byte) 0x11,
+ * (byte) 0x01, (byte) 0x43,
+ * (byte) 0x4e, (byte) 0x53,
+ * (byte) 0x11, (byte) 0x31,
+ * (byte) 0x80, (byte) 0x8e
+ */
+ },
+ // mask (ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff /*
+ * , (byte) 0xff, (byte) 0xff,
+ * (byte) 0xff, (byte) 0xff,
+ * (byte) 0xff, (byte) 0xff,
+ * (byte) 0xff, (byte) 0xff,
+ * (byte) 0xff, (byte) 0xff,
+ * (byte) 0xff, (byte) 0xff
+ */
+ }, "at.gv.egiz.smcc.ITCard"));
+ supportedCards.add(new SupportedCard(
+ // ATR
+ // (3B:FF:18:00:FF:C1:0A:31:FE:55:00:6B:05:08:C8:05:01:01:01:43:4E:53:10:31:80:1C)
+ new byte[] { (byte) 0x3b, (byte) 0xff, (byte) 0x18,
+ (byte) 0x00, (byte) 0xFF, (byte) 0xC1, (byte) 0x0a,
+ (byte) 0x31, (byte) 0xfe, (byte) 0x55, (byte) 0x00,
+ (byte) 0x6B, (byte) 0x05, (byte) 0x08, (byte) 0xC8,
+ (byte) 0x05, (byte) 0x01, (byte) 0x01, (byte) 0x01,
+ (byte) 0x43, (byte) 0x4E, (byte) 0x53, (byte) 0x10,
+ (byte) 0x31, (byte) 0x80, (byte) 0x1C },
+ // mask
+ // (ff:ff:ff:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:00:00:00:00)
+ new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff },
+ "at.gv.egiz.smcc.ITCard"));
+
+
+
+ }
+
+ /**
+ * Creates a SignatureCard instance with the given smart card.
+ *
+ * @param card
+ * the smart card, or <code>null</code> if a software card should be
+ * created
+ * @param cardTerminal TODO
+ *
+ * @return a SignatureCard instance
+ *
+ * @throws CardNotSupportedException
+ * if no implementation of the given <code>card</code> could be
+ * found
+ */
+ public SignatureCard createSignatureCard(Card card, CardTerminal cardTerminal)
+ throws CardNotSupportedException {
+
+ if(card == null) {
+ SignatureCard sCard = new SWCard();
+ sCard.init(card, cardTerminal);
+ return sCard;
+ }
+
+ ATR atr = card.getATR();
+ Iterator<SupportedCard> cards = supportedCards.iterator();
+ while (cards.hasNext()) {
+ SupportedCard supportedCard = cards.next();
+ if(supportedCard.matches(atr)) {
+
+ ClassLoader cl = SignatureCardFactory.class.getClassLoader();
+ SignatureCard sc;
+ try {
+ Class<?> scClass = cl.loadClass(supportedCard.getImplementationClassName());
+ sc = (SignatureCard) scClass.newInstance();
+
+ sc = ExclSignatureCardProxy.newInstance(sc);
+
+ sc.init(card, cardTerminal);
+
+ return sc;
+
+ } catch (ClassNotFoundException e) {
+ log.warn("Cannot find signature card implementation class.", e);
+ throw new CardNotSupportedException("Cannot find signature card implementation class.", e);
+ } catch (InstantiationException e) {
+ log.warn("Failed to instantiate signature card implementation.", e);
+ throw new CardNotSupportedException("Failed to instantiate signature card implementation.", e);
+ } catch (IllegalAccessException e) {
+ log.warn("Failed to instantiate signature card implementation.", e);
+ throw new CardNotSupportedException("Failed to instantiate signature card implementation.", e);
+ }
+
+ }
+ }
+
+ throw new CardNotSupportedException("Card not supported: ATR=" + toString(atr.getBytes()));
+
+ }
+
+ public static String toString(byte[] b) {
+ StringBuffer sb = new StringBuffer();
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ }
+ for(int i = 1; i < b.length; i++) {
+ sb.append(':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ return sb.toString();
+ }
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/TimeoutException.java b/smcc/src/main/java/at/gv/egiz/smcc/TimeoutException.java
new file mode 100644
index 00000000..d14a4c15
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/TimeoutException.java
@@ -0,0 +1,39 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class TimeoutException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public TimeoutException() {
+ super();
+ }
+
+ public TimeoutException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public TimeoutException(String message) {
+ super(message);
+ }
+
+ public TimeoutException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/VerificationFailedException.java b/smcc/src/main/java/at/gv/egiz/smcc/VerificationFailedException.java
new file mode 100644
index 00000000..fa066ff9
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/VerificationFailedException.java
@@ -0,0 +1,65 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class VerificationFailedException extends SignatureCardException {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final int UNKNOWN = -1;
+
+ private int retries = UNKNOWN;
+
+ public VerificationFailedException() {
+ }
+
+ public VerificationFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public VerificationFailedException(String message) {
+ super(message);
+ }
+
+ public VerificationFailedException(Throwable cause) {
+ super(cause);
+ }
+
+ public VerificationFailedException(int retries) {
+ this.retries = retries;
+ }
+
+ public VerificationFailedException(int retries, String message, Throwable cause) {
+ super(message, cause);
+ this.retries = retries;
+ }
+
+ public VerificationFailedException(int retries, String message) {
+ super(message);
+ this.retries = retries;
+ }
+
+ public VerificationFailedException(int retries, Throwable cause) {
+ super(cause);
+ this.retries = retries;
+ }
+
+ public int getRetries() {
+ return retries;
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/VerifyAPDUSpec.java b/smcc/src/main/java/at/gv/egiz/smcc/VerifyAPDUSpec.java
new file mode 100644
index 00000000..23c1f0fd
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/VerifyAPDUSpec.java
@@ -0,0 +1,200 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc;
+
+public class VerifyAPDUSpec {
+
+ public static final int PIN_JUSTIFICATION_LEFT = 0;
+
+ public static final int PIN_JUSTIFICATION_RIGHT = 1;
+
+ public static final int PIN_FORMAT_BINARY = 0;
+
+ public static final int PIN_FORMAT_BCD = 1;
+
+ public static final int PIN_FORMAT_ASCII = 2;
+
+ /**
+ * The APDU template.
+ */
+ protected byte[] apdu;
+
+ /**
+ * The PIN position in bytes.
+ */
+ protected int pinPosition;
+
+ /**
+ * The PIN justification (either {@link #PIN_JUSTIFICATION_LEFT} or
+ * {@link #PIN_JUSTIFICATION_RIGHT}).
+ */
+ protected int pinJustification = PIN_JUSTIFICATION_LEFT;
+
+ /**
+ * The PIN encoding format (one of {@value #PIN_FORMAT_BCD},
+ * {@link #PIN_FORMAT_ASCII}).
+ */
+ protected int pinFormat;
+
+ /**
+ * The size of the PIN length in bits or 0 for no PIN length. (Default: 0)
+ */
+ protected int pinLengthSize = 0;
+
+ /**
+ * The PIN length in the template in bytes.
+ */
+ protected int pinLength;
+
+ /**
+ * The PIN length position in the template in bits or 0 for no PIN length.
+ * (Default: 0)
+ */
+ protected int pinLengthPos = 0;
+
+ /**
+ * @param apdu
+ * @param pinPosition
+ * @param pinFormat
+ * @param pinLength TODO
+ */
+ public VerifyAPDUSpec(byte[] apdu, int pinPosition, int pinFormat, int pinLength) {
+ super();
+ this.apdu = apdu;
+ this.pinPosition = pinPosition;
+ this.pinFormat = pinFormat;
+ this.pinLength = pinLength;
+ }
+
+ /**
+ * @param apdu
+ * @param pinPosition
+ * @param pinFormat
+ * @param pinLength
+ * @param pinLengthSize
+ * @param pinLengthPos
+ */
+ public VerifyAPDUSpec(byte[] apdu, int pinPosition, int pinFormat,
+ int pinLength, int pinLengthSize, int pinLengthPos) {
+ super();
+ this.apdu = apdu;
+ this.pinPosition = pinPosition;
+ this.pinFormat = pinFormat;
+ this.pinLength = pinLength;
+ this.pinLengthSize = pinLengthSize;
+ this.pinLengthPos = pinLengthPos;
+ }
+
+ /**
+ * @return the apdu
+ */
+ public byte[] getApdu() {
+ return apdu;
+ }
+
+ /**
+ * @param apdu the apdu to set
+ */
+ public void setApdu(byte[] apdu) {
+ this.apdu = apdu;
+ }
+
+ /**
+ * @return the pinPosition
+ */
+ public int getPinPosition() {
+ return pinPosition;
+ }
+
+ /**
+ * @param pinPosition the pinPosition to set
+ */
+ public void setPinPosition(int pinPosition) {
+ this.pinPosition = pinPosition;
+ }
+
+ /**
+ * @return the pinJustification
+ */
+ public int getPinJustification() {
+ return pinJustification;
+ }
+
+ /**
+ * @param pinJustification the pinJustification to set
+ */
+ public void setPinJustification(int pinJustification) {
+ this.pinJustification = pinJustification;
+ }
+
+ /**
+ * @return the pinFormat
+ */
+ public int getPinFormat() {
+ return pinFormat;
+ }
+
+ /**
+ * @param pinFormat the pinFormat to set
+ */
+ public void setPinFormat(int pinFormat) {
+ this.pinFormat = pinFormat;
+ }
+
+ /**
+ * @return the pinLengthSize
+ */
+ public int getPinLengthSize() {
+ return pinLengthSize;
+ }
+
+ /**
+ * @param pinLengthSize the pinLengthSize to set
+ */
+ public void setPinLengthSize(int pinLengthSize) {
+ this.pinLengthSize = pinLengthSize;
+ }
+
+ /**
+ * @return the pinLength
+ */
+ public int getPinLength() {
+ return pinLength;
+ }
+
+ /**
+ * @param pinLength the pinLength to set
+ */
+ public void setPinLength(int pinLength) {
+ this.pinLength = pinLength;
+ }
+
+ /**
+ * @return the pinLengthPos
+ */
+ public int getPinLengthPos() {
+ return pinLengthPos;
+ }
+
+ /**
+ * @param pinLengthPos the pinLengthPos to set
+ */
+ public void setPinLengthPos(int pinLengthPos) {
+ this.pinLengthPos = pinLengthPos;
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/conf/SMCCConfiguration.java b/smcc/src/main/java/at/gv/egiz/smcc/conf/SMCCConfiguration.java
new file mode 100644
index 00000000..136ca283
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/conf/SMCCConfiguration.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package at.gv.egiz.smcc.conf;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class SMCCConfiguration {
+
+ private static final long serialVersionUID = 1L;
+
+ boolean disablePinpad;
+
+ /**
+ * @return the disablePinpad
+ */
+ public boolean isDisablePinpad() {
+ return disablePinpad;
+ }
+
+ /**
+ * @param disablePinpad the disablePinpad to set
+ */
+ public void setDisablePinpad(boolean disablePinpad) {
+ this.disablePinpad = disablePinpad;
+ }
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINGUI.java b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINGUI.java
new file mode 100644
index 00000000..00dc2d0e
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINGUI.java
@@ -0,0 +1,36 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.pin.gui;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.PINSpec;
+
+
+public interface ModifyPINGUI extends ModifyPINProvider {
+
+ void modifyPINDirect(PINSpec spec, int retries) throws CancelledException, InterruptedException;
+ void finishDirect();
+
+ void enterCurrentPIN(PINSpec spec, int retries);
+ void enterNewPIN(PINSpec spec);
+ void confirmNewPIN(PINSpec spec);
+ void validKeyPressed();
+ void correctionButtonPressed();
+ void allKeysCleared();
+ /** called prior to MODIFY_PIN_FINISH control command transmission (clear display or display wait message) */
+ void finish();
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINProvider.java b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINProvider.java
new file mode 100644
index 00000000..36f0097d
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/ModifyPINProvider.java
@@ -0,0 +1,48 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.pin.gui;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.PINSpec;
+
+
+/**
+ * user interface for "software pin-entry" of
+ * <ul>
+ * <li> current pin and new pin (change pin)
+ * <li> new pin (pin activation, no current pin)
+ * <li> puk and new pin (probably verify only?)
+ * </ul>
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public interface ModifyPINProvider {
+
+ /**
+ *
+ * @param spec
+ * @param retries
+ * @return null if no old value for this pin
+ * @throws at.gv.egiz.smcc.CancelledException if cancelled by user
+ * @throws java.lang.InterruptedException
+ */
+ public char[] provideCurrentPIN(PINSpec spec, int retries)
+ throws CancelledException, InterruptedException;
+
+ public char[] provideNewPIN(PINSpec spec)
+ throws CancelledException, InterruptedException;
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINGUI.java b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINGUI.java
new file mode 100644
index 00000000..5199977b
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINGUI.java
@@ -0,0 +1,42 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.pin.gui;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.PINSpec;
+
+
+/**
+ * Display messages for pinpad pin-entry.
+ * Provides an interface for two types of pinpad pin-entry: pinpad-direct and pinpad-start/finish
+ * @author clemens.orthacker@iaik.tugraz.at
+ */
+public interface PINGUI extends PINProvider {
+
+ void enterPINDirect(PINSpec spec, int retries)
+ throws CancelledException, InterruptedException;
+
+ /**
+ * @throws CancelledException, InterruptedException if signature-data dialog is interrupted or cancelled
+ */
+ void enterPIN(PINSpec spec, int retries)
+ throws CancelledException, InterruptedException;
+ void validKeyPressed();
+ void correctionButtonPressed();
+ void allKeysCleared();
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINProvider.java b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINProvider.java
new file mode 100644
index 00000000..7443ee30
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/pin/gui/PINProvider.java
@@ -0,0 +1,51 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.pin.gui;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.PINSpec;
+
+
+/**
+ * The number of retries is not fixed and there is no way (?) to obtain this value.
+ * A PINProvider should therefore maintain an internal retry counter or flag
+ * to decide whether or not to warn the user (num retries passed in providePIN).
+ *
+ * Therefore PINProvider objects should not be reused.
+ *
+ * (ACOS: reload counter: between 0 and 15, where 15 meens deactivated)
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public interface PINProvider {
+
+ /**
+ * TODO change interface to void providePIN(char[] pin, pinspec, retries)
+ * to allow caller to clear pin afterwards.
+ *
+ * @param spec
+ * @param retries num of remaining retries or -1 if unknown
+ * (a positive value does <em>not</em> necessarily signify that there was
+ * already an unsuccessful PIN verification)
+ * @return pin != null
+ * @throws at.gv.egiz.smcc.CancelledException
+ * @throws java.lang.InterruptedException
+ */
+ char[] providePIN(PINSpec pinSpec, int retries)
+ throws CancelledException, InterruptedException;
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/reader/CardReader.java b/smcc/src/main/java/at/gv/egiz/smcc/reader/CardReader.java
new file mode 100644
index 00000000..a1246dd6
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/reader/CardReader.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package at.gv.egiz.smcc.reader;
+
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.ResponseAPDU;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.ChangeReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.NewReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.PINSpec;
+import at.gv.egiz.smcc.ResetRetryCounterAPDUSpec;
+import at.gv.egiz.smcc.SignatureCardException;
+import at.gv.egiz.smcc.VerifyAPDUSpec;
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import javax.smartcardio.Card;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public interface CardReader {
+
+
+ String[] FEATURES = new String[]{"NO_FEATURE",
+ "FEATURE_VERIFY_PIN_START",
+ "FEATURE_VERIFY_PIN_FINISH",
+ "FEATURE_MODIFY_PIN_START",
+ "FEATURE_MODIFY_PIN_FINISH",
+ "FEATURE_GET_KEY_PRESSED",
+ "FEATURE_VERIFY_PIN_DIRECT",
+ "FEATURE_MODIFY_PIN_DIRECT",
+ "FEATURE_MCT_READER_DIRECT",
+ "FEATURE_MCT_UNIVERSAL",
+ "FEATURE_IFD_PIN_PROPERTIES",
+ "FEATURE_ABORT",
+ "FEATURE_SET_SPE_MESSAGE",
+ "FEATURE_VERIFY_PIN_DIRECT_APP_ID",
+ "FEATURE_MODIFY_PIN_DIRECT_APP_ID",
+ "FEATURE_WRITE_DISPLAY",
+ "FEATURE_GET_KEY",
+ "FEATURE_IFD_DISPLAY_PROPERTIES"};
+
+ Byte FEATURE_VERIFY_PIN_START = new Byte((byte) 0x01);
+ Byte FEATURE_VERIFY_PIN_FINISH = new Byte((byte) 0x02);
+ Byte FEATURE_MODIFY_PIN_START = new Byte((byte) 0x03);
+ Byte FEATURE_MODIFY_PIN_FINISH = new Byte((byte) 0x04);
+ Byte FEATURE_GET_KEY_PRESSED = new Byte((byte) 0x05);
+ Byte FEATURE_VERIFY_PIN_DIRECT = new Byte((byte) 0x06);
+ Byte FEATURE_MODIFY_PIN_DIRECT = new Byte((byte) 0x07);
+ Byte FEATURE_MCT_READER_DIRECT = new Byte((byte) 0x08);
+ Byte FEATURE_MCT_UNIVERSAL = new Byte((byte) 0x09);
+ Byte FEATURE_IFD_PIN_PROPERTIES = new Byte((byte) 0x0a);
+ //TODO continue list
+
+
+ Card connect() throws CardException;
+
+ boolean hasFeature(Byte feature);
+
+ ResponseAPDU verify(CardChannel channel, VerifyAPDUSpec apduSpec,
+ PINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws CancelledException, InterruptedException, CardException, SignatureCardException;
+
+ ResponseAPDU modify(CardChannel channel, ChangeReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws CancelledException, InterruptedException, CardException, SignatureCardException;
+
+ ResponseAPDU modify(CardChannel channel, NewReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec)
+ throws CancelledException, InterruptedException, CardException, SignatureCardException;
+
+ ResponseAPDU modify(CardChannel channel, ResetRetryCounterAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws CancelledException, InterruptedException, CardException, SignatureCardException;
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/reader/DefaultCardReader.java b/smcc/src/main/java/at/gv/egiz/smcc/reader/DefaultCardReader.java
new file mode 100644
index 00000000..03a794fe
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/reader/DefaultCardReader.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package at.gv.egiz.smcc.reader;
+
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.ChangeReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.NewReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.PINSpec;
+import at.gv.egiz.smcc.ResetRetryCounterAPDUSpec;
+import at.gv.egiz.smcc.SignatureCardException;
+import at.gv.egiz.smcc.VerifyAPDUSpec;
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import at.gv.egiz.smcc.util.ISO7816Utils;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class DefaultCardReader implements CardReader {
+
+ protected final static Log log = LogFactory.getLog(DefaultCardReader.class);
+
+ protected CardTerminal ct;
+ protected String name;
+
+ public DefaultCardReader(CardTerminal ct) {
+ if (ct == null) {
+ throw new NullPointerException("no card or card terminal provided");
+ }
+ this.ct = ct;
+ this.name = ct.getName();
+ }
+
+ @Override
+ public ResponseAPDU verify(CardChannel channel, VerifyAPDUSpec apduSpec,
+ PINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws SignatureCardException, CardException, InterruptedException {
+
+ log.debug("VERIFY");
+ return channel.transmit(ISO7816Utils.createVerifyAPDU(apduSpec, pinGUI.providePIN(pinSpec, retries)));
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, ChangeReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws SignatureCardException, CardException, InterruptedException {
+ log.debug("MODIFY (CHANGE_REFERENCE_DATA)");
+ char[] oldPIN = pinGUI.provideCurrentPIN(pinSpec, retries);
+ char[] newPIN = pinGUI.provideNewPIN(pinSpec);
+ return channel.transmit(ISO7816Utils.createChangeReferenceDataAPDU(apduSpec, oldPIN, newPIN));
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, NewReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec)
+ throws SignatureCardException, CardException, InterruptedException {
+ log.debug("MODIFY (NEW_REFERENCE_DATA)");
+ char[] newPIN = pinGUI.provideNewPIN(pinSpec);
+ return channel.transmit(ISO7816Utils.createNewReferenceDataAPDU(apduSpec, newPIN));
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, ResetRetryCounterAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws InterruptedException, CardException, SignatureCardException {
+ log.debug("MODIFY (RESET_RETRY_COUNTER)");
+ //TODO
+ return modify(channel, (ChangeReferenceDataAPDUSpec) apduSpec, pinGUI, pinSpec, retries);
+ }
+
+ @Override
+ public Card connect() throws CardException {
+ log.debug("connect icc");
+ return ct.connect("*");
+ }
+
+ @Override
+ public boolean hasFeature(Byte feature) {
+ return false;
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/reader/PinpadCardReader.java b/smcc/src/main/java/at/gv/egiz/smcc/reader/PinpadCardReader.java
new file mode 100644
index 00000000..c2537af8
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/reader/PinpadCardReader.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package at.gv.egiz.smcc.reader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.ResponseAPDU;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.CancelledException;
+import at.gv.egiz.smcc.ChangeReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.NewReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.PINConfirmationException;
+import at.gv.egiz.smcc.PINFormatException;
+import at.gv.egiz.smcc.PINOperationAbortedException;
+import at.gv.egiz.smcc.PINSpec;
+import at.gv.egiz.smcc.ResetRetryCounterAPDUSpec;
+import at.gv.egiz.smcc.SignatureCardException;
+import at.gv.egiz.smcc.TimeoutException;
+import at.gv.egiz.smcc.VerifyAPDUSpec;
+import at.gv.egiz.smcc.pin.gui.ModifyPINGUI;
+import at.gv.egiz.smcc.pin.gui.PINGUI;
+import at.gv.egiz.smcc.util.SMCCHelper;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class PinpadCardReader extends DefaultCardReader {
+
+ public static final int PIN_ENTRY_POLLING_INTERVAL = 10;
+
+ protected final static Log log = LogFactory.getLog(PinpadCardReader.class);
+
+ protected byte bEntryValidationCondition = 0x02; // validation key pressed
+ protected byte bTimeOut = 0x3c; // 60sec (= max on ReinerSCT)
+ protected byte bTimeOut2 = 0x00; // default (attention with SCM)
+ protected byte wPINMaxExtraDigitH = 0x00; // min pin length zero digits
+ protected byte wPINMaxExtraDigitL = 0x0c; // max pin length 12 digits
+
+ /**
+ * supported features and respective control codes
+ */
+ protected Map<Byte, Integer> features;
+ protected boolean VERIFY, MODIFY, VERIFY_DIRECT, MODIFY_DIRECT;
+
+ public PinpadCardReader(CardTerminal ct, Map<Byte, Integer> features) {
+ super(ct);
+ if (features == null) {
+ throw new NullPointerException("Pinpad card reader does not support any features");
+ }
+ this.features = features;
+
+ if (features.containsKey(FEATURE_VERIFY_PIN_START) &&
+ features.containsKey(FEATURE_GET_KEY_PRESSED) &&
+ features.containsKey(FEATURE_VERIFY_PIN_FINISH)) {
+ VERIFY = true;
+ }
+ if (features.containsKey(FEATURE_MODIFY_PIN_START) &&
+ features.containsKey(FEATURE_GET_KEY_PRESSED) &&
+ features.containsKey(FEATURE_MODIFY_PIN_FINISH)) {
+ MODIFY = true;
+ }
+ if (features.containsKey(FEATURE_VERIFY_PIN_DIRECT)) {
+ VERIFY_DIRECT = true;
+ }
+ if (features.containsKey(FEATURE_MODIFY_PIN_DIRECT)) {
+ MODIFY_DIRECT = true;
+ }
+
+ if (name != null) {
+ name = name.toLowerCase();
+ //ReinerSCT: http://support.reiner-sct.de/downloads/LINUX
+ // http://www.linux-club.de/viewtopic.php?f=61&t=101287&start=0
+ //old: REINER SCT CyberJack 00 00
+ //new (CCID): 0C4B/0300 Reiner-SCT cyberJack pinpad(a) 00 00
+ //Snow Leopard: Reiner-SCT cyberJack pinpad(a) 00 00
+ //display: REINER SCT CyberJack 00 00
+ if(name.startsWith("gemplus gempc pinpad") || name.startsWith("gemalto gempc pinpad")) {
+ log.debug("setting custom wPINMaxExtraDigitH (0x04) for " + name);
+ wPINMaxExtraDigitH = 0x04;
+ log.debug("setting custom wPINMaxExtraDigitL (0x08) for " + name);
+ wPINMaxExtraDigitL = 0x08;
+ } else if (name.startsWith("omnikey cardman 3621")) {
+ log.debug("setting custom wPINMaxExtraDigitH (0x01) for " + name);
+ wPINMaxExtraDigitH = 0x01;
+ } else if (name.startsWith("scm spr 532") || name.startsWith("scm microsystems inc. sprx32 usb smart card reader")) {
+ log.debug("setting custom bTimeOut (0x3c) for " + name);
+ bTimeOut = 0x3c;
+ log.debug("setting custom bTimeOut2 (0x0f) for " + name);
+ bTimeOut2 = 0x0f;
+ } else if (name.startsWith("cherry smartboard xx44")) {
+ log.debug("setting custom wPINMaxExtraDigitH (0x01) for " + name);
+ wPINMaxExtraDigitH = 0x01;
+ }
+ }
+
+ }
+
+ @Override
+ public boolean hasFeature(Byte feature) {
+ return features.containsKey(feature);
+ }
+
+ private void VERIFY_PIN_START(Card icc, byte[] PIN_VERIFY) throws CardException {
+ int ioctl = features.get(FEATURE_VERIFY_PIN_START);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_START (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_VERIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY);
+ if (resp != null && resp.length > 0) {
+ if (resp[0] == (byte) 0x57) {
+ log.error("Invalid parameter in PIN_VERIFY structure");
+ throw new CardException("ERROR_INVALID_PARAMETER");
+ } else {
+ log.error("unexpected response to VERIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to VERIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ }
+ }
+ }
+
+ private byte GET_KEY_PRESSED(Card icc) throws CardException {
+ int ioctl = features.get(FEATURE_GET_KEY_PRESSED);
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 1) {
+// if (log.isTraceEnabled()) {
+// log.trace("response " + SMCCHelper.toString(resp));
+// }
+ return resp[0];
+ }
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ SMCCHelper.toString(resp));
+ }
+
+ private byte[] VERIFY_PIN_FINISH(Card icc) throws CardException {
+ int ioctl = features.get(FEATURE_VERIFY_PIN_FINISH);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")");
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 2) {
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+ log.error("unexpected response to VERIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to VERIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ }
+
+ private void MODIFY_PIN_START(Card icc, byte[] PIN_MODIFY) throws CardException {
+ int ioctl = features.get(FEATURE_MODIFY_PIN_START);
+ if (log.isTraceEnabled()) {
+ log.trace("MODFIY_PIN_START (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_MODIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY);
+ if (resp != null && resp.length > 0) {
+ if (resp[0] == (byte) 0x57) {
+ log.error("Invalid parameter in PIN_MODIFY structure");
+ throw new CardException("ERROR_INVALID_PARAMETER");
+ } else {
+ log.error("unexpected response to MODIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to MODIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ }
+ }
+ }
+
+ private byte[] MODIFY_PIN_FINISH(Card icc) throws CardException {
+ int ioctl = features.get(FEATURE_MODIFY_PIN_FINISH);
+ if (log.isTraceEnabled()) {
+ log.trace("MODIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")");
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 2) {
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+ log.error("unexpected response to MODIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to MODIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ }
+
+ private byte[] VERIFY_PIN_DIRECT(Card icc, byte[] PIN_VERIFY) throws CardException {
+ int ioctl = features.get(FEATURE_VERIFY_PIN_DIRECT);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_VERIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY);
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+
+ private byte[] verifyPin(Card icc, byte[] PIN_VERIFY, PINGUI pinGUI)
+ throws SignatureCardException, CardException, InterruptedException {
+
+// pinGUI.enterPIN(pinSpec, retries);
+
+ log.debug("VERIFY_PIN_START [" + FEATURES[FEATURE_VERIFY_PIN_START] + "]");
+ VERIFY_PIN_START(icc, PIN_VERIFY);
+
+ byte resp;
+ do {
+ resp = GET_KEY_PRESSED(icc);
+ if (resp == (byte) 0x00) {
+ synchronized(this) {
+ try {
+ wait(PIN_ENTRY_POLLING_INTERVAL);
+ } catch (InterruptedException ex) {
+ log.error("interrupted in VERIFY_PIN");
+ }
+ }
+ } else if (resp == (byte) 0x0d) {
+ log.debug("GET_KEY_PRESSED: 0x0d (user confirmed)");
+ break;
+ } else if (resp == (byte) 0x2b) {
+ log.trace("GET_KEY_PRESSED: 0x2b (user entered valid key 0-9)");
+ pinGUI.validKeyPressed();
+ } else if (resp == (byte) 0x1b) {
+ log.debug("GET_KEY_PRESSED: 0x1b (user cancelled VERIFY_PIN via cancel button)");
+ break; // returns 0x6401
+ } else if (resp == (byte) 0x08) {
+ log.debug("GET_KEY_PRESSED: 0x08 (user pressed correction/backspace button)");
+ pinGUI.correctionButtonPressed();
+ } else if (resp == (byte) 0x0e) {
+ log.debug("GET_KEY_PRESSED: 0x0e (timeout occured)");
+ break; // return 0x6400
+ } else if (resp == (byte) 0x40) {
+ log.debug("GET_KEY_PRESSED: 0x40 (PIN_Operation_Aborted)");
+ throw new PINOperationAbortedException("PIN_Operation_Aborted (0x40)");
+ } else if (resp == (byte) 0x0a) {
+ log.debug("GET_KEY_PRESSED: 0x0a (all keys cleared");
+ pinGUI.allKeysCleared();
+ } else {
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ }
+ } while (true);
+
+ return VERIFY_PIN_FINISH(icc);
+ }
+
+ /**
+ * does not display the first pin dialog (enterCurrentPIN or enterNewPIN, depends on bConfirmPIN),
+ * since this is easier to do in calling modify()
+ */
+ private byte[] modifyPin(Card icc, byte[] PIN_MODIFY, ModifyPINGUI pinGUI, PINSpec pINSpec)
+ throws PINOperationAbortedException, CardException {
+
+ byte pinConfirmations = (byte) 0x00; //b0: new pin not entered (0) / entered (1)
+ //b1: current pin not entered (0) / entered (1)
+ byte bConfirmPIN = PIN_MODIFY[9];
+
+// if ((bConfirmPIN & (byte) 0x02) == 0) {
+// log.debug("no current PIN entry requested");
+// pinGUI.enterNewPIN(pINSpec);
+// } else {
+// log.debug("current PIN entry requested");
+// pinGUI.enterCurrentPIN(pINSpec, retries);
+// }
+
+ log.debug("MODIFY_PIN_START [" + FEATURES[FEATURE_MODIFY_PIN_START] + "]");
+ MODIFY_PIN_START(icc, PIN_MODIFY);
+
+ byte resp;
+ while (true) {
+ resp = GET_KEY_PRESSED(icc);
+ if (resp == (byte) 0x00) {
+ synchronized(this) {
+ try {
+ wait(PIN_ENTRY_POLLING_INTERVAL);
+ } catch (InterruptedException ex) {
+ log.error("interrupted in MODIFY_PIN");
+ }
+ }
+ } else if (resp == (byte) 0x0d) {
+ if (log.isTraceEnabled()) {
+ log.trace("requested pin confirmations: 0b" + Integer.toBinaryString(bConfirmPIN & 0xff));
+ log.trace("performed pin confirmations: 0b" + Integer.toBinaryString(pinConfirmations & 0xff));
+ }
+ log.debug("GET_KEY_PRESSED: 0x0d (user confirmed)");
+ if (pinConfirmations == bConfirmPIN) {
+ break;
+ } else if ((bConfirmPIN & (byte) 0x02) == 0 ||
+ (pinConfirmations & (byte) 0x02) == (byte) 0x02) {
+ // no current pin entry or current pin entry already performed
+ if ((pinConfirmations & (byte) 0x01) == 0) {
+ // new pin
+ pinConfirmations |= (byte) 0x01;
+ pinGUI.confirmNewPIN(pINSpec);
+ } // else: new pin confirmed
+ } else {
+ // current pin entry
+ pinConfirmations |= (byte) 0x02;
+ pinGUI.enterNewPIN(pINSpec);
+ }
+ } else if (resp == (byte) 0x2b) {
+ log.trace("GET_KEY_PRESSED: 0x2b (user entered valid key 0-9)");
+ pinGUI.validKeyPressed();
+ } else if (resp == (byte) 0x1b) {
+ log.debug("GET_KEY_PRESSED: 0x1b (user cancelled VERIFY_PIN via cancel button)");
+ break; // returns 0x6401
+ } else if (resp == (byte) 0x08) {
+ log.debug("GET_KEY_PRESSED: 0x08 (user pressed correction/backspace button)");
+ pinGUI.correctionButtonPressed();
+ } else if (resp == (byte) 0x0e) {
+ log.debug("GET_KEY_PRESSED: 0x0e (timeout occured)");
+ break; // return 0x6400
+ } else if (resp == (byte) 0x40) {
+ log.debug("GET_KEY_PRESSED: 0x40 (PIN_Operation_Aborted)");
+ throw new PINOperationAbortedException("PIN_Operation_Aborted (0x40)");
+ } else if (resp == (byte) 0x0a) {
+ log.debug("GET_KEY_PRESSED: 0x0a (all keys cleared");
+ pinGUI.allKeysCleared();
+ } else {
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ }
+
+ }
+
+ pinGUI.finish();
+ return MODIFY_PIN_FINISH(icc);
+ }
+
+ private byte[] MODIFY_PIN_DIRECT(Card icc, byte[] PIN_MODIFY) throws CardException {
+ int ioctl = features.get(FEATURE_MODIFY_PIN_DIRECT);
+ if (log.isTraceEnabled()) {
+ log.trace("MODIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_MODIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY);
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+
+ protected byte[] createPINModifyStructure(NewReferenceDataAPDUSpec apduSpec, PINSpec pinSpec) {
+
+ ByteArrayOutputStream s = new ByteArrayOutputStream();
+ // bTimeOut
+ s.write(bTimeOut);
+ // bTimeOut2
+ s.write(bTimeOut2);
+ // bmFormatString
+ s.write(1 << 7 // system unit = byte
+ | (0xF & apduSpec.getPinPosition()) << 3
+ | (0x1 & apduSpec.getPinJustification() << 2)
+ | (0x3 & apduSpec.getPinFormat()));
+ // bmPINBlockString
+ s.write((0xF & apduSpec.getPinLengthSize()) << 4
+ | (0xF & apduSpec.getPinLength()));
+ // bmPINLengthFormat
+ s.write(// system unit = bit
+ (0xF & apduSpec.getPinLengthPos()));
+ // bInsertionOffsetOld
+ s.write(0x00);
+ // bInsertionOffsetNew
+ s.write(apduSpec.getPinInsertionOffsetNew());
+ // wPINMaxExtraDigit
+ s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL));
+ s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH));
+ // bConfirmPIN
+ s.write(0x01);
+ // bEntryValidationCondition
+ s.write(bEntryValidationCondition);
+ // bNumberMessage
+ s.write(0x02);
+ // wLangId English (United States), see http://www.usb.org/developers/docs/USB_LANGIDs.pdf
+ s.write(0x09);
+ s.write(0x04);
+ // bMsgIndex1
+ s.write(0x01);
+ // bMsgIndex2
+ s.write(0x02);
+ // bMsgIndex3
+ s.write(0x00);
+
+ // bTeoPrologue
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // ulDataLength
+ s.write(apduSpec.getApdu().length);
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // abData
+ try {
+ s.write(apduSpec.getApdu());
+ } catch (IOException e) {
+ // As we are dealing with ByteArrayOutputStreams no exception is to be
+ // expected.
+ throw new RuntimeException(e);
+ }
+
+ return s.toByteArray();
+
+ }
+
+ protected byte[] createPINModifyStructure(ChangeReferenceDataAPDUSpec apduSpec, PINSpec pinSpec) {
+ //TODO bInsertionOffsetOld (0x00), bConfirmPIN (0x01), bNumberMessage (0x02), bMsgIndex1/2/3
+
+ ByteArrayOutputStream s = new ByteArrayOutputStream();
+ // bTimeOut
+ s.write(bTimeOut);
+ // bTimeOut2
+ s.write(bTimeOut2);
+ // bmFormatString
+ s.write(1 << 7 // system unit = byte
+ | (0xF & apduSpec.getPinPosition()) << 3
+ | (0x1 & apduSpec.getPinJustification() << 2)
+ | (0x3 & apduSpec.getPinFormat()));
+ // bmPINBlockString
+ s.write((0xF & apduSpec.getPinLengthSize()) << 4
+ | (0xF & apduSpec.getPinLength()));
+ // bmPINLengthFormat
+ s.write(// system unit = bit
+ (0xF & apduSpec.getPinLengthPos()));
+ // bInsertionOffsetOld (0x00 for no old pin?)
+ s.write(apduSpec.getPinInsertionOffsetOld());
+ // bInsertionOffsetNew
+ s.write(apduSpec.getPinInsertionOffsetNew());
+ // wPINMaxExtraDigit
+ s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL));
+ s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH));
+ // bConfirmPIN
+ s.write(0x03);
+ // bEntryValidationCondition
+ s.write(bEntryValidationCondition);
+ // bNumberMessage
+ s.write(0x03);
+ // wLangId English (United States), see http://www.usb.org/developers/docs/USB_LANGIDs.pdf
+ s.write(0x09);
+ s.write(0x04);
+ // bMsgIndex1
+ s.write(0x00);
+ // bMsgIndex2
+ s.write(0x01);
+ // bMsgIndex3
+ s.write(0x02);
+
+ // bTeoPrologue
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // ulDataLength
+ s.write(apduSpec.getApdu().length);
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // abData
+ try {
+ s.write(apduSpec.getApdu());
+ } catch (IOException e) {
+ // As we are dealing with ByteArrayOutputStreams no exception is to be
+ // expected.
+ throw new RuntimeException(e);
+ }
+
+ return s.toByteArray();
+
+ }
+
+ protected byte[] createPINVerifyStructure(VerifyAPDUSpec apduSpec, PINSpec pinSpec) {
+
+ ByteArrayOutputStream s = new ByteArrayOutputStream();
+ // bTimeOut
+ s.write(bTimeOut);
+ // bTimeOut2
+ s.write(bTimeOut2);
+ // bmFormatString
+ s.write(1 << 7 // system unit = byte
+ | (0xF & apduSpec.getPinPosition()) << 3
+ | (0x1 & apduSpec.getPinJustification() << 2)
+ | (0x3 & apduSpec.getPinFormat()));
+ // bmPINBlockString
+ s.write((0xF & apduSpec.getPinLengthSize()) << 4
+ | (0xF & apduSpec.getPinLength()));
+ // bmPINLengthFormat
+ s.write(// system unit = bit
+ (0xF & apduSpec.getPinLengthPos()));
+ // wPINMaxExtraDigit
+ s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL)); // max PIN length
+ s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH)); // min PIN length
+ // bEntryValidationCondition
+ s.write(bEntryValidationCondition);
+ // bNumberMessage
+ s.write(0x01);
+ // wLangId
+ s.write(0x09);
+ s.write(0x04);
+ // bMsgIndex
+ s.write(0x00);
+ // bTeoPrologue
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // ulDataLength
+ s.write(apduSpec.getApdu().length);
+ s.write(0x00);
+ s.write(0x00);
+ s.write(0x00);
+ // abData
+ try {
+ s.write(apduSpec.getApdu());
+ } catch (IOException e) {
+ // As we are dealing with ByteArrayOutputStreams no exception is to be
+ // expected.
+ throw new RuntimeException(e);
+ }
+
+ return s.toByteArray();
+
+ }
+
+ @Override
+ public ResponseAPDU verify(CardChannel channel, VerifyAPDUSpec apduSpec,
+ PINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws SignatureCardException, CardException, InterruptedException {
+
+ ResponseAPDU resp = null;
+
+ byte[] s = createPINVerifyStructure(apduSpec, pinSpec);
+ Card icc = channel.getCard();
+
+ if (VERIFY) {
+ pinGUI.enterPIN(pinSpec, retries);
+ resp = new ResponseAPDU(verifyPin(icc, s, pinGUI));
+ } else if (VERIFY_DIRECT) {
+ pinGUI.enterPINDirect(pinSpec, retries);
+ log.debug("VERIFY_PIN_DIRECT [" + FEATURES[FEATURE_VERIFY_PIN_DIRECT] + "]");
+ resp = new ResponseAPDU(VERIFY_PIN_DIRECT(icc, s));
+ } else {
+ log.warn("falling back to default pin-entry");
+ return super.verify(channel, apduSpec, pinGUI, pinSpec, retries);
+ }
+
+ switch (resp.getSW()) {
+ case 0x6400:
+ log.debug("SPE operation timed out.");
+ throw new TimeoutException();
+ case 0x6401:
+ log.debug("SPE operation was cancelled by the 'Cancel' button.");
+ throw new CancelledException();
+ case 0x6403:
+ log.debug("User entered too short or too long PIN "
+ + "regarding MIN/MAX PIN length.");
+ throw new PINFormatException();
+ case 0x6480:
+ log.debug("SPE operation was aborted by the 'Cancel' operation "
+ + "at the host system.");
+ case 0x6b80:
+ log.info("Invalid parameter in passed structure.");
+ default:
+ return resp;
+ }
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, ChangeReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws SignatureCardException, CardException, InterruptedException {
+
+ ResponseAPDU resp = null;
+
+ byte[] s = createPINModifyStructure(apduSpec, pinSpec);
+ Card icc = channel.getCard();
+
+ if (MODIFY) {
+ pinGUI.enterCurrentPIN(pinSpec, retries);
+ resp = new ResponseAPDU(modifyPin(icc, s, pinGUI, pinSpec));
+ } else if (MODIFY_DIRECT) {
+ pinGUI.modifyPINDirect(pinSpec, retries);
+ log.debug("MODIFY_PIN_DIRECT [" + FEATURES[FEATURE_MODIFY_PIN_DIRECT] + "]");
+ resp = new ResponseAPDU(MODIFY_PIN_DIRECT(icc, s));
+ } else {
+ log.warn("falling back to default pin-entry");
+ return super.modify(channel, apduSpec, pinGUI, pinSpec, retries);
+ }
+
+ switch (resp.getSW()) {
+ case 0x6400:
+ log.debug("SPE operation timed out.");
+ throw new TimeoutException();
+ case 0x6401:
+ log.debug("SPE operation was cancelled by the 'Cancel' button.");
+ throw new CancelledException();
+ case 0x6402:
+ log.debug("Modify PIN operation failed because two 'new PIN' " +
+ "entries do not match");
+ throw new PINConfirmationException();
+ case 0x6403:
+ log.debug("User entered too short or too long PIN "
+ + "regarding MIN/MAX PIN length.");
+ throw new PINFormatException();
+ case 0x6480:
+ log.debug("SPE operation was aborted by the 'Cancel' operation "
+ + "at the host system.");
+ case 0x6b80:
+ log.info("Invalid parameter in passed structure.");
+ default:
+ return resp;
+ }
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, NewReferenceDataAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec)
+ throws SignatureCardException, CardException, InterruptedException {
+
+ ResponseAPDU resp = null;
+
+ byte[] s = createPINModifyStructure(apduSpec, pinSpec);
+ Card icc = channel.getCard();
+
+ if (MODIFY) {
+ pinGUI.enterNewPIN(pinSpec);
+ resp = new ResponseAPDU(modifyPin(icc, s, pinGUI, pinSpec));
+ } else if (MODIFY_DIRECT) {
+ pinGUI.modifyPINDirect(pinSpec, -1);
+ log.debug("MODIFY_PIN_DIRECT [" + FEATURES[FEATURE_MODIFY_PIN_DIRECT] + "]");
+ resp = new ResponseAPDU(MODIFY_PIN_DIRECT(icc, s));
+ } else {
+ log.warn("falling back to default pin-entry");
+ return super.modify(channel, apduSpec, pinGUI, pinSpec);
+ }
+
+ switch (resp.getSW()) {
+ case 0x6400:
+ log.debug("SPE operation timed out.");
+ throw new TimeoutException();
+ case 0x6401:
+ log.debug("SPE operation was cancelled by the 'Cancel' button.");
+ throw new CancelledException();
+ case 0x6402:
+ log.debug("Modify PIN operation failed because two 'new PIN' " +
+ "entries do not match");
+ throw new PINConfirmationException();
+ case 0x6403:
+ log.debug("User entered too short or too long PIN "
+ + "regarding MIN/MAX PIN length.");
+ throw new PINFormatException();
+ case 0x6480:
+ log.debug("SPE operation was aborted by the 'Cancel' operation "
+ + "at the host system.");
+ case 0x6b80:
+ log.info("Invalid parameter in passed structure.");
+ default:
+ return resp;
+ }
+ }
+
+ @Override
+ public ResponseAPDU modify(CardChannel channel, ResetRetryCounterAPDUSpec apduSpec,
+ ModifyPINGUI pinGUI, PINSpec pinSpec, int retries)
+ throws InterruptedException, CardException, SignatureCardException {
+ //TODO
+ return modify(channel, (ChangeReferenceDataAPDUSpec) apduSpec, pinGUI, pinSpec, retries);
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/reader/ReaderFactory.java b/smcc/src/main/java/at/gv/egiz/smcc/reader/ReaderFactory.java
new file mode 100644
index 00000000..bf1730e9
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/reader/ReaderFactory.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package at.gv.egiz.smcc.reader;
+
+import at.gv.egiz.smcc.conf.SMCCConfiguration;
+import at.gv.egiz.smcc.util.SMCCHelper;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.smartcardio.Card;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class ReaderFactory {
+
+ protected final static Log log = LogFactory.getLog(ReaderFactory.class);
+
+ protected static SMCCConfiguration configuration;
+
+ public void setConfiguration(SMCCConfiguration configuration) {
+ if (configuration != null) {
+ log.debug("reader configuration: disablePinpad=" + configuration.isDisablePinpad());
+ }
+ //spring injects configuration into singleton ReaderFactory instance,
+ //but we access the ReaderFactory statically (getReader)
+ //(we rather should query the application context to obtain a reader factory)
+ ReaderFactory.configuration = configuration;
+ }
+
+ public static CardReader getReader(Card icc, CardTerminal ct) {
+
+ String name = ct.getName();
+ log.info("creating reader " + name);
+
+ Map<Byte, Integer> features;
+ if (configuration != null && configuration.isDisablePinpad()) {
+ features = Collections.emptyMap();
+ } else {
+ features = queryFeatures(icc);
+ }
+
+ CardReader reader;
+ if (features.isEmpty()) {
+ reader = new DefaultCardReader(ct);
+ } else {
+ reader = new PinpadCardReader(ct, features);
+ }
+
+ return reader;
+ }
+
+ private static int CTL_CODE(int code) {
+ String os_name = System.getProperty("os.name").toLowerCase();
+ if (os_name.indexOf("windows") > -1) {
+ // cf. WinIOCTL.h
+ return (0x31 << 16 | (code) << 2);
+ }
+ // cf. reader.h
+ return 0x42000000 + (code);
+ }
+
+ static int IOCTL_GET_FEATURE_REQUEST = CTL_CODE(3400);
+
+ private static Map<Byte, Integer> queryFeatures(Card icc) {
+ Map<Byte, Integer> features = new HashMap<Byte, Integer>();
+
+ if (icc == null) {
+ log.warn("invalid card handle, cannot query ifd features");
+ } else {
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("GET_FEATURE_REQUEST " + Integer.toHexString(IOCTL_GET_FEATURE_REQUEST));
+ }
+ byte[] resp = icc.transmitControlCommand(IOCTL_GET_FEATURE_REQUEST,
+ new byte[0]);
+
+ if (log.isTraceEnabled()) {
+ log.trace("Response TLV " + SMCCHelper.toString(resp));
+ }
+ // tag
+ // length in bytes (always 4)
+ // control code value for supported feature (in big endian)
+ for (int i = 0; i < resp.length; i += 6) {
+ Byte feature = new Byte(resp[i]);
+ Integer ioctl = new Integer((0xff & resp[i + 2]) << 24) |
+ ((0xff & resp[i + 3]) << 16) |
+ ((0xff & resp[i + 4]) << 8) |
+ (0xff & resp[i + 5]);
+ if (log.isInfoEnabled()) {
+ log.info("IFD supports " + CardReader.FEATURES[feature.intValue()] +
+ ": " + Integer.toHexString(ioctl.intValue()));
+ }
+ features.put(feature, ioctl);
+ }
+ } catch (CardException ex) {
+ log.debug("Failed to query IFD features: " + ex.getMessage());
+ log.trace(ex);
+ log.info("IFD does not support secure pin entry");
+ }
+ }
+ return features;
+ }
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/util/ISO7816Utils.java b/smcc/src/main/java/at/gv/egiz/smcc/util/ISO7816Utils.java
new file mode 100644
index 00000000..fcd0b876
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/util/ISO7816Utils.java
@@ -0,0 +1,368 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+import javax.smartcardio.CardChannel;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CommandAPDU;
+import javax.smartcardio.ResponseAPDU;
+
+import at.gv.egiz.smcc.ChangeReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.NewReferenceDataAPDUSpec;
+import at.gv.egiz.smcc.SecurityStatusNotSatisfiedException;
+import at.gv.egiz.smcc.SignatureCardException;
+import at.gv.egiz.smcc.VerifyAPDUSpec;
+
+public class ISO7816Utils {
+
+ public static TransparentFileInputStream openTransparentFileInputStream(
+ final CardChannel channel, int maxSize) {
+
+ TransparentFileInputStream file = new TransparentFileInputStream(maxSize) {
+
+ @Override
+ protected byte[] readBinary(int offset, int len) throws IOException {
+
+ ResponseAPDU resp;
+ try {
+ resp = channel.transmit(new CommandAPDU(0x00, 0xB0,
+ 0x7F & (offset >> 8), offset & 0xFF, len));
+ } catch (CardException e) {
+ throw new IOException(e);
+ }
+
+ Throwable cause;
+ if (resp.getSW() == 0x9000) {
+ return resp.getData();
+ } else if (resp.getSW() == 0x6982) {
+ cause = new SecurityStatusNotSatisfiedException();
+ } else {
+ cause = new SignatureCardException("Failed to read bytes (offset=" + offset + ",len="
+ + len + ") SW=" + Integer.toHexString(resp.getSW()) + ".");
+ }
+ throw new IOException(cause);
+
+ }
+
+ };
+
+ return file;
+
+ }
+
+ public static byte[] readTransparentFile(CardChannel channel, int maxSize)
+ throws CardException, SignatureCardException {
+
+ TransparentFileInputStream is = openTransparentFileInputStream(channel, maxSize);
+
+ try {
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ int len;
+ for (byte[] b = new byte[256]; (len = is.read(b)) != -1;) {
+ os.write(b, 0, len);
+ }
+
+ return os.toByteArray();
+
+ } catch (IOException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof CardException) {
+ throw (CardException) cause;
+ }
+ if (cause instanceof SignatureCardException) {
+ throw (SignatureCardException) cause;
+ }
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ public static byte[] readTransparentFileTLV(CardChannel channel, int maxSize,
+ byte expectedType) throws CardException, SignatureCardException {
+
+ TransparentFileInputStream is = openTransparentFileInputStream(channel,
+ maxSize);
+
+ return readTransparentFileTLV(is, maxSize, expectedType);
+
+ }
+
+ public static byte[] readTransparentFileTLV(TransparentFileInputStream is, int maxSize,
+ byte expectedType) throws CardException, SignatureCardException {
+
+
+ try {
+
+ is.mark(256);
+
+ // check expected type
+ int b = is.read();
+ if (b == 0x00) {
+ return null;
+ }
+ if (b == -1 || expectedType != (0xFF & b)) {
+ throw new SignatureCardException("Unexpected TLV type. Expected "
+ + Integer.toHexString(expectedType) + " but was "
+ + Integer.toHexString(b) + ".");
+ }
+
+ // get actual length
+ int actualSize = 2;
+ b = is.read();
+ if (b == -1) {
+ return null;
+ } else if ((0x80 & b) > 0) {
+ int octets = (0x0F & b);
+ actualSize += octets;
+ for (int i = 1; i <= octets; i++) {
+ b = is.read();
+ if (b == -1) {
+ return null;
+ }
+ actualSize += (0xFF & b) << ((octets - i) * 8);
+ }
+ } else {
+ actualSize += 0xFF & b;
+ }
+
+ // set limit to actual size and read into buffer
+ is.reset();
+ is.setLimit(actualSize);
+ byte[] buf = new byte[actualSize];
+ if (is.read(buf) == actualSize) {
+ return buf;
+ } else {
+ return null;
+ }
+
+ } catch (IOException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof CardException) {
+ throw (CardException) cause;
+ }
+ if (cause instanceof SignatureCardException) {
+ throw (SignatureCardException) cause;
+ }
+ throw new SignatureCardException(e);
+ }
+
+ }
+
+ public static int getLengthFromFCx(byte[] fcx) {
+
+ int len = -1;
+
+ if (fcx.length != 0 && (fcx[0] == (byte) 0x62 || fcx[0] == (byte) 0x6F)) {
+ int pos = 2;
+ while (pos < (fcx[1] - 2)) {
+ switch (fcx[pos]) {
+
+ case (byte) 0x80:
+ case (byte) 0x81: {
+ len = 0xFF & fcx[pos + 2];
+ for (int i = 1; i < fcx[pos + 1]; i++) {
+ len<<=8;
+ len+=0xFF & fcx[pos + i + 2];
+ }
+ }
+
+ default:
+ pos += 0xFF & fcx[pos + 1] + 2;
+ }
+ }
+ }
+
+ return len;
+
+ }
+
+ public static byte[] readRecord(CardChannel channel, int record) throws CardException, SignatureCardException {
+
+ ResponseAPDU resp = channel.transmit(
+ new CommandAPDU(0x00, 0xB2, record, 0x04, 256));
+ if (resp.getSW() == 0x9000) {
+ return resp.getData();
+ } else {
+ throw new SignatureCardException("Failed to read records. SW="
+ + Integer.toHexString(resp.getSW()));
+ }
+
+ }
+
+ public static void formatPIN(int pinFormat, int pinJustification, byte[] fpin, byte[] mask, char[] pin) {
+
+ boolean left = (pinJustification == VerifyAPDUSpec.PIN_JUSTIFICATION_LEFT);
+
+ int j = (left) ? 0 : fpin.length - 1;
+ int step = (left) ? 1 : - 1;
+ switch (pinFormat) {
+ case VerifyAPDUSpec.PIN_FORMAT_BINARY:
+ if (fpin.length < pin.length) {
+ throw new IllegalArgumentException();
+ }
+ for (int i = 0; i < pin.length; i++) {
+ fpin[j] = (byte) Character.digit(pin[i], 10);
+ mask[j] = (byte) 0xFF;
+ j += step;
+ }
+ break;
+
+ case VerifyAPDUSpec.PIN_FORMAT_BCD:
+ if (fpin.length * 2 < pin.length) {
+ throw new IllegalArgumentException();
+ }
+ for (int i = 0; i < pin.length; i++) {
+ int digit = Character.digit(pin[i], 10);
+ boolean h = (i % 2 == 0) ^ left;
+ fpin[j] |= h ? digit : digit << 4 ;
+ mask[j] |= h ? (byte) 0x0F : (byte) 0xF0;
+ j += (i % 2) * step;
+ }
+ break;
+
+ case VerifyAPDUSpec.PIN_FORMAT_ASCII:
+ if (fpin.length < pin.length) {
+ throw new IllegalArgumentException();
+ }
+ byte[] asciiPin = Charset.forName("ASCII").encode(CharBuffer.wrap(pin)).array();
+ for (int i = 0; i < pin.length; i++) {
+ fpin[j] = asciiPin[i];
+ mask[j] = (byte) 0xFF;
+ j += step;
+ }
+ break;
+ }
+
+ }
+
+ public static void insertPIN(byte[] apdu, int pos, byte[] fpin, byte[] mask) {
+ for (int i = 0; i < fpin.length; i++) {
+ apdu[pos + i] &= ~mask[i];
+ apdu[pos + i] |= fpin[i];
+ }
+ }
+
+ public static void insertPINLength(byte[] apdu, int length, int lengthSize, int pos, int offset) {
+
+ // use short (2 byte) to be able to shift the pin length
+ // by the number of bits given by the pin length position
+ short size = (short) (0x00FF & length);
+ short sMask = (short) ((1 << lengthSize) - 1);
+ // shift to the proper position
+ int shift = 16 - lengthSize - (pos % 8);
+ offset += (pos / 8) + 5;
+ size <<= shift;
+ sMask <<= shift;
+ // insert upper byte
+ apdu[offset] &= (0xFF & (~sMask >> 8));
+ apdu[offset] |= (0xFF & (size >> 8));
+ // insert lower byte
+ apdu[offset + 1] &= (0xFF & ~sMask);
+ apdu[offset + 1] |= (0xFF & size);
+
+ }
+
+ public static CommandAPDU createVerifyAPDU(VerifyAPDUSpec apduSpec, char[] pin) {
+
+ // format pin
+ byte[] fpin = new byte[apduSpec.getPinLength()];
+ byte[] mask = new byte[apduSpec.getPinLength()];
+ formatPIN(apduSpec.getPinFormat(), apduSpec.getPinJustification(), fpin, mask, pin);
+
+ byte[] apdu = apduSpec.getApdu();
+
+ // insert formated pin
+ insertPIN(apdu, apduSpec.getPinPosition() + 5, fpin, mask);
+
+ // insert pin length
+ if (apduSpec.getPinLengthSize() != 0) {
+ insertPINLength(apdu, pin.length, apduSpec.getPinLengthSize(), apduSpec.getPinLengthPos(), 0);
+ }
+
+ return new CommandAPDU(apdu);
+
+ }
+
+ public static CommandAPDU createChangeReferenceDataAPDU(
+ ChangeReferenceDataAPDUSpec apduSpec, char[] oldPin, char[] newPin) {
+
+ // format old pin
+ byte[] fpin = new byte[apduSpec.getPinLength()];
+ byte[] mask = new byte[apduSpec.getPinLength()];
+ formatPIN(apduSpec.getPinFormat(), apduSpec.getPinJustification(), fpin, mask, oldPin);
+
+ byte[] apdu = apduSpec.getApdu();
+
+ // insert formated old pin
+ insertPIN(apdu, apduSpec.getPinPosition() + apduSpec.getPinInsertionOffsetOld() + 5, fpin, mask);
+
+ // insert pin length
+ if (apduSpec.getPinLengthSize() != 0) {
+ insertPINLength(apdu, oldPin.length, apduSpec.getPinLengthSize(),
+ apduSpec.getPinLengthPos(), apduSpec.getPinInsertionOffsetOld());
+ }
+
+ // format new pin
+ fpin = new byte[apduSpec.getPinLength()];
+ mask = new byte[apduSpec.getPinLength()];
+ formatPIN(apduSpec.getPinFormat(), apduSpec.getPinJustification(), fpin, mask, newPin);
+
+ // insert formated new pin
+ insertPIN(apdu, apduSpec.getPinPosition() + apduSpec.getPinInsertionOffsetNew() + 5, fpin, mask);
+
+ // insert pin length
+ if (apduSpec.getPinLengthSize() != 0) {
+ insertPINLength(apdu, newPin.length, apduSpec.getPinLengthSize(),
+ apduSpec.getPinLengthPos(), apduSpec.getPinInsertionOffsetNew());
+ }
+
+ return new CommandAPDU(apdu);
+
+ }
+
+ public static CommandAPDU createNewReferenceDataAPDU(
+ NewReferenceDataAPDUSpec apduSpec, char[] newPin) {
+
+ // format old pin
+ byte[] fpin = new byte[apduSpec.getPinLength()];
+ byte[] mask = new byte[apduSpec.getPinLength()];
+ formatPIN(apduSpec.getPinFormat(), apduSpec.getPinJustification(), fpin, mask, newPin);
+
+ byte[] apdu = apduSpec.getApdu();
+
+ // insert formated new pin
+ insertPIN(apdu, apduSpec.getPinPosition() + apduSpec.getPinInsertionOffsetNew() + 5, fpin, mask);
+
+ // insert pin length
+ if (apduSpec.getPinLengthSize() != 0) {
+ insertPINLength(apdu, newPin.length, apduSpec.getPinLengthSize(),
+ apduSpec.getPinLengthPos(), apduSpec.getPinInsertionOffsetNew());
+ }
+
+ return new CommandAPDU(apdu);
+
+ }
+
+
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/util/SMCCHelper.java b/smcc/src/main/java/at/gv/egiz/smcc/util/SMCCHelper.java
new file mode 100644
index 00000000..f7d3bab7
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/util/SMCCHelper.java
@@ -0,0 +1,150 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.util;
+
+import java.util.Locale;
+import java.util.Map;
+
+import javax.smartcardio.ATR;
+import javax.smartcardio.Card;
+import javax.smartcardio.CardTerminal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.smcc.CardNotSupportedException;
+import at.gv.egiz.smcc.SignatureCard;
+import at.gv.egiz.smcc.SignatureCardFactory;
+
+public class SMCCHelper {
+
+ public final static int NO_CARD = 0;
+ public final static int PC_SC_NOT_SUPPORTED = 1;
+ public final static int TERMINAL_NOT_PRESENT = 2;
+ public final static int CARD_NOT_SUPPORTED = 3;
+ public final static int CARD_FOUND = 4;
+
+ private final static Log log = LogFactory.getLog(SMCCHelper.class);
+
+ protected SmartCardIO smartCardIO = new SmartCardIO();
+ protected int resultCode = NO_CARD;
+ protected SignatureCard signatureCard = null;
+ protected static boolean useSWCard = false;
+
+ public SMCCHelper() {
+ update();
+ }
+
+ public synchronized void update() {
+ update(-1);
+ }
+
+ public synchronized void update(int sleep) {
+ SignatureCardFactory factory = SignatureCardFactory.getInstance();
+ if (useSWCard) {
+ try {
+ signatureCard = factory.createSignatureCard(null, null);
+ resultCode = CARD_FOUND;
+ } catch (CardNotSupportedException e) {
+ resultCode = CARD_NOT_SUPPORTED;
+ signatureCard = null;
+ }
+ return;
+ }
+ signatureCard = null;
+ resultCode = NO_CARD;
+ // find pcsc support
+ if (smartCardIO.isPCSCSupported()) {
+ // find supported card
+ if (smartCardIO.isTerminalPresent()) {
+ Map<CardTerminal, Card> newCards = null;
+ if (sleep > 0) {
+ smartCardIO.waitForInserted(sleep);
+
+ }
+ newCards = smartCardIO.getCards();
+ for (CardTerminal cardTerminal : newCards.keySet()) {
+ try {
+ Card c = newCards.get(cardTerminal);
+ if (c == null) {
+ throw new CardNotSupportedException();
+ }
+ signatureCard = factory.createSignatureCard(c, cardTerminal);
+ ATR atr = newCards.get(cardTerminal).getATR();
+ log.trace("Found supported card (" + signatureCard.toString() + ") "
+ + "in terminal '" + cardTerminal.getName() + "', ATR = "
+ + toString(atr.getBytes()) + ".");
+ resultCode = CARD_FOUND;
+ break;
+
+ } catch (CardNotSupportedException e) {
+ Card c = newCards.get(cardTerminal);
+ if (c != null) {
+ ATR atr = c.getATR();
+ log.info("Found unsupported card" + " in terminal '"
+ + cardTerminal.getName() + "', ATR = "
+ + toString(atr.getBytes()) + ".");
+ } else {
+ log.info("Found unsupported card in terminal '"
+ + cardTerminal.getName() + "' without ATR");
+ }
+ resultCode = CARD_NOT_SUPPORTED;
+ }
+ }
+ } else {
+ resultCode = TERMINAL_NOT_PRESENT;
+ }
+ } else {
+ resultCode = PC_SC_NOT_SUPPORTED;
+ }
+ }
+
+ public synchronized SignatureCard getSignatureCard(Locale locale) {
+ if (signatureCard != null) {
+ signatureCard.setLocale(locale);
+ }
+ return signatureCard;
+ }
+
+ public int getResultCode() {
+ return resultCode;
+ }
+
+ public static String toString(byte[] b) {
+ StringBuffer sb = new StringBuffer();
+ sb.append('[');
+ if (b != null && b.length > 0) {
+ sb.append(Integer.toHexString((b[0] & 240) >> 4));
+ sb.append(Integer.toHexString(b[0] & 15));
+ for (int i = 1; i < b.length; i++) {
+ sb.append((i % 32 == 0) ? '\n' : ':');
+ sb.append(Integer.toHexString((b[i] & 240) >> 4));
+ sb.append(Integer.toHexString(b[i] & 15));
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ public static boolean isUseSWCard() {
+ return useSWCard;
+ }
+
+ public static void setUseSWCard(boolean useSWCard) {
+ SMCCHelper.useSWCard = useSWCard;
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java b/smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java
new file mode 100644
index 00000000..b1866894
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java
@@ -0,0 +1,204 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.util;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardException;
+import javax.smartcardio.CardTerminal;
+import javax.smartcardio.CardTerminals;
+import javax.smartcardio.TerminalFactory;
+import javax.smartcardio.CardTerminals.State;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @author mcentner
+ */
+public class SmartCardIO {
+
+ private static final int STATE_INITIALIZED = 1;
+
+ private static final int STATE_TERMINAL_FACTORY = 2;
+
+ private static final int STATE_TERMINALS = 3;
+
+ private static Log log = LogFactory.getLog(SmartCardIO.class);
+
+ final Map<CardTerminal, Card> terminalCard_ = new HashMap<CardTerminal, Card>();
+
+ int state_ = STATE_INITIALIZED;
+
+ TerminalFactory terminalFactory_ = null;
+
+ CardTerminals cardTerminals_;
+
+ private void updateTerminalFactory() {
+ TerminalFactory terminalFactory;
+ try {
+ terminalFactory = TerminalFactory.getInstance("PC/SC", null);
+ } catch (NoSuchAlgorithmException e) {
+ log.info("Failed to get TerminalFactory of type 'PC/SC'.", e);
+ terminalFactory = TerminalFactory.getDefault();
+ }
+ log.debug("TerminalFactory : " + terminalFactory);
+ if ("PC/SC".equals(terminalFactory.getType())) {
+ terminalFactory_ = terminalFactory;
+ }
+ if(state_ < STATE_TERMINAL_FACTORY) {
+ state_ = STATE_TERMINAL_FACTORY;
+ }
+ }
+
+ public boolean isPCSCSupported() {
+ if(state_ < STATE_TERMINAL_FACTORY) {
+ updateTerminalFactory();
+ }
+ return terminalFactory_ != null;
+ }
+
+ private void updateCardTerminals() {
+ if(terminalFactory_ != null) {
+ cardTerminals_ = terminalFactory_.terminals();
+ }
+ log.debug("CardTerminals : " + cardTerminals_);
+ if (state_ < STATE_TERMINALS) {
+ state_ = STATE_TERMINALS;
+ }
+ }
+
+ public CardTerminals getCardTerminals() {
+ if(state_ < STATE_TERMINAL_FACTORY) {
+ updateTerminalFactory();
+ }
+ if(state_ < STATE_TERMINALS) {
+ updateCardTerminals();
+ }
+ return cardTerminals_;
+ }
+
+ public boolean isTerminalPresent() {
+ CardTerminals cardTerminals = getCardTerminals();
+ if (cardTerminals != null) {
+ List<CardTerminal> terminals = null;
+ try {
+ terminals = cardTerminals.list(State.ALL);
+
+ // logging
+ if(log.isInfoEnabled()) {
+ if (terminals == null || terminals.isEmpty()) {
+ log.info("No card terminal found.");
+ } else {
+ StringBuffer msg = new StringBuffer();
+ msg.append("Found " + terminals.size() + " card terminal(s):");
+ for (CardTerminal terminal : terminals) {
+ msg.append("\n " + terminal.getName());
+ }
+ log.info(msg.toString());
+ }
+ }
+
+ return terminals != null && !terminals.isEmpty();
+ } catch (CardException e) {
+ log.info("Failed to list card terminals.", e);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ private Map<CardTerminal, Card> updateCards() {
+
+ // clear card references if removed
+ try {
+ log.trace("terminals.list(State.CARD_REMOVAL)");
+ for (CardTerminal terminal : cardTerminals_.list(CardTerminals.State.CARD_REMOVAL)) {
+ Card card = terminalCard_.remove(terminal);
+ log.trace("card removed : " + card);
+ }
+ } catch (CardException e) {
+ log.debug(e);
+ }
+
+ // check inserted cards
+ Map<CardTerminal, Card> newCards = new HashMap<CardTerminal, Card>();
+ try {
+ log.trace("terminals.list(State.CARD_INSERTION)");
+ for (CardTerminal terminal : cardTerminals_.list(CardTerminals.State.CARD_INSERTION)) {
+
+ Card card = null;
+ try {
+ log.trace("Trying to connect to card.");
+ // try to connect to card
+ card = terminal.connect("*");
+ } catch (CardException e) {
+ log.trace("Failed to connect to card.", e);
+ }
+
+ // have we seen this card before?
+ if (terminalCard_.put(terminal, card) == null) {
+ terminalCard_.put(terminal, card);
+ newCards.put(terminal, card);
+ log.trace("terminal '" + terminal + "' card inserted : " + card);
+ }
+ }
+ } catch (CardException e) {
+ log.debug(e);
+ }
+ return newCards;
+
+ }
+
+ public Map<CardTerminal, Card> getCards() {
+ if(state_ < STATE_TERMINAL_FACTORY) {
+ updateTerminalFactory();
+ }
+ if(state_ < STATE_TERMINALS) {
+ updateCardTerminals();
+ }
+ updateCards();
+ Map<CardTerminal, Card> terminalCard = new HashMap<CardTerminal, Card>();
+ terminalCard.putAll(terminalCard_);
+ return Collections.unmodifiableMap(terminalCard);
+ }
+
+ public Map<CardTerminal, Card> waitForInserted(int timeout) {
+ if(state_ < STATE_TERMINAL_FACTORY) {
+ updateTerminalFactory();
+ }
+ if(state_ < STATE_TERMINALS) {
+ updateCardTerminals();
+ }
+ try {
+ // just waiting for a short period of time to allow for abort
+ cardTerminals_.waitForChange(timeout);
+ } catch (CardException e) {
+ log.debug("CardTerminals.waitForChange(" + timeout + ") failed.", e);
+ }
+ Map<CardTerminal, Card> newCards = new HashMap<CardTerminal, Card>();
+ newCards.putAll(updateCards());
+ return Collections.unmodifiableMap(newCards);
+ }
+} \ No newline at end of file
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/util/TransparentFileInputStream.java b/smcc/src/main/java/at/gv/egiz/smcc/util/TransparentFileInputStream.java
new file mode 100644
index 00000000..781f9137
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/util/TransparentFileInputStream.java
@@ -0,0 +1,194 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package at.gv.egiz.smcc.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class TransparentFileInputStream extends InputStream {
+
+ private final int chunkSize = 256;
+
+ private byte[] buf = new byte[chunkSize];
+ private int start = 0;
+ private int end = 0;
+
+ private int offset = 0;
+
+ private int length = -1;
+
+ private int limit = -1;
+
+ private int mark = -1;
+
+ private int readlimit = -1;
+
+ public TransparentFileInputStream() {
+ }
+
+ public TransparentFileInputStream(int length) {
+ this.length = length;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ private int fill() throws IOException {
+ if (start == end && (limit < 0 || offset < limit)) {
+ int l;
+ if (limit > 0 && limit - offset < chunkSize) {
+ l = limit - offset;
+ } else if (length > 0) {
+ if (length - offset < chunkSize) {
+ l = length - offset;
+ } else {
+ l = chunkSize - 1;
+ }
+ } else {
+ l = chunkSize;
+ }
+ byte[] b = readBinary(offset, l);
+ offset += b.length;
+ if (mark < 0) {
+ start = 0;
+ end = b.length;
+ System.arraycopy(b, 0, buf, start, b.length);
+ } else {
+ if (end - mark + b.length > buf.length) {
+ // double buffer size
+ byte[] nbuf = new byte[buf.length * 2];
+ System.arraycopy(buf, mark, nbuf, 0, end - mark);
+ buf = nbuf;
+ } else {
+ System.arraycopy(buf, mark, buf, 0, end - mark);
+ }
+ start = start - mark;
+ end = end - mark + b.length;
+ mark = 0;
+ System.arraycopy(b, 0, buf, start, b.length);
+ }
+ if (l > b.length) {
+ // end of file reached
+ setLimit(offset);
+ }
+ }
+ return end - start;
+ }
+
+ protected abstract byte[] readBinary(int offset, int len) throws IOException;
+
+ @Override
+ public int read() throws IOException {
+ int b = (fill() > 0) ? 0xFF & buf[start++] : -1;
+ if (readlimit > 0 && start > readlimit) {
+ mark = -1;
+ readlimit = -1;
+ }
+ return b;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ int count = 0;
+ int l;
+ while (count < len) {
+ if (fill() > 0) {
+ l = Math.min(end - start, len - count);
+ System.arraycopy(buf, start, b, off, l);
+ start += l;
+ off += l;
+ count += l;
+ if (readlimit > 0 && start > readlimit) {
+ mark = -1;
+ readlimit = -1;
+ }
+ } else {
+ return (count > 0) ? count : -1;
+ }
+ }
+
+ return count;
+
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ this.readlimit = readlimit;
+ mark = start;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mark < 0) {
+ throw new IOException();
+ } else {
+ start = mark;
+ }
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ if (n <= end - start) {
+ start += n;
+ return n;
+ } else {
+
+ mark = -1;
+
+ long remaining = n - (end - start);
+ start = end;
+
+ if (limit >= 0 && limit < offset + remaining) {
+ remaining -= limit - offset;
+ offset = limit;
+ return n - remaining;
+ }
+
+ if (length >= 0 && length < offset + remaining) {
+ remaining -= length - offset;
+ offset = length;
+ return n - remaining;
+ }
+
+ offset += remaining;
+
+ return n;
+
+ }
+
+ }
+
+}