/src/opensc/src/libopensc/card-muscle.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * card-muscle.c: Support for MuscleCard Applet from musclecard.com |
3 | | * |
4 | | * Copyright (C) 2006, Identity Alliance, Thomas Harning <support@identityalliance.com> |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | | */ |
20 | | |
21 | | #ifdef HAVE_CONFIG_H |
22 | | #include "config.h" |
23 | | #endif |
24 | | |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | |
28 | | #include "internal.h" |
29 | | #include "cardctl.h" |
30 | | #include "muscle.h" |
31 | | #include "muscle-filesystem.h" |
32 | | #include "types.h" |
33 | | #include "opensc.h" |
34 | | |
35 | | static struct sc_card_operations muscle_ops; |
36 | | static const struct sc_card_operations *iso_ops = NULL; |
37 | | |
38 | | static struct sc_card_driver muscle_drv = { |
39 | | "MuscleApplet", |
40 | | "muscle", |
41 | | &muscle_ops, |
42 | | NULL, 0, NULL |
43 | | }; |
44 | | |
45 | | static const struct sc_atr_table muscle_atrs[] = { |
46 | | /* Tyfone JCOP 242R2 cards */ |
47 | | { "3b:6d:00:00:ff:54:79:66:6f:6e:65:20:32:34:32:52:32", NULL, NULL, SC_CARD_TYPE_MUSCLE_JCOP242R2_NO_EXT_APDU, 0, NULL }, |
48 | | /* Aladdin eToken PRO USB 72K Java */ |
49 | | { "3b:d5:18:00:81:31:3a:7d:80:73:c8:21:10:30", NULL, NULL, SC_CARD_TYPE_MUSCLE_ETOKEN_72K, 0, NULL }, |
50 | | /* JCOP31 v2.4.1 contact interface */ |
51 | | { "3b:f8:13:00:00:81:31:fe:45:4a:43:4f:50:76:32:34:31:b7", NULL, NULL, SC_CARD_TYPE_MUSCLE_JCOP241, 0, NULL }, |
52 | | /* JCOP31 v2.4.1 RF interface */ |
53 | | { "3b:88:80:01:4a:43:4f:50:76:32:34:31:5e", NULL, NULL, SC_CARD_TYPE_MUSCLE_JCOP241, 0, NULL }, |
54 | | { NULL, NULL, NULL, 0, 0, NULL } |
55 | | }; |
56 | | |
57 | 1.25k | #define MUSCLE_DATA(card) ( (muscle_private_t*)card->drv_data ) |
58 | 0 | #define MUSCLE_FS(card) ( ((muscle_private_t*)card->drv_data)->fs ) |
59 | | typedef struct muscle_private { |
60 | | sc_security_env_t env; |
61 | | unsigned short verifiedPins; |
62 | | mscfs_t *fs; |
63 | | int rsa_key_ref; |
64 | | |
65 | | } muscle_private_t; |
66 | | |
67 | | static int muscle_finish(sc_card_t *card) |
68 | 419 | { |
69 | 419 | muscle_private_t *priv = MUSCLE_DATA(card); |
70 | 419 | mscfs_free(priv->fs); |
71 | 419 | free(priv); |
72 | 419 | return 0; |
73 | 419 | } |
74 | | |
75 | | |
76 | | static u8 muscleAppletId[] = { 0xA0, 0x00,0x00,0x00, 0x01, 0x01 }; |
77 | | |
78 | | static int muscle_match_card(sc_card_t *card) |
79 | 6.06k | { |
80 | 6.06k | sc_apdu_t apdu; |
81 | 6.06k | u8 response[64]; |
82 | 6.06k | int r; |
83 | | |
84 | 6.06k | if (msc_select_applet(card, muscleAppletId, sizeof muscleAppletId) == 1) { |
85 | | /* Muscle applet is present, check the protocol version to be sure */ |
86 | 419 | sc_format_apdu(card, &apdu, SC_APDU_CASE_2, 0x3C, 0x00, 0x00); |
87 | 419 | apdu.cla = 0xB0; |
88 | 419 | apdu.le = 64; |
89 | 419 | apdu.resplen = 64; |
90 | 419 | apdu.resp = response; |
91 | 419 | r = sc_transmit_apdu(card, &apdu); |
92 | 419 | if (r == SC_SUCCESS && apdu.resplen > 1 && response[0] == 0x01) { |
93 | 1 | card->type = SC_CARD_TYPE_MUSCLE_V1; |
94 | 418 | } else { |
95 | 418 | card->type = SC_CARD_TYPE_MUSCLE_GENERIC; |
96 | 418 | } |
97 | 419 | return 1; |
98 | 419 | } |
99 | 5.64k | return 0; |
100 | 6.06k | } |
101 | | |
102 | | /* Since Musclecard has a different ACL system then PKCS15 |
103 | | * objects need to have their READ/UPDATE/DELETE permissions mapped for files |
104 | | * and directory ACLS need to be set |
105 | | * For keys.. they have different ACLS, but are accessed in different locations, so it shouldn't be an issue here |
106 | | */ |
107 | | static unsigned short muscle_parse_singleAcl(const sc_acl_entry_t* acl) |
108 | 0 | { |
109 | 0 | unsigned short acl_entry = 0; |
110 | 0 | while(acl) { |
111 | 0 | int key = acl->key_ref; |
112 | 0 | int method = acl->method; |
113 | 0 | switch(method) { |
114 | 0 | case SC_AC_NEVER: |
115 | 0 | return 0xFFFF; |
116 | | /* Ignore... other items overwrite these */ |
117 | 0 | case SC_AC_NONE: |
118 | 0 | case SC_AC_UNKNOWN: |
119 | 0 | break; |
120 | 0 | case SC_AC_CHV: |
121 | | /* Ignore shift with more bits that acl_entry has */ |
122 | 0 | if ((size_t) key < sizeof(acl_entry) * 8) |
123 | 0 | acl_entry |= (1u << key); /* Assuming key 0 == SO */ |
124 | 0 | break; |
125 | 0 | case SC_AC_AUT: |
126 | 0 | case SC_AC_TERM: |
127 | 0 | case SC_AC_PRO: |
128 | 0 | default: |
129 | | /* Ignored */ |
130 | 0 | break; |
131 | 0 | } |
132 | 0 | acl = acl->next; |
133 | 0 | } |
134 | 0 | return acl_entry; |
135 | 0 | } |
136 | | |
137 | | static void muscle_parse_acls(const sc_file_t* file, unsigned short* read_perm, unsigned short* write_perm, unsigned short* delete_perm) |
138 | 0 | { |
139 | 0 | assert(read_perm && write_perm && delete_perm); |
140 | 0 | *read_perm = muscle_parse_singleAcl(sc_file_get_acl_entry(file, SC_AC_OP_READ)); |
141 | 0 | *write_perm = muscle_parse_singleAcl(sc_file_get_acl_entry(file, SC_AC_OP_UPDATE)); |
142 | 0 | *delete_perm = muscle_parse_singleAcl(sc_file_get_acl_entry(file, SC_AC_OP_DELETE)); |
143 | 0 | } |
144 | | |
145 | | static int muscle_create_directory(sc_card_t *card, sc_file_t *file) |
146 | 0 | { |
147 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
148 | 0 | msc_id objectId; |
149 | 0 | u8* oid = objectId.id; |
150 | 0 | unsigned id = file->id; |
151 | 0 | unsigned short read_perm = 0, write_perm = 0, delete_perm = 0; |
152 | 0 | size_t objectSize; |
153 | 0 | int r; |
154 | 0 | if(id == 0) /* No null name files */ |
155 | 0 | return SC_ERROR_INVALID_ARGUMENTS; |
156 | | |
157 | | /* No nesting directories */ |
158 | 0 | if(fs->currentPath[0] != 0x3F || fs->currentPath[1] != 0x00) |
159 | 0 | return SC_ERROR_NOT_SUPPORTED; |
160 | 0 | oid[0] = ((id & 0xFF00) >> 8) & 0xFF; |
161 | 0 | oid[1] = id & 0xFF; |
162 | 0 | oid[2] = oid[3] = 0; |
163 | |
|
164 | 0 | objectSize = file->size; |
165 | |
|
166 | 0 | muscle_parse_acls(file, &read_perm, &write_perm, &delete_perm); |
167 | 0 | r = msc_create_object(card, objectId, objectSize, read_perm, write_perm, delete_perm); |
168 | 0 | mscfs_clear_cache(fs); |
169 | 0 | if(r >= 0) return 0; |
170 | 0 | return r; |
171 | 0 | } |
172 | | |
173 | | |
174 | | static int muscle_create_file(sc_card_t *card, sc_file_t *file) |
175 | 0 | { |
176 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
177 | 0 | size_t objectSize = file->size; |
178 | 0 | unsigned short read_perm = 0, write_perm = 0, delete_perm = 0; |
179 | 0 | msc_id objectId; |
180 | 0 | int r; |
181 | 0 | if(file->type == SC_FILE_TYPE_DF) |
182 | 0 | return muscle_create_directory(card, file); |
183 | 0 | if(file->type != SC_FILE_TYPE_WORKING_EF) |
184 | 0 | return SC_ERROR_NOT_SUPPORTED; |
185 | 0 | if(file->id == 0) /* No null name files */ |
186 | 0 | return SC_ERROR_INVALID_ARGUMENTS; |
187 | | |
188 | 0 | muscle_parse_acls(file, &read_perm, &write_perm, &delete_perm); |
189 | |
|
190 | 0 | mscfs_lookup_local(fs, file->id, &objectId); |
191 | 0 | r = msc_create_object(card, objectId, objectSize, read_perm, write_perm, delete_perm); |
192 | 0 | mscfs_clear_cache(fs); |
193 | 0 | if(r >= 0) return 0; |
194 | 0 | return r; |
195 | 0 | } |
196 | | |
197 | | static int muscle_read_binary(sc_card_t *card, unsigned int idx, u8* buf, size_t count, unsigned long *flags) |
198 | 0 | { |
199 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
200 | 0 | int r; |
201 | 0 | msc_id objectId; |
202 | 0 | u8* oid = objectId.id; |
203 | 0 | mscfs_file_t *file; |
204 | |
|
205 | 0 | r = mscfs_check_selection(fs, -1); |
206 | 0 | if(r < 0) LOG_FUNC_RETURN(card->ctx, r); |
207 | 0 | file = &fs->cache.array[fs->currentFileIndex]; |
208 | 0 | objectId = file->objectId; |
209 | | /* memcpy(objectId.id, file->objectId.id, 4); */ |
210 | 0 | if(!file->ef) { |
211 | 0 | oid[0] = oid[2]; |
212 | 0 | oid[1] = oid[3]; |
213 | 0 | oid[2] = oid[3] = 0; |
214 | 0 | } |
215 | 0 | r = msc_read_object(card, objectId, idx, buf, count); |
216 | 0 | LOG_FUNC_RETURN(card->ctx, r); |
217 | 0 | } |
218 | | |
219 | | static int muscle_update_binary(sc_card_t *card, unsigned int idx, const u8* buf, size_t count, unsigned long flags) |
220 | 0 | { |
221 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
222 | 0 | int r; |
223 | 0 | mscfs_file_t *file; |
224 | 0 | msc_id objectId; |
225 | 0 | u8* oid = objectId.id; |
226 | |
|
227 | 0 | r = mscfs_check_selection(fs, -1); |
228 | 0 | if(r < 0) LOG_FUNC_RETURN(card->ctx, r); |
229 | 0 | file = &fs->cache.array[fs->currentFileIndex]; |
230 | |
|
231 | 0 | objectId = file->objectId; |
232 | | /* memcpy(objectId.id, file->objectId.id, 4); */ |
233 | 0 | if(!file->ef) { |
234 | 0 | oid[0] = oid[2]; |
235 | 0 | oid[1] = oid[3]; |
236 | 0 | oid[2] = oid[3] = 0; |
237 | 0 | } |
238 | 0 | if(file->size < idx + count) { |
239 | 0 | size_t newFileSize = idx + count; |
240 | 0 | u8* buffer = malloc(newFileSize); |
241 | 0 | if(buffer == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); |
242 | | |
243 | 0 | r = msc_read_object(card, objectId, 0, buffer, file->size); |
244 | | /* TODO: RETRIEVE ACLS */ |
245 | 0 | if(r < 0) goto update_bin_free_buffer; |
246 | 0 | r = msc_delete_object(card, objectId, 0); |
247 | 0 | if(r < 0) goto update_bin_free_buffer; |
248 | 0 | r = msc_create_object(card, objectId, newFileSize, 0,0,0); |
249 | 0 | if(r < 0) goto update_bin_free_buffer; |
250 | 0 | memcpy(buffer + idx, buf, count); |
251 | 0 | r = msc_update_object(card, objectId, 0, buffer, newFileSize); |
252 | 0 | if(r < 0) goto update_bin_free_buffer; |
253 | 0 | file->size = newFileSize; |
254 | 0 | update_bin_free_buffer: |
255 | 0 | free(buffer); |
256 | 0 | LOG_FUNC_RETURN(card->ctx, r); |
257 | 0 | } else { |
258 | 0 | r = msc_update_object(card, objectId, idx, buf, count); |
259 | 0 | } |
260 | | /* mscfs_clear_cache(fs); */ |
261 | 0 | return r; |
262 | 0 | } |
263 | | |
264 | | /* TODO: Evaluate correctness */ |
265 | | static int muscle_delete_mscfs_file(sc_card_t *card, mscfs_file_t *file_data) |
266 | 0 | { |
267 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
268 | 0 | msc_id id = file_data->objectId; |
269 | 0 | u8* oid = id.id; |
270 | 0 | int r; |
271 | 0 | file_data->deleteFile = 1; |
272 | |
|
273 | 0 | if(!file_data->ef) { |
274 | 0 | int x; |
275 | 0 | mscfs_file_t *childFile; |
276 | | /* Delete children */ |
277 | 0 | r = mscfs_check_cache(fs); |
278 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r); |
279 | | |
280 | 0 | sc_log(card->ctx, |
281 | 0 | "DELETING Children of: %02X%02X%02X%02X\n", |
282 | 0 | oid[0],oid[1],oid[2],oid[3]); |
283 | 0 | for(x = 0; x < fs->cache.size; x++) { |
284 | 0 | msc_id objectId; |
285 | 0 | childFile = &fs->cache.array[x]; |
286 | 0 | objectId = childFile->objectId; |
287 | |
|
288 | 0 | if(0 == memcmp(oid + 2, objectId.id, 2) && !childFile->deleteFile) { |
289 | 0 | sc_log(card->ctx, |
290 | 0 | "DELETING: %02X%02X%02X%02X\n", |
291 | 0 | objectId.id[0],objectId.id[1], |
292 | 0 | objectId.id[2],objectId.id[3]); |
293 | 0 | r = muscle_delete_mscfs_file(card, childFile); |
294 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
295 | |
|
296 | 0 | } |
297 | 0 | } |
298 | 0 | oid[0] = oid[2]; |
299 | 0 | oid[1] = oid[3]; |
300 | 0 | oid[2] = oid[3] = 0; |
301 | | /* ??? objectId = objectId >> 16; */ |
302 | 0 | } |
303 | 0 | r = msc_delete_object(card, id, 1); |
304 | | /* Check if its the root... this file generally is virtual |
305 | | * So don't return an error if it fails */ |
306 | 0 | if((0 == memcmp(oid, "\x3F\x00\x00\x00", 4)) |
307 | 0 | || (0 == memcmp(oid, "\x3F\x00\x3F\x00", 4))) |
308 | 0 | return 0; |
309 | | |
310 | 0 | if(r < 0) { |
311 | 0 | printf("ID: %02X%02X%02X%02X\n", |
312 | 0 | oid[0],oid[1],oid[2],oid[3]); |
313 | 0 | SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
314 | 0 | } |
315 | 0 | return 0; |
316 | 0 | } |
317 | | |
318 | | static int muscle_delete_file(sc_card_t *card, const sc_path_t *path_in) |
319 | 0 | { |
320 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
321 | 0 | mscfs_file_t *file_data = NULL; |
322 | 0 | int r = 0; |
323 | |
|
324 | 0 | r = mscfs_loadFileInfo(fs, path_in->value, path_in->len, &file_data, NULL); |
325 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
326 | 0 | for(int x = 0; x < fs->cache.size; x++) { |
327 | 0 | mscfs_file_t *file = &fs->cache.array[x]; |
328 | 0 | file->deleteFile = 0; |
329 | 0 | } |
330 | 0 | r = muscle_delete_mscfs_file(card, file_data); |
331 | 0 | mscfs_clear_cache(fs); |
332 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
333 | 0 | return 0; |
334 | 0 | } |
335 | | |
336 | | static void muscle_load_single_acl(sc_file_t* file, int operation, unsigned short acl) |
337 | 0 | { |
338 | 0 | int key; |
339 | | /* Everybody by default.... */ |
340 | 0 | sc_file_add_acl_entry(file, operation, SC_AC_NONE, 0); |
341 | 0 | if(acl == 0xFFFF) { |
342 | 0 | sc_file_add_acl_entry(file, operation, SC_AC_NEVER, 0); |
343 | 0 | return; |
344 | 0 | } |
345 | 0 | for(key = 0; key < 16; key++) { |
346 | 0 | if(acl >> key & 1) { |
347 | 0 | sc_file_add_acl_entry(file, operation, SC_AC_CHV, key); |
348 | 0 | } |
349 | 0 | } |
350 | 0 | } |
351 | | static void muscle_load_file_acls(sc_file_t* file, mscfs_file_t *file_data) |
352 | 0 | { |
353 | 0 | muscle_load_single_acl(file, SC_AC_OP_READ, file_data->read); |
354 | 0 | muscle_load_single_acl(file, SC_AC_OP_WRITE, file_data->write); |
355 | 0 | muscle_load_single_acl(file, SC_AC_OP_UPDATE, file_data->write); |
356 | 0 | muscle_load_single_acl(file, SC_AC_OP_DELETE, file_data->delete); |
357 | 0 | } |
358 | | static void muscle_load_dir_acls(sc_file_t* file, mscfs_file_t *file_data) |
359 | 0 | { |
360 | 0 | muscle_load_single_acl(file, SC_AC_OP_SELECT, 0); |
361 | 0 | muscle_load_single_acl(file, SC_AC_OP_LIST_FILES, 0); |
362 | 0 | muscle_load_single_acl(file, SC_AC_OP_LOCK, 0xFFFF); |
363 | 0 | muscle_load_single_acl(file, SC_AC_OP_DELETE, file_data->delete); |
364 | 0 | muscle_load_single_acl(file, SC_AC_OP_CREATE, file_data->write); |
365 | 0 | } |
366 | | |
367 | | /* Required type = -1 for don't care, 1 for EF, 0 for DF */ |
368 | | static int select_item(sc_card_t *card, const sc_path_t *path_in, sc_file_t ** file_out, int requiredType) |
369 | 0 | { |
370 | 0 | mscfs_t *fs = MUSCLE_FS(card); |
371 | 0 | mscfs_file_t *file_data = NULL; |
372 | 0 | size_t pathlen = path_in->len; |
373 | 0 | int r = 0; |
374 | 0 | int objectIndex; |
375 | 0 | u8* oid; |
376 | |
|
377 | 0 | r = mscfs_check_cache(fs); |
378 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r); |
379 | 0 | r = mscfs_loadFileInfo(fs, path_in->value, path_in->len, &file_data, &objectIndex); |
380 | 0 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
381 | | |
382 | | /* Check if its the right type */ |
383 | 0 | if(requiredType >= 0 && requiredType != file_data->ef) { |
384 | 0 | LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); |
385 | 0 | } |
386 | 0 | oid = file_data->objectId.id; |
387 | | /* Is it a file or directory */ |
388 | 0 | if(file_data->ef) { |
389 | 0 | fs->currentPath[0] = oid[0]; |
390 | 0 | fs->currentPath[1] = oid[1]; |
391 | 0 | fs->currentFile[0] = oid[2]; |
392 | 0 | fs->currentFile[1] = oid[3]; |
393 | 0 | } else { |
394 | 0 | if(pathlen < 2) { |
395 | 0 | LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); |
396 | 0 | } |
397 | 0 | fs->currentPath[0] = oid[pathlen - 2]; |
398 | 0 | fs->currentPath[1] = oid[pathlen - 1]; |
399 | 0 | fs->currentFile[0] = 0; |
400 | 0 | fs->currentFile[1] = 0; |
401 | 0 | } |
402 | | |
403 | 0 | fs->currentFileIndex = objectIndex; |
404 | 0 | if(file_out) { |
405 | 0 | sc_file_t *file; |
406 | 0 | file = sc_file_new(); |
407 | 0 | file->path = *path_in; |
408 | 0 | file->size = file_data->size; |
409 | 0 | file->id = (oid[2] << 8) | oid[3]; |
410 | 0 | if(!file_data->ef) { |
411 | 0 | file->type = SC_FILE_TYPE_DF; |
412 | 0 | } else { |
413 | 0 | file->type = SC_FILE_TYPE_WORKING_EF; |
414 | 0 | file->ef_structure = SC_FILE_EF_TRANSPARENT; |
415 | 0 | } |
416 | | |
417 | | /* Setup ACLS */ |
418 | 0 | if(file_data->ef) { |
419 | 0 | muscle_load_file_acls(file, file_data); |
420 | 0 | } else { |
421 | 0 | muscle_load_dir_acls(file, file_data); |
422 | | /* Setup directory acls... */ |
423 | 0 | } |
424 | |
|
425 | 0 | file->magic = SC_FILE_MAGIC; |
426 | 0 | *file_out = file; |
427 | 0 | } |
428 | 0 | return 0; |
429 | 0 | } |
430 | | |
431 | | static int muscle_select_file(sc_card_t *card, const sc_path_t *path_in, |
432 | | sc_file_t **file_out) |
433 | 0 | { |
434 | 0 | int r; |
435 | |
|
436 | 0 | assert(card != NULL && path_in != NULL); |
437 | | |
438 | 0 | switch (path_in->type) { |
439 | 0 | case SC_PATH_TYPE_FILE_ID: |
440 | 0 | r = select_item(card, path_in, file_out, 1); |
441 | 0 | break; |
442 | 0 | case SC_PATH_TYPE_DF_NAME: |
443 | 0 | r = select_item(card, path_in, file_out, 0); |
444 | 0 | break; |
445 | 0 | case SC_PATH_TYPE_PATH: |
446 | 0 | r = select_item(card, path_in, file_out, -1); |
447 | 0 | break; |
448 | 0 | default: |
449 | 0 | SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_ARGUMENTS); |
450 | 0 | } |
451 | 0 | if(r > 0) r = 0; |
452 | 0 | SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE,r); |
453 | 0 | } |
454 | | |
455 | | static int _listFile(mscfs_file_t *file, int reset, void *udata) |
456 | 97.1k | { |
457 | 97.1k | int next = reset ? 0x00 : 0x01; |
458 | 97.1k | return msc_list_objects( (sc_card_t*)udata, next, file); |
459 | 97.1k | } |
460 | | |
461 | | static int muscle_init(sc_card_t *card) |
462 | 419 | { |
463 | 419 | muscle_private_t *priv; |
464 | 419 | int r; |
465 | | |
466 | 419 | card->name = "MuscleApplet"; |
467 | 419 | card->drv_data = malloc(sizeof(muscle_private_t)); |
468 | 419 | if(!card->drv_data) { |
469 | 0 | LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); |
470 | 0 | } |
471 | 419 | memset(card->drv_data, 0, sizeof(muscle_private_t)); |
472 | 419 | priv = MUSCLE_DATA(card); |
473 | 419 | priv->verifiedPins = 0; |
474 | 419 | priv->fs = mscfs_new(); |
475 | 419 | if(!priv->fs) { |
476 | 0 | free(card->drv_data); |
477 | 0 | LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); |
478 | 0 | } |
479 | 419 | priv->fs->udata = card; |
480 | 419 | priv->fs->listFile = _listFile; |
481 | | |
482 | 419 | card->cla = 0xB0; |
483 | | |
484 | 419 | card->flags |= SC_CARD_FLAG_RNG; |
485 | 419 | card->caps |= SC_CARD_CAP_RNG; |
486 | | |
487 | | /* Card type detection */ |
488 | 419 | r = _sc_match_atr(card, muscle_atrs, &card->type); |
489 | 419 | if (r < 0) { |
490 | 409 | sc_log(card->ctx, "Failed to match the ATRs"); |
491 | 409 | } |
492 | 419 | if(card->type == SC_CARD_TYPE_MUSCLE_ETOKEN_72K) { |
493 | 0 | card->caps |= SC_CARD_CAP_APDU_EXT; |
494 | 0 | } |
495 | 419 | if(card->type == SC_CARD_TYPE_MUSCLE_JCOP241) { |
496 | 10 | card->caps |= SC_CARD_CAP_APDU_EXT; |
497 | 10 | } |
498 | 419 | if (!(card->caps & SC_CARD_CAP_APDU_EXT)) { |
499 | 409 | card->max_recv_size = 255; |
500 | 409 | card->max_send_size = 255; |
501 | 409 | } |
502 | 419 | if(card->type == SC_CARD_TYPE_MUSCLE_JCOP242R2_NO_EXT_APDU) { |
503 | | /* Tyfone JCOP v242R2 card that doesn't support extended APDUs */ |
504 | 0 | } |
505 | | |
506 | | |
507 | | /* FIXME: Card type detection */ |
508 | 419 | if (1) { |
509 | 419 | unsigned long flags; |
510 | | |
511 | 419 | flags = SC_ALGORITHM_RSA_RAW; |
512 | 419 | flags |= SC_ALGORITHM_RSA_HASH_NONE; |
513 | 419 | flags |= SC_ALGORITHM_ONBOARD_KEY_GEN; |
514 | | |
515 | 419 | _sc_card_add_rsa_alg(card, 1024, flags, 0); |
516 | 419 | _sc_card_add_rsa_alg(card, 2048, flags, 0); |
517 | 419 | } |
518 | 419 | return SC_SUCCESS; |
519 | 419 | } |
520 | | |
521 | | static int muscle_list_files(sc_card_t *card, u8 *buf, size_t bufLen) |
522 | 419 | { |
523 | 419 | muscle_private_t* priv = MUSCLE_DATA(card); |
524 | 419 | mscfs_t *fs = priv->fs; |
525 | 419 | int x, r; |
526 | 419 | int count = 0; |
527 | | |
528 | 419 | r = mscfs_check_cache(priv->fs); |
529 | 419 | if(r < 0) SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_VERBOSE, r); |
530 | | |
531 | 34.5k | for(x = 0; x < fs->cache.size; x++) { |
532 | 34.4k | u8* oid = fs->cache.array[x].objectId.id; |
533 | 34.4k | if (bufLen < 2) |
534 | 0 | break; |
535 | 34.4k | sc_log(card->ctx, |
536 | 34.4k | "FILE: %02X%02X%02X%02X\n", |
537 | 34.4k | oid[0],oid[1],oid[2],oid[3]); |
538 | 34.4k | if(0 == memcmp(fs->currentPath, oid, 2)) { |
539 | 1.24k | buf[0] = oid[2]; |
540 | 1.24k | buf[1] = oid[3]; |
541 | 1.24k | if(buf[0] == 0x00 && buf[1] == 0x00) continue; /* No directories/null names outside of root */ |
542 | 1.11k | buf += 2; |
543 | 1.11k | count += 2; |
544 | 1.11k | bufLen -= 2; |
545 | 1.11k | } |
546 | 34.4k | } |
547 | 75 | return count; |
548 | 419 | } |
549 | | |
550 | | static int muscle_pin_cmd(sc_card_t *card, struct sc_pin_cmd_data *cmd, |
551 | | int *tries_left) |
552 | 0 | { |
553 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
554 | 0 | const int bufferLength = MSC_MAX_PIN_COMMAND_LENGTH; |
555 | 0 | u8 buffer[MSC_MAX_PIN_COMMAND_LENGTH]; |
556 | 0 | switch(cmd->cmd) { |
557 | 0 | case SC_PIN_CMD_VERIFY: |
558 | 0 | switch(cmd->pin_type) { |
559 | 0 | case SC_AC_CHV: { |
560 | 0 | sc_apdu_t apdu; |
561 | 0 | int r; |
562 | 0 | r = msc_verify_pin_apdu(card, &apdu, buffer, bufferLength, cmd->pin_reference, cmd->pin1.data, cmd->pin1.len); |
563 | 0 | if (r < 0) |
564 | 0 | return r; |
565 | 0 | cmd->apdu = &apdu; |
566 | 0 | cmd->pin1.offset = 5; |
567 | 0 | r = iso_ops->pin_cmd(card, cmd, tries_left); |
568 | 0 | if(r >= 0) |
569 | 0 | priv->verifiedPins |= (1 << cmd->pin_reference); |
570 | 0 | return r; |
571 | 0 | } |
572 | 0 | case SC_AC_TERM: |
573 | 0 | case SC_AC_PRO: |
574 | 0 | case SC_AC_AUT: |
575 | 0 | case SC_AC_NONE: |
576 | 0 | default: |
577 | 0 | sc_log(card->ctx, "Unsupported authentication method\n"); |
578 | 0 | return SC_ERROR_NOT_SUPPORTED; |
579 | 0 | } |
580 | 0 | case SC_PIN_CMD_CHANGE: |
581 | 0 | switch(cmd->pin_type) { |
582 | 0 | case SC_AC_CHV: { |
583 | 0 | sc_apdu_t apdu; |
584 | 0 | int r; |
585 | 0 | r = msc_change_pin_apdu(card, &apdu, buffer, bufferLength, cmd->pin_reference, cmd->pin1.data, cmd->pin1.len, cmd->pin2.data, cmd->pin2.len); |
586 | 0 | if (r < 0) |
587 | 0 | return r; |
588 | 0 | cmd->apdu = &apdu; |
589 | 0 | return iso_ops->pin_cmd(card, cmd, tries_left); |
590 | 0 | } |
591 | 0 | case SC_AC_TERM: |
592 | 0 | case SC_AC_PRO: |
593 | 0 | case SC_AC_AUT: |
594 | 0 | case SC_AC_NONE: |
595 | 0 | default: |
596 | 0 | sc_log(card->ctx, "Unsupported authentication method\n"); |
597 | 0 | return SC_ERROR_NOT_SUPPORTED; |
598 | 0 | } |
599 | 0 | case SC_PIN_CMD_UNBLOCK: |
600 | 0 | switch(cmd->pin_type) { |
601 | 0 | case SC_AC_CHV: { |
602 | 0 | sc_apdu_t apdu; |
603 | 0 | int r; |
604 | 0 | r = msc_unblock_pin_apdu(card, &apdu, buffer, bufferLength, cmd->pin_reference, cmd->pin1.data, cmd->pin1.len); |
605 | 0 | if (r < 0) |
606 | 0 | return r; |
607 | 0 | cmd->apdu = &apdu; |
608 | 0 | return iso_ops->pin_cmd(card, cmd, tries_left); |
609 | 0 | } |
610 | 0 | case SC_AC_TERM: |
611 | 0 | case SC_AC_PRO: |
612 | 0 | case SC_AC_AUT: |
613 | 0 | case SC_AC_NONE: |
614 | 0 | default: |
615 | 0 | sc_log(card->ctx, "Unsupported authentication method\n"); |
616 | 0 | return SC_ERROR_NOT_SUPPORTED; |
617 | 0 | } |
618 | 0 | default: |
619 | 0 | sc_log(card->ctx, "Unsupported command\n"); |
620 | 0 | return SC_ERROR_NOT_SUPPORTED; |
621 | |
|
622 | 0 | } |
623 | |
|
624 | 0 | } |
625 | | |
626 | | static int muscle_card_extract_key(sc_card_t *card, sc_cardctl_muscle_key_info_t *info) |
627 | 0 | { |
628 | | /* CURRENTLY DONT SUPPORT EXTRACTING PRIVATE KEYS... */ |
629 | 0 | switch(info->keyType) { |
630 | 0 | case 1: /* RSA */ |
631 | 0 | return msc_extract_rsa_public_key(card, |
632 | 0 | info->keyLocation, |
633 | 0 | &info->modLength, |
634 | 0 | &info->modValue, |
635 | 0 | &info->expLength, |
636 | 0 | &info->expValue); |
637 | 0 | default: |
638 | 0 | return SC_ERROR_NOT_SUPPORTED; |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | | static int muscle_card_import_key(sc_card_t *card, sc_cardctl_muscle_key_info_t *info) |
643 | 0 | { |
644 | | /* CURRENTLY DONT SUPPORT EXTRACTING PRIVATE KEYS... */ |
645 | 0 | switch(info->keyType) { |
646 | 0 | case 0x02: /* RSA_PRIVATE */ |
647 | 0 | case 0x03: /* RSA_PRIVATE_CRT */ |
648 | 0 | return msc_import_key(card, |
649 | 0 | info->keyLocation, |
650 | 0 | info); |
651 | 0 | default: |
652 | 0 | return SC_ERROR_NOT_SUPPORTED; |
653 | 0 | } |
654 | 0 | } |
655 | | |
656 | | static int muscle_card_generate_key(sc_card_t *card, sc_cardctl_muscle_gen_key_info_t *info) |
657 | 0 | { |
658 | 0 | return msc_generate_keypair(card, |
659 | 0 | info->privateKeyLocation, |
660 | 0 | info->publicKeyLocation, |
661 | 0 | info->keyType, |
662 | 0 | info->keySize, |
663 | 0 | 0); |
664 | 0 | } |
665 | | |
666 | | static int muscle_card_verified_pins(sc_card_t *card, sc_cardctl_muscle_verified_pins_info_t *info) |
667 | 0 | { |
668 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
669 | 0 | info->verifiedPins = priv->verifiedPins; |
670 | 0 | return 0; |
671 | 0 | } |
672 | | static int muscle_card_ctl(sc_card_t *card, unsigned long request, void *data) |
673 | 0 | { |
674 | 0 | switch(request) { |
675 | 0 | case SC_CARDCTL_MUSCLE_GENERATE_KEY: |
676 | 0 | return muscle_card_generate_key(card, (sc_cardctl_muscle_gen_key_info_t*) data); |
677 | 0 | case SC_CARDCTL_MUSCLE_EXTRACT_KEY: |
678 | 0 | return muscle_card_extract_key(card, (sc_cardctl_muscle_key_info_t*) data); |
679 | 0 | case SC_CARDCTL_MUSCLE_IMPORT_KEY: |
680 | 0 | return muscle_card_import_key(card, (sc_cardctl_muscle_key_info_t*) data); |
681 | 0 | case SC_CARDCTL_MUSCLE_VERIFIED_PINS: |
682 | 0 | return muscle_card_verified_pins(card, (sc_cardctl_muscle_verified_pins_info_t*) data); |
683 | 0 | default: |
684 | 0 | return SC_ERROR_NOT_SUPPORTED; /* Unsupported.. whatever it is */ |
685 | 0 | } |
686 | 0 | } |
687 | | |
688 | | static int muscle_set_security_env(sc_card_t *card, |
689 | | const sc_security_env_t *env, |
690 | | int se_num) |
691 | 0 | { |
692 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
693 | |
|
694 | 0 | if (env->operation != SC_SEC_OPERATION_SIGN && |
695 | 0 | env->operation != SC_SEC_OPERATION_DECIPHER) { |
696 | 0 | sc_log(card->ctx, "Invalid crypto operation supplied.\n"); |
697 | 0 | return SC_ERROR_NOT_SUPPORTED; |
698 | 0 | } |
699 | 0 | if (env->algorithm != SC_ALGORITHM_RSA) { |
700 | 0 | sc_log(card->ctx, "Invalid crypto algorithm supplied.\n"); |
701 | 0 | return SC_ERROR_NOT_SUPPORTED; |
702 | 0 | } |
703 | | /* ADJUST FOR PKCS1 padding support for decryption only */ |
704 | 0 | if ((env->algorithm_flags & SC_ALGORITHM_RSA_PADS) || |
705 | 0 | (env->algorithm_flags & SC_ALGORITHM_RSA_HASHES)) { |
706 | 0 | sc_log(card->ctx, "Card supports only raw RSA.\n"); |
707 | 0 | return SC_ERROR_NOT_SUPPORTED; |
708 | 0 | } |
709 | 0 | if (env->flags & SC_SEC_ENV_KEY_REF_PRESENT) { |
710 | 0 | if (env->key_ref_len != 1 || |
711 | 0 | (env->key_ref[0] > 0x0F)) { |
712 | 0 | sc_log(card->ctx, "Invalid key reference supplied.\n"); |
713 | 0 | return SC_ERROR_NOT_SUPPORTED; |
714 | 0 | } |
715 | 0 | priv->rsa_key_ref = env->key_ref[0]; |
716 | 0 | } |
717 | 0 | if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) { |
718 | 0 | sc_log(card->ctx, "Algorithm reference not supported.\n"); |
719 | 0 | return SC_ERROR_NOT_SUPPORTED; |
720 | 0 | } |
721 | | /* if (env->flags & SC_SEC_ENV_FILE_REF_PRESENT) |
722 | | if (memcmp(env->file_ref.value, "\x00\x12", 2) != 0) { |
723 | | sc_log(card->ctx, "File reference is not 0012.\n"); |
724 | | return SC_ERROR_NOT_SUPPORTED; |
725 | | } */ |
726 | 0 | priv->env = *env; |
727 | 0 | return 0; |
728 | 0 | } |
729 | | |
730 | | static int muscle_restore_security_env(sc_card_t *card, int se_num) |
731 | 0 | { |
732 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
733 | 0 | memset(&priv->env, 0, sizeof(priv->env)); |
734 | 0 | return 0; |
735 | 0 | } |
736 | | |
737 | | |
738 | | static int muscle_decipher(sc_card_t * card, |
739 | | const u8 * crgram, size_t crgram_len, u8 * out, |
740 | | size_t out_len) |
741 | 0 | { |
742 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
743 | |
|
744 | 0 | u8 key_id; |
745 | 0 | int r; |
746 | | |
747 | | /* sanity check */ |
748 | 0 | if (priv->env.operation != SC_SEC_OPERATION_DECIPHER) |
749 | 0 | return SC_ERROR_INVALID_ARGUMENTS; |
750 | | |
751 | 0 | key_id = priv->rsa_key_ref * 2; /* Private key */ |
752 | |
|
753 | 0 | if (out_len < crgram_len) { |
754 | 0 | sc_log(card->ctx, "Output buffer too small"); |
755 | 0 | return SC_ERROR_BUFFER_TOO_SMALL; |
756 | 0 | } |
757 | | |
758 | 0 | r = msc_compute_crypt(card, |
759 | 0 | key_id, |
760 | 0 | 0x00, /* RSA NO PADDING */ |
761 | 0 | 0x04, /* decrypt */ |
762 | 0 | crgram, |
763 | 0 | out, |
764 | 0 | crgram_len, |
765 | 0 | out_len); |
766 | 0 | LOG_TEST_RET(card->ctx, r, "Card signature failed"); |
767 | 0 | return r; |
768 | 0 | } |
769 | | |
770 | | static int muscle_compute_signature(sc_card_t *card, const u8 *data, |
771 | | size_t data_len, u8 * out, size_t outlen) |
772 | 0 | { |
773 | 0 | muscle_private_t* priv = MUSCLE_DATA(card); |
774 | 0 | u8 key_id; |
775 | 0 | int r; |
776 | |
|
777 | 0 | key_id = priv->rsa_key_ref * 2; /* Private key */ |
778 | |
|
779 | 0 | if (outlen < data_len) { |
780 | 0 | sc_log(card->ctx, "Output buffer too small"); |
781 | 0 | return SC_ERROR_BUFFER_TOO_SMALL; |
782 | 0 | } |
783 | | |
784 | 0 | r = msc_compute_crypt(card, |
785 | 0 | key_id, |
786 | 0 | 0x00, /* RSA NO PADDING */ |
787 | 0 | 0x04, /* -- decrypt raw... will do what we need since signing isn't yet supported */ |
788 | 0 | data, |
789 | 0 | out, |
790 | 0 | data_len, |
791 | 0 | outlen); |
792 | 0 | LOG_TEST_RET(card->ctx, r, "Card signature failed"); |
793 | 0 | return r; |
794 | 0 | } |
795 | | |
796 | | static int muscle_get_challenge(sc_card_t *card, u8 *rnd, size_t len) |
797 | 334 | { |
798 | 334 | if (len == 0) |
799 | 0 | return SC_SUCCESS; |
800 | 334 | else { |
801 | 334 | LOG_TEST_RET(card->ctx, |
802 | 18 | msc_get_challenge(card, len, 0, NULL, rnd), |
803 | 18 | "GET CHALLENGE cmd failed"); |
804 | 18 | return (int) len; |
805 | 334 | } |
806 | 334 | } |
807 | | |
808 | 97.8k | static int muscle_check_sw(sc_card_t * card, unsigned int sw1, unsigned int sw2) { |
809 | 97.8k | if(sw1 == 0x9C) { |
810 | 157 | switch(sw2) { |
811 | 14 | case 0x01: /* SW_NO_MEMORY_LEFT */ |
812 | 14 | return SC_ERROR_NOT_ENOUGH_MEMORY; |
813 | 12 | case 0x02: /* SW_AUTH_FAILED */ |
814 | 12 | return SC_ERROR_PIN_CODE_INCORRECT; |
815 | 10 | case 0x03: /* SW_OPERATION_NOT_ALLOWED */ |
816 | 10 | return SC_ERROR_NOT_ALLOWED; |
817 | 10 | case 0x05: /* SW_UNSUPPORTED_FEATURE */ |
818 | 10 | return SC_ERROR_NO_CARD_SUPPORT; |
819 | 10 | case 0x06: /* SW_UNAUTHORIZED */ |
820 | 10 | return SC_ERROR_SECURITY_STATUS_NOT_SATISFIED; |
821 | 12 | case 0x07: /* SW_OBJECT_NOT_FOUND */ |
822 | 12 | return SC_ERROR_FILE_NOT_FOUND; |
823 | 10 | case 0x08: /* SW_OBJECT_EXISTS */ |
824 | 10 | return SC_ERROR_FILE_ALREADY_EXISTS; |
825 | 11 | case 0x09: /* SW_INCORRECT_ALG */ |
826 | 11 | return SC_ERROR_INCORRECT_PARAMETERS; |
827 | 10 | case 0x0B: /* SW_SIGNATURE_INVALID */ |
828 | 10 | return SC_ERROR_CARD_CMD_FAILED; |
829 | 11 | case 0x0C: /* SW_IDENTITY_BLOCKED */ |
830 | 11 | return SC_ERROR_AUTH_METHOD_BLOCKED; |
831 | 12 | case 0x0F: /* SW_INVALID_PARAMETER */ |
832 | 22 | case 0x10: /* SW_INCORRECT_P1 */ |
833 | 33 | case 0x11: /* SW_INCORRECT_P2 */ |
834 | 33 | return SC_ERROR_INCORRECT_PARAMETERS; |
835 | 157 | } |
836 | 157 | } |
837 | 97.7k | return iso_ops->check_sw(card, sw1, sw2); |
838 | 97.8k | } |
839 | | |
840 | | static int muscle_card_reader_lock_obtained(sc_card_t *card, int was_reset) |
841 | 104k | { |
842 | 104k | int r = SC_SUCCESS; |
843 | | |
844 | 104k | SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); |
845 | | |
846 | 104k | if (was_reset > 0) { |
847 | 0 | if (msc_select_applet(card, muscleAppletId, sizeof muscleAppletId) != 1) { |
848 | 0 | r = SC_ERROR_INVALID_CARD; |
849 | 0 | } |
850 | 0 | } |
851 | | |
852 | 104k | LOG_FUNC_RETURN(card->ctx, r); |
853 | 104k | } |
854 | | |
855 | | static int muscle_logout(sc_card_t *card) |
856 | 0 | { |
857 | 0 | int r = SC_ERROR_NOT_SUPPORTED; |
858 | |
|
859 | 0 | SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); |
860 | |
|
861 | 0 | if (msc_select_applet(card, muscleAppletId, sizeof muscleAppletId) == 1) { |
862 | 0 | r = SC_SUCCESS; |
863 | 0 | } |
864 | |
|
865 | 0 | LOG_FUNC_RETURN(card->ctx, r); |
866 | 0 | } |
867 | | |
868 | | |
869 | | static struct sc_card_driver * sc_get_driver(void) |
870 | 9.35k | { |
871 | 9.35k | struct sc_card_driver *iso_drv = sc_get_iso7816_driver(); |
872 | 9.35k | if (iso_ops == NULL) |
873 | 1 | iso_ops = iso_drv->ops; |
874 | | |
875 | 9.35k | muscle_ops = *iso_drv->ops; |
876 | 9.35k | muscle_ops.check_sw = muscle_check_sw; |
877 | 9.35k | muscle_ops.pin_cmd = muscle_pin_cmd; |
878 | 9.35k | muscle_ops.match_card = muscle_match_card; |
879 | 9.35k | muscle_ops.init = muscle_init; |
880 | 9.35k | muscle_ops.finish = muscle_finish; |
881 | | |
882 | 9.35k | muscle_ops.get_challenge = muscle_get_challenge; |
883 | | |
884 | 9.35k | muscle_ops.set_security_env = muscle_set_security_env; |
885 | 9.35k | muscle_ops.restore_security_env = muscle_restore_security_env; |
886 | 9.35k | muscle_ops.compute_signature = muscle_compute_signature; |
887 | 9.35k | muscle_ops.decipher = muscle_decipher; |
888 | 9.35k | muscle_ops.card_ctl = muscle_card_ctl; |
889 | 9.35k | muscle_ops.read_binary = muscle_read_binary; |
890 | 9.35k | muscle_ops.update_binary = muscle_update_binary; |
891 | 9.35k | muscle_ops.create_file = muscle_create_file; |
892 | 9.35k | muscle_ops.select_file = muscle_select_file; |
893 | 9.35k | muscle_ops.delete_file = muscle_delete_file; |
894 | 9.35k | muscle_ops.list_files = muscle_list_files; |
895 | 9.35k | muscle_ops.card_reader_lock_obtained = muscle_card_reader_lock_obtained; |
896 | 9.35k | muscle_ops.logout = muscle_logout; |
897 | | |
898 | 9.35k | return &muscle_drv; |
899 | 9.35k | } |
900 | | |
901 | | struct sc_card_driver * sc_get_muscle_driver(void) |
902 | 9.35k | { |
903 | 9.35k | return sc_get_driver(); |
904 | 9.35k | } |