Coverage Report

Created: 2026-05-30 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensc/src/libopensc/card-srbeid.c
Line
Count
Source
1
/*
2
 * card-srbeid.c: Driver for Serbian cards using the CardEdge PKI applet.
3
 *
4
 * Serbian eID, health insurance, and Chamber of Commerce cards use the
5
 * same CardEdge PKCS#15 applet.  Cards are matched by ATR whitelist,
6
 * with a PKCS#15 AID select as a confirmation step.
7
 *
8
 * Copyright (C) 2026 LibreSCRS contributors
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23
 */
24
25
#ifdef HAVE_CONFIG_H
26
#include "config.h"
27
#endif
28
29
#include <string.h>
30
31
#include "card-srbeid.h"
32
#include "internal.h"
33
#include "log.h"
34
35
/* MSE algorithm byte for RSA-2048 PKCS#1 v1.5 */
36
0
#define CE_MSE_ALG_RSA2048 0x02u
37
38
static struct sc_card_operations srbeid_ops;
39
static const struct sc_card_operations *iso_ops;
40
41
static struct sc_card_driver srbeid_drv = {
42
    "Serbian CardEdge driver",
43
    "srbeid",
44
    &srbeid_ops,
45
    NULL, 0, NULL};
46
47
/* Full ATR list provided by NetSeT (the CardEdge PKI applet vendor). Covers
48
 * citizen eID, foreigner eID, PKS Chamber of Commerce, and RFZO health
49
 * insurance cards across their various underlying card platforms. */
50
static const struct sc_atr_table srbeid_atrs[] = {
51
    {"3B:FF:94:00:00:81:31:80:43:80:31:80:65:B0:85:02:01:F3:12:0F:FF:82:90:00:79", NULL, "Serbian eID (Gemalto Multiapp 80K)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
52
    {"3B:F8:13:00:00:81:31:FE:45:4A:43:4F:50:76:32:34:31:B7",       NULL, "Serbian eID (NXP JCOP v2.4.1 80K)",         SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
53
    {"3B:FA:13:00:00:81:31:FE:45:4A:43:4F:50:32:31:56:32:33:31:91",   NULL, "Serbian eID (NXP JCOP21 v2.3.1)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
54
    {"3B:F4:13:00:00:81:31:FE:45:52:46:5A:4F:ED",             NULL, "Serbian health card (NXP JCOP21 v2.4.1)",         SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
55
    {"3B:7A:96:00:00:80:65:A2:01:01:02:3D:72:D6:43",         NULL, "Serbian eID (Gemalto Multiapp 80K IDCore10)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
56
    {"3B:F9:96:00:00:80:31:FE:45:53:43:45:37:20:47:43:4E:33:5E",         NULL, "Serbian eID (SmartCafe Expert v7.0)",     SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
57
    {"3B:FD:94:00:00:81:31:80:43:80:31:80:65:B1:F3:01:07:0F:83:01:90:00:17",   NULL, "Serbian eID (Gemalto Dual-Interface 144K Contact)", SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
58
    {"3B:89:80:01:53:43:45:2E:37:20:20:20:20:44",             NULL, "Serbian eID (SmartCafe Expert v7.0 Contactless)",   SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
59
    {"3B:F9:96:00:00:80:31:FE:45:53:43:45:37:20:20:00:20:20:07",         NULL, "Serbian eID (SmartCafe Expert v7.1)",     SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
60
    {"3B:DE:97:00:80:31:FE:45:53:43:45:20:38:2E:30:2D:43:31:56:30:0D:0A:2E",   NULL, "Serbian PKS card (SmartCafe Expert v8.0 B)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
61
    {"3B:9E:96:80:31:FE:45:53:43:45:20:38:2E:30:2D:43:31:56:30:0D:0A:6F",       NULL, "Serbian eID (SmartCafe Expert v8.0)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
62
    {"3B:9E:96:80:31:FE:45:53:43:45:20:38:2E:30:2D:43:32:56:30:0D:0A:6C",       NULL, "Serbian eID (SmartCafe Expert v8.0 C2)",       SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
63
    {"3B:D5:18:FF:81:91:FE:1F:C3:80:73:C8:21:10:0A",         NULL, "Serbian eID (NXP JCOP4 P71)",           SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
64
    {"3B:DE:97:00:80:31:FE:45:53:43:45:20:38:2E:30:2D:43:32:56:30:0D:0A:2D",   NULL, "Serbian PKS card (SmartCafe Expert v8.0 C2 B)", SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
65
    {"3B:9E:97:80:31:FE:45:53:43:45:20:38:2E:30:2D:43:31:56:30:0D:0A:6E",       NULL, "Serbian health card (SmartCafe Expert v8.0 S)",     SC_CARD_TYPE_SRBEID_BASE, 0, NULL},
66
    {NULL,                   NULL, NULL,            0,            0, NULL}
67
};
68
69
static int
70
srbeid_match_card(sc_card_t *card)
71
5.29k
{
72
5.29k
  int i = _sc_match_atr(card, srbeid_atrs, &card->type);
73
5.29k
  if (i >= 0 && iso7816_select_aid(card, AID_PKCS15, AID_PKCS15_LEN, NULL, NULL) == SC_SUCCESS) {
74
1
    card->name = srbeid_atrs[i].name;
75
1
    return 1;
76
1
  }
77
5.29k
  return 0;
78
5.29k
}
79
80
static int
81
srbeid_init(sc_card_t *card)
82
1
{
83
1
  LOG_FUNC_CALLED(card->ctx);
84
85
1
  card->caps |= SC_CARD_CAP_ISO7816_PIN_INFO;
86
87
1
  _sc_card_add_rsa_alg(card, 2048,
88
1
      SC_ALGORITHM_RSA_PAD_PKCS1 | SC_ALGORITHM_RSA_HASH_NONE, 0);
89
90
1
  LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
91
1
}
92
93
/*
94
 * select_file — handle CardEdge's proprietary 10-byte FCI response.
95
 *
96
 * CardEdge FCI layout (10 bytes, big-endian):
97
 *   [FID_H FID_L Size_H Size_L ACL*6]
98
 *
99
 * iso7816_select_file() would try to parse this as ISO 7816-4 TLV (tag 0x6F)
100
 * and fail with SC_ERROR_UNKNOWN_DATA_RECEIVED.
101
 *
102
 * DF_NAME (AID) selection is delegated to the ISO layer.
103
 */
104
static int
105
srbeid_select_file(sc_card_t *card, const sc_path_t *in_path,
106
    sc_file_t **file_out)
107
0
{
108
0
  sc_apdu_t apdu;
109
0
  u8 fci[16];
110
0
  sc_file_t *file;
111
0
  int r;
112
113
0
  if (in_path->type == SC_PATH_TYPE_DF_NAME)
114
0
    return iso_ops->select_file(card, in_path, file_out);
115
116
  /* AID-only path (path.len==0, path.aid.len>0): PKCS#15 layer wants
117
   * to select the applet before a PIN or key operation. */
118
0
  if (in_path->len == 0 && in_path->aid.len > 0)
119
0
    return iso7816_select_aid(card, in_path->aid.value,
120
0
        in_path->aid.len, NULL, NULL);
121
122
0
  if (in_path->len != 2)
123
0
    LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
124
125
0
  sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0x00, 0x00);
126
0
  apdu.data = in_path->value;
127
0
  apdu.datalen = 2;
128
0
  apdu.lc = 2;
129
0
  apdu.resp = fci;
130
0
  apdu.resplen = sizeof(fci);
131
0
  apdu.le = 10;
132
133
0
  r = sc_transmit_apdu(card, &apdu);
134
0
  LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
135
0
  r = sc_check_sw(card, apdu.sw1, apdu.sw2);
136
0
  LOG_TEST_RET(card->ctx, r, "SELECT FILE failed");
137
138
0
  if (apdu.resplen < 4)
139
0
    LOG_FUNC_RETURN(card->ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED);
140
141
0
  if (file_out) {
142
0
    file = sc_file_new();
143
0
    if (!file)
144
0
      LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY);
145
146
0
    file->id = ((unsigned)in_path->value[0] << 8) | in_path->value[1];
147
0
    file->path = *in_path;
148
0
    file->size = ((size_t)fci[2] << 8) | (size_t)fci[3];
149
0
    file->type = SC_FILE_TYPE_WORKING_EF;
150
0
    *file_out = file;
151
0
  }
152
153
0
  LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
154
0
}
155
156
/*
157
 * set_security_env — send MSE SET to the card.
158
 *
159
 * The PKCS#15 layer selects the PKI applet via the AID attached to
160
 * key_info.path before calling this function (see select_key_file()
161
 * in pkcs15-sec.c).
162
 *
163
 * OpenSC populates env->key_ref[0] from key_info.key_reference (low byte).
164
 * The high byte is always 0x60 (CE_KEYS_BASE_FID >> 8) for all CardEdge
165
 * key FIDs, so the full 2-byte FID is reconstructed here.
166
 *
167
 * MSE SET template P2: 0xB6 for signing, 0xB8 for deciphering.
168
 */
169
static int
170
srbeid_set_security_env(sc_card_t *card,
171
    const struct sc_security_env *env, int se_num)
172
0
{
173
0
  sc_apdu_t apdu;
174
0
  u8 mse_data[7];
175
0
  unsigned key_ref;
176
0
  u8 p2;
177
0
  int r;
178
179
0
  LOG_FUNC_CALLED(card->ctx);
180
0
  (void)se_num;
181
182
  /* Extract key FID. */
183
0
  if ((env->flags & SC_SEC_ENV_FILE_REF_PRESENT) && env->file_ref.len >= 2) {
184
0
    key_ref = ((unsigned)env->file_ref.value[0] << 8) | (unsigned)env->file_ref.value[1];
185
0
  } else if ((env->flags & SC_SEC_ENV_KEY_REF_PRESENT) && env->key_ref_len >= 1) {
186
0
    key_ref = CE_KEYS_BASE_FID | (unsigned)env->key_ref[0];
187
0
  } else {
188
0
    sc_log(card->ctx, "srbeid: set_security_env: no key reference");
189
0
    return SC_ERROR_INCORRECT_PARAMETERS;
190
0
  }
191
192
  /* Determine MSE SET template from operation type. */
193
0
  switch (env->operation) {
194
0
  case SC_SEC_OPERATION_SIGN:
195
0
    p2 = 0xB6;
196
0
    break;
197
0
  case SC_SEC_OPERATION_DECIPHER:
198
0
    p2 = 0xB8;
199
0
    break;
200
0
  default:
201
0
    return SC_ERROR_NOT_SUPPORTED;
202
0
  }
203
204
  /* MSE SET: tag 0x80 = algorithm (RSA2048), tag 0x84 = key ref (2 bytes BE) */
205
0
  mse_data[0] = 0x80;
206
0
  mse_data[1] = 0x01;
207
0
  mse_data[2] = CE_MSE_ALG_RSA2048;
208
0
  mse_data[3] = 0x84;
209
0
  mse_data[4] = 0x02;
210
0
  mse_data[5] = (u8)((key_ref >> 8) & 0xFF);
211
0
  mse_data[6] = (u8)(key_ref & 0xFF);
212
213
0
  sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x41, p2);
214
0
  apdu.data = mse_data;
215
0
  apdu.datalen = sizeof(mse_data);
216
0
  apdu.lc = sizeof(mse_data);
217
218
0
  r = sc_transmit_apdu(card, &apdu);
219
0
  LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
220
0
  r = sc_check_sw(card, apdu.sw1, apdu.sw2);
221
0
  LOG_TEST_RET(card->ctx, r, "MSE SET failed");
222
223
0
  sc_log(card->ctx, "srbeid: set_security_env: key_ref=0x%04x p2=0x%02x", key_ref, p2);
224
0
  LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
225
0
}
226
227
/*
228
 * compute_signature — PSO COMPUTE DIGITAL SIGNATURE (00 2A 9E 00).
229
 *
230
 * MSE SET has already been sent by set_security_env().
231
 * CardEdge uses P2=0x00 (not 0x9A as in ISO 7816-8), so we cannot
232
 * delegate to iso7816_compute_signature().
233
 */
234
static int
235
srbeid_compute_signature(sc_card_t *card,
236
    const u8 *data, size_t datalen, u8 *out, size_t outlen)
237
0
{
238
0
  sc_apdu_t apdu;
239
0
  u8 resp[256];
240
0
  int r;
241
242
0
  LOG_FUNC_CALLED(card->ctx);
243
244
0
  sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x9E, 0x00);
245
0
  apdu.data = data;
246
0
  apdu.datalen = datalen;
247
0
  apdu.lc = datalen;
248
0
  apdu.resp = resp;
249
0
  apdu.resplen = sizeof(resp);
250
0
  apdu.le = sizeof(resp);
251
252
0
  r = sc_transmit_apdu(card, &apdu);
253
0
  LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
254
0
  r = sc_check_sw(card, apdu.sw1, apdu.sw2);
255
0
  LOG_TEST_RET(card->ctx, r, "PSO COMPUTE DIGITAL SIGNATURE failed");
256
257
0
  if (apdu.resplen > outlen)
258
0
    LOG_FUNC_RETURN(card->ctx, SC_ERROR_BUFFER_TOO_SMALL);
259
0
  memcpy(out, resp, apdu.resplen);
260
0
  LOG_FUNC_RETURN(card->ctx, (int)apdu.resplen);
261
0
}
262
263
/*
264
 * decipher — PSO DECIPHER (00 2A 80 86).
265
 *
266
 * MSE SET has already been sent by set_security_env().
267
 * CardEdge does not use a padding indicator byte, so we cannot
268
 * delegate to iso7816_decipher().
269
 */
270
static int
271
srbeid_decipher(sc_card_t *card,
272
    const u8 *crgram, size_t crgram_len, u8 *out, size_t outlen)
273
0
{
274
0
  sc_apdu_t apdu;
275
0
  u8 resp[256];
276
0
  int r;
277
278
0
  LOG_FUNC_CALLED(card->ctx);
279
280
0
  sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x80, 0x86);
281
0
  apdu.data = crgram;
282
0
  apdu.datalen = crgram_len;
283
0
  apdu.lc = crgram_len;
284
0
  apdu.resp = resp;
285
0
  apdu.resplen = sizeof(resp);
286
0
  apdu.le = sizeof(resp);
287
288
0
  r = sc_transmit_apdu(card, &apdu);
289
0
  LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
290
0
  r = sc_check_sw(card, apdu.sw1, apdu.sw2);
291
0
  LOG_TEST_RET(card->ctx, r, "PSO DECIPHER failed");
292
293
0
  if (apdu.resplen > outlen)
294
0
    LOG_FUNC_RETURN(card->ctx, SC_ERROR_BUFFER_TOO_SMALL);
295
0
  memcpy(out, resp, apdu.resplen);
296
0
  LOG_FUNC_RETURN(card->ctx, (int)apdu.resplen);
297
0
}
298
299
struct sc_card_driver *
300
sc_get_srbeid_driver(void)
301
8.11k
{
302
  /* Save ISO ops for delegation, then override what we handle. */
303
8.11k
  iso_ops = sc_get_iso7816_driver()->ops;
304
8.11k
  srbeid_ops = *iso_ops;
305
8.11k
  srbeid_ops.match_card = srbeid_match_card;
306
8.11k
  srbeid_ops.init = srbeid_init;
307
8.11k
  srbeid_ops.select_file = srbeid_select_file;
308
8.11k
  srbeid_ops.set_security_env = srbeid_set_security_env;
309
8.11k
  srbeid_ops.compute_signature = srbeid_compute_signature;
310
8.11k
  srbeid_ops.decipher = srbeid_decipher;
311
312
8.11k
  return &srbeid_drv;
313
8.11k
}