From c2ae3db1bc6dcb8ba3eb3461c05e293917c004ca Mon Sep 17 00:00:00 2001 From: mcentner Date: Thu, 30 Oct 2008 10:33:29 +0000 Subject: Updated SMCC to use exclusive access and to throw exceptions upon locked or not activated cards. Improved locale support in the security layer request and response processing. Fixed issue in STAL which prevented the use of RSA-SHA1 signatures. Added additional parameters to the applet test pages. git-svn-id: https://joinup.ec.europa.eu/svn/mocca/trunk@128 8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4 --- smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java | 348 ++++++++++++--------- .../at/gv/egiz/smcc/AbstractSignatureCard.java | 169 +++++++--- .../at/gv/egiz/smcc/FileNotFoundException.java | 38 +++ .../main/java/at/gv/egiz/smcc/LockedException.java | 38 +++ .../at/gv/egiz/smcc/NotActivatedException.java | 44 +++ .../src/main/java/at/gv/egiz/smcc/STARCOSCard.java | 339 ++++++++++++-------- smcc/src/main/java/at/gv/egiz/smcc/SWCard.java | 79 ++++- .../at/gv/egiz/smcc/SignatureCardException.java | 2 +- .../java/at/gv/egiz/smcc/SignatureCardFactory.java | 223 +++++++++++-- .../java/at/gv/egiz/smcc/util/SmartCardIO.java | 3 +- 10 files changed, 942 insertions(+), 341 deletions(-) create mode 100644 smcc/src/main/java/at/gv/egiz/smcc/FileNotFoundException.java create mode 100644 smcc/src/main/java/at/gv/egiz/smcc/LockedException.java create mode 100644 smcc/src/main/java/at/gv/egiz/smcc/NotActivatedException.java (limited to 'smcc/src/main') diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java b/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java index abe086ee..9e56701f 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/ACOSCard.java @@ -30,12 +30,18 @@ package at.gv.egiz.smcc; import java.nio.charset.Charset; +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 ACOSCard extends AbstractSignatureCard implements SignatureCard { + + 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 }; @@ -97,7 +103,145 @@ public class ACOSCard extends AbstractSignatureCard implements SignatureCard { super("at/gv/egiz/smcc/ACOSCard"); } - byte[] selectFileAID(byte[] fid) throws CardException, SignatureCardException { + /* (non-Javadoc) + * @see at.gv.egiz.smcc.SignatureCard#getCertificate(at.gv.egiz.smcc.SignatureCard.KeyboxName) + */ + public byte[] getCertificate(KeyboxName keyboxName) + throws SignatureCardException { + + byte[] aid; + byte[] efc; + int maxsize; + if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) { + aid = AID_SIG; + efc = EF_C_CH_DS; + maxsize = EF_C_CH_DS_MAX_SIZE; + } else if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) { + aid = AID_DEC; + efc = EF_C_CH_EKEY; + maxsize = EF_C_CH_EKEY_MAX_SIZE; + } else { + throw new IllegalArgumentException("Keybox " + keyboxName + + " not supported."); + } + + log.debug("Get certificate for keybox '" + keyboxName.getKeyboxName() + "'" + + " (AID=" + toString(aid) + " EF=" + toString(efc) + ")."); + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + return readTLVFile(aid, efc, maxsize + 15000); + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + + + } + + /* (non-Javadoc) + * @see at.gv.egiz.smcc.SignatureCard#getInfobox(java.lang.String, at.gv.egiz.smcc.PINProvider, java.lang.String) + */ + public byte[] getInfobox(String infobox, PINProvider provider, String domainId) + throws SignatureCardException { + + if ("IdentityLink".equals(infobox)) { + + PINSpec spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString("inf.pin.name")); + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + return readTLVFilePIN(AID_DEC, EF_INFOBOX, KID_PIN_INF, provider, + spec, EF_INFOBOX_MAX_SIZE); + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + + } else { + throw new IllegalArgumentException("Infobox '" + infobox + + "' not supported."); + } + + } + + public byte[] createSignature(byte[] hash, KeyboxName keyboxName, + PINProvider provider) throws SignatureCardException { + + if (hash.length != 20) { + throw new IllegalArgumentException("Hash value must be of length 20."); + } + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + + if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) { + + // SELECT DF + selectFileFID(DF_SIG); + // VERIFY + verifyPIN(provider, new PINSpec(6, 10, "[0-9]", getResourceBundle() + .getString("sig.pin.name")), KID_PIN_SIG); + // MSE: SET DST + mseSetDST(0x81, 0xb6, DST_SIG); + // PSO: HASH + psoHash(hash); + // PSO: COMPUTE DIGITAL SIGNATURE + return psoComputDigitalSiganture(); + + } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) { + + // SELECT DF + selectFileFID(DF_DEC); + // VERIFY + verifyPIN(provider, new PINSpec(4, 4, "[0-9]", getResourceBundle() + .getString("dec.pin.name")), KID_PIN_DEC); + // MSE: SET DST + mseSetDST(0x41, 0xa4, DST_DEC); + // INTERNAL AUTHENTICATE + return internalAuthenticate(hash); + + + // 00 88 10 00 23 30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14 54 26 F0 EA AF EA F0 4E D4 A1 AD BF 66 D4 A5 9B 45 6F AF 79 00 + // 00 88 10 00 23 30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14 DF 8C AB 8F E2 AD AC 7B 5A AF BE E9 44 5E 95 99 FA AF 2F 48 00 + + } else { + throw new IllegalArgumentException("KeyboxName '" + keyboxName + + "' not supported."); + } + + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + + } + + protected byte[] selectFileAID(byte[] fid) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0xA4, 0x04, 0x00, fid, 256)); @@ -109,18 +253,40 @@ public class ACOSCard extends AbstractSignatureCard implements SignatureCard { } } - byte[] selectFileFID(byte[] fid) throws CardException, SignatureCardException { + protected ResponseAPDU selectFileFID(byte[] fid) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); - ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0xA4, 0x00, + return transmit(channel, new CommandAPDU(0x00, 0xA4, 0x00, 0x00, fid, 256)); - if (resp.getSW() == 0x6a82) { - throw new SignatureCardException("Failed to select file (FID=" - + toString(fid) + "): SW=" + Integer.toHexString(resp.getSW()) + ")"); + } + + protected int verifyPIN(String pin, byte kid) throws CardException, SignatureCardException { + + CardChannel channel = getCardChannel(); + + byte[] asciiPIN = pin.getBytes(Charset.forName("ASCII")); + byte[] encodedPIN = new byte[8]; + System.arraycopy(asciiPIN, 0, encodedPIN, 0, Math.min(asciiPIN.length, + encodedPIN.length)); + + ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0x20, 0x00, + kid, encodedPIN), false); + + if (resp.getSW() == 0x63c0) { + throw new LockedException("PIN locked."); + } else if (resp.getSW1() == 0x63 && resp.getSW2() >> 4 == 0xc) { + // return number of possible retries + return resp.getSW2() & 0x0f; + } else if (resp.getSW() == 0x6983) { + throw new NotActivatedException(); + } else if (resp.getSW() == 0x9000) { + return -1; } else { - return resp.getBytes(); + throw new SignatureCardException("Failed to verify pin: SW=" + + Integer.toHexString(resp.getSW()) + "."); } - } + } + /** * * @param pinProvider @@ -128,56 +294,30 @@ public class ACOSCard extends AbstractSignatureCard implements SignatureCard { * the PIN spec to be given to the pinProvider * @param kid * the KID (key identifier) of the PIN to be verified - * @param kfpc - * acutal value of the KFCP (key fault presentation counter) or less - * than 0 if actual value is unknown - * - * @return -1 if the PIN has been verifyed successfully, or else the new value - * of the KFCP (key fault presentation counter) - * * @throws CancelledException * if the user canceld the operation * @throws javax.smartcardio.CardException * @throws at.gv.egiz.smcc.SignatureCardException */ - int verifyPIN(PINProvider pinProvider, PINSpec spec, byte kid, int kfpc) + protected void verifyPIN(PINProvider pinProvider, PINSpec spec, byte kid) throws CardException, CancelledException, SignatureCardException { - CardChannel channel = getCardChannel(); - - // get PIN - String pin = pinProvider.providePIN(spec, kfpc); - if (pin == null) { - // User canceld operation - // throw new CancelledException("User canceld PIN entry"); - return -2; - } - - byte[] asciiPIN = pin.getBytes(Charset.forName("ASCII")); - byte[] encodedPIN = new byte[8]; - System.arraycopy(asciiPIN, 0, encodedPIN, 0, Math.min(asciiPIN.length, - encodedPIN.length)); - - ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0x20, 0x00, - kid, encodedPIN)); - if (resp.getSW1() == (byte) 0x63 && resp.getSW2() >> 4 == (byte) 0xc) { - return resp.getSW2() & (byte) 0x0f; - } else if (resp.getSW() == 0x6983) { - // PIN blocked - throw new SignatureCardException(spec.getLocalizedName() + " blocked."); - } else if (resp.getSW() != 0x9000) { - throw new SignatureCardException("Failed to verify pin: SW=" - + Integer.toHexString(resp.getSW()) + "."); - } else { - return -1; - } - + int retries = -1; + do { + String pin = pinProvider.providePIN(spec, retries); + if (pin == null) { + // user canceled operation + throw new CancelledException("User canceled operation"); + } + retries = verifyPIN(pin, kid); + } while (retries > 0); + } - - void mseSetDST(byte[] dst) throws CardException, SignatureCardException { + + void mseSetDST(int p1, int p2, byte[] dst) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); - ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0x22, 0x81, - 0xB6, dst)); + ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0x22, p1, + p2, dst)); if (resp.getSW() != 0x9000) { throw new SignatureCardException("MSE:SET DST failed: SW=" + Integer.toHexString(resp.getSW())); @@ -207,102 +347,30 @@ public class ACOSCard extends AbstractSignatureCard implements SignatureCard { return resp.getData(); } } - - public byte[] getCertificate(KeyboxName keyboxName) - throws SignatureCardException { - - if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) { - return readTLVFile(AID_SIG, EF_C_CH_DS, EF_C_CH_DS_MAX_SIZE); - } else if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) { - return readTLVFile(AID_DEC, EF_C_CH_EKEY, EF_C_CH_EKEY_MAX_SIZE); - } else { - throw new IllegalArgumentException("Keybox " + keyboxName - + " not supported."); - } - - } - - public byte[] getInfobox(String infobox, PINProvider provider, String domainId) - throws SignatureCardException { - - if ("IdentityLink".equals(infobox)) { - - PINSpec spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString( - "inf.pin.name")); - try { - byte[] res = readTLVFilePIN(AID_DEC, EF_INFOBOX, KID_PIN_INF, provider, - spec, EF_INFOBOX_MAX_SIZE); - return res; - } catch (Exception e) { - throw new SecurityException(e); - } - + + byte[] internalAuthenticate(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); + + CardChannel channel = getCardChannel(); + + ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0x88, 0x10, 0x00, data, 256)); + if (resp.getSW() != 0x9000) { + throw new SignatureCardException("INTERNAL AUTHENTICATE failed: SW=" + Integer.toHexString(resp.getSW())); } else { - throw new IllegalArgumentException("Infobox '" + infobox - + "' not supported."); + return resp.getData(); } - } public String toString() { return "a-sign premium"; } - - public byte[] createSignature(byte[] hash, KeyboxName keyboxName, - PINProvider provider) throws SignatureCardException { - - if (hash.length != 20) { - throw new IllegalArgumentException("Hash value must be of length 20"); - } - - byte[] fid; - byte kid; - byte[] dst; - PINSpec spec; - if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) { - fid = DF_SIG; - kid = KID_PIN_SIG; - dst = DST_SIG; - spec = new PINSpec(6, 10, "[0-9]", getResourceBundle().getString( - "sig.pin.name")); - - } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) { - fid = DF_DEC; - kid = KID_PIN_DEC; - dst = DST_DEC; - spec = new PINSpec(6, 10, "[0-9]", getResourceBundle().getString( - "dec.pin.name")); - - } else { - throw new IllegalArgumentException("KeyboxName '" + keyboxName - + "' not supported."); - } - - try { - - // SELECT DF - selectFileFID(fid); - // VERIFY - int kfpc = -1; - while (true) { - kfpc = verifyPIN(provider, spec, kid, kfpc); - if (kfpc < -1) { - return null; - } else if (kfpc < 0) { - break; - } - } - // MSE: SET DST - mseSetDST(dst); - // PSO: HASH - psoHash(hash); - // PSO: COMPUTE DIGITAL SIGNATURE - byte[] rs = psoComputDigitalSiganture(); - - return rs; - - } catch (CardException e) { - throw new SignatureCardException("Failed to create signature.", e); - } - } } diff --git a/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java b/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java index 77a3e2ea..cebc63fc 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/AbstractSignatureCard.java @@ -31,7 +31,6 @@ package at.gv.egiz.smcc; import java.nio.ByteBuffer; import java.util.Locale; import java.util.ResourceBundle; -import java.util.logging.Logger; import javax.smartcardio.ATR; import javax.smartcardio.Card; @@ -60,7 +59,7 @@ public abstract class AbstractSignatureCard implements SignatureCard { this.resourceBundleName = resourceBundleName; } - String toString(byte[] b) { + protected String toString(byte[] b) { StringBuffer sb = new StringBuffer(); if (b != null && b.length > 0) { sb.append(Integer.toHexString((b[0] & 240) >> 4)); @@ -74,13 +73,46 @@ public abstract class AbstractSignatureCard implements SignatureCard { return sb.toString(); } - abstract byte[] selectFileAID(byte[] fid) throws CardException, + protected abstract byte[] selectFileAID(byte[] fid) throws CardException, SignatureCardException; - abstract byte[] selectFileFID(byte[] fid) throws CardException, + protected abstract ResponseAPDU selectFileFID(byte[] fid) throws CardException, SignatureCardException; - byte[] readBinary(CardChannel channel, int offset, int len) + /** + * VERIFY PIN + * + *

+ * Implementations of this method should call + * {@link PINProvider#providePIN(PINSpec, int)} to retrieve the PIN entered by + * the user and VERIFY PIN on the smart card until the PIN has been + * successfully verified. + *

+ * + * @param pinProvider + * the PINProvider + * @param spec + * the PINSpec + * @param kid + * the key ID (KID) of the PIN to verify + * + * @throws CardException + * if smart card communication fails + * + * @throws CancelledException + * if the PINProvider indicated that the user canceled the PIN entry + * @throws NotActivatedException + * if the card application has not been activated + * @throws LockedException + * if the card application is locked + * + * @throws SignatureCardException + * if VERIFY PIN fails + */ + protected abstract void verifyPIN(PINProvider pinProvider, PINSpec spec, + byte kid) throws CardException, SignatureCardException; + + protected byte[] readBinary(CardChannel channel, int offset, int len) throws CardException, SignatureCardException { ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0xB0, @@ -94,7 +126,7 @@ public abstract class AbstractSignatureCard implements SignatureCard { } - int readBinary(int offset, int len, byte[] b) throws CardException, + protected int readBinary(int offset, int len, byte[] b) throws CardException, SignatureCardException { if (b.length < len) { @@ -114,7 +146,7 @@ public abstract class AbstractSignatureCard implements SignatureCard { } - byte[] readBinaryTLV(int maxSize, byte expectedType) throws CardException, + protected byte[] readBinaryTLV(int maxSize, byte expectedType) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); @@ -150,15 +182,38 @@ public abstract class AbstractSignatureCard implements SignatureCard { } - abstract int verifyPIN(PINProvider pinProvider, PINSpec spec, byte kid, - int kfpc) throws CardException, SignatureCardException; - - public byte[] readTLVFile(byte[] aid, byte[] ef, int maxLength) + /** + * Read the content of a TLV file. + * + * @param aid the application ID (AID) + * @param ef the elementary file (EF) + * @param maxLength the maximum length of the file + * + * @return the content of the file + * + * @throws SignatureCardException + */ + protected byte[] readTLVFile(byte[] aid, byte[] ef, int maxLength) throws SignatureCardException { return readTLVFilePIN(aid, ef, (byte) 0, null, null, maxLength); } - public byte[] readTLVFilePIN(byte[] aid, byte[] ef, byte kid, + + /** + * Read the content of a TLV file wich may require a PIN. + * + * @param aid the application ID (AID) + * @param ef the elementary file (EF) + * @param kid the key ID (KID) of the corresponding PIN + * @param provider the PINProvider + * @param spec the PINSpec + * @param maxLength the maximum length of the file + * + * @return the content of the file + * + * @throws SignatureCardException + */ + protected byte[] readTLVFilePIN(byte[] aid, byte[] ef, byte kid, PINProvider provider, PINSpec spec, int maxLength) throws SignatureCardException { @@ -179,33 +234,38 @@ public abstract class AbstractSignatureCard implements SignatureCard { } // SELECT FILE (EF) - rb = selectFileFID(ef); - if (rb[rb.length - 2] != (byte) 0x90 || rb[rb.length - 1] != (byte) 0x00) { + ResponseAPDU resp = selectFileFID(ef); + if (resp.getSW() == 0x6a82) { + + // EF not found + throw new FileNotFoundException("EF " + toString(ef) + " not found."); + + } else if (resp.getSW() != 0x9000) { throw new SignatureCardException("SELECT FILE with " + "FID=" + toString(ef) + " failed (" + "SW=" - + Integer.toHexString((0xFF & (int) rb[rb.length - 1]) - | (0xFF & (int) rb[rb.length - 2]) << 8) + ")."); + + Integer.toHexString(resp.getSW()) + ")."); + } // try to READ BINARY - int sw = readBinary(0, 1, new byte[1]); + byte[] b = new byte[1]; + int sw = readBinary(0, 1, b); + if (provider != null && sw == 0x6982) { // VERIFY - int kfpc = -1; // unknown - while (true) { - kfpc = verifyPIN(provider, spec, kid, kfpc); - if (kfpc < -1) { - return null; - } else if (kfpc < 0) { - break; - } + verifyPIN(provider, spec, kid); + + } else if (sw == 0x9000) { + // not expected type + if (b[0] != 0x30) { + throw new NotActivatedException(); } - } else if (sw != 0x9000) { + } else { throw new SignatureCardException("READ BINARY failed (SW=" + Integer.toHexString(sw) + ")."); } @@ -221,17 +281,56 @@ public abstract class AbstractSignatureCard implements SignatureCard { } - ResponseAPDU transmit(CardChannel channel, CommandAPDU commandAPDU) + /** + * Transmit the given command APDU using the given card channel. + * + * @param channel + * the card channel + * @param commandAPDU + * the command APDU + * @param logData + * true if command APDU data may be logged, or + * false otherwise + * + * @return the corresponding response APDU + * + * @throws CardException + * if smart card communication fails + */ + protected ResponseAPDU transmit(CardChannel channel, CommandAPDU commandAPDU, boolean logData) + throws CardException { + + if (log.isTraceEnabled()) { + log.trace(commandAPDU + + (logData ? "\n" + toString(commandAPDU.getBytes()) : "")); + long t0 = System.currentTimeMillis(); + ResponseAPDU responseAPDU = channel.transmit(commandAPDU); + long t1 = System.currentTimeMillis(); + log.trace(responseAPDU + "\n[" + (t1 - t0) + "ms] " + + (logData ? "\n" + toString(responseAPDU.getBytes()) : "")); + return responseAPDU; + } else { + return channel.transmit(commandAPDU); + } + + } + + /** + * Transmit the given command APDU using the given card channel. + * + * @param channel the card channel + * @param commandAPDU the command APDU + * + * @return the corresponding response APDU + * + * @throws CardException if smart card communication fails + */ + protected ResponseAPDU transmit(CardChannel channel, CommandAPDU commandAPDU) throws CardException { - log.trace(commandAPDU + "\n" + toString(commandAPDU.getBytes())); - long t0 = System.currentTimeMillis(); - ResponseAPDU responseAPDU = channel.transmit(commandAPDU); - long t1 = System.currentTimeMillis(); - log.trace(responseAPDU + "\n[" + (t1 - t0) + "ms] " - + toString(responseAPDU.getBytes())); - return responseAPDU; + return transmit(channel, commandAPDU, true); } + public void init(Card card) { card_ = card; ATR atr = card.getATR(); @@ -242,7 +341,7 @@ public abstract class AbstractSignatureCard implements SignatureCard { } } - CardChannel getCardChannel() { + protected CardChannel getCardChannel() { return card_.getBasicChannel(); } 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/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/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/STARCOSCard.java b/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java index 79e2663e..99acbc0f 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/STARCOSCard.java @@ -31,12 +31,21 @@ package at.gv.egiz.smcc; import java.math.BigInteger; import java.util.Arrays; +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 STARCOSCard extends AbstractSignatureCard implements SignatureCard { + + /** + * Logging facility. + */ + private static Log log = LogFactory.getLog(STARCOSCard.class); public static final byte[] MF = new byte[] { (byte) 0x3F, (byte) 0x00 }; @@ -83,7 +92,7 @@ public class STARCOSCard extends AbstractSignatureCard implements SignatureCard public static final byte KID_PIN_SS = (byte) 0x81; - // Gew�hnliche Signatur (GS) + // 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, @@ -104,31 +113,157 @@ public class STARCOSCard extends AbstractSignatureCard implements SignatureCard // . // ) (byte) 0x80, (byte) 0x02, (byte) 0x00, // local, key ID, key version - (byte) 0x89, (byte) 0x01, // tag, length (algorithm ID) - (byte) 0x14 // ECDSA + (byte) 0x89, (byte) 0x03, // tag, length (algorithm ID) + (byte) 0x13, (byte) 0x35, (byte) 0x10 // ECDSA }; public static final byte KID_PIN_CARD = (byte) 0x01; + /** + * Creates an new instance. + */ public STARCOSCard() { super("at/gv/egiz/smcc/STARCOSCard"); } + /* (non-Javadoc) + * @see at.gv.egiz.smcc.SignatureCard#getCertificate(at.gv.egiz.smcc.SignatureCard.KeyboxName) + */ public byte[] getCertificate(KeyboxName keyboxName) throws SignatureCardException { + byte[] aid; + byte[] efc; if (keyboxName == KeyboxName.SECURE_SIGNATURE_KEYPAIR) { - return readTLVFile(AID_DF_SS, EF_C_X509_CH_DS, 2000); + aid = AID_DF_SS; + efc = EF_C_X509_CH_DS; } else if (keyboxName == KeyboxName.CERITIFIED_KEYPAIR) { - return readTLVFile(AID_DF_GS, EF_C_X509_CH_AUT, 2000); + aid = AID_DF_GS; + efc = EF_C_X509_CH_AUT; } else { throw new IllegalArgumentException("Keybox " + keyboxName + " not supported."); } + log.debug("Get certificate for keybox '" + keyboxName.getKeyboxName() + "'" + + " (AID=" + toString(aid) + " EF=" + toString(efc) + ")."); + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + return readTLVFile(aid, efc, 2000); + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + + } + + /* (non-Javadoc) + * @see at.gv.egiz.smcc.SignatureCard#getInfobox(java.lang.String, at.gv.egiz.smcc.PINProvider, java.lang.String) + */ + public byte[] getInfobox(String infobox, PINProvider provider, String domainId) + throws SignatureCardException { + + if ("IdentityLink".equals(infobox)) { + + PINSpec spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString("card.pin.name")); + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + return readTLVFilePIN(AID_INFOBOX, EF_INFOBOX, KID_PIN_CARD, + provider, spec, 2000); + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + + } else { + throw new IllegalArgumentException("Infobox '" + infobox + + "' not supported."); + } + + } + + /* (non-Javadoc) + * @see at.gv.egiz.smcc.SignatureCard#createSignature(byte[], at.gv.egiz.smcc.SignatureCard.KeyboxName, at.gv.egiz.smcc.PINProvider) + */ + public byte[] createSignature(byte[] hash, KeyboxName keyboxName, + PINProvider provider) throws SignatureCardException { + + if (hash.length != 20) { + throw new IllegalArgumentException("Hash value must be of length 20."); + } + + byte[] aid; + byte kid; + byte[] dst; + PINSpec spec; + if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) { + aid = AID_DF_SS; + kid = KID_PIN_SS; + dst = DST_SS; + spec = new PINSpec(6, 10, "[0-9]", getResourceBundle().getString("sig.pin.name")); + + } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) { + aid = AID_DF_GS; + kid = KID_PIN_CARD; + dst = DST_GS; + spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString("card.pin.name")); + + } else { + throw new IllegalArgumentException("KeyboxName '" + keyboxName + + "' not supported."); + } + + try { + Card card = getCardChannel().getCard(); + try { + card.beginExclusive(); + + // SELECT MF + selectMF(); + // SELECT DF + selectFileAID(aid); + // VERIFY + verifyPIN(provider, spec, kid); + // MSE: SET DST + mseSetDST(dst); + // PSO: HASH + psoHash(hash); + // PSO: COMPUTE DIGITAL SIGNATURE + return psoComputDigitalSiganture(); + + + } catch (FileNotFoundException e) { + // if certificate is not present, + // the citizen card application has not been activated + throw new NotActivatedException(); + } finally { + card.endExclusive(); + } + } catch (CardException e) { + throw new SignatureCardException("Failed to get exclusive card access."); + } + } - byte[] selectFileAID(byte[] fid) throws CardException, SignatureCardException { + protected byte[] selectFileAID(byte[] fid) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0xA4, 0x04, 0x04, fid, 256)); @@ -150,16 +285,10 @@ public class STARCOSCard extends AbstractSignatureCard implements SignatureCard } } - byte[] selectFileFID(byte[] fid) throws CardException, SignatureCardException { + protected ResponseAPDU selectFileFID(byte[] fid) throws CardException, SignatureCardException { CardChannel channel = getCardChannel(); - ResponseAPDU resp = transmit(channel, new CommandAPDU(0x00, 0xA4, 0x02, + return transmit(channel, new CommandAPDU(0x00, 0xA4, 0x02, 0x04, fid, 256)); - if (resp.getSW() == 0x6a82) { - throw new SignatureCardException("Failed to select file (FID=" - + toString(fid) + "): SW=" + Integer.toHexString(resp.getSW()) + "."); - } else { - return resp.getBytes(); - } } void mseSetDST(byte[] dst) throws CardException, SignatureCardException { @@ -201,134 +330,86 @@ public class STARCOSCard extends AbstractSignatureCard implements SignatureCard } } - int verifyPIN(PINProvider pinProvider, PINSpec spec, byte kid, int kfpc) - throws CardException, SignatureCardException { - + /** + * VERIFY PIN + *

+ * If pin is null only the PIN status is checked and + * returned. + *

+ * + * @param pin + * the PIN (may be null) + * @param kid + * the KID of the PIN to be verified + * + * @return -1 if VERIFY PIN was successful, or the number of possible retries + * + * @throws CardException + * if communication with the smart card fails. + * @throws NotActivatedException + * if the card application has not been activated + * @throws SignatureCardException + * if VERIFY PIN fails + */ + private int verifyPIN(String pin, byte kid) throws CardException, SignatureCardException { + CardChannel channel = getCardChannel(); - // get number of possible retries - ResponseAPDU resp = transmit(channel, - new CommandAPDU(0x00, 0x20, 0x00, kid)); - int retries; - if (resp.getSW1() == 0x63 && resp.getSW2() >> 4 == 0xc) { - retries = resp.getSW2() & 0x0f; - } else if (resp.getSW() == 0x6984) { - // PIN LCS = "Initilized" (not activated) - throw new SignatureCardException(spec.getLocalizedName() + " not set."); - } else { - throw new SignatureCardException("Failed to get PIN retries: SW=" - + Integer.toHexString(resp.getSW())); - } - - // get PIN - String pin = pinProvider.providePIN(spec, retries); + ResponseAPDU resp; if (pin == null) { - // User canceled operation - // throw new CancelledException("User canceld PIN entry"); - return -2; - } - // PIN length in bytes - int len = (int) Math.ceil(pin.length() / 2); - - // BCD encode PIN and marshal PIN block - byte[] pinBytes = new BigInteger(pin, 16).toByteArray(); - byte[] pinBlock = new byte[8]; - if (len < pinBytes.length) { - System.arraycopy(pinBytes, pinBytes.length - len, pinBlock, 1, len); + resp = transmit(channel, new CommandAPDU(0x00, 0x20, 0x00, kid)); } else { - System.arraycopy(pinBytes, 0, pinBlock, len - pinBytes.length + 1, - pinBytes.length); + // PIN length in bytes + int len = (int) Math.ceil(pin.length() / 2); + + // BCD encode PIN and marshal PIN block + byte[] pinBytes = new BigInteger(pin, 16).toByteArray(); + byte[] pinBlock = new byte[8]; + if (len < pinBytes.length) { + System.arraycopy(pinBytes, pinBytes.length - len, pinBlock, 1, len); + } else { + System.arraycopy(pinBytes, 0, pinBlock, len - pinBytes.length + 1, + pinBytes.length); + } + pinBlock[0] = (byte) (0x20 + len * 2); + Arrays.fill(pinBlock, len + 1, 8, (byte) 0xff); + + resp = transmit(channel, new CommandAPDU(0x00, 0x20, 0x00, kid, pinBlock), false); + } - pinBlock[0] = (byte) (0x20 + len * 2); - Arrays.fill(pinBlock, len + 1, 8, (byte) 0xff); - resp = transmit(channel, new CommandAPDU(0x00, 0x20, 0x00, kid, pinBlock)); - if (resp.getSW1() == 0x63 && resp.getSW2() >> 4 == 0xc) { + if (resp.getSW() == 0x63c0) { + throw new LockedException("PIN locked."); + } else if (resp.getSW1() == 0x63 && resp.getSW2() >> 4 == 0xc) { + // return number of possible retries return resp.getSW2() & 0x0f; - } else if (resp.getSW() != 0x9000) { + } else if (resp.getSW() == 0x6984) { + // PIN LCS = "Initialized" (-> not activated) + throw new NotActivatedException("PIN not set."); + } else if (resp.getSW() == 0x9000) { + return -1; // success + } else { throw new SignatureCardException("Failed to verify pin: SW=" + Integer.toHexString(resp.getSW())); - } else { - return -1; - } - - } - - public byte[] createSignature(byte[] hash, KeyboxName keyboxName, - PINProvider provider) throws SignatureCardException { - - if (hash.length != 20) { - throw new IllegalArgumentException("Hash value must be of length 20"); - } - - byte[] aid; - byte kid; - byte[] dst; - PINSpec spec; - if (KeyboxName.SECURE_SIGNATURE_KEYPAIR.equals(keyboxName)) { - aid = AID_DF_SS; - kid = KID_PIN_SS; - dst = DST_SS; - spec = new PINSpec(6, 10, "[0-9]", getResourceBundle().getString("sig.pin.name")); - - } else if (KeyboxName.CERITIFIED_KEYPAIR.equals(keyboxName)) { - aid = AID_DF_GS; - kid = KID_PIN_CARD; - dst = DST_GS; - spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString("card.pin.name")); - - } else { - throw new IllegalArgumentException("KeyboxName '" + keyboxName - + "' not supported."); } - - try { - - // SELECT MF - selectMF(); - // SELECT DF - selectFileAID(aid); - // VERIFY - int retr = -1; // unknown - while (true) { - retr = verifyPIN(provider, spec, kid, retr); - if (retr < -1) { - return null; - } else if (retr < 0) { - break; - } - } - // MSE: SET DST - mseSetDST(dst); - // PSO: HASH - psoHash(hash); - // PSO: COMPUTE DIGITAL SIGNATURE - byte[] rs = psoComputDigitalSiganture(); - return rs; - - } catch (CardException e) { - throw new SignatureCardException("Failed to create signature.", e); - } - + } + + /* (non-Javadoc) + * @see at.gv.egiz.smcc.AbstractSignatureCard#verifyPIN(at.gv.egiz.smcc.PINProvider, at.gv.egiz.smcc.PINSpec, byte, int) + */ + protected void verifyPIN(PINProvider pinProvider, PINSpec spec, byte kid) + throws CardException, SignatureCardException { - public byte[] getInfobox(String infobox, PINProvider provider, String domainId) - throws SignatureCardException { - - if ("IdentityLink".equals(infobox)) { - - PINSpec spec = new PINSpec(4, 4, "[0-9]", getResourceBundle().getString("card.pin.name")); - try { - byte[] res = readTLVFilePIN(AID_INFOBOX, EF_INFOBOX, KID_PIN_CARD, - provider, spec, 2000); - return res; - } catch (Exception e) { - throw new SignatureCardException(e); + int retries = verifyPIN(null, kid); + do { + String pin = pinProvider.providePIN(spec, retries); + if (pin == null) { + // user canceled operation + throw new CancelledException("User canceld operation."); } - } else { - throw new IllegalArgumentException("Infobox '" + infobox - + "' not supported."); - } + retries = verifyPIN(pin, kid); + } while (retries > 0); } diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java b/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java index 68a6f6df..22a66c3f 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/SWCard.java @@ -25,6 +25,8 @@ import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyStore; @@ -52,16 +54,20 @@ import org.apache.commons.logging.LogFactory; */ public class SWCard implements SignatureCard { - private static final String BKU_USER_DIR = ".bku"; + 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; @@ -70,8 +76,12 @@ public class SWCard implements SignatureCard { private KeyStore certifiedKeyStore; + private String certifiedKeyStorePassword; + private KeyStore secureKeyStore; + private String secureKeyStorePassword; + private Certificate certifiedCertificate; private Certificate secureCertificate; @@ -168,7 +178,7 @@ public class SWCard implements SignatureCard { } try { - keyStore.load(keyStoreFile, null); + keyStore.load(keyStoreFile, password); } catch (Exception e) { String msg = "Failed to load KeyStore from file '" + fileName + "'."; log.info(msg, e); @@ -176,10 +186,33 @@ public class SWCard implements SignatureCard { } return keyStore; - } + private String 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(); + } 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) { @@ -198,6 +231,23 @@ public class SWCard implements SignatureCard { } + private String 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 { @@ -254,9 +304,21 @@ public class SWCard implements SignatureCard { public byte[] createSignature(byte[] hash, KeyboxName keyboxName, PINProvider provider) throws SignatureCardException { // KeyStore password - PINSpec pinSpec = new PINSpec(0, -1, ".", "KeyStore-Password"); - - KeyStore keyStore = getKeyStore(keyboxName, null); + String password = getPassword(keyboxName); + + if (password == null) { + + PINSpec pinSpec = new PINSpec(0, -1, ".", "KeyStore-Password"); + + password = provider.providePIN(pinSpec, -1); + + if (password == null) { + return null; + } + + } + + KeyStore keyStore = getKeyStore(keyboxName, password.toCharArray()); PrivateKey privateKey = null; @@ -269,8 +331,7 @@ public class SWCard implements SignatureCard { Key key = null; while (key == null) { try { - String pin = provider.providePIN(pinSpec, -1); - key = keyStore.getKey(alias, pin.toCharArray()); + key = keyStore.getKey(alias, password.toCharArray()); } catch (UnrecoverableKeyException e) { log.info("Failed to get Key from KeyStore. Wrong password?", e); } @@ -315,8 +376,6 @@ public class SWCard implements SignatureCard { @Override public void setLocale(Locale locale) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Not supported yet."); } @Override diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java index f2a964fe..f296f3a2 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardException.java @@ -34,7 +34,7 @@ public class SignatureCardException extends Exception { * */ private static final long serialVersionUID = 1L; - + /** * Creates a new instance of this SignatureCardException. * diff --git a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java index 2131a737..777299d9 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/SignatureCardFactory.java @@ -28,19 +28,189 @@ // 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 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 { + + /** + * 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; - public static SignatureCardFactory getInstance() { - return new SignatureCardFactory(); + /** + * 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 atrPattern or atrMask is + * null. + * @throws IllegalArgumentException + * if the lengths of atrPattern and + * atrMask 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 true if the given ATR matches the ATR pattern and + * mask of this SupportedCard object, or false + * 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 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(); + + // 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")); + // a-sign premium + 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")); + } + /** + * Creates a SignatureCard instance with the given smart card. + * + * @param card + * the smart card, or null if a software card should be + * created + * + * @return a SignatureCard instance + * + * @throws CardNotSupportedException + * if no implementation of the given card could be + * found + */ public SignatureCard createSignatureCard(Card card) throws CardNotSupportedException { @@ -51,31 +221,34 @@ public class SignatureCardFactory { } ATR atr = card.getATR(); - byte[] historicalBytes = atr.getHistoricalBytes(); - if(historicalBytes == null || historicalBytes.length < 3) { - throw new CardNotSupportedException("Card not supported: ATR=" + toString(atr.getBytes())); + Iterator 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.init(card); + 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); + } + + } } - int t = ((0xFF & (int) historicalBytes[0]) << 16) + - ((0xFF & (int) historicalBytes[1]) << 8) + - (0xFF & (int) historicalBytes[2]); - - SignatureCard sCard; - switch (t) { - case 0x455041 : - case 0x4D4341 : - sCard = new ACOSCard(); - break; - - case 0x805102 : - sCard = new STARCOSCard(); - break; - - default : - throw new CardNotSupportedException("Card not supported: ATR=" + toString(atr.getBytes())); - } - sCard.init(card); - return sCard; + throw new CardNotSupportedException("Card not supported: ATR=" + toString(atr.getBytes())); } 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 index ffffd3af..b70b44a7 100644 --- a/smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java +++ b/smcc/src/main/java/at/gv/egiz/smcc/util/SmartCardIO.java @@ -142,7 +142,8 @@ public class SmartCardIO { for (CardTerminal terminal : cardTerminals_.list(CardTerminals.State.CARD_INSERTION)) { Card card = null; - try { + try { + log.trace("Trying to connect to card."); // try to connect to card card = terminal.connect("*"); } catch (CardException e) { -- cgit v1.2.3