/src/opensc/src/libopensc/pkcs15-srbeid.c
Line | Count | Source |
1 | | /* |
2 | | * pkcs15-srbeid.c: PKCS#15 emulation for Serbian cards using the |
3 | | * CardEdge PKI applet. |
4 | | * |
5 | | * Copyright (C) 2026 LibreSCRS contributors |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * This library is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public |
18 | | * License along with this library; if not, write to the Free Software |
19 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | | */ |
21 | | |
22 | | #ifdef HAVE_CONFIG_H |
23 | | #include "config.h" |
24 | | #endif |
25 | | |
26 | | #include <stdlib.h> |
27 | | #include <string.h> |
28 | | |
29 | | #ifdef ENABLE_ZLIB |
30 | | #include "compression.h" |
31 | | #endif |
32 | | #include "card-srbeid.h" |
33 | | #include "internal.h" |
34 | | #include "log.h" |
35 | | #include "pkcs15.h" |
36 | | |
37 | | /* CardEdge cmapfile constants. */ |
38 | 108 | #define CE_CMAP_RECORD_SIZE 86u |
39 | 50 | #define CE_CMAP_FLAGS_OFFSET 80u |
40 | 9 | #define CE_CMAP_SIG_SIZE_OFFSET 82u |
41 | 27 | #define CE_CMAP_KX_SIZE_OFFSET 84u |
42 | 50 | #define CE_CMAP_VALID_CONTAINER 0x01u |
43 | 30 | #define CE_KEY_KIND_PRIVATE 1u |
44 | 521 | #define CE_AT_KEYEXCHANGE 1u |
45 | 61 | #define CE_AT_SIGNATURE 2u |
46 | 253 | #define CE_PKI_ROOT_DIR_FID 0x7000u |
47 | 6.14k | #define CE_DIR_HEADER_SIZE 10u |
48 | 5.77k | #define CE_DIR_ENTRY_SIZE 12u |
49 | | |
50 | | /* CardEdge PIN constants */ |
51 | 184 | #define CE_PIN_REFERENCE 0x80u |
52 | 184 | #define CE_PIN_MAX_LENGTH 8u |
53 | | |
54 | | /* Private key FID formula. */ |
55 | | static unsigned int |
56 | | ce_private_key_fid(unsigned int cont_idx, |
57 | | unsigned int key_pair_id) |
58 | 30 | { |
59 | 30 | return CE_KEYS_BASE_FID | ((cont_idx << 4) & 0x0FF0u) | ((key_pair_id << 2) & 0x000Cu) | CE_KEY_KIND_PRIVATE; |
60 | 30 | } |
61 | | |
62 | | /* |
63 | | * Select FID and read the entire file into a malloc'd buffer. |
64 | | * Uses sc_select_file() (dispatched to card driver's select_file which |
65 | | * handles CardEdge's proprietary FCI) and sc_read_binary(). |
66 | | * |
67 | | * *out_len receives the byte count; caller must free() the buffer. |
68 | | * Returns SC_SUCCESS or a negative SC_ERROR_* code. |
69 | | */ |
70 | | static int |
71 | | srbeid_read_file(sc_card_t *card, unsigned int fid, |
72 | | u8 **buf_out, size_t *out_len) |
73 | 722 | { |
74 | 722 | sc_path_t path = {0}; |
75 | 722 | sc_file_t *file = NULL; |
76 | 722 | u8 *buf; |
77 | 722 | int r; |
78 | | |
79 | 722 | *buf_out = NULL; |
80 | 722 | *out_len = 0; |
81 | | |
82 | 722 | path.value[0] = (u8)((fid >> 8) & 0xFF); |
83 | 722 | path.value[1] = (u8)(fid & 0xFF); |
84 | 722 | path.len = 2; |
85 | 722 | path.type = SC_PATH_TYPE_FILE_ID; |
86 | | |
87 | 722 | r = sc_select_file(card, &path, &file); |
88 | 722 | if (r < 0) |
89 | 253 | return r; |
90 | | |
91 | 469 | if (!file || file->size == 0) { |
92 | 8 | sc_file_free(file); |
93 | 8 | return SC_SUCCESS; |
94 | 8 | } |
95 | | |
96 | 461 | if (file->size > 65536) { |
97 | 0 | sc_file_free(file); |
98 | 0 | return SC_ERROR_INVALID_DATA; |
99 | 0 | } |
100 | | |
101 | 461 | buf = malloc(file->size); |
102 | 461 | if (!buf) { |
103 | 0 | sc_file_free(file); |
104 | 0 | return SC_ERROR_OUT_OF_MEMORY; |
105 | 0 | } |
106 | | |
107 | 461 | r = sc_read_binary(card, 0, buf, file->size, 0); |
108 | 461 | sc_file_free(file); |
109 | 461 | if (r < 0) { |
110 | 30 | free(buf); |
111 | 30 | return r; |
112 | 30 | } |
113 | | |
114 | 431 | *buf_out = buf; |
115 | 431 | *out_len = (size_t)r; |
116 | 431 | return SC_SUCCESS; |
117 | 461 | } |
118 | | |
119 | | /* One entry from a CardEdge directory file. */ |
120 | | typedef struct ce_dir_entry { |
121 | | char name[9]; /* 8-char name + NUL */ |
122 | | unsigned fid; |
123 | | int is_dir; |
124 | | } ce_dir_entry_t; |
125 | | |
126 | | /* |
127 | | * Parse a CardEdge directory file into an array of ce_dir_entry_t. |
128 | | * |
129 | | * CardEdge directories use a proprietary binary format: |
130 | | * [10-byte header] [12-byte entries...] |
131 | | * This is NOT ISO 7816-4 EF.DIR (ASN.1 BER-TLV application templates), |
132 | | * so standard sc_enum_apps() / iso7816_read_ef_dir() cannot be used. |
133 | | * |
134 | | * *entries_out: caller must free(). Returns entry count or -1 on error. |
135 | | */ |
136 | | static int |
137 | | ce_parse_dir(const u8 *data, size_t len, ce_dir_entry_t **entries_out) |
138 | 367 | { |
139 | 367 | size_t count, i; |
140 | 367 | ce_dir_entry_t *entries; |
141 | | |
142 | 367 | *entries_out = NULL; |
143 | 367 | if (len < CE_DIR_HEADER_SIZE) |
144 | 6 | return -1; |
145 | | |
146 | 361 | count = (size_t)data[6] | ((size_t)data[7] << 8); |
147 | 361 | if (count == 0) |
148 | 1 | return 0; |
149 | | |
150 | | /* Bound count against buffer size before allocation. */ |
151 | 360 | if (count > (len - CE_DIR_HEADER_SIZE) / CE_DIR_ENTRY_SIZE) |
152 | 14 | return -1; |
153 | | |
154 | 346 | entries = calloc(count, sizeof(ce_dir_entry_t)); |
155 | 346 | if (!entries) |
156 | 0 | return -1; |
157 | | |
158 | 5.76k | for (i = 0; i < count; i++) { |
159 | 5.41k | size_t off = CE_DIR_HEADER_SIZE + i * CE_DIR_ENTRY_SIZE; |
160 | 5.41k | int k; |
161 | | |
162 | | /* Name: up to 8 ASCII chars, may not be NUL-terminated on card. */ |
163 | 5.41k | memcpy(entries[i].name, data + off, 8); |
164 | 5.41k | entries[i].name[8] = '\0'; |
165 | | /* Strip trailing spaces/NULs. */ |
166 | 5.41k | k = 7; |
167 | 10.8k | while (k >= 0 && (entries[i].name[k] == ' ' || entries[i].name[k] == '\0')) |
168 | 5.39k | entries[i].name[k--] = '\0'; |
169 | 5.41k | entries[i].fid = (unsigned)data[off + 8] | ((unsigned)data[off + 9] << 8); |
170 | 5.41k | entries[i].is_dir = (data[off + 10] != 0); |
171 | 5.41k | } |
172 | | |
173 | 346 | *entries_out = entries; |
174 | 346 | return (int)count; |
175 | 346 | } |
176 | | |
177 | | typedef struct cert_entry { |
178 | | char label[32]; |
179 | | unsigned cert_fid; |
180 | | unsigned key_fid; |
181 | | unsigned key_size_bits; |
182 | | unsigned cont_id; |
183 | | unsigned key_pair_id; /* CE_AT_KEYEXCHANGE or CE_AT_SIGNATURE */ |
184 | | } cert_entry_t; |
185 | | |
186 | | /* Select AID_PKCS15 and enumerate certificates from mscp/cmapfile. |
187 | | * *certs_out: caller must free(). Returns cert count or negative error. */ |
188 | | static int |
189 | | srbeid_enum_certs(sc_card_t *card, cert_entry_t **certs_out) |
190 | 267 | { |
191 | 267 | u8 *dir_buf = NULL, *mscp_buf = NULL, *cmap_buf = NULL; |
192 | 267 | size_t dir_len = 0, mscp_len = 0, cmap_len = 0; |
193 | 267 | ce_dir_entry_t *root_entries = NULL, *mscp_entries = NULL; |
194 | 267 | int root_count = 0, mscp_count = 0; |
195 | 267 | unsigned mscp_fid = 0, cmap_fid = 0; |
196 | 267 | cert_entry_t *certs = NULL; |
197 | 267 | int ncerts = 0, cap = 8; |
198 | 267 | int r, i; |
199 | 267 | size_t cmap_offset = 0, cmap_nrec = 0; |
200 | | |
201 | 267 | *certs_out = NULL; |
202 | | |
203 | | /* Select PKI applet. */ |
204 | 267 | if (iso7816_select_aid(card, AID_PKCS15, AID_PKCS15_LEN, NULL, NULL) != SC_SUCCESS) { |
205 | 14 | r = SC_ERROR_CARD_CMD_FAILED; |
206 | 14 | goto out; |
207 | 14 | } |
208 | | |
209 | | /* Read root directory (FID 0x7000). */ |
210 | 253 | r = srbeid_read_file(card, CE_PKI_ROOT_DIR_FID, &dir_buf, &dir_len); |
211 | 253 | if (r < 0) |
212 | 26 | goto out; |
213 | | |
214 | 227 | root_count = ce_parse_dir(dir_buf, dir_len, &root_entries); |
215 | 227 | if (root_count < 0) { |
216 | 19 | r = SC_ERROR_INVALID_DATA; |
217 | 19 | goto out; |
218 | 19 | } |
219 | | |
220 | 1.43k | for (i = 0; i < root_count; i++) { |
221 | 1.36k | if (root_entries[i].is_dir && strcmp(root_entries[i].name, "mscp") == 0) { |
222 | 141 | mscp_fid = root_entries[i].fid; |
223 | 141 | break; |
224 | 141 | } |
225 | 1.36k | } |
226 | 208 | if (mscp_fid == 0) { |
227 | 67 | r = SC_ERROR_FILE_NOT_FOUND; |
228 | 67 | goto out; |
229 | 67 | } |
230 | | |
231 | | /* Read mscp directory. */ |
232 | 141 | r = srbeid_read_file(card, mscp_fid, &mscp_buf, &mscp_len); |
233 | 141 | if (r < 0) |
234 | 1 | goto out; |
235 | | |
236 | 140 | mscp_count = ce_parse_dir(mscp_buf, mscp_len, &mscp_entries); |
237 | 140 | if (mscp_count < 0) { |
238 | 1 | r = SC_ERROR_INVALID_DATA; |
239 | 1 | goto out; |
240 | 1 | } |
241 | | |
242 | 139 | certs = calloc((size_t)cap, sizeof(cert_entry_t)); |
243 | 139 | if (!certs) { |
244 | 0 | r = SC_ERROR_OUT_OF_MEMORY; |
245 | 0 | goto out; |
246 | 0 | } |
247 | | |
248 | 3.48k | for (i = 0; i < mscp_count; i++) { |
249 | 3.34k | ce_dir_entry_t *e = &mscp_entries[i]; |
250 | 3.34k | if (e->is_dir) |
251 | 2.20k | continue; |
252 | | |
253 | 1.14k | if (strcmp(e->name, "cmapfile") == 0) { |
254 | 65 | cmap_fid = e->fid; |
255 | 1.08k | } else if (strlen(e->name) == 5) { |
256 | 369 | unsigned kp_id; |
257 | 369 | const char *lbl; |
258 | | |
259 | 369 | if (strncmp(e->name, "kxc", 3) == 0) { |
260 | 212 | kp_id = CE_AT_KEYEXCHANGE; |
261 | 212 | lbl = "Key Exchange Certificate"; |
262 | 212 | } else if (strncmp(e->name, "ksc", 3) == 0) { |
263 | 61 | kp_id = CE_AT_SIGNATURE; |
264 | 61 | lbl = "Digital Signature Certificate"; |
265 | 96 | } else { |
266 | 96 | continue; |
267 | 96 | } |
268 | | |
269 | 273 | if (ncerts >= cap) { |
270 | 2 | cert_entry_t *tmp = realloc(certs, |
271 | 2 | (size_t)(cap * 2) * sizeof(cert_entry_t)); |
272 | 2 | if (!tmp) { |
273 | 0 | r = SC_ERROR_OUT_OF_MEMORY; |
274 | 0 | goto out; |
275 | 0 | } |
276 | 2 | certs = tmp; |
277 | 2 | cap *= 2; |
278 | 2 | } |
279 | | |
280 | 273 | certs[ncerts].cont_id = (unsigned)(e->name[3] - '0') * 10 + (unsigned)(e->name[4] - '0'); |
281 | 273 | certs[ncerts].cert_fid = e->fid; |
282 | 273 | certs[ncerts].key_pair_id = kp_id; |
283 | 273 | snprintf(certs[ncerts].label, sizeof(certs[ncerts].label), "%s", lbl); |
284 | 273 | ncerts++; |
285 | 273 | } |
286 | 1.14k | } |
287 | | |
288 | | /* Read cmapfile and resolve key FIDs. */ |
289 | 139 | if (cmap_fid != 0) { |
290 | 55 | r = srbeid_read_file(card, cmap_fid, &cmap_buf, &cmap_len); |
291 | 55 | if (r == SC_SUCCESS) { |
292 | | /* Optional 2-byte prefix present when (len-2) is a multiple of 86. */ |
293 | 32 | if (cmap_len >= 2 && (cmap_len - 2) % CE_CMAP_RECORD_SIZE == 0) |
294 | 3 | cmap_offset = 2; |
295 | 32 | cmap_nrec = (cmap_len - cmap_offset) / CE_CMAP_RECORD_SIZE; |
296 | 32 | } |
297 | 55 | } |
298 | | |
299 | 412 | for (i = 0; i < ncerts; i++) { |
300 | 273 | unsigned ci = certs[i].cont_id; |
301 | | |
302 | 273 | if (cmap_buf && ci < cmap_nrec) { |
303 | 50 | size_t rec = cmap_offset + (size_t)ci * CE_CMAP_RECORD_SIZE; |
304 | 50 | u8 flags = cmap_buf[rec + CE_CMAP_FLAGS_OFFSET]; |
305 | | |
306 | 50 | if (flags & CE_CMAP_VALID_CONTAINER) { |
307 | 36 | size_t sz_off = (certs[i].key_pair_id == CE_AT_KEYEXCHANGE) |
308 | 36 | ? rec + CE_CMAP_KX_SIZE_OFFSET |
309 | 36 | : rec + CE_CMAP_SIG_SIZE_OFFSET; |
310 | 36 | unsigned kbits = (unsigned)cmap_buf[sz_off] | ((unsigned)cmap_buf[sz_off + 1] << 8); |
311 | 36 | if (kbits != 0) { |
312 | 30 | certs[i].key_size_bits = kbits; |
313 | 30 | certs[i].key_fid = ce_private_key_fid(ci, certs[i].key_pair_id); |
314 | 30 | } |
315 | 36 | } |
316 | 50 | } |
317 | 273 | sc_log(card->ctx, |
318 | 273 | "srbeid: cert[%d] \"%s\" cert_fid=0x%04x key_fid=0x%04x key_size=%u", |
319 | 273 | i, certs[i].label, certs[i].cert_fid, |
320 | 273 | certs[i].key_fid, certs[i].key_size_bits); |
321 | 273 | } |
322 | | |
323 | 139 | *certs_out = certs; |
324 | 139 | certs = NULL; |
325 | 139 | r = ncerts; |
326 | | |
327 | 267 | out: |
328 | 267 | free(dir_buf); |
329 | 267 | free(mscp_buf); |
330 | 267 | free(cmap_buf); |
331 | 267 | free(root_entries); |
332 | 267 | free(mscp_entries); |
333 | 267 | free(certs); |
334 | 267 | return r; |
335 | 139 | } |
336 | | |
337 | | /* |
338 | | * Read the raw (possibly zlib-compressed) cert file and return DER bytes. |
339 | | * |
340 | | * CardEdge cert file layout: |
341 | | * [CardFS len prefix: 2 bytes LE] |
342 | | * [0x01 0x00] [uncompressed len: 2 bytes LE] [zlib data] — compressed |
343 | | * OR [0x30 ...] — raw DER |
344 | | */ |
345 | | static int |
346 | | srbeid_read_cert_der(sc_card_t *card, unsigned cert_fid, |
347 | | u8 **der_out, size_t *der_len_out) |
348 | 273 | { |
349 | 273 | u8 *raw = NULL; |
350 | 273 | size_t raw_len = 0; |
351 | 273 | const u8 *data; |
352 | 273 | size_t dlen; |
353 | 273 | int r; |
354 | | |
355 | 273 | *der_out = NULL; |
356 | 273 | *der_len_out = 0; |
357 | | |
358 | 273 | r = srbeid_read_file(card, cert_fid, &raw, &raw_len); |
359 | 273 | if (r < 0) |
360 | 233 | return r; |
361 | | |
362 | 40 | if (raw_len < 6) { |
363 | 11 | free(raw); |
364 | 11 | return SC_ERROR_INVALID_DATA; |
365 | 11 | } |
366 | | |
367 | | /* Skip 2-byte CardFS length prefix. */ |
368 | 29 | data = raw + 2; |
369 | 29 | dlen = raw_len - 2; |
370 | | |
371 | 29 | if (dlen >= 4 && data[0] == 0x01 && data[1] == 0x00) { |
372 | | /* zlib-compressed DER */ |
373 | 3 | #ifdef ENABLE_ZLIB |
374 | 3 | size_t uncompressed_len = (size_t)data[2] | ((size_t)data[3] << 8); |
375 | 3 | u8 *der = NULL; |
376 | | |
377 | 3 | r = sc_decompress_alloc(&der, &uncompressed_len, |
378 | 3 | data + 4, dlen - 4, COMPRESSION_ZLIB); |
379 | 3 | if (r != SC_SUCCESS) { |
380 | 3 | sc_log(card->ctx, "srbeid: zlib decompress failed (ret=%d)", r); |
381 | 3 | free(raw); |
382 | 3 | return SC_ERROR_INVALID_DATA; |
383 | 3 | } |
384 | 0 | *der_out = der; |
385 | 0 | *der_len_out = uncompressed_len; |
386 | | #else |
387 | | sc_log(card->ctx, "srbeid: cert is zlib-compressed but zlib not available"); |
388 | | free(raw); |
389 | | return SC_ERROR_NOT_SUPPORTED; |
390 | | #endif |
391 | 26 | } else if (dlen >= 1 && data[0] == 0x30) { |
392 | | /* Uncompressed DER (ASN.1 SEQUENCE tag). */ |
393 | 3 | u8 *der = malloc(dlen); |
394 | 3 | if (!der) { |
395 | 0 | free(raw); |
396 | 0 | return SC_ERROR_OUT_OF_MEMORY; |
397 | 0 | } |
398 | 3 | memcpy(der, data, dlen); |
399 | 3 | *der_out = der; |
400 | 3 | *der_len_out = dlen; |
401 | 23 | } else { |
402 | 23 | sc_log(card->ctx, |
403 | 23 | "srbeid: cert FID 0x%04x: unknown format (byte0=0x%02x)", |
404 | 23 | cert_fid, data[0]); |
405 | 23 | free(raw); |
406 | 23 | return SC_ERROR_INVALID_DATA; |
407 | 23 | } |
408 | | |
409 | 3 | free(raw); |
410 | 3 | return SC_SUCCESS; |
411 | 29 | } |
412 | | |
413 | | static int |
414 | | sc_pkcs15emu_srbeid_init(sc_pkcs15_card_t *p15card) |
415 | 267 | { |
416 | 267 | sc_card_t *card = p15card->card; |
417 | 267 | cert_entry_t *certs = NULL; |
418 | 267 | int ncerts, i, r = SC_SUCCESS; |
419 | | |
420 | 267 | sc_log(card->ctx, "srbeid: pkcs15 bind"); |
421 | | |
422 | 267 | ncerts = srbeid_enum_certs(card, &certs); |
423 | 267 | if (ncerts < 0) { |
424 | 128 | sc_log(card->ctx, "srbeid: cert enumeration failed: %d", ncerts); |
425 | 128 | return ncerts; |
426 | 128 | } |
427 | 139 | if (ncerts == 0) { |
428 | 47 | sc_log(card->ctx, "srbeid: no certificates found"); |
429 | 47 | goto out; |
430 | 47 | } |
431 | | |
432 | | /* Set card label and manufacturer. */ |
433 | 92 | set_string(&p15card->tokeninfo->label, "Serbian CardEdge"); |
434 | 92 | set_string(&p15card->tokeninfo->manufacturer_id, "CardEdge"); |
435 | | |
436 | | /* Query PIN tries_left via card driver's pin_cmd. */ |
437 | 92 | { |
438 | 92 | struct sc_pin_cmd_data pin_data = {0}; |
439 | | |
440 | 92 | pin_data.cmd = SC_PIN_CMD_GET_INFO; |
441 | 92 | pin_data.pin_type = SC_AC_CHV; |
442 | 92 | pin_data.pin_reference = CE_PIN_REFERENCE; |
443 | 92 | pin_data.pin1.tries_left = -1; |
444 | | |
445 | | /* Best-effort: failure to query PIN status is not fatal. */ |
446 | 92 | sc_pin_cmd(card, &pin_data); |
447 | 92 | sc_log(card->ctx, "srbeid: PIN tries_left=%d", pin_data.pin1.tries_left); |
448 | | |
449 | | /* ---- PIN auth object ---- |
450 | | * Must be registered before private keys so auth_id links work. */ |
451 | 92 | { |
452 | 92 | sc_pkcs15_auth_info_t auth_info = {0}; |
453 | 92 | sc_pkcs15_object_t auth_obj = {0}; |
454 | | |
455 | 92 | auth_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN; |
456 | 92 | auth_info.auth_method = SC_AC_CHV; |
457 | 92 | auth_info.tries_left = pin_data.pin1.tries_left; |
458 | 92 | auth_info.attrs.pin.reference = CE_PIN_REFERENCE; |
459 | 92 | auth_info.attrs.pin.min_length = 4; |
460 | 92 | auth_info.attrs.pin.max_length = CE_PIN_MAX_LENGTH; |
461 | 92 | auth_info.attrs.pin.stored_length = CE_PIN_MAX_LENGTH; |
462 | 92 | auth_info.attrs.pin.type = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC; |
463 | 92 | auth_info.attrs.pin.pad_char = 0x00; |
464 | 92 | auth_info.attrs.pin.flags = SC_PKCS15_PIN_FLAG_INITIALIZED | SC_PKCS15_PIN_FLAG_LOCAL | SC_PKCS15_PIN_FLAG_NEEDS_PADDING; |
465 | 92 | auth_info.path.aid.len = AID_PKCS15_LEN; |
466 | 92 | memcpy(auth_info.path.aid.value, AID_PKCS15, AID_PKCS15_LEN); |
467 | 92 | auth_info.auth_id.len = 1; |
468 | 92 | auth_info.auth_id.value[0] = 1; |
469 | | |
470 | 92 | strncpy(auth_obj.label, "User PIN", sizeof(auth_obj.label) - 1); |
471 | 92 | auth_obj.auth_id.len = 0; |
472 | 92 | auth_obj.flags = SC_PKCS15_CO_FLAG_MODIFIABLE; |
473 | | |
474 | 92 | r = sc_pkcs15emu_add_pin_obj(p15card, &auth_obj, &auth_info); |
475 | 92 | if (r < 0) { |
476 | 0 | sc_log(card->ctx, "srbeid: add PIN obj failed: %d", r); |
477 | 0 | goto out; |
478 | 0 | } |
479 | 92 | } |
480 | 92 | } |
481 | | |
482 | 365 | for (i = 0; i < ncerts; i++) { |
483 | 273 | sc_pkcs15_prkey_info_t key_info = {0}; |
484 | 273 | sc_pkcs15_object_t key_obj = {0}; |
485 | 273 | sc_pkcs15_cert_info_t cert_info = {0}; |
486 | 273 | sc_pkcs15_object_t cert_obj = {0}; |
487 | 273 | u8 *der = NULL; |
488 | 273 | size_t der_len = 0; |
489 | 273 | int is_kxc = (certs[i].key_pair_id == CE_AT_KEYEXCHANGE); |
490 | | |
491 | | /* ---- Private key object ---- */ |
492 | | |
493 | 273 | key_info.id.len = 1; |
494 | 273 | key_info.id.value[0] = (u8)(i + 1); |
495 | 273 | key_info.native = 1; |
496 | 273 | key_info.key_reference = (int)certs[i].key_fid; |
497 | 273 | key_info.modulus_length = certs[i].key_size_bits |
498 | 273 | ? certs[i].key_size_bits |
499 | 273 | : 2048; |
500 | | |
501 | | /* |
502 | | * Key usage flags by type: |
503 | | * kxc (AT_KEYEXCHANGE) — encryption / key wrapping / decryption + signing |
504 | | * (TLS client auth uses the key exchange cert for signing) |
505 | | * ksc (AT_SIGNATURE) — digital signature / non-repudiation only |
506 | | */ |
507 | 273 | if (is_kxc) { |
508 | 212 | key_info.usage = SC_PKCS15_PRKEY_USAGE_ENCRYPT | SC_PKCS15_PRKEY_USAGE_DECRYPT | SC_PKCS15_PRKEY_USAGE_WRAP | SC_PKCS15_PRKEY_USAGE_UNWRAP | SC_PKCS15_PRKEY_USAGE_SIGN; |
509 | 212 | } else { |
510 | 61 | key_info.usage = SC_PKCS15_PRKEY_USAGE_SIGN | SC_PKCS15_PRKEY_USAGE_NONREPUDIATION; |
511 | 61 | } |
512 | | |
513 | | /* |
514 | | * Set only the AID on key_info.path (path.len stays 0). |
515 | | * This makes select_key_file() select the PKI applet via AID |
516 | | * before calling set_security_env(), without appending a file |
517 | | * path that would fail on CardEdge's non-TLV FCI. |
518 | | * |
519 | | * The key FID is passed via key_info.key_reference and |
520 | | * reconstructed in set_security_env() from the low byte. |
521 | | */ |
522 | 273 | key_info.path.aid.len = AID_PKCS15_LEN; |
523 | 273 | memcpy(key_info.path.aid.value, AID_PKCS15, AID_PKCS15_LEN); |
524 | | |
525 | 273 | strncpy(key_obj.label, certs[i].label, sizeof(key_obj.label) - 1); |
526 | 273 | key_obj.flags = SC_PKCS15_CO_FLAG_PRIVATE; |
527 | 273 | key_obj.auth_id.len = 1; |
528 | 273 | key_obj.auth_id.value[0] = 1; |
529 | | |
530 | 273 | r = sc_pkcs15emu_add_rsa_prkey(p15card, &key_obj, &key_info); |
531 | 273 | if (r < 0) { |
532 | 0 | sc_log(card->ctx, "srbeid: add prkey[%d] failed: %d", i, r); |
533 | 0 | goto out; |
534 | 0 | } |
535 | | |
536 | | /* ---- Certificate object ---- */ |
537 | 273 | if (srbeid_read_cert_der(card, certs[i].cert_fid, &der, &der_len) < 0) { |
538 | 270 | sc_log(card->ctx, "srbeid: could not read cert[%d] DER", i); |
539 | 270 | continue; |
540 | 270 | } |
541 | | |
542 | 3 | cert_info.id.len = 1; |
543 | 3 | cert_info.id.value[0] = (u8)(i + 1); |
544 | 3 | cert_info.authority = 0; |
545 | | |
546 | | /* Store DER directly in the PKCS#15 value buffer. */ |
547 | 3 | cert_info.value.value = der; /* ownership transferred */ |
548 | 3 | cert_info.value.len = der_len; |
549 | | |
550 | 3 | strncpy(cert_obj.label, certs[i].label, sizeof(cert_obj.label) - 1); |
551 | | |
552 | 3 | r = sc_pkcs15emu_add_x509_cert(p15card, &cert_obj, &cert_info); |
553 | 3 | if (r < 0) { |
554 | 0 | sc_log(card->ctx, "srbeid: add cert[%d] failed: %d", i, r); |
555 | 0 | free(der); |
556 | 0 | goto out; |
557 | 0 | } |
558 | | /* der ownership now belongs to p15card; do not free. */ |
559 | 3 | } |
560 | | |
561 | 92 | sc_log(card->ctx, "srbeid: pkcs15 bind OK (%d certs)", ncerts); |
562 | | |
563 | 139 | out: |
564 | 139 | free(certs); |
565 | 139 | return r; |
566 | 92 | } |
567 | | |
568 | | int |
569 | | sc_pkcs15emu_srbeid_init_ex(sc_pkcs15_card_t *p15card, struct sc_aid *aid) |
570 | 6.63k | { |
571 | 6.63k | (void)aid; |
572 | | |
573 | 6.63k | if (p15card->card->type != SC_CARD_TYPE_SRBEID_BASE) |
574 | 6.37k | return SC_ERROR_WRONG_CARD; |
575 | | |
576 | 267 | return sc_pkcs15emu_srbeid_init(p15card); |
577 | 6.63k | } |