From 890c3c0d321c3b59ec1224d4630b3f2401ef94e7 Mon Sep 17 00:00:00 2001 From: tkellner Date: Tue, 7 Jan 2014 17:28:54 +0000 Subject: Add custom PC/SC library for MacOS X from OpeneCard project git-svn-id: https://joinup.ec.europa.eu/svn/mocca/trunk@1276 8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4 --- .../java/at/gv/egiz/smcc/util/SmartCardIO.java | 9 +- .../main/java/org/openecard/scio/osx/CardImpl.java | 289 ++++++++++++++++++++ .../java/org/openecard/scio/osx/ChannelImpl.java | 298 +++++++++++++++++++++ .../src/main/java/org/openecard/scio/osx/PCSC.java | 173 ++++++++++++ .../java/org/openecard/scio/osx/PCSCException.java | 178 ++++++++++++ .../java/org/openecard/scio/osx/PCSCTerminals.java | 290 ++++++++++++++++++++ .../java/org/openecard/scio/osx/PlatformPCSC.java | 125 +++++++++ .../java/org/openecard/scio/osx/SunOSXPCSC.java | 89 ++++++ .../java/org/openecard/scio/osx/TerminalImpl.java | 154 +++++++++++ .../java/org/openecard/scio/osx/package-info.java | 29 ++ .../egiz/smcc/osx-pcsc-jni/jre6.libosxj2pcsc.dylib | Bin 0 -> 39484 bytes .../egiz/smcc/osx-pcsc-jni/jre7.libosxj2pcsc.dylib | Bin 0 -> 14908 bytes 12 files changed, 1633 insertions(+), 1 deletion(-) create mode 100644 smcc/src/main/java/org/openecard/scio/osx/CardImpl.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/ChannelImpl.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/PCSC.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/PCSCException.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/PCSCTerminals.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/PlatformPCSC.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/SunOSXPCSC.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/TerminalImpl.java create mode 100644 smcc/src/main/java/org/openecard/scio/osx/package-info.java create mode 100644 smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre6.libosxj2pcsc.dylib create mode 100644 smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre7.libosxj2pcsc.dylib 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 08a1ea39..b646fb0a 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 @@ -37,9 +37,11 @@ import javax.smartcardio.CardTerminals; import javax.smartcardio.TerminalFactory; import javax.smartcardio.CardTerminals.State; +import org.openecard.scio.osx.SunOSXPCSC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * * @author mcentner @@ -65,7 +67,12 @@ public class SmartCardIO { private void updateTerminalFactory() { TerminalFactory terminalFactory; try { - terminalFactory = TerminalFactory.getInstance("PC/SC", null); + String osName = System.getProperty("os.name"); + if (osName.contains("OS X")) { + terminalFactory = TerminalFactory.getInstance("PC/SC", null, new SunOSXPCSC()); + } else { + terminalFactory = TerminalFactory.getInstance("PC/SC", null); + } } catch (NoSuchAlgorithmException e) { log.info("Failed to get TerminalFactory of type 'PC/SC'.", e); terminalFactory = TerminalFactory.getDefault(); diff --git a/smcc/src/main/java/org/openecard/scio/osx/CardImpl.java b/smcc/src/main/java/org/openecard/scio/osx/CardImpl.java new file mode 100644 index 00000000..de798817 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/CardImpl.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +import static org.openecard.scio.osx.PCSC.SCARD_LEAVE_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_RESET_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_SHARE_DIRECT; +import static org.openecard.scio.osx.PCSC.SCARD_SHARE_SHARED; +import static org.openecard.scio.osx.PCSC.SCARD_W_REMOVED_CARD; +import static org.openecard.scio.osx.PCSC.SCardBeginTransaction; +import static org.openecard.scio.osx.PCSC.SCardConnect; +import static org.openecard.scio.osx.PCSC.SCardControl; +import static org.openecard.scio.osx.PCSC.SCardDisconnect; +import static org.openecard.scio.osx.PCSC.SCardEndTransaction; +import static org.openecard.scio.osx.PCSC.SCardStatus; +import static org.openecard.scio.osx.PCSC.SCardTransmit; +import static org.openecard.scio.osx.PlatformPCSC.SCARD_PROTOCOL_T0; +import static org.openecard.scio.osx.PlatformPCSC.SCARD_PROTOCOL_T1; + +import javax.smartcardio.ATR; +import javax.smartcardio.Card; +import javax.smartcardio.CardChannel; +import javax.smartcardio.CardException; +import javax.smartcardio.CardPermission; + +/** + * Card implementation. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class CardImpl extends Card { + + private static enum State { + OK, REMOVED, DISCONNECTED + }; + + // the terminal that created this card + private final TerminalImpl terminal; + + // the native SCARDHANDLE + final long cardId; + + // atr of this card + private final ATR atr; + + // protocol in use, one of SCARD_PROTOCOL_T0 and SCARD_PROTOCOL_T1 + final int protocol; + + // the basic logical channel (channel 0) + private final ChannelImpl basicChannel; + + // state of this card connection + private volatile State state; + + // thread holding exclusive access to the card, or null + private volatile Thread exclusiveThread; + + CardImpl(TerminalImpl terminal, String protocol) throws PCSCException { + this.terminal = terminal; + int sharingMode = SCARD_SHARE_SHARED; + int connectProtocol; + if (protocol.equals("*")) { + connectProtocol = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1; + } else if (protocol.equalsIgnoreCase("T=0")) { + connectProtocol = SCARD_PROTOCOL_T0; + } else if (protocol.equalsIgnoreCase("T=1")) { + connectProtocol = SCARD_PROTOCOL_T1; + } else if (protocol.equalsIgnoreCase("direct")) { + // testing + connectProtocol = 0; + sharingMode = SCARD_SHARE_DIRECT; + } else { + throw new IllegalArgumentException("Unsupported protocol " + protocol); + } + cardId = SCardConnect(terminal.contextId, terminal.name, sharingMode, + connectProtocol); + byte[] status = new byte[2]; + byte[] atrBytes = SCardStatus(cardId, status); + atr = new ATR(atrBytes); + this.protocol = status[1] & 0xff; + basicChannel = new ChannelImpl(this, 0); + state = State.OK; + } + + void checkState() { + State s = state; + if (s == State.DISCONNECTED) { + throw new IllegalStateException("Card has been disconnected"); + } else if (s == State.REMOVED) { + throw new IllegalStateException("Card has been removed"); + } + } + + boolean isValid() { + if (state != State.OK) { + return false; + } + // ping card via SCardStatus + try { + SCardStatus(cardId, new byte[2]); + return true; + } catch (PCSCException e) { + state = State.REMOVED; + return false; + } + } + + private void checkSecurity(String action) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new CardPermission(terminal.name, action)); + } + } + + void handleError(PCSCException e) { + if (e.code == SCARD_W_REMOVED_CARD) { + state = State.REMOVED; + } + } + + public ATR getATR() { + return atr; + } + + public String getProtocol() { + switch (protocol) { + case SCARD_PROTOCOL_T0: + return "T=0"; + case SCARD_PROTOCOL_T1: + return "T=1"; + default: + // should never occur + return "Unknown protocol " + protocol; + } + } + + public CardChannel getBasicChannel() { + checkSecurity("getBasicChannel"); + checkState(); + return basicChannel; + } + + private static int getSW(byte[] b) { + if (b.length < 2) { + return -1; + } + int sw1 = b[b.length - 2] & 0xff; + int sw2 = b[b.length - 1] & 0xff; + return (sw1 << 8) | sw2; + } + + private static byte[] commandOpenChannel = new byte[] { 0, 0x70, 0, 0, 1 }; + + public CardChannel openLogicalChannel() throws CardException { + checkSecurity("openLogicalChannel"); + checkState(); + checkExclusive(); + try { + byte[] response = SCardTransmit(cardId, protocol, commandOpenChannel, 0, + commandOpenChannel.length); + if ((response.length != 3) || (getSW(response) != 0x9000)) { + throw new CardException("openLogicalChannel() failed, card response: " + + PCSC.toString(response)); + } + return new ChannelImpl(this, response[0]); + } catch (PCSCException e) { + handleError(e); + throw new CardException("openLogicalChannel() failed", e); + } + } + + void checkExclusive() throws CardException { + Thread t = exclusiveThread; + if (t == null) { + return; + } + if (t != Thread.currentThread()) { + throw new CardException("Exclusive access established by another Thread"); + } + } + + public synchronized void beginExclusive() throws CardException { + checkSecurity("exclusive"); + checkState(); + if (exclusiveThread != null) { + throw new CardException( + "Exclusive access has already been assigned to Thread " + + exclusiveThread.getName()); + } + try { + SCardBeginTransaction(cardId); + } catch (PCSCException e) { + handleError(e); + throw new CardException("beginExclusive() failed", e); + } + exclusiveThread = Thread.currentThread(); + } + + public synchronized void endExclusive() throws CardException { + checkState(); + if (exclusiveThread != Thread.currentThread()) { + throw new IllegalStateException( + "Exclusive access not assigned to current Thread"); + } + try { + SCardEndTransaction(cardId, SCARD_LEAVE_CARD); + } catch (PCSCException e) { + handleError(e); + throw new CardException("endExclusive() failed", e); + } finally { + exclusiveThread = null; + } + } + + public byte[] transmitControlCommand(int controlCode, byte[] command) + throws CardException { + checkSecurity("transmitControl"); + checkState(); + checkExclusive(); + if (command == null) { + throw new NullPointerException(); + } + try { + byte[] r = SCardControl(cardId, controlCode, command); + return r; + } catch (PCSCException e) { + handleError(e); + throw new CardException("transmitControlCommand() failed", e); + } + } + + public void disconnect(boolean reset) throws CardException { + if (reset) { + checkSecurity("reset"); + } + if (state != State.OK) { + return; + } + checkExclusive(); + try { + SCardDisconnect(cardId, (reset ? SCARD_LEAVE_CARD : SCARD_RESET_CARD)); + } catch (PCSCException e) { + throw new CardException("disconnect() failed", e); + } finally { + state = State.DISCONNECTED; + exclusiveThread = null; + } + } + + public String toString() { + return "PC/SC card in " + terminal.getName() + ", protocol " + + getProtocol() + ", state " + state; + } + + protected void finalize() throws Throwable { + try { + if (state == State.OK) { + SCardDisconnect(cardId, SCARD_LEAVE_CARD); + } + } finally { + super.finalize(); + } + } + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/ChannelImpl.java b/smcc/src/main/java/org/openecard/scio/osx/ChannelImpl.java new file mode 100644 index 00000000..1094fb9d --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/ChannelImpl.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +import static org.openecard.scio.osx.PCSC.SCardTransmit; +import static org.openecard.scio.osx.PlatformPCSC.SCARD_PROTOCOL_T0; +import static org.openecard.scio.osx.PlatformPCSC.SCARD_PROTOCOL_T1; + +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.security.AccessController; +import javax.smartcardio.Card; +import javax.smartcardio.CardChannel; +import javax.smartcardio.CardException; +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; + +import sun.security.action.GetPropertyAction; + +/** + * CardChannel implementation. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class ChannelImpl extends CardChannel { + + // the card this channel is associated with + private final CardImpl card; + + // the channel number, 0 for the basic logical channel + private final int channel; + + // whether this channel has been closed. only logical channels can be closed + private volatile boolean isClosed; + + ChannelImpl(CardImpl card, int channel) { + this.card = card; + this.channel = channel; + } + + void checkClosed() { + card.checkState(); + if (isClosed) { + throw new IllegalStateException("Logical channel has been closed"); + } + } + + public Card getCard() { + return card; + } + + public int getChannelNumber() { + checkClosed(); + return channel; + } + + private static void checkManageChannel(byte[] b) { + if (b.length < 4) { + throw new IllegalArgumentException( + "Command APDU must be at least 4 bytes long"); + } + if ((b[0] >= 0) && (b[1] == 0x70)) { + throw new IllegalArgumentException( + "Manage channel command not allowed, use openLogicalChannel()"); + } + } + + public ResponseAPDU transmit(CommandAPDU command) throws CardException { + checkClosed(); + card.checkExclusive(); + byte[] commandBytes = command.getBytes(); + byte[] responseBytes = doTransmit(commandBytes); + return new ResponseAPDU(responseBytes); + } + + public int transmit(ByteBuffer command, ByteBuffer response) + throws CardException { + checkClosed(); + card.checkExclusive(); + if ((command == null) || (response == null)) { + throw new NullPointerException(); + } + if (response.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + if (command == response) { + throw new IllegalArgumentException( + "command and response must not be the same object"); + } + if (response.remaining() < 258) { + throw new IllegalArgumentException( + "Insufficient space in response buffer"); + } + byte[] commandBytes = new byte[command.remaining()]; + command.get(commandBytes); + byte[] responseBytes = doTransmit(commandBytes); + response.put(responseBytes); + return responseBytes.length; + } + + private final static boolean t0GetResponse = getBooleanProperty( + "sun.security.smartcardio.t0GetResponse", true); + + private final static boolean t1GetResponse = getBooleanProperty( + "sun.security.smartcardio.t1GetResponse", true); + + private final static boolean t1StripLe = getBooleanProperty( + "sun.security.smartcardio.t1StripLe", false); + + private static boolean getBooleanProperty(String name, boolean def) { + String val = AccessController.doPrivileged(new GetPropertyAction(name)); + if (val == null) { + return def; + } + if (val.equalsIgnoreCase("true")) { + return true; + } else if (val.equalsIgnoreCase("false")) { + return false; + } else { + throw new IllegalArgumentException(name + + " must be either 'true' or 'false'"); + } + } + + private byte[] concat(byte[] b1, byte[] b2, int n2) { + int n1 = b1.length; + if ((n1 == 0) && (n2 == b2.length)) { + return b2; + } + byte[] res = new byte[n1 + n2]; + System.arraycopy(b1, 0, res, 0, n1); + System.arraycopy(b2, 0, res, n1, n2); + return res; + } + + private final static byte[] B0 = new byte[0]; + + private byte[] doTransmit(byte[] command) throws CardException { + // note that we modify the 'command' array in some cases, so it must + // be a copy of the application provided data. + try { + checkManageChannel(command); + setChannel(command); + int n = command.length; + boolean t0 = card.protocol == SCARD_PROTOCOL_T0; + boolean t1 = card.protocol == SCARD_PROTOCOL_T1; + if (t0 && (n >= 7) && (command[4] == 0)) { + throw new CardException("Extended length forms not supported for T=0"); + } + if ((t0 || (t1 && t1StripLe)) && (n >= 7)) { + int lc = command[4] & 0xff; + if (lc != 0) { + if (n == lc + 6) { + n--; + } + } else { + lc = ((command[5] & 0xff) << 8) | (command[6] & 0xff); + if (n == lc + 9) { + n -= 2; + } + } + } + boolean getresponse = (t0 && t0GetResponse) || (t1 && t1GetResponse); + int k = 0; + byte[] result = B0; + while (true) { + if (++k >= 32) { + throw new CardException("Could not obtain response"); + } + byte[] response = SCardTransmit(card.cardId, card.protocol, command, 0, + n); + int rn = response.length; + if (getresponse && (rn >= 2)) { + // see ISO 7816/2005, 5.1.3 + if ((rn == 2) && (response[0] == 0x6c)) { + // Resend command using SW2 as short Le field + command[n - 1] = response[1]; + continue; + } + if (response[rn - 2] == 0x61) { + // Issue a GET RESPONSE command with the same CLA + // using SW2 as short Le field + if (rn > 2) { + result = concat(result, response, rn - 2); + } + command[1] = (byte) 0xC0; + command[2] = 0; + command[3] = 0; + command[4] = response[rn - 1]; + n = 5; + continue; + } + + } + result = concat(result, response, rn); + break; + } + return result; + } catch (PCSCException e) { + card.handleError(e); + throw new CardException(e); + } + } + + private static int getSW(byte[] res) throws CardException { + if (res.length < 2) { + throw new CardException("Invalid response length: " + res.length); + } + int sw1 = res[res.length - 2] & 0xff; + int sw2 = res[res.length - 1] & 0xff; + return (sw1 << 8) | sw2; + } + + private static boolean isOK(byte[] res) throws CardException { + return (res.length == 2) && (getSW(res) == 0x9000); + } + + private void setChannel(byte[] com) { + int cla = com[0]; + if (cla < 0) { + // proprietary class format, cannot set or check logical channel + // for now, just return + return; + } + // classes 001x xxxx is reserved for future use in ISO, ignore + if ((cla & 0xe0) == 0x20) { + return; + } + // see ISO 7816/2005, table 2 and 3 + if (channel <= 3) { + // mask of bits 7, 1, 0 (channel number) + // 0xbc == 1011 1100 + com[0] &= 0xbc; + com[0] |= channel; + } else if (channel <= 19) { + // mask of bits 7, 3, 2, 1, 0 (channel number) + // 0xbc == 1011 0000 + com[0] &= 0xb0; + com[0] |= 0x40; + com[0] |= (channel - 4); + } else { + throw new RuntimeException("Unsupported channel number: " + channel); + } + } + + public void close() throws CardException { + if (getChannelNumber() == 0) { + throw new IllegalStateException("Cannot close basic logical channel"); + } + if (isClosed) { + return; + } + card.checkExclusive(); + try { + byte[] com = new byte[] { 0x00, 0x70, (byte) 0x80, 0 }; + com[3] = (byte) getChannelNumber(); + setChannel(com); + byte[] res = SCardTransmit(card.cardId, card.protocol, com, 0, com.length); + if (isOK(res) == false) { + throw new CardException("close() failed: " + PCSC.toString(res)); + } + } catch (PCSCException e) { + card.handleError(e); + throw new CardException("Could not close channel", e); + } finally { + isClosed = true; + } + } + + public String toString() { + return "PC/SC channel " + channel; + } + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/PCSC.java b/smcc/src/main/java/org/openecard/scio/osx/PCSC.java new file mode 100644 index 00000000..1cdba441 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/PCSC.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +/** + * Access to native PC/SC functions and definition of PC/SC constants. + * Initialization and platform specific PC/SC constants are handled in the + * platform specific superclass. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class PCSC extends PlatformPCSC { + + private PCSC() { + // no instantiation + } + + static void checkAvailable() throws RuntimeException { + if (INIT_EXCEPTION != null) { + throw new UnsupportedOperationException( + "PC/SC not available on this platform", INIT_EXCEPTION); + } + } + + // returns SCARDCONTEXT (contextId) + static native long SCardEstablishContext(int scope) throws PCSCException; + + static native String[] SCardListReaders(long contextId) throws PCSCException; + + // returns SCARDHANDLE (cardId) + static native long SCardConnect(long contextId, String readerName, + int shareMode, int preferredProtocols) throws PCSCException; + + static native byte[] SCardTransmit(long cardId, int protocol, byte[] buf, + int ofs, int len) throws PCSCException; + + // returns the ATR of the card, updates status[] with reader state and + // protocol + static native byte[] SCardStatus(long cardId, byte[] status) + throws PCSCException; + + static native void SCardDisconnect(long cardId, int disposition) + throws PCSCException; + + // returns dwEventState[] of the same size and order as readerNames[] + static native int[] SCardGetStatusChange(long contextId, long timeout, + int[] currentState, String[] readerNames) throws PCSCException; + + static native void SCardBeginTransaction(long cardId) throws PCSCException; + + static native void SCardEndTransaction(long cardId, int disposition) + throws PCSCException; + + static native byte[] SCardControl(long cardId, int controlCode, + byte[] sendBuffer) throws PCSCException; + + // PCSC success/error/failure/warning codes + final static int SCARD_S_SUCCESS = 0x00000000; + final static int SCARD_E_CANCELLED = 0x80100002; + final static int SCARD_E_CANT_DISPOSE = 0x8010000E; + final static int SCARD_E_INSUFFICIENT_BUFFER = 0x80100008; + final static int SCARD_E_INVALID_ATR = 0x80100015; + final static int SCARD_E_INVALID_HANDLE = 0x80100003; + final static int SCARD_E_INVALID_PARAMETER = 0x80100004; + final static int SCARD_E_INVALID_TARGET = 0x80100005; + final static int SCARD_E_INVALID_VALUE = 0x80100011; + final static int SCARD_E_NO_MEMORY = 0x80100006; + final static int SCARD_F_COMM_ERROR = 0x80100013; + final static int SCARD_F_INTERNAL_ERROR = 0x80100001; + final static int SCARD_F_UNKNOWN_ERROR = 0x80100014; + final static int SCARD_F_WAITED_TOO_LONG = 0x80100007; + final static int SCARD_E_UNKNOWN_READER = 0x80100009; + final static int SCARD_E_TIMEOUT = 0x8010000A; + final static int SCARD_E_SHARING_VIOLATION = 0x8010000B; + final static int SCARD_E_NO_SMARTCARD = 0x8010000C; + final static int SCARD_E_UNKNOWN_CARD = 0x8010000D; + final static int SCARD_E_PROTO_MISMATCH = 0x8010000F; + final static int SCARD_E_NOT_READY = 0x80100010; + final static int SCARD_E_SYSTEM_CANCELLED = 0x80100012; + final static int SCARD_E_NOT_TRANSACTED = 0x80100016; + final static int SCARD_E_READER_UNAVAILABLE = 0x80100017; + + final static int SCARD_W_UNSUPPORTED_CARD = 0x80100065; + final static int SCARD_W_UNRESPONSIVE_CARD = 0x80100066; + final static int SCARD_W_UNPOWERED_CARD = 0x80100067; + final static int SCARD_W_RESET_CARD = 0x80100068; + final static int SCARD_W_REMOVED_CARD = 0x80100069; + final static int SCARD_W_INSERTED_CARD = 0x8010006A; + + final static int SCARD_E_UNSUPPORTED_FEATURE = 0x8010001F; + final static int SCARD_E_PCI_TOO_SMALL = 0x80100019; + final static int SCARD_E_READER_UNSUPPORTED = 0x8010001A; + final static int SCARD_E_DUPLICATE_READER = 0x8010001B; + final static int SCARD_E_CARD_UNSUPPORTED = 0x8010001C; + final static int SCARD_E_NO_SERVICE = 0x8010001D; + final static int SCARD_E_SERVICE_STOPPED = 0x8010001E; + + // MS undocumented + final static int SCARD_E_NO_READERS_AVAILABLE = 0x8010002E; + // std. Windows invalid handle return code, used instead of SCARD code + final static int WINDOWS_ERROR_INVALID_HANDLE = 6; + final static int WINDOWS_ERROR_INVALID_PARAMETER = 87; + + // + final static int SCARD_SCOPE_USER = 0x0000; + final static int SCARD_SCOPE_TERMINAL = 0x0001; + final static int SCARD_SCOPE_SYSTEM = 0x0002; + final static int SCARD_SCOPE_GLOBAL = 0x0003; + + final static int SCARD_SHARE_EXCLUSIVE = 0x0001; + final static int SCARD_SHARE_SHARED = 0x0002; + final static int SCARD_SHARE_DIRECT = 0x0003; + + final static int SCARD_LEAVE_CARD = 0x0000; + final static int SCARD_RESET_CARD = 0x0001; + final static int SCARD_UNPOWER_CARD = 0x0002; + final static int SCARD_EJECT_CARD = 0x0003; + + final static int SCARD_STATE_UNAWARE = 0x0000; + final static int SCARD_STATE_IGNORE = 0x0001; + final static int SCARD_STATE_CHANGED = 0x0002; + final static int SCARD_STATE_UNKNOWN = 0x0004; + final static int SCARD_STATE_UNAVAILABLE = 0x0008; + final static int SCARD_STATE_EMPTY = 0x0010; + final static int SCARD_STATE_PRESENT = 0x0020; + final static int SCARD_STATE_ATRMATCH = 0x0040; + final static int SCARD_STATE_EXCLUSIVE = 0x0080; + final static int SCARD_STATE_INUSE = 0x0100; + final static int SCARD_STATE_MUTE = 0x0200; + final static int SCARD_STATE_UNPOWERED = 0x0400; + + final static int TIMEOUT_INFINITE = 0xffffffff; + + private final static char[] hexDigits = "0123456789abcdef".toCharArray(); + + public static String toString(byte[] b) { + StringBuffer sb = new StringBuffer(b.length * 3); + for (int i = 0; i < b.length; i++) { + int k = b[i] & 0xff; + if (i != 0) { + sb.append(':'); + } + sb.append(hexDigits[k >>> 4]); + sb.append(hexDigits[k & 0xf]); + } + return sb.toString(); + } + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/PCSCException.java b/smcc/src/main/java/org/openecard/scio/osx/PCSCException.java new file mode 100644 index 00000000..fc25be1d --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/PCSCException.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +import static org.openecard.scio.osx.PCSC.SCARD_E_CANCELLED; +import static org.openecard.scio.osx.PCSC.SCARD_E_CANT_DISPOSE; +import static org.openecard.scio.osx.PCSC.SCARD_E_CARD_UNSUPPORTED; +import static org.openecard.scio.osx.PCSC.SCARD_E_DUPLICATE_READER; +import static org.openecard.scio.osx.PCSC.SCARD_E_INSUFFICIENT_BUFFER; +import static org.openecard.scio.osx.PCSC.SCARD_E_INVALID_ATR; +import static org.openecard.scio.osx.PCSC.SCARD_E_INVALID_HANDLE; +import static org.openecard.scio.osx.PCSC.SCARD_E_INVALID_PARAMETER; +import static org.openecard.scio.osx.PCSC.SCARD_E_INVALID_TARGET; +import static org.openecard.scio.osx.PCSC.SCARD_E_INVALID_VALUE; +import static org.openecard.scio.osx.PCSC.SCARD_E_NOT_READY; +import static org.openecard.scio.osx.PCSC.SCARD_E_NOT_TRANSACTED; +import static org.openecard.scio.osx.PCSC.SCARD_E_NO_MEMORY; +import static org.openecard.scio.osx.PCSC.SCARD_E_NO_READERS_AVAILABLE; +import static org.openecard.scio.osx.PCSC.SCARD_E_NO_SERVICE; +import static org.openecard.scio.osx.PCSC.SCARD_E_NO_SMARTCARD; +import static org.openecard.scio.osx.PCSC.SCARD_E_PCI_TOO_SMALL; +import static org.openecard.scio.osx.PCSC.SCARD_E_PROTO_MISMATCH; +import static org.openecard.scio.osx.PCSC.SCARD_E_READER_UNAVAILABLE; +import static org.openecard.scio.osx.PCSC.SCARD_E_READER_UNSUPPORTED; +import static org.openecard.scio.osx.PCSC.SCARD_E_SERVICE_STOPPED; +import static org.openecard.scio.osx.PCSC.SCARD_E_SHARING_VIOLATION; +import static org.openecard.scio.osx.PCSC.SCARD_E_SYSTEM_CANCELLED; +import static org.openecard.scio.osx.PCSC.SCARD_E_TIMEOUT; +import static org.openecard.scio.osx.PCSC.SCARD_E_UNKNOWN_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_E_UNKNOWN_READER; +import static org.openecard.scio.osx.PCSC.SCARD_E_UNSUPPORTED_FEATURE; +import static org.openecard.scio.osx.PCSC.SCARD_F_COMM_ERROR; +import static org.openecard.scio.osx.PCSC.SCARD_F_INTERNAL_ERROR; +import static org.openecard.scio.osx.PCSC.SCARD_F_UNKNOWN_ERROR; +import static org.openecard.scio.osx.PCSC.SCARD_F_WAITED_TOO_LONG; +import static org.openecard.scio.osx.PCSC.SCARD_S_SUCCESS; +import static org.openecard.scio.osx.PCSC.SCARD_W_INSERTED_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_W_REMOVED_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_W_RESET_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_W_UNPOWERED_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_W_UNRESPONSIVE_CARD; +import static org.openecard.scio.osx.PCSC.SCARD_W_UNSUPPORTED_CARD; +import static org.openecard.scio.osx.PCSC.WINDOWS_ERROR_INVALID_HANDLE; +import static org.openecard.scio.osx.PCSC.WINDOWS_ERROR_INVALID_PARAMETER; + +/** + * Exception for PC/SC errors. The native code portion checks the return value + * of the SCard* functions. If it indicates an error, the native code constructs + * an instance of this exception, throws it, and returns to Java. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class PCSCException extends Exception { + + private static final long serialVersionUID = -3703197031255879618L; + + final int code; + + PCSCException(int code) { + super(toErrorString(code)); + this.code = code; + } + + private static String toErrorString(int code) { + switch (code) { + case SCARD_S_SUCCESS: + return "SCARD_S_SUCCESS"; + case SCARD_E_CANCELLED: + return "SCARD_E_CANCELLED"; + case SCARD_E_CANT_DISPOSE: + return "SCARD_E_CANT_DISPOSE"; + case SCARD_E_INSUFFICIENT_BUFFER: + return "SCARD_E_INSUFFICIENT_BUFFER"; + case SCARD_E_INVALID_ATR: + return "SCARD_E_INVALID_ATR"; + case SCARD_E_INVALID_HANDLE: + return "SCARD_E_INVALID_HANDLE"; + case SCARD_E_INVALID_PARAMETER: + return "SCARD_E_INVALID_PARAMETER"; + case SCARD_E_INVALID_TARGET: + return "SCARD_E_INVALID_TARGET"; + case SCARD_E_INVALID_VALUE: + return "SCARD_E_INVALID_VALUE"; + case SCARD_E_NO_MEMORY: + return "SCARD_E_NO_MEMORY"; + case SCARD_F_COMM_ERROR: + return "SCARD_F_COMM_ERROR"; + case SCARD_F_INTERNAL_ERROR: + return "SCARD_F_INTERNAL_ERROR"; + case SCARD_F_UNKNOWN_ERROR: + return "SCARD_F_UNKNOWN_ERROR"; + case SCARD_F_WAITED_TOO_LONG: + return "SCARD_F_WAITED_TOO_LONG"; + case SCARD_E_UNKNOWN_READER: + return "SCARD_E_UNKNOWN_READER"; + case SCARD_E_TIMEOUT: + return "SCARD_E_TIMEOUT"; + case SCARD_E_SHARING_VIOLATION: + return "SCARD_E_SHARING_VIOLATION"; + case SCARD_E_NO_SMARTCARD: + return "SCARD_E_NO_SMARTCARD"; + case SCARD_E_UNKNOWN_CARD: + return "SCARD_E_UNKNOWN_CARD"; + case SCARD_E_PROTO_MISMATCH: + return "SCARD_E_PROTO_MISMATCH"; + case SCARD_E_NOT_READY: + return "SCARD_E_NOT_READY"; + case SCARD_E_SYSTEM_CANCELLED: + return "SCARD_E_SYSTEM_CANCELLED"; + case SCARD_E_NOT_TRANSACTED: + return "SCARD_E_NOT_TRANSACTED"; + case SCARD_E_READER_UNAVAILABLE: + return "SCARD_E_READER_UNAVAILABLE"; + + case SCARD_W_UNSUPPORTED_CARD: + return "SCARD_W_UNSUPPORTED_CARD"; + case SCARD_W_UNRESPONSIVE_CARD: + return "SCARD_W_UNRESPONSIVE_CARD"; + case SCARD_W_UNPOWERED_CARD: + return "SCARD_W_UNPOWERED_CARD"; + case SCARD_W_RESET_CARD: + return "SCARD_W_RESET_CARD"; + case SCARD_W_REMOVED_CARD: + return "SCARD_W_REMOVED_CARD"; + case SCARD_W_INSERTED_CARD: + return "SCARD_W_INSERTED_CARD"; + + case SCARD_E_UNSUPPORTED_FEATURE: + return "SCARD_E_UNSUPPORTED_FEATURE"; + case SCARD_E_PCI_TOO_SMALL: + return "SCARD_E_PCI_TOO_SMALL"; + case SCARD_E_READER_UNSUPPORTED: + return "SCARD_E_READER_UNSUPPORTED"; + case SCARD_E_DUPLICATE_READER: + return "SCARD_E_DUPLICATE_READER"; + case SCARD_E_CARD_UNSUPPORTED: + return "SCARD_E_CARD_UNSUPPORTED"; + case SCARD_E_NO_SERVICE: + return "SCARD_E_NO_SERVICE"; + case SCARD_E_SERVICE_STOPPED: + return "SCARD_E_SERVICE_STOPPED"; + + case SCARD_E_NO_READERS_AVAILABLE: + return "SCARD_E_NO_READERS_AVAILABLE"; + case WINDOWS_ERROR_INVALID_HANDLE: + return "WINDOWS_ERROR_INVALID_HANDLE"; + case WINDOWS_ERROR_INVALID_PARAMETER: + return "WINDOWS_ERROR_INVALID_PARAMETER"; + + default: + return "Unknown error 0x" + Integer.toHexString(code); + } + } +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/PCSCTerminals.java b/smcc/src/main/java/org/openecard/scio/osx/PCSCTerminals.java new file mode 100644 index 00000000..495d0679 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/PCSCTerminals.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +import static javax.smartcardio.CardTerminals.State.CARD_ABSENT; +import static javax.smartcardio.CardTerminals.State.CARD_INSERTION; +import static javax.smartcardio.CardTerminals.State.CARD_PRESENT; +import static javax.smartcardio.CardTerminals.State.CARD_REMOVAL; +import static org.openecard.scio.osx.PCSC.SCARD_E_TIMEOUT; +import static org.openecard.scio.osx.PCSC.SCARD_SCOPE_USER; +import static org.openecard.scio.osx.PCSC.SCARD_STATE_PRESENT; +import static org.openecard.scio.osx.PCSC.SCARD_STATE_UNAWARE; +import static org.openecard.scio.osx.PCSC.SCardEstablishContext; +import static org.openecard.scio.osx.PCSC.SCardGetStatusChange; +import static org.openecard.scio.osx.PCSC.SCardListReaders; +import static org.openecard.scio.osx.PCSC.TIMEOUT_INFINITE; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.smartcardio.CardException; +import javax.smartcardio.CardTerminal; +import javax.smartcardio.CardTerminals; + +/** + * TerminalFactorySpi implementation class. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class PCSCTerminals extends CardTerminals { + + // SCARDCONTEXT, currently shared between all threads/terminals + private static long contextId; + + // terminal state used by waitForCard() + private Map stateMap; + + PCSCTerminals() { + // empty + } + + static synchronized void initContext() throws PCSCException { + if (contextId == 0) { + contextId = SCardEstablishContext(SCARD_SCOPE_USER); + } + } + + private static final Map> terminals = new HashMap>(); + + private static synchronized TerminalImpl implGetTerminal(String name) { + Reference ref = terminals.get(name); + TerminalImpl terminal = (ref != null) ? ref.get() : null; + if (terminal != null) { + return terminal; + } + terminal = new TerminalImpl(contextId, name); + terminals.put(name, new WeakReference(terminal)); + return terminal; + + } + + public synchronized List list(State state) throws CardException { + if (state == null) { + throw new NullPointerException(); + } + try { + String[] readerNames = SCardListReaders(contextId); + List list = new ArrayList(readerNames.length); + if (stateMap == null) { + // If waitForChange() has never been called, treat event + // queries as status queries. + if (state == CARD_INSERTION) { + state = CARD_PRESENT; + } else if (state == CARD_REMOVAL) { + state = CARD_ABSENT; + } + } + for (String readerName : readerNames) { + CardTerminal terminal = implGetTerminal(readerName); + ReaderState readerState; + switch (state) { + case ALL: + list.add(terminal); + break; + case CARD_PRESENT: + if (terminal.isCardPresent()) { + list.add(terminal); + } + break; + case CARD_ABSENT: + if (terminal.isCardPresent() == false) { + list.add(terminal); + } + break; + case CARD_INSERTION: + readerState = stateMap.get(readerName); + if ((readerState != null) && readerState.isInsertion()) { + list.add(terminal); + } + break; + case CARD_REMOVAL: + readerState = stateMap.get(readerName); + if ((readerState != null) && readerState.isRemoval()) { + list.add(terminal); + } + break; + default: + throw new CardException("Unknown state: " + state); + } + } + return Collections.unmodifiableList(list); + } catch (PCSCException e) { + throw new CardException("list() failed", e); + } + } + + private static class ReaderState { + private int current, previous; + + ReaderState() { + current = SCARD_STATE_UNAWARE; + previous = SCARD_STATE_UNAWARE; + } + + int get() { + return current; + } + + void update(int newState) { + previous = current; + current = newState; + } + + boolean isInsertion() { + return !present(previous) && present(current); + } + + boolean isRemoval() { + return present(previous) && !present(current); + } + + static boolean present(int state) { + return (state & SCARD_STATE_PRESENT) != 0; + } + } + + public synchronized boolean waitForChange(long timeout) throws CardException { + if (timeout < 0) { + throw new IllegalArgumentException("Timeout must not be negative: " + + timeout); + } + if (stateMap == null) { + // We need to initialize the state database. + // Do that with a recursive call, which will return immediately + // because we pass SCARD_STATE_UNAWARE. + // After that, proceed with the real call. + stateMap = new HashMap(); + waitForChange(0); + } + if (timeout == 0) { + timeout = TIMEOUT_INFINITE; + } + try { + String[] readerNames = SCardListReaders(contextId); + int n = readerNames.length; + if (n == 0) { + throw new IllegalStateException("No terminals available"); + } + int[] status = new int[n]; + ReaderState[] readerStates = new ReaderState[n]; + for (int i = 0; i < readerNames.length; i++) { + String name = readerNames[i]; + ReaderState state = stateMap.get(name); + if (state == null) { + state = new ReaderState(); + } + readerStates[i] = state; + status[i] = state.get(); + } + status = SCardGetStatusChange(contextId, timeout, status, readerNames); + stateMap.clear(); // remove any readers that are no longer available + for (int i = 0; i < n; i++) { + ReaderState state = readerStates[i]; + state.update(status[i]); + stateMap.put(readerNames[i], state); + } + return true; + } catch (PCSCException e) { + if (e.code == SCARD_E_TIMEOUT) { + return false; + } else { + throw new CardException("waitForChange() failed", e); + } + } + } + + static List waitForCards( + List terminals, long timeout, boolean wantPresent) + throws CardException { + // the argument sanity checks are performed in + // javax.smartcardio.TerminalFactory or TerminalImpl + + long thisTimeout; + if (timeout == 0) { + timeout = TIMEOUT_INFINITE; + thisTimeout = TIMEOUT_INFINITE; + } else { + // if timeout is not infinite, do the initial call that retrieves + // the status with a 0 timeout. Otherwise, we might get incorrect + // timeout exceptions (seen on Solaris with PC/SC shim) + thisTimeout = 0; + } + + String[] names = new String[terminals.size()]; + int i = 0; + for (CardTerminal terminal : terminals) { + if (terminal instanceof TerminalImpl == false) { + throw new IllegalArgumentException("Invalid terminal type: " + + terminal.getClass().getName()); + } + TerminalImpl impl = (TerminalImpl) terminal; + names[i++] = impl.name; + } + + int[] status = new int[names.length]; + Arrays.fill(status, SCARD_STATE_UNAWARE); + + try { + while (true) { + // note that we pass "timeout" on each native PC/SC call + // that means that if we end up making multiple (more than 2) + // calls, we might wait too long. + // for now assume that is unlikely and not a problem. + status = SCardGetStatusChange(contextId, thisTimeout, status, names); + thisTimeout = timeout; + + List results = null; + for (i = 0; i < names.length; i++) { + boolean nowPresent = (status[i] & SCARD_STATE_PRESENT) != 0; + if (nowPresent == wantPresent) { + if (results == null) { + results = new ArrayList(); + } + results.add(implGetTerminal(names[i])); + } + } + + if (results != null) { + return Collections.unmodifiableList(results); + } + } + } catch (PCSCException e) { + if (e.code == SCARD_E_TIMEOUT) { + return Collections.emptyList(); + } else { + throw new CardException("waitForCard() failed", e); + } + } + } + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/PlatformPCSC.java b/smcc/src/main/java/org/openecard/scio/osx/PlatformPCSC.java new file mode 100644 index 00000000..bbc48bc0 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/PlatformPCSC.java @@ -0,0 +1,125 @@ +/**************************************************************************** + * Copyright (C) 2012-2013 ecsec GmbH. + * All rights reserved. + * Contact: ecsec GmbH (info@ecsec.de) + * + * This file is part of the Open eCard App. + * + * GNU General Public License Usage + * This file may be used under the terms of the GNU General Public + * License version 3.0 as published by the Free Software Foundation + * and appearing in the file LICENSE.GPL included in the packaging of + * this file. Please review the following information to ensure the + * GNU General Public License version 3.0 requirements will be met: + * http://www.gnu.org/copyleft/gpl.html. + * + * Other Usage + * Alternatively, this file may be used in accordance with the terms + * and conditions contained in a signed written agreement between + * you and ecsec GmbH. + * + ***************************************************************************/ + +package org.openecard.scio.osx; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OS X specific PlatformPCSC. For more information see {@link package-info}. + * + * @author Benedikt Biallowons + */ +public class PlatformPCSC { + + private static final String PCSC_JNI_LIBRARY_PATH = "/at/gv/egiz/smcc/osx-pcsc-jni/"; + private static final String PCSC_JNI_LIBRARY_NAME = "libosxj2pcsc.dylib"; + private static final String PCSC_FRAMEWORK = "/System/Library/Frameworks/PCSC.framework/Versions/Current/PCSC"; + + private final static Logger log = LoggerFactory.getLogger(PlatformPCSC.class); + + public static final int SCARD_PROTOCOL_T0 = 0x0001; + public static final int SCARD_PROTOCOL_T1 = 0x0002; + public static final int SCARD_PROTOCOL_RAW = 0x0004; + public static final int SCARD_UNKNOWN = 0x0001; + public static final int SCARD_ABSENT = 0x0002; + public static final int SCARD_PRESENT = 0x0004; + public static final int SCARD_SWALLOWED = 0x0008; + public static final int SCARD_POWERED = 0x0010; + public static final int SCARD_NEGOTIABLE = 0x0020; + public static final int SCARD_SPECIFIC = 0x0040; + + public static final Exception INIT_EXCEPTION; + + static { + INIT_EXCEPTION = AccessController + .doPrivileged(new PrivilegedAction() { + public Exception run() { + try { + String[] parts = PCSC_JNI_LIBRARY_NAME.split("\\."); + + File tempFile = File.createTempFile(parts[0], parts[1]); + tempFile.deleteOnExit(); + + log.debug("Copying " + PCSC_JNI_LIBRARY_PATH + getLibrary() + " to " + tempFile); + InputStream is = PlatformPCSC.class + .getResourceAsStream(PCSC_JNI_LIBRARY_PATH + getLibrary()); + + if (is == null) { + return new FileNotFoundException(getLibrary() + " not found."); + } + + OutputStream os = new FileOutputStream(tempFile); + + try { + byte[] buffer = new byte[1024]; + int readBytes; + + while ((readBytes = is.read(buffer)) != -1) { + os.write(buffer, 0, readBytes); + } + + log.debug("loading " + tempFile.getAbsolutePath()); + System.load(tempFile.getAbsolutePath()); + initialize(PCSC_FRAMEWORK); + } finally { + os.close(); + is.close(); + } + + return null; + } catch (Exception e) { + return e; + } + } + }); + } + + /** + * Chooses a suitable JNI library depending the current JRE. + * + * @return library filename + */ + private static String getLibrary() { + String javaVersion = System.getProperty("java.version"); + + if (javaVersion.startsWith("1.6")) { + return "jre6." + PCSC_JNI_LIBRARY_NAME; + } else if (javaVersion.startsWith("1.7")) { + return "jre7." + PCSC_JNI_LIBRARY_NAME; + } + + return null; + } + + private static native void initialize(String libraryName); + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/SunOSXPCSC.java b/smcc/src/main/java/org/openecard/scio/osx/SunOSXPCSC.java new file mode 100644 index 00000000..f05d0373 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/SunOSXPCSC.java @@ -0,0 +1,89 @@ +/**************************************************************************** + * Copyright (C) 2012-2013 ecsec GmbH. + * All rights reserved. + * Contact: ecsec GmbH (info@ecsec.de) + * + * This file is part of the Open eCard App. + * + * GNU General Public License Usage + * This file may be used under the terms of the GNU General Public + * License version 3.0 as published by the Free Software Foundation + * and appearing in the file LICENSE.GPL included in the packaging of + * this file. Please review the following information to ensure the + * GNU General Public License version 3.0 requirements will be met: + * http://www.gnu.org/copyleft/gpl.html. + * + * Other Usage + * Alternatively, this file may be used in accordance with the terms + * and conditions contained in a signed written agreement between + * you and ecsec GmbH. + * + ***************************************************************************/ + +package org.openecard.scio.osx; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.Provider; +import javax.smartcardio.CardTerminals; +import javax.smartcardio.TerminalFactorySpi; + +/** + * OS X PC/SC provider. For more information see {@link package-info}. + * + * @author Benedikt Biallowons + */ +public final class SunOSXPCSC extends Provider { + + private static final long serialVersionUID = -8720099822090870310L; + + /** + * Default constructor registering this provider. + */ + public SunOSXPCSC() { + super("SunOSXPCSC", 1.0d, "Sun OS X PC/SC provider"); + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + put("TerminalFactory.PC/SC", + "org.openecard.scio.osx.SunOSXPCSC$Factory"); + return null; + } + }); + } + + /** + * Internal provider factory. For more information see + * {@link TerminalFactorySpi} + * + * @author Benedikt Biallowons + */ + public static final class Factory extends TerminalFactorySpi { + /** + * Constructor checking availability & initializing context. + * + * @param obj + * provider parameters + * @throws PCSCException + * if problems occur. + */ + public Factory(Object obj) throws PCSCException { + if (obj != null) { + throw new IllegalArgumentException( + "SunOSXPCSC factory doesn't take parameters"); + } + PCSC.checkAvailable(); + PCSCTerminals.initContext(); + } + + /** + * Returns the available readers. This must be a new object for each call. + * + * @return CardTerminals card terminals wrapper object + */ + @Override + protected CardTerminals engineTerminals() { + return new PCSCTerminals(); + } + } + +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/TerminalImpl.java b/smcc/src/main/java/org/openecard/scio/osx/TerminalImpl.java new file mode 100644 index 00000000..993fe67a --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/TerminalImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openecard.scio.osx; + +import static org.openecard.scio.osx.PCSC.SCARD_E_TIMEOUT; +import static org.openecard.scio.osx.PCSC.SCARD_STATE_PRESENT; +import static org.openecard.scio.osx.PCSC.SCARD_STATE_UNAWARE; +import static org.openecard.scio.osx.PCSC.SCARD_W_REMOVED_CARD; +import static org.openecard.scio.osx.PCSC.SCardGetStatusChange; +import static org.openecard.scio.osx.PCSC.TIMEOUT_INFINITE; + +import javax.smartcardio.Card; +import javax.smartcardio.CardException; +import javax.smartcardio.CardNotPresentException; +import javax.smartcardio.CardPermission; +import javax.smartcardio.CardTerminal; + +/** + * CardTerminal implementation. + * + * @since 1.6 + * @author Andreas Sterbenz + */ +final class TerminalImpl extends CardTerminal { + + // native SCARDCONTEXT + final long contextId; + + // the name of this terminal (native PC/SC name) + final String name; + + private CardImpl card; + + TerminalImpl(long contextId, String name) { + this.contextId = contextId; + this.name = name; + } + + public String getName() { + return name; + } + + public synchronized Card connect(String protocol) throws CardException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new CardPermission(name, "connect")); + } + if (card != null) { + if (card.isValid()) { + String cardProto = card.getProtocol(); + if (protocol.equals("*") || protocol.equalsIgnoreCase(cardProto)) { + return card; + } else { + throw new CardException("Cannot connect using " + protocol + + ", connection already established using " + cardProto); + } + } else { + card = null; + } + } + try { + card = new CardImpl(this, protocol); + return card; + } catch (PCSCException e) { + if (e.code == SCARD_W_REMOVED_CARD) { + throw new CardNotPresentException("No card present", e); + } else { + throw new CardException("connect() failed", e); + } + } + } + + public boolean isCardPresent() throws CardException { + try { + int[] status = SCardGetStatusChange(contextId, 0, + new int[] { SCARD_STATE_UNAWARE }, new String[] { name }); + return (status[0] & SCARD_STATE_PRESENT) != 0; + } catch (PCSCException e) { + throw new CardException("isCardPresent() failed", e); + } + } + + private boolean waitForCard(boolean wantPresent, long timeout) + throws CardException { + if (timeout < 0) { + throw new IllegalArgumentException("timeout must not be negative"); + } + if (timeout == 0) { + timeout = TIMEOUT_INFINITE; + } + int[] status = new int[] { SCARD_STATE_UNAWARE }; + String[] readers = new String[] { name }; + try { + // check if card status already matches + status = SCardGetStatusChange(contextId, 0, status, readers); + boolean present = (status[0] & SCARD_STATE_PRESENT) != 0; + if (wantPresent == present) { + return true; + } + // no match, wait (until timeout expires) + long end = System.currentTimeMillis() + timeout; + while (wantPresent != present && timeout != 0) { + // set remaining timeout + if (timeout != TIMEOUT_INFINITE) { + timeout = Math.max(end - System.currentTimeMillis(), 0l); + } + status = SCardGetStatusChange(contextId, timeout, status, readers); + present = (status[0] & SCARD_STATE_PRESENT) != 0; + } + return wantPresent == present; + } catch (PCSCException e) { + if (e.code == SCARD_E_TIMEOUT) { + return false; + } else { + throw new CardException("waitForCard() failed", e); + } + } + } + + public boolean waitForCardPresent(long timeout) throws CardException { + return waitForCard(true, timeout); + } + + public boolean waitForCardAbsent(long timeout) throws CardException { + return waitForCard(false, timeout); + } + + public String toString() { + return "PC/SC terminal " + name; + } +} diff --git a/smcc/src/main/java/org/openecard/scio/osx/package-info.java b/smcc/src/main/java/org/openecard/scio/osx/package-info.java new file mode 100644 index 00000000..5d066a40 --- /dev/null +++ b/smcc/src/main/java/org/openecard/scio/osx/package-info.java @@ -0,0 +1,29 @@ +/**************************************************************************** + * Copyright (C) 2012-2013 ecsec GmbH. + * All rights reserved. + * Contact: ecsec GmbH (info@ecsec.de) + * + * This file is part of the Open eCard App. + * + * GNU General Public License Usage + * This file may be used under the terms of the GNU General Public + * License version 3.0 as published by the Free Software Foundation + * and appearing in the file LICENSE.GPL included in the packaging of + * this file. Please review the following information to ensure the + * GNU General Public License version 3.0 requirements will be met: + * http://www.gnu.org/copyleft/gpl.html. + * + * Other Usage + * Alternatively, this file may be used in accordance with the terms + * and conditions contained in a signed written agreement between + * you and ecsec GmbH. + * + ***************************************************************************/ + +/** + * This package contains a OS X specific SmartcardIO implementation. We've to do + * this, because the current PC/SC JNI libraries on OS X (jre6 & jre7) are too buggy + * to be useful and the design of the sun.security.smartcardio package doesn't allow + * to specify another JNI library path. + */ +package org.openecard.scio.osx; diff --git a/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre6.libosxj2pcsc.dylib b/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre6.libosxj2pcsc.dylib new file mode 100644 index 00000000..2bc454e3 Binary files /dev/null and b/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre6.libosxj2pcsc.dylib differ diff --git a/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre7.libosxj2pcsc.dylib b/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre7.libosxj2pcsc.dylib new file mode 100644 index 00000000..6228a60c Binary files /dev/null and b/smcc/src/main/resources/at/gv/egiz/smcc/osx-pcsc-jni/jre7.libosxj2pcsc.dylib differ -- cgit v1.2.3