1/*
2 * Copyright (c) 2005, 2017, 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
26package sun.security.smartcardio;
27
28import java.nio.ByteBuffer;
29import java.security.AccessController;
30import java.security.PrivilegedAction;
31import javax.smartcardio.*;
32import static sun.security.smartcardio.PCSC.*;
33
34/**
35 * Card implementation.
36 *
37 * @since   1.6
38 * @author  Andreas Sterbenz
39 */
40final 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    public void disconnect(boolean reset) throws CardException {
255        if (reset) {
256            checkSecurity("reset");
257        }
258        if (state != State.OK) {
259            return;
260        }
261        checkExclusive();
262        try {
263            SCardDisconnect(cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD));
264        } catch (PCSCException e) {
265            throw new CardException("disconnect() failed", e);
266        } finally {
267            state = State.DISCONNECTED;
268            exclusiveThread = null;
269        }
270    }
271
272    public String toString() {
273        return "PC/SC card in " + terminal.getName()
274            + ", protocol " + getProtocol() + ", state " + state;
275    }
276
277    @SuppressWarnings("deprecation")
278    protected void finalize() throws Throwable {
279        try {
280            if (state == State.OK) {
281                SCardDisconnect(cardId, SCARD_LEAVE_CARD);
282            }
283        } finally {
284            super.finalize();
285        }
286    }
287
288}
289