]>
Commit | Line | Data |
---|---|---|
edbb2136 AL |
1 | /* |
2 | * CCID Passthru Card Device emulation | |
3 | * | |
4 | * Copyright (c) 2011 Red Hat. | |
5 | * Written by Alon Levy. | |
6 | * | |
7 | * This work is licensed under the terms of the GNU GPL, version 2.1 or later. | |
8 | * See the COPYING file in the top-level directory. | |
9 | */ | |
10 | ||
edbb2136 | 11 | #include "qemu-char.h" |
6d65516f | 12 | #include "qemu_socket.h" |
edbb2136 AL |
13 | #include "monitor.h" |
14 | #include "hw/ccid.h" | |
15 | #include "libcacard/vscard_common.h" | |
16 | ||
17 | #define DPRINTF(card, lvl, fmt, ...) \ | |
18 | do { \ | |
19 | if (lvl <= card->debug) { \ | |
20 | printf("ccid-card-passthru: " fmt , ## __VA_ARGS__); \ | |
21 | } \ | |
22 | } while (0) | |
23 | ||
24 | #define D_WARN 1 | |
25 | #define D_INFO 2 | |
26 | #define D_MORE_INFO 3 | |
27 | #define D_VERBOSE 4 | |
28 | ||
29 | /* TODO: do we still need this? */ | |
30 | uint8_t DEFAULT_ATR[] = { | |
31 | /* | |
32 | * From some example somewhere | |
33 | * 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28 | |
34 | */ | |
35 | ||
36 | /* From an Athena smart card */ | |
37 | 0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21, | |
38 | 0x13, 0x08 | |
39 | }; | |
40 | ||
41 | ||
42 | #define PASSTHRU_DEV_NAME "ccid-card-passthru" | |
43 | #define VSCARD_IN_SIZE 65536 | |
44 | ||
45 | /* maximum size of ATR - from 7816-3 */ | |
46 | #define MAX_ATR_SIZE 40 | |
47 | ||
48 | typedef struct PassthruState PassthruState; | |
49 | ||
50 | struct PassthruState { | |
51 | CCIDCardState base; | |
52 | CharDriverState *cs; | |
53 | uint8_t vscard_in_data[VSCARD_IN_SIZE]; | |
54 | uint32_t vscard_in_pos; | |
55 | uint32_t vscard_in_hdr; | |
56 | uint8_t atr[MAX_ATR_SIZE]; | |
57 | uint8_t atr_length; | |
58 | uint8_t debug; | |
59 | }; | |
60 | ||
61 | /* | |
62 | * VSCard protocol over chardev | |
63 | * This code should not depend on the card type. | |
64 | */ | |
65 | ||
66 | static void ccid_card_vscard_send_msg(PassthruState *s, | |
67 | VSCMsgType type, uint32_t reader_id, | |
68 | const uint8_t *payload, uint32_t length) | |
69 | { | |
70 | VSCMsgHeader scr_msg_header; | |
71 | ||
72 | scr_msg_header.type = htonl(type); | |
73 | scr_msg_header.reader_id = htonl(reader_id); | |
74 | scr_msg_header.length = htonl(length); | |
75 | qemu_chr_write(s->cs, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader)); | |
76 | qemu_chr_write(s->cs, payload, length); | |
77 | } | |
78 | ||
79 | static void ccid_card_vscard_send_apdu(PassthruState *s, | |
80 | const uint8_t *apdu, uint32_t length) | |
81 | { | |
82 | ccid_card_vscard_send_msg( | |
83 | s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length); | |
84 | } | |
85 | ||
86 | static void ccid_card_vscard_send_error(PassthruState *s, | |
87 | uint32_t reader_id, VSCErrorCode code) | |
88 | { | |
89 | VSCMsgError msg = {.code = htonl(code)}; | |
90 | ||
91 | ccid_card_vscard_send_msg( | |
92 | s, VSC_Error, reader_id, (uint8_t *)&msg, sizeof(msg)); | |
93 | } | |
94 | ||
95 | static void ccid_card_vscard_send_init(PassthruState *s) | |
96 | { | |
97 | VSCMsgInit msg = { | |
98 | .version = htonl(VSCARD_VERSION), | |
99 | .magic = VSCARD_MAGIC, | |
100 | .capabilities = {0} | |
101 | }; | |
102 | ||
103 | ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID, | |
104 | (uint8_t *)&msg, sizeof(msg)); | |
105 | } | |
106 | ||
107 | static int ccid_card_vscard_can_read(void *opaque) | |
108 | { | |
109 | PassthruState *card = opaque; | |
110 | ||
111 | return VSCARD_IN_SIZE >= card->vscard_in_pos ? | |
112 | VSCARD_IN_SIZE - card->vscard_in_pos : 0; | |
113 | } | |
114 | ||
115 | static void ccid_card_vscard_handle_init( | |
116 | PassthruState *card, VSCMsgHeader *hdr, VSCMsgInit *init) | |
117 | { | |
118 | uint32_t *capabilities; | |
119 | int num_capabilities; | |
120 | int i; | |
121 | ||
122 | capabilities = init->capabilities; | |
123 | num_capabilities = | |
124 | 1 + ((hdr->length - sizeof(VSCMsgInit)) / sizeof(uint32_t)); | |
125 | init->version = ntohl(init->version); | |
126 | for (i = 0 ; i < num_capabilities; ++i) { | |
127 | capabilities[i] = ntohl(capabilities[i]); | |
128 | } | |
129 | if (init->magic != VSCARD_MAGIC) { | |
130 | error_report("wrong magic"); | |
131 | /* we can't disconnect the chardev */ | |
132 | } | |
133 | if (init->version != VSCARD_VERSION) { | |
134 | DPRINTF(card, D_WARN, | |
135 | "got version %d, have %d", init->version, VSCARD_VERSION); | |
136 | } | |
137 | /* future handling of capabilities, none exist atm */ | |
138 | ccid_card_vscard_send_init(card); | |
139 | } | |
140 | ||
141 | static void ccid_card_vscard_handle_message(PassthruState *card, | |
142 | VSCMsgHeader *scr_msg_header) | |
143 | { | |
144 | uint8_t *data = (uint8_t *)&scr_msg_header[1]; | |
145 | ||
146 | switch (scr_msg_header->type) { | |
147 | case VSC_ATR: | |
148 | DPRINTF(card, D_INFO, "VSC_ATR %d\n", scr_msg_header->length); | |
149 | if (scr_msg_header->length > MAX_ATR_SIZE) { | |
150 | error_report("ATR size exceeds spec, ignoring"); | |
151 | ccid_card_vscard_send_error(card, scr_msg_header->reader_id, | |
152 | VSC_GENERAL_ERROR); | |
153 | } | |
154 | memcpy(card->atr, data, scr_msg_header->length); | |
155 | card->atr_length = scr_msg_header->length; | |
156 | ccid_card_card_inserted(&card->base); | |
157 | ccid_card_vscard_send_error(card, scr_msg_header->reader_id, | |
158 | VSC_SUCCESS); | |
159 | break; | |
160 | case VSC_APDU: | |
161 | ccid_card_send_apdu_to_guest( | |
162 | &card->base, data, scr_msg_header->length); | |
163 | break; | |
164 | case VSC_CardRemove: | |
165 | DPRINTF(card, D_INFO, "VSC_CardRemove\n"); | |
166 | ccid_card_card_removed(&card->base); | |
167 | ccid_card_vscard_send_error(card, | |
168 | scr_msg_header->reader_id, VSC_SUCCESS); | |
169 | break; | |
170 | case VSC_Init: | |
171 | ccid_card_vscard_handle_init( | |
172 | card, scr_msg_header, (VSCMsgInit *)data); | |
173 | break; | |
174 | case VSC_Error: | |
175 | ccid_card_card_error(&card->base, *(uint32_t *)data); | |
176 | break; | |
177 | case VSC_ReaderAdd: | |
178 | if (ccid_card_ccid_attach(&card->base) < 0) { | |
179 | ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID, | |
180 | VSC_CANNOT_ADD_MORE_READERS); | |
181 | } else { | |
182 | ccid_card_vscard_send_error(card, VSCARD_MINIMAL_READER_ID, | |
183 | VSC_SUCCESS); | |
184 | } | |
185 | break; | |
186 | case VSC_ReaderRemove: | |
187 | ccid_card_ccid_detach(&card->base); | |
188 | ccid_card_vscard_send_error(card, | |
189 | scr_msg_header->reader_id, VSC_SUCCESS); | |
190 | break; | |
191 | default: | |
192 | printf("usb-ccid: chardev: unexpected message of type %X\n", | |
193 | scr_msg_header->type); | |
194 | ccid_card_vscard_send_error(card, scr_msg_header->reader_id, | |
195 | VSC_GENERAL_ERROR); | |
196 | } | |
197 | } | |
198 | ||
199 | static void ccid_card_vscard_drop_connection(PassthruState *card) | |
200 | { | |
201 | qemu_chr_close(card->cs); | |
202 | card->vscard_in_pos = card->vscard_in_hdr = 0; | |
203 | } | |
204 | ||
205 | static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size) | |
206 | { | |
207 | PassthruState *card = opaque; | |
208 | VSCMsgHeader *hdr; | |
209 | ||
210 | if (card->vscard_in_pos + size > VSCARD_IN_SIZE) { | |
211 | error_report( | |
212 | "no room for data: pos %d + size %d > %d. dropping connection.", | |
213 | card->vscard_in_pos, size, VSCARD_IN_SIZE); | |
214 | ccid_card_vscard_drop_connection(card); | |
215 | return; | |
216 | } | |
217 | assert(card->vscard_in_pos < VSCARD_IN_SIZE); | |
218 | assert(card->vscard_in_hdr < VSCARD_IN_SIZE); | |
219 | memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size); | |
220 | card->vscard_in_pos += size; | |
221 | hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); | |
222 | ||
223 | while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader)) | |
224 | &&(card->vscard_in_pos - card->vscard_in_hdr >= | |
225 | sizeof(VSCMsgHeader) + ntohl(hdr->length))) { | |
226 | hdr->reader_id = ntohl(hdr->reader_id); | |
227 | hdr->length = ntohl(hdr->length); | |
228 | hdr->type = ntohl(hdr->type); | |
229 | ccid_card_vscard_handle_message(card, hdr); | |
230 | card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader); | |
231 | hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); | |
232 | } | |
233 | if (card->vscard_in_hdr == card->vscard_in_pos) { | |
234 | card->vscard_in_pos = card->vscard_in_hdr = 0; | |
235 | } | |
236 | } | |
237 | ||
238 | static void ccid_card_vscard_event(void *opaque, int event) | |
239 | { | |
240 | PassthruState *card = opaque; | |
241 | ||
242 | switch (event) { | |
243 | case CHR_EVENT_BREAK: | |
244 | card->vscard_in_pos = card->vscard_in_hdr = 0; | |
245 | break; | |
246 | case CHR_EVENT_FOCUS: | |
247 | break; | |
248 | case CHR_EVENT_OPENED: | |
249 | DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__); | |
250 | break; | |
251 | } | |
252 | } | |
253 | ||
254 | /* End VSCard handling */ | |
255 | ||
256 | static void passthru_apdu_from_guest( | |
257 | CCIDCardState *base, const uint8_t *apdu, uint32_t len) | |
258 | { | |
259 | PassthruState *card = DO_UPCAST(PassthruState, base, base); | |
260 | ||
261 | if (!card->cs) { | |
262 | printf("ccid-passthru: no chardev, discarding apdu length %d\n", len); | |
263 | return; | |
264 | } | |
265 | ccid_card_vscard_send_apdu(card, apdu, len); | |
266 | } | |
267 | ||
268 | static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len) | |
269 | { | |
270 | PassthruState *card = DO_UPCAST(PassthruState, base, base); | |
271 | ||
272 | *len = card->atr_length; | |
273 | return card->atr; | |
274 | } | |
275 | ||
276 | static int passthru_initfn(CCIDCardState *base) | |
277 | { | |
278 | PassthruState *card = DO_UPCAST(PassthruState, base, base); | |
279 | ||
280 | card->vscard_in_pos = 0; | |
281 | card->vscard_in_hdr = 0; | |
282 | if (card->cs) { | |
283 | DPRINTF(card, D_INFO, "initing chardev\n"); | |
284 | qemu_chr_add_handlers(card->cs, | |
285 | ccid_card_vscard_can_read, | |
286 | ccid_card_vscard_read, | |
287 | ccid_card_vscard_event, card); | |
288 | ccid_card_vscard_send_init(card); | |
289 | } else { | |
290 | error_report("missing chardev"); | |
291 | return -1; | |
292 | } | |
293 | assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE); | |
294 | memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR)); | |
295 | card->atr_length = sizeof(DEFAULT_ATR); | |
296 | return 0; | |
297 | } | |
298 | ||
299 | static int passthru_exitfn(CCIDCardState *base) | |
300 | { | |
301 | return 0; | |
302 | } | |
303 | ||
304 | static VMStateDescription passthru_vmstate = { | |
305 | .name = PASSTHRU_DEV_NAME, | |
306 | .version_id = 1, | |
307 | .minimum_version_id = 1, | |
308 | .fields = (VMStateField[]) { | |
309 | VMSTATE_BUFFER(vscard_in_data, PassthruState), | |
310 | VMSTATE_UINT32(vscard_in_pos, PassthruState), | |
311 | VMSTATE_UINT32(vscard_in_hdr, PassthruState), | |
312 | VMSTATE_BUFFER(atr, PassthruState), | |
313 | VMSTATE_UINT8(atr_length, PassthruState), | |
314 | VMSTATE_END_OF_LIST() | |
315 | } | |
316 | }; | |
317 | ||
318 | static CCIDCardInfo passthru_card_info = { | |
319 | .qdev.name = PASSTHRU_DEV_NAME, | |
320 | .qdev.desc = "passthrough smartcard", | |
321 | .qdev.size = sizeof(PassthruState), | |
322 | .qdev.vmsd = &passthru_vmstate, | |
323 | .initfn = passthru_initfn, | |
324 | .exitfn = passthru_exitfn, | |
325 | .get_atr = passthru_get_atr, | |
326 | .apdu_from_guest = passthru_apdu_from_guest, | |
327 | .qdev.props = (Property[]) { | |
328 | DEFINE_PROP_CHR("chardev", PassthruState, cs), | |
329 | DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), | |
330 | DEFINE_PROP_END_OF_LIST(), | |
331 | }, | |
332 | }; | |
333 | ||
334 | static void ccid_card_passthru_register_devices(void) | |
335 | { | |
336 | ccid_card_qdev_register(&passthru_card_info); | |
337 | } | |
338 | ||
339 | device_init(ccid_card_passthru_register_devices) |