• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #386

24 Oct 2025 09:55AM UTC coverage: 80.64% (-2.4%) from 83.057%
#386

push

ebourg
CI build with Java 25

4965 of 6157 relevant lines covered (80.64%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.5
/jsign-crypto/src/main/java/net/jsign/jca/SmartCard.java
1
/*
2
 * Copyright 2024 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign.jca;
18

19
import java.io.IOException;
20
import java.util.ArrayList;
21
import java.util.HashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.logging.Level;
25
import java.util.logging.Logger;
26
import javax.smartcardio.Card;
27
import javax.smartcardio.CardChannel;
28
import javax.smartcardio.CardException;
29
import javax.smartcardio.CardTerminal;
30
import javax.smartcardio.CardTerminals;
31
import javax.smartcardio.CommandAPDU;
32
import javax.smartcardio.ResponseAPDU;
33
import javax.smartcardio.TerminalFactory;
34

35
import org.apache.commons.io.HexDump;
36

37
/**
38
 * Base class for the smart card implementations.
39
 *
40
 * @since 6.0
41
 */
42
abstract class SmartCard {
43

44
    private final Logger log = Logger.getLogger(getClass().getName());
×
45

46
    private final CardChannel channel;
47

48
    /** Personal Identification Number */
49
    protected String pin;
50

51
    /** Data Object cache */
52
    protected final Map<Integer, byte[]> dataObjectCache = new HashMap<>();
×
53

54
    protected SmartCard(CardChannel channel) {
×
55
        this.channel = channel;
×
56
    }
×
57

58
    /**
59
     * Select an application on the card.
60
     *
61
     * @param name the name of the application (for logging purposes)
62
     * @param aid  the AID of the application
63
     */
64
    void select(String name, byte[] aid) throws CardException {
65
        ResponseAPDU response = transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid)); // SELECT
×
66
        switch (response.getSW()) {
×
67
            case 0x6A82:
68
            case 0x6A86:
69
                throw new CardException(name + " application not found on the card/token");
×
70
        }
71
        handleError(response);
×
72
    }
×
73

74
    /**
75
     * Set the PIN for the verify operation.
76
     */
77
    public void verify(String pin) {
78
        this.pin = pin;
×
79
    }
×
80

81
    /**
82
     * Transmit the command to the card and display the APDU request/response if debug is enabled.
83
     */
84
    protected ResponseAPDU transmit(CommandAPDU command) throws CardException {
85
        if (log.isLoggable(Level.FINEST)) {
×
86
            log.finest(command.toString());
×
87
            try {
88
                StringBuffer out = new StringBuffer();
×
89
                HexDump.dump(command.getBytes(), 0, out, 0, command.getBytes().length);
×
90
                log.finest(out.toString());
×
91
            } catch (IOException e) {
×
92
                throw new RuntimeException(e);
×
93
            }
×
94
        }
95

96
        long t1 = System.nanoTime();
×
97
        ResponseAPDU response = channel.transmit(command);
×
98
        long t2 = System.nanoTime();
×
99

100
        if (log.isLoggable(Level.FINEST)) {
×
101
            log.finest(response + " (" + (t2 - t1) / 1000000 + " ms)");
×
102
            if (response.getData().length > 0) {
×
103
                try {
104
                    StringBuffer out = new StringBuffer();
×
105
                    HexDump.dump(response.getData(), 0, out, 0, response.getData().length);
×
106
                    log.finest(out.toString());
×
107
                } catch (IOException e) {
×
108
                    throw new RuntimeException(e);
×
109
                }
×
110
            }
111
            log.finest("");
×
112
        }
113

114
        return response;
×
115
    }
116

117
    /**
118
     * Throws a CardException with a meaningful message if the APDU response status indicates an error.
119
     */
120
    protected void handleError(ResponseAPDU response) throws CardException {
121
        switch (response.getSW()) {
×
122
            case 0x9000:
123
                return;
×
124
            case 0x63C0:
125
            case 0x63C1:
126
            case 0x63C2:
127
            case 0x63C3:
128
            case 0x63C4:
129
            case 0x63C5:
130
            case 0x63C6:
131
            case 0x63C7:
132
            case 0x63C8:
133
            case 0x63C9:
134
            case 0x63CA:
135
            case 0x63CB:
136
            case 0x63CC:
137
            case 0x63CD:
138
            case 0x63CE:
139
            case 0x63CF:
140
                throw new CardException("PIN verification failed, " + (response.getSW() & 0x0F) + " tries left");
×
141
            case 0x6700:
142
                throw new CardException("Wrong length");
×
143
            case 0x6982:
144
                throw new CardException("PIN verification required");
×
145
            case 0x6983:
146
                throw new CardException("PIN blocked");
×
147
            case 0x6985:
148
                throw new CardException("Conditions of use not satisfied");
×
149
            case 0x6A80:
150
                throw new CardException("The parameters in the data field are incorrect");
×
151
            case 0x6A82:
152
                throw new CardException("Incorrect P1 or P2 parameter");
×
153
            case 0x6A88:
154
                throw new CardException("Referenced data not found");
×
155
            case 0x6B00:
156
                throw new CardException("Wrong parameter(s) P1-P2");
×
157
            case 0x6D00:
158
                throw new CardException("Instruction code not supported or invalid");
×
159
            default:
160
                throw new CardException("Error " + Integer.toHexString(response.getSW()));
×
161
        }
162
    }
163

164
    /**
165
     * Opens a channel to the first available smart card hosting the specified application.
166
     *
167
     * @param aid the AID of the application
168
     */
169
    static CardChannel openChannel(byte[] aid) throws CardException {
170
        for (CardTerminal terminal : getTerminals(null)) {
1✔
171
            try {
172
                Card card = terminal.connect("*");
×
173
                CardChannel channel = card.getBasicChannel();
×
174
                ResponseAPDU response = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid)); // SELECT
×
175
                if (response.getSW() == 0x9000) {
×
176
                    return channel;
×
177
                } else {
178
                    card.disconnect(false);
×
179
                }
180
            } catch (CardException e) {
×
181
                // ignore and try the next terminal
182
            }
×
183
        }
×
184

185
        return null;
1✔
186
    }
187

188
    /**
189
     * Opens a channel to the first available smart card matching the specified name.
190
     *
191
     * @param name the partial name of the terminal
192
     */
193
    static CardChannel openChannel(String name) throws CardException {
194
        CardTerminal terminal = getTerminal(name);
×
195
        if (terminal != null) {
×
196
            try {
197
                Card card = terminal.connect("*");
×
198
                return card.getBasicChannel();
×
199
            } catch (CardException e) {
×
200
                e.printStackTrace();
×
201
            }
202
        }
203

204
        return null;
×
205
    }
206

207
    /**
208
     * Returns the first available smart card terminal matching the specified name.
209
     *
210
     * @param name the partial name of the terminal
211
     */
212
    static CardTerminal getTerminal(String name) throws CardException {
213
        List<CardTerminal> terminals = getTerminals(name);
1✔
214
        return terminals.isEmpty() ? null : terminals.get(0);
1✔
215
    }
216

217
    /**
218
     * Returns the available smart card terminals matching the specified name.
219
     *
220
     * @param name the partial name of the terminal
221
     */
222
    static List<CardTerminal> getTerminals(String name) throws CardException {
223
        List<CardTerminal> activeTerminals = TerminalFactory.getDefault().terminals().list(CardTerminals.State.CARD_PRESENT);
1✔
224
        if (name != null) {
1✔
225
            activeTerminals = new ArrayList<>(activeTerminals);
1✔
226
            activeTerminals.removeIf(terminal -> !terminal.getName().toLowerCase().contains(name.toLowerCase()));
1✔
227
        }
228
        return activeTerminals;
1✔
229
    }
230
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc