/* * slot.c: reader, smart card and slot related management functions * * Copyright (C) 2002 Timo Teräs * Copyright (C) 2009 Martin Paljak * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "libopensc/opensc.h" #include #include #include "sc-pkcs11.h" /* Print virtual_slots list. Called by DEBUG_VSS(S, C) */ void _debug_virtual_slots(sc_pkcs11_slot_t *p) { int i, vs_size; sc_pkcs11_slot_t * slot; vs_size = list_size(&virtual_slots); _sc_debug(context, 10, "VSS size:%d", vs_size); _sc_debug(context, 10, "VSS [i] id flags LU events nsessions slot_info.flags reader p11card description"); for (i = 0; i < vs_size; i++) { slot = (sc_pkcs11_slot_t *) list_get_at(&virtual_slots, i); if (slot) { _sc_debug(context, 10, "VSS %s[%d] 0x%2.2lx 0x%4.4x %d %d %d %4.4lx %p %p %.64s", ((slot == p) ? "*" : " "), i, slot->id, slot->flags, slot->login_user, slot->events, slot->nsessions, slot->slot_info.flags, slot->reader, slot->p11card, slot->slot_info.slotDescription); } } _sc_debug(context, 10, "VSS END"); } static struct sc_pkcs11_framework_ops *frameworks[] = { &framework_pkcs15, #ifdef USE_PKCS15_INIT /* This should be the last framework, because it * will assume the card is blank and try to initialize it */ &framework_pkcs15init, #endif NULL }; static struct sc_pkcs11_slot * reader_reclaim_slot(sc_reader_t *reader) { unsigned int i; CK_UTF8CHAR slotDescription[64]; CK_UTF8CHAR manufacturerID[32]; strcpy_bp(slotDescription, reader->name, 64); strcpy_bp(manufacturerID, reader->vendor, 32); /* Locate a slot related to the reader */ for (i = 0; ireader == NULL && reader != NULL && 0 == memcmp(slot->slot_info.slotDescription, slotDescription, 64) && 0 == memcmp(slot->slot_info.manufacturerID, manufacturerID, 32) && slot->slot_info.hardwareVersion.major == reader->version_major && slot->slot_info.hardwareVersion.minor == reader->version_minor) { return slot; } } return NULL; } void init_slot_info(CK_SLOT_INFO_PTR pInfo, sc_reader_t *reader) { if (reader) { strcpy_bp(pInfo->slotDescription, reader->name, 64); strcpy_bp(pInfo->manufacturerID, reader->vendor, 32); pInfo->hardwareVersion.major = reader->version_major; pInfo->hardwareVersion.minor = reader->version_minor; } else { strcpy_bp(pInfo->slotDescription, "Virtual hotplug slot", 64); strcpy_bp(pInfo->manufacturerID, OPENSC_VS_FF_COMPANY_NAME, 32); pInfo->hardwareVersion.major = OPENSC_VERSION_MAJOR; pInfo->hardwareVersion.minor = OPENSC_VERSION_MINOR; } pInfo->flags = CKF_REMOVABLE_DEVICE | CKF_HW_SLOT; pInfo->firmwareVersion.major = 0; pInfo->firmwareVersion.minor = 0; } /* simclist helpers to locate interesting objects by ID */ static int object_list_seeker(const void *el, const void *key) { const struct sc_pkcs11_object *object = (struct sc_pkcs11_object *)el; if ((el == NULL) || (key == NULL)) return 0; if (object->handle == *(CK_OBJECT_HANDLE*)key) return 1; return 0; } CK_RV create_slot(sc_reader_t *reader) { /* find unused slots previously allocated for the same reader */ struct sc_pkcs11_slot *slot = reader_reclaim_slot(reader); /* create a new slot if no empty slot is available */ if (!slot) { sc_log(context, "Creating new slot"); if (list_size(&virtual_slots) >= sc_pkcs11_conf.max_virtual_slots) return CKR_FUNCTION_FAILED; slot = (struct sc_pkcs11_slot *)calloc(1, sizeof(struct sc_pkcs11_slot)); if (!slot) return CKR_HOST_MEMORY; list_append(&virtual_slots, slot); if (0 != list_init(&slot->objects)) { return CKR_HOST_MEMORY; } list_attributes_seeker(&slot->objects, object_list_seeker); if (0 != list_init(&slot->logins)) { return CKR_HOST_MEMORY; } } else { DEBUG_VSS(slot, "Reusing this old slot"); /* reuse the old list of logins/objects since they should be empty */ list_t logins = slot->logins; list_t objects = slot->objects; memset(slot, 0, sizeof *slot); slot->logins = logins; slot->objects = objects; } slot->login_user = -1; slot->id = (CK_SLOT_ID) list_locate(&virtual_slots, slot); init_slot_info(&slot->slot_info, reader); slot->reader = reader; DEBUG_VSS(slot, "Finished initializing this slot"); return CKR_OK; } CK_RV card_removed(sc_reader_t * reader) { unsigned int i; struct sc_pkcs11_card *p11card = NULL; /* Mark all slots as "token not present" */ sc_log(context, "%s: card removed", reader->name); for (i=0; i < list_size(&virtual_slots); i++) { sc_pkcs11_slot_t *slot = (sc_pkcs11_slot_t *) list_get_at(&virtual_slots, i); if (slot->reader == reader) { /* Save the "card" object */ if (slot->p11card) p11card = slot->p11card; slot_token_removed(slot->id); } } if (p11card) { p11card->framework->unbind(p11card); sc_disconnect_card(p11card->card); for (i=0; i < p11card->nmechanisms; ++i) { if (p11card->mechanisms[i]->free_mech_data) { p11card->mechanisms[i]->free_mech_data(p11card->mechanisms[i]->mech_data); } free(p11card->mechanisms[i]); } free(p11card->mechanisms); free(p11card); } return CKR_OK; } CK_RV card_detect(sc_reader_t *reader) { struct sc_pkcs11_card *p11card = NULL; int free_p11card = 0; int rc; CK_RV rv; unsigned int i; int j; sc_log(context, "%s: Detecting smart card", reader->name); /* Check if someone inserted a card */ again: rc = sc_detect_card_presence(reader); if (rc < 0) { sc_log(context, "%s: failed, %s", reader->name, sc_strerror(rc)); return sc_to_cryptoki_error(rc, NULL); } if (rc == 0) { sc_log(context, "%s: card absent", reader->name); card_removed(reader); /* Release all resources */ return CKR_TOKEN_NOT_PRESENT; } /* If the card was changed, disconnect the current one */ if (rc & SC_READER_CARD_CHANGED) { sc_log(context, "%s: Card changed", reader->name); /* The following should never happen - but if it * does we'll be stuck in an endless loop. * So better be fussy. if (!retry--) return CKR_TOKEN_NOT_PRESENT; */ card_removed(reader); goto again; } /* Locate a slot related to the reader */ for (i=0; ireader == reader) { p11card = slot->p11card; break; } } /* Detect the card if it's not known already */ if (p11card == NULL) { sc_log(context, "%s: First seen the card ", reader->name); p11card = (struct sc_pkcs11_card *)calloc(1, sizeof(struct sc_pkcs11_card)); if (!p11card) return CKR_HOST_MEMORY; free_p11card = 1; p11card->reader = reader; } if (p11card->card == NULL) { sc_log(context, "%s: Connecting ... ", reader->name); rc = sc_connect_card(reader, &p11card->card); if (rc != SC_SUCCESS) { sc_log(context, "%s: SC connect card error %i", reader->name, rc); rv = sc_to_cryptoki_error(rc, NULL); goto fail; } /* escape commands are only guaranteed to be working with a card * inserted. That's why by now, after sc_connect_card() the reader's * metadata may have changed. We re-initialize the metadata for every * slot of this reader here. */ if (reader->flags & SC_READER_ENABLE_ESCAPE) { for (i = 0; ireader == reader) init_slot_info(&slot->slot_info, reader); } } sc_log(context, "%s: Connected SC card %p", reader->name, p11card->card); } /* Detect the framework */ if (p11card->framework == NULL) { struct sc_app_info *app_generic = sc_pkcs15_get_application_by_type(p11card->card, "generic"); sc_log(context, "%s: Detecting Framework. %i on-card applications", reader->name, p11card->card->app_count); sc_log(context, "%s: generic application %s", reader->name, app_generic ? app_generic->label : ""); for (i = 0; frameworks[i]; i++) if (frameworks[i]->bind != NULL) break; /*TODO: only first framework is used: pkcs15init framework is not reachable here */ if (frameworks[i] == NULL) { rv = CKR_GENERAL_ERROR; goto fail; } p11card->framework = frameworks[i]; /* Initialize framework */ sc_log(context, "%s: Detected framework %d. Creating tokens.", reader->name, i); /* Bind 'generic' application or (emulated?) card without applications */ if (app_generic || !p11card->card->app_count) { scconf_block *conf_block = NULL; int enable_InitToken = 0; conf_block = sc_match_atr_block(p11card->card->ctx, NULL, &p11card->reader->atr); if (!conf_block) /* check default block */ conf_block = sc_get_conf_block(context, "framework", "pkcs15", 1); enable_InitToken = scconf_get_bool(conf_block, "pkcs11_enable_InitToken", 0); sc_log(context, "%s: Try to bind 'generic' token.", reader->name); rv = frameworks[i]->bind(p11card, app_generic); if (rv == CKR_TOKEN_NOT_RECOGNIZED && enable_InitToken) { sc_log(context, "%s: 'InitToken' enabled -- accept non-binded card", reader->name); rv = CKR_OK; } if (rv != CKR_OK) { sc_log(context, "%s: cannot bind 'generic' token: rv 0x%lX", reader->name, rv); goto fail; } sc_log(context, "%s: Creating 'generic' token.", reader->name); rv = frameworks[i]->create_tokens(p11card, app_generic); if (rv != CKR_OK) { sc_log(context, "%s: create 'generic' token error 0x%lX", reader->name, rv); goto fail; } } /* Now bind the rest of applications that are not 'generic' */ for (j = 0; j < p11card->card->app_count; j++) { struct sc_app_info *app_info = p11card->card->app[j]; char *app_name = app_info ? app_info->label : ""; if (app_generic && app_generic == p11card->card->app[j]) continue; sc_log(context, "%s: Binding %s token.", reader->name, app_name); rv = frameworks[i]->bind(p11card, app_info); if (rv != CKR_OK) { sc_log(context, "%s: bind %s token error Ox%lX", reader->name, app_name, rv); continue; } sc_log(context, "%s: Creating %s token.", reader->name, app_name); rv = frameworks[i]->create_tokens(p11card, app_info); if (rv != CKR_OK) { sc_log(context, "%s: create %s token error 0x%lX", reader->name, app_name, rv); goto fail; } } } sc_log(context, "%s: Detection ended", reader->name); return CKR_OK; fail: if (free_p11card) { if (p11card->framework) p11card->framework->unbind(p11card); if (p11card->card != NULL) sc_disconnect_card(p11card->card); free(p11card); } return rv; } CK_RV card_detect_all(void) { unsigned int i, j; sc_log(context, "Detect all cards"); /* Detect cards in all initialized readers */ for (i=0; i< sc_ctx_get_reader_count(context); i++) { sc_reader_t *reader = sc_ctx_get_reader(context, i); if (reader->flags & SC_READER_REMOVED) { card_removed(reader); /* do not remove slots related to this reader which would be * possible according to PKCS#11 2.20 and later, because NSS can't * handle a shrinking slot list * https://bugzilla.mozilla.org/show_bug.cgi?id=1613632 */ /* Instead, remove the relation between reader and slot */ for (j = 0; jreader == reader) { slot->reader = NULL; } } } else { /* Locate a slot related to the reader */ int found = 0; for (j = 0; jreader == reader) { found = 1; break; } } if (!found) { for (j = 0; j < sc_pkcs11_conf.slots_per_card; j++) { CK_RV rv = create_slot(reader); if (rv != CKR_OK) return rv; } } card_detect(reader); } } sc_log(context, "All cards detected"); return CKR_OK; } /* Allocates an existing slot to a card */ CK_RV slot_allocate(struct sc_pkcs11_slot ** slot, struct sc_pkcs11_card * p11card) { unsigned int i; struct sc_pkcs11_slot *tmp_slot = NULL; /* Locate a free slot for this reader */ for (i=0; i< list_size(&virtual_slots); i++) { tmp_slot = (struct sc_pkcs11_slot *)list_get_at(&virtual_slots, i); if (tmp_slot->reader == p11card->reader && tmp_slot->p11card == NULL) break; } if (!tmp_slot || (i == list_size(&virtual_slots))) return CKR_FUNCTION_FAILED; sc_log(context, "Allocated slot 0x%lx for card in reader %s", tmp_slot->id, p11card->reader->name); tmp_slot->p11card = p11card; tmp_slot->events = SC_EVENT_CARD_INSERTED; *slot = tmp_slot; return CKR_OK; } CK_RV slot_get_slot(CK_SLOT_ID id, struct sc_pkcs11_slot ** slot) { if (context == NULL) return CKR_CRYPTOKI_NOT_INITIALIZED; *slot = list_seek(&virtual_slots, &id); /* FIXME: check for null? */ if (!*slot) return CKR_SLOT_ID_INVALID; return CKR_OK; } CK_RV slot_get_token(CK_SLOT_ID id, struct sc_pkcs11_slot ** slot) { CK_RV rv; sc_log(context, "Slot(id=0x%lX): get token", id); rv = slot_get_slot(id, slot); if (rv != CKR_OK) return rv; if (!((*slot)->slot_info.flags & CKF_TOKEN_PRESENT)) { if ((*slot)->reader == NULL) return CKR_TOKEN_NOT_PRESENT; sc_log(context, "Slot(id=0x%lX): get token: now detect card", id); rv = card_detect((*slot)->reader); if (rv != CKR_OK) return rv; } if (!((*slot)->slot_info.flags & CKF_TOKEN_PRESENT)) { sc_log(context, "card detected, but slot not presenting token"); return CKR_TOKEN_NOT_PRESENT; } sc_log(context, "Slot-get-token returns OK"); return CKR_OK; } CK_RV slot_token_removed(CK_SLOT_ID id) { CK_RV rv; int token_was_present; struct sc_pkcs11_slot *slot; struct sc_pkcs11_object *object; sc_log(context, "slot_token_removed(0x%lx)", id); rv = slot_get_slot(id, &slot); if (rv != CKR_OK) return rv; token_was_present = (slot->slot_info.flags & CKF_TOKEN_PRESENT); /* Terminate active sessions */ sc_pkcs11_close_all_sessions(id); while ((object = list_fetch(&slot->objects))) { if (object->ops->release) object->ops->release(object); } /* Release framework stuff */ if (slot->p11card != NULL) { if (slot->fw_data != NULL && slot->p11card->framework != NULL && slot->p11card->framework->release_token != NULL) { slot->p11card->framework->release_token(slot->p11card, slot->fw_data); slot->fw_data = NULL; } slot->p11card = NULL; } /* Reset relevant slot properties */ slot->slot_info.flags &= ~CKF_TOKEN_PRESENT; slot->login_user = -1; pop_all_login_states(slot); if (token_was_present) slot->events = SC_EVENT_CARD_REMOVED; memset(&slot->token_info, 0, sizeof slot->token_info); return CKR_OK; } /* Called from C_WaitForSlotEvent */ CK_RV slot_find_changed(CK_SLOT_ID_PTR idp, int mask) { unsigned int i; LOG_FUNC_CALLED(context); card_detect_all(); for (i=0; iid, (slot->slot_info.flags & CKF_TOKEN_PRESENT), slot->events); if ((slot->events & SC_EVENT_CARD_INSERTED) && !(slot->slot_info.flags & CKF_TOKEN_PRESENT)) { /* If a token has not been initialized, clear the inserted event */ slot->events &= ~SC_EVENT_CARD_INSERTED; } sc_log(context, "mask: 0x%02X events: 0x%02X result: %d", mask, slot->events, (slot->events & mask)); if (slot->events & mask) { slot->events &= ~mask; *idp = slot->id; LOG_FUNC_RETURN(context, CKR_OK); } } LOG_FUNC_RETURN(context, CKR_NO_EVENT); }