1 /*
   2  * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.smartcardio;
  27 
  28 import java.nio.ByteBuffer;
  29 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import javax.smartcardio.*;
  32 import static sun.security.smartcardio.PCSC.*;
  33 
  34 /**
  35  * Card implementation.
  36  *
  37  * @since   1.6
  38  * @author  Andreas Sterbenz
  39  */
  40 final class CardImpl extends Card {
  41 
  42     private static enum State { OK, REMOVED, DISCONNECTED };
  43 
  44     // the terminal that created this card
  45     private final TerminalImpl terminal;
  46 
  47     // the native SCARDHANDLE
  48     final long cardId;
  49 
  50     // atr of this card
  51     private final ATR atr;
  52 
  53     // protocol in use, one of SCARD_PROTOCOL_T0 and SCARD_PROTOCOL_T1
  54     final int protocol;
  55 
  56     // the basic logical channel (channel 0)
  57     private final ChannelImpl basicChannel;
  58 
  59     // state of this card connection
  60     private volatile State state;
  61 
  62     // thread holding exclusive access to the card, or null
  63     private volatile Thread exclusiveThread;
  64 
  65     // used for platform specific logic
  66     private static final boolean isWindows;
  67 
  68     static {
  69         final String osName = AccessController.doPrivileged(
  70             (PrivilegedAction<String>) () -> System.getProperty("os.name"));
  71         isWindows = osName.startsWith("Windows");
  72     }
  73 
  74     CardImpl(TerminalImpl terminal, String protocol) throws PCSCException {
  75         this.terminal = terminal;
  76         int sharingMode = SCARD_SHARE_SHARED;
  77         int connectProtocol;
  78         if (protocol.equals("*")) {
  79             connectProtocol = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
  80         } else if (protocol.equalsIgnoreCase("T=0")) {
  81             connectProtocol = SCARD_PROTOCOL_T0;
  82         } else if (protocol.equalsIgnoreCase("T=1")) {
  83             connectProtocol = SCARD_PROTOCOL_T1;
  84         } else if (protocol.equalsIgnoreCase("direct")) {
  85             // testing
  86 
  87             // MSDN states that the preferred protocol can be zero, but doesn't
  88             //     specify whether other values are allowed.
  89             // pcsc-lite implementation expects the preferred protocol to be non zero.
  90             connectProtocol = isWindows ? 0 : SCARD_PROTOCOL_RAW;
  91 
  92             sharingMode = SCARD_SHARE_DIRECT;
  93         } else {
  94             throw new IllegalArgumentException("Unsupported protocol " + protocol);
  95         }
  96         cardId = SCardConnect(terminal.contextId, terminal.name,
  97                     sharingMode, connectProtocol);
  98         byte[] status = new byte[2];
  99         byte[] atrBytes = SCardStatus(cardId, status);
 100         atr = new ATR(atrBytes);
 101         this.protocol = status[1] & 0xff;
 102         basicChannel = new ChannelImpl(this, 0);
 103         state = State.OK;
 104     }
 105 
 106     void checkState()  {
 107         State s = state;
 108         if (s == State.DISCONNECTED) {
 109             throw new IllegalStateException("Card has been disconnected");
 110         } else if (s == State.REMOVED) {
 111             throw new IllegalStateException("Card has been removed");
 112         }
 113     }
 114 
 115     boolean isValid() {
 116         if (state != State.OK) {
 117             return false;
 118         }
 119         // ping card via SCardStatus
 120         try {
 121             SCardStatus(cardId, new byte[2]);
 122             return true;
 123         } catch (PCSCException e) {
 124             state = State.REMOVED;
 125             return false;
 126         }
 127     }
 128 
 129     private void checkSecurity(String action) {
 130         SecurityManager sm = System.getSecurityManager();
 131         if (sm != null) {
 132             sm.checkPermission(new CardPermission(terminal.name, action));
 133         }
 134     }
 135 
 136     void handleError(PCSCException e) {
 137         if (e.code == SCARD_W_REMOVED_CARD) {
 138             state = State.REMOVED;
 139         }
 140     }
 141 
 142     public ATR getATR() {
 143         return atr;
 144     }
 145 
 146     public String getProtocol() {
 147         switch (protocol) {
 148         case SCARD_PROTOCOL_T0:
 149             return "T=0";
 150         case SCARD_PROTOCOL_T1:
 151             return "T=1";
 152         default:
 153             // should never occur
 154             return "Unknown protocol " + protocol;
 155         }
 156     }
 157 
 158     public CardChannel getBasicChannel() {
 159         checkSecurity("getBasicChannel");
 160         checkState();
 161         return basicChannel;
 162     }
 163 
 164     private static int getSW(byte[] b) {
 165         if (b.length < 2) {
 166             return -1;
 167         }
 168         int sw1 = b[b.length - 2] & 0xff;
 169         int sw2 = b[b.length - 1] & 0xff;
 170         return (sw1 << 8) | sw2;
 171     }
 172 
 173     private static byte[] commandOpenChannel = new byte[] {0, 0x70, 0, 0, 1};
 174 
 175     public CardChannel openLogicalChannel() throws CardException {
 176         checkSecurity("openLogicalChannel");
 177         checkState();
 178         checkExclusive();
 179         try {
 180             byte[] response = SCardTransmit
 181                 (cardId, protocol, commandOpenChannel, 0, commandOpenChannel.length);
 182             if ((response.length != 3) || (getSW(response) != 0x9000)) {
 183                 throw new CardException
 184                         ("openLogicalChannel() failed, card response: "
 185                         + PCSC.toString(response));
 186             }
 187             return new ChannelImpl(this, response[0]);
 188         } catch (PCSCException e) {
 189             handleError(e);
 190             throw new CardException("openLogicalChannel() failed", e);
 191         }
 192     }
 193 
 194     void checkExclusive() throws CardException {
 195         Thread t = exclusiveThread;
 196         if (t == null) {
 197             return;
 198         }
 199         if (t != Thread.currentThread()) {
 200             throw new CardException("Exclusive access established by another Thread");
 201         }
 202     }
 203 
 204     public synchronized void beginExclusive() throws CardException {
 205         checkSecurity("exclusive");
 206         checkState();
 207         if (exclusiveThread != null) {
 208             throw new CardException
 209                     ("Exclusive access has already been assigned to Thread "
 210                     + exclusiveThread.getName());
 211         }
 212         try {
 213             SCardBeginTransaction(cardId);
 214         } catch (PCSCException e) {
 215             handleError(e);
 216             throw new CardException("beginExclusive() failed", e);
 217         }
 218         exclusiveThread = Thread.currentThread();
 219     }
 220 
 221     public synchronized void endExclusive() throws CardException {
 222         checkState();
 223         if (exclusiveThread != Thread.currentThread()) {
 224             throw new IllegalStateException
 225                     ("Exclusive access not assigned to current Thread");
 226         }
 227         try {
 228             SCardEndTransaction(cardId, SCARD_LEAVE_CARD);
 229         } catch (PCSCException e) {
 230             handleError(e);
 231             throw new CardException("endExclusive() failed", e);
 232         } finally {
 233             exclusiveThread = null;
 234         }
 235     }
 236 
 237     public byte[] transmitControlCommand(int controlCode, byte[] command)
 238             throws CardException {
 239         checkSecurity("transmitControl");
 240         checkState();
 241         checkExclusive();
 242         if (command == null) {
 243             throw new NullPointerException();
 244         }
 245         try {
 246             byte[] r = SCardControl(cardId, controlCode, command);
 247             return r;
 248         } catch (PCSCException e) {
 249             handleError(e);
 250             throw new CardException("transmitControlCommand() failed", e);
 251         }
 252     }
 253 
 254     private static final boolean invertReset =
 255         Boolean.parseBoolean(
 256             java.security.AccessController.doPrivileged(
 257                 new sun.security.action.GetPropertyAction(
 258                     "sun.security.smartcardio.invertCardReset", "false")));
 259 
 260     public void disconnect(boolean reset) throws CardException {
 261         if (reset) {
 262             checkSecurity("reset");
 263         }
 264         if (state != State.OK) {
 265             return;
 266         }
 267         checkExclusive();
 268         // to preserve old behaviour, don't change flag until here
 269         if (invertReset) {
 270             reset = !reset;
 271         }
 272         try {
 273             SCardDisconnect(cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD));
 274         } catch (PCSCException e) {
 275             throw new CardException("disconnect() failed", e);
 276         } finally {
 277             state = State.DISCONNECTED;
 278             exclusiveThread = null;
 279         }
 280     }
 281 
 282     public String toString() {
 283         return "PC/SC card in " + terminal.getName()
 284             + ", protocol " + getProtocol() + ", state " + state;
 285     }
 286 
 287     protected void finalize() throws Throwable {
 288         try {
 289             if (state == State.OK) {
 290                 SCardDisconnect(cardId, SCARD_LEAVE_CARD);
 291             }
 292         } finally {
 293             super.finalize();
 294         }
 295     }
 296 
 297 }