Skip to content

Commit

Permalink
Provide notification about and handle card resets by other contexts
Browse files Browse the repository at this point in the history
Most of the cards supported by OpenSC do not support the "logout" method
required to deauthenticate a card.
This means that the only way to deauthenticate the card without removing it
from reader is to reset it.

This is done for example by Windows minidriver when asked to
deauthenticate such card (strictly speaking by Windows Base CSP if
minidriver is unable to do a logout on its own).
And Windows libraries (like WinHTTP used by IE and WebDAV client) usually
prefer to deauthenticate card after only a few seconds of inactivity, while
keeping PIN in cache for future operations.

Now, if the card is reset its state is lost for all other users, too.
Currently, "other user" usually means PKCS#11 library, which will break
in this case, since it doesn't know that the card is no longer
authenticated. Also for at least some cards (for example OpenPGP card and
cards having multiple applets) all operations will fail as card needs
specific post-reset initialization.

This all means that we need to catch card resets by other contexts in
reader backends and provide higher layers with this information so they
can decide what to do when it happens. A SC_READER_CARD_RESET reader flag
was added for this.

Unfortunately, when exactly the card reset is announced to PC/SC client
isn't well defined in PC/SC specification and implementations do it
differently. This means that we need to be prepared to receive "card reset"
error code on both transaction begin and individual card operations.
Windows is special here, since SCardBeginTransaction() is documented to
return success even if the card has been reset, but at least Vista returns
SCARD_E_INVALID_VALUE in this case instead, needing a second check what
has actually happened via SCardStatus().

sc_lock() function gains new "resetok" parameter.
When set it means to continue operation even if the card was reset as long
as the card wasn't reset between individual operations.
Basically, with this option set it would be OK for card to be reset either
during locking attempt or before first operation like transmit completes
successfully.
If this happens no SC_READER_CARD_RESET will be set since it is only for
card resets which are not retried automatically.

Currently, such automatic retry on first operation (as opposed to locking)
is disabled due to lack of support for it in higher layers but it will be
easy to reenable it once callers (sc_single_transmit() and
sc_sm_single_transmit()) are adapted accordingly.

For compatibility, all existing users call sc_lock() function with
"resetok" parameter unset, but in future it might be possible to set this
parameters in places where this is determined to be safe.
Examples here would be card probing, very first operation during
initialization and operations on cards that do not have any state which is
cleared by reset.

This all applies to PC/SC reader type. CT-API reader backend doesn't handle
card locking at all, while OpenCT reader backend will need to be adapted
for this semantics for card resets by other contexts to be handled
correctly (if that is possible at all since it looks like OpenCT doesn't
provide card reset notification).

For now, PKCS#11 library does full card detachment + reattachment when
it receives card reset notification (this means that for example on
Firefox the user is prompted to type his PIN again).
Note this is still better that the old behavior of simply being stuck
in non-working state until application restart (or card reinsertion).
In future, it might be possible to teach the PKCS#11 library to handle card
reset gracefully.

Best solution would be to have a ability to reinitialize card after reset
without detaching and reattaching it. This will need first an
implementation of such operation in all card drivers which will need
keeping enough card state to know what needs to be set again in card in at
least some of them.
Such state will then need to be shared between contexts accessing the
same card (even from different processes) to avoid different context
having a different view of card, so it's no trivial task.
Other option would be to reload necessary card state at the beginning
of every transaction (after the card was locked), but this might not be
viable for every card type.

Minidriver have a bit different requirements so it will be adapted to
handle card resets in next commits.

Signed-off-by: Maciej S. Szmigiero <[email protected]>
  • Loading branch information
maciejsszmigiero committed Oct 22, 2016
1 parent 1e98645 commit 7eff745
Show file tree
Hide file tree
Showing 28 changed files with 461 additions and 201 deletions.
2 changes: 1 addition & 1 deletion src/libopensc/apdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu)
if (r != SC_SUCCESS)
return SC_ERROR_INVALID_ARGUMENTS;

r = sc_lock(card); /* acquire card lock*/
r = sc_lock(card, 0); /* acquire card lock */
if (r != SC_SUCCESS) {
sc_log(card->ctx, "unable to acquire lock");
return r;
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-belpic.c
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ static int get_language(sc_card_t *card)
apdu.resplen = 0;
apdu.le = 0;

r = sc_lock(card);
r = sc_lock(card, 0);
if (r < 0)
goto prefs_error;

Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-dnie.c
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ static int dnie_select_file(struct sc_card *card,
sc_log(ctx, "select_file(PATH): requested:%s ", sc_dump_hex(in_path->value, in_path->len));

/* convert to SC_PATH_TYPE_FILE_ID */
res = sc_lock(card); /* lock to ensure path traversal */
res = sc_lock(card, 0); /* lock to ensure path traversal */
LOG_TEST_RET(ctx, res, "sc_lock() failed");
if (memcmp(in_path->value, "\x3F\x00", 2) == 0) {
/* if MF, use the name as path */
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-epass2003.c
Original file line number Diff line number Diff line change
Expand Up @@ -1573,7 +1573,7 @@ epass2003_set_security_env(struct sc_card *card, const sc_security_env_t * env,
apdu.datalen = r;
apdu.data = sbuf;
if (se_num > 0) {
r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
locked = 1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-flex.c
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ static int flex_select_file(sc_card_t *card, const sc_path_t *path,
return 0;
if (pathlen != 2 || memcmp(pathptr, "\x3F\x00", 2) != 0) {
locked = 1;
r = sc_lock(card);
r = sc_lock(card, 0);
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "sc_lock() failed");
if (!magic_done && memcmp(pathptr, "\x3F\x00", 2) != 0) {
r = select_file_id(card, (const u8 *) "\x3F\x00", 2, 0, NULL);
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-gids.c
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ static int gids_set_security_env(sc_card_t *card,
apdu.datalen = r;
apdu.data = sbuf;
if (se_num > 0) {
r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
locked = 1;
}
Expand Down
4 changes: 2 additions & 2 deletions src/libopensc/card-gpk.c
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ gpk_select_file(sc_card_t *card, const sc_path_t *path,
leaf_type = GPK_SEL_MF;
} else {
if (!locked++) {
r = sc_lock(card);
r = sc_lock(card, 0);
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "sc_lock() failed");
}

Expand Down Expand Up @@ -1611,7 +1611,7 @@ static int gpk_get_info(sc_card_t *card, int p1, int p2, u8 *buf,
* calling logout(), which in turn does a SELECT MF
* without collecting the response :)
*/
r = sc_lock(card);
r = sc_lock(card, 0);
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "sc_lock() failed");

do {
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-mcrd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,7 @@ static int mcrd_set_security_env(sc_card_t * card,
apdu.data = sbuf;
apdu.resplen = 0;
if (se_num > 0) {
r = sc_lock(card);
r = sc_lock(card, 0);
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "sc_lock() failed");
locked = 1;
}
Expand Down
8 changes: 4 additions & 4 deletions src/libopensc/card-piv.c
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ static int piv_general_io(sc_card_t *card, int ins, int p1, int p2,
rbuflen = *recvbuflen;
}

r = sc_lock(card);
r = sc_lock(card, 0);
if (r != SC_SUCCESS)
LOG_FUNC_RETURN(card->ctx, r);

Expand Down Expand Up @@ -1608,7 +1608,7 @@ static int piv_general_mutual_authenticate(sc_card_t *card,
goto err;
}

r = sc_lock(card);
r = sc_lock(card, 0);
if (r != SC_SUCCESS) {
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "sc_lock failed\n");
goto err; /* cleanup */
Expand Down Expand Up @@ -1886,7 +1886,7 @@ static int piv_general_external_authenticate(sc_card_t *card,
goto err;
}

r = sc_lock(card);
r = sc_lock(card, 0);
if (r != SC_SUCCESS) {
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "sc_lock failed\n");
goto err; /* cleanup */
Expand Down Expand Up @@ -2207,7 +2207,7 @@ static int piv_get_challenge(sc_card_t *card, u8 *rnd, size_t len)

sc_log(card->ctx, "challenge len=%d",len);

r = sc_lock(card);
r = sc_lock(card, 0);
if (r != SC_SUCCESS)
LOG_FUNC_RETURN(card->ctx, r);

Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/card-setcos.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ static int setcos_set_security_env2(sc_card_t *card,
apdu.data = sbuf;
apdu.resplen = 0;
if (se_num > 0) {
r = sc_lock(card);
r = sc_lock(card, 0);
SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "sc_lock() failed");
locked = 1;
}
Expand Down
30 changes: 19 additions & 11 deletions src/libopensc/card.c
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ int sc_reset(sc_card_t *card, int do_cold_reset)
return r;
}

int sc_lock(sc_card_t *card)
int sc_lock(sc_card_t *card, int resetok)
{
int r = 0, r2 = 0;
int was_reset = 0;
Expand All @@ -401,14 +401,15 @@ int sc_lock(sc_card_t *card)
return r;
if (card->lock_count == 0) {
if (card->reader->ops->lock != NULL) {
r = card->reader->ops->lock(card->reader);
while (r == SC_ERROR_CARD_RESET || r == SC_ERROR_READER_REATTACHED) {
r = card->reader->ops->lock(card->reader, resetok);
while (resetok && r == SC_ERROR_CARD_RESET) {
/* invalidate cache */
memset(&card->cache, 0, sizeof(card->cache));
card->cache.valid = 0;
if (was_reset++ > 4) /* TODO retry a few times */
break;
r = card->reader->ops->lock(card->reader);
r = card->reader->ops->lock(card->reader,
resetok);
}
if (r == 0)
reader_lock_obtained = 1;
Expand Down Expand Up @@ -454,15 +455,22 @@ int sc_unlock(sc_card_t *card)

assert(card->lock_count >= 1);
if (--card->lock_count == 0) {
const int invalidate_on_unlock =
#ifdef INVALIDATE_CARD_CACHE_IN_UNLOCK
/* invalidate cache */
memset(&card->cache, 0, sizeof(card->cache));
card->cache.valid = 0;
sc_log(card->ctx, "cache invalidated");
1;
#else
0;
#endif
/* release reader lock */
if (card->reader->ops->unlock != NULL)
r = card->reader->ops->unlock(card->reader);

if (invalidate_on_unlock || r == SC_ERROR_CARD_RESET) {
/* invalidate cache */
memset(&card->cache, 0, sizeof(card->cache));
card->cache.valid = 0;
sc_log(card->ctx, "cache invalidated");
}
}
r2 = sc_mutex_unlock(card->ctx, card->mutex);
if (r2 != SC_SUCCESS) {
Expand Down Expand Up @@ -557,7 +565,7 @@ int sc_read_binary(sc_card_t *card, unsigned int idx,
int bytes_read = 0;
unsigned char *p = buf;

r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
while (count > 0) {
size_t n = count > max_le ? max_le : count;
Expand Down Expand Up @@ -599,7 +607,7 @@ int sc_write_binary(sc_card_t *card, unsigned int idx,
int bytes_written = 0;
const u8 *p = buf;

r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
while (count > 0) {
size_t n = count > max_lc? max_lc : count;
Expand Down Expand Up @@ -651,7 +659,7 @@ int sc_update_binary(sc_card_t *card, unsigned int idx,
int bytes_written = 0;
const u8 *p = buf;

r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
while (count > 0) {
size_t n = count > max_lc? max_lc : count;
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/iso7816.c
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ iso7816_set_security_env(struct sc_card *card,
apdu.datalen = r;
apdu.data = sbuf;
if (se_num > 0) {
r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(card->ctx, r, "sc_lock() failed");
locked = 1;
}
Expand Down
22 changes: 15 additions & 7 deletions src/libopensc/opensc.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ struct sc_reader_driver {
#define SC_READER_CARD_EXCLUSIVE 0x00000008
#define SC_READER_HAS_WAITING_AREA 0x00000010
#define SC_READER_REMOVED 0x00000020
#define SC_READER_CARD_RESET 0x00000040

/* reader capabilities */
#define SC_READER_CAP_DISPLAY 0x00000001
Expand Down Expand Up @@ -410,7 +411,11 @@ struct sc_reader_operations {
int (*connect)(struct sc_reader *reader);
int (*disconnect)(struct sc_reader *reader);
int (*transmit)(struct sc_reader *reader, sc_apdu_t *apdu);
int (*lock)(struct sc_reader *reader);
/*
* resetok means to continue even if the card was reset
* as long as it wasn't reset between individual operations
*/
int (*lock)(struct sc_reader *reader, int resetok);
int (*unlock)(struct sc_reader *reader);
int (*set_protocol)(struct sc_reader *reader, unsigned int proto);
/* Pin pad functions */
Expand Down Expand Up @@ -888,11 +893,12 @@ int sc_disconnect_card(struct sc_card *card);
* Checks if a card is present in a reader
* @param reader Reader structure
* @retval If an error occured, the return value is a (negative)
* OpenSC error code. If no card is present, 0 is returned.
* Otherwise, a positive value is returned, which is a
* combination of flags. The flag SC_READER_CARD_PRESENT is
* always set. In addition, if the card was exchanged,
* the SC_READER_CARD_CHANGED flag is set.
* OpenSC error code. Otherwise, a positive value is returned, which
* is a combination of zero or more flags. See "reader flags" above.
* The flag SC_READER_CARD_PRESENT is set if there is a card present.
* In addition, if the card was exchanged or reset
* SC_READER_CARD_CHANGED or SC_READER_CARD_RESET flag,
* respectively, will be set.
*/
int sc_detect_card_presence(sc_reader_t *reader);

Expand Down Expand Up @@ -938,9 +944,11 @@ int sc_cancel(sc_context_t *ctx);
/**
* Tries acquire the reader lock.
* @param card The card to lock
* @param resetok If nonzero means to continue even if the card was reset
* as long as it wasn't reset between individual operations
* @retval SC_SUCCESS on success
*/
int sc_lock(struct sc_card *card);
int sc_lock(struct sc_card *card, int resetok);
/**
* Unlocks a previously acquired reader lock.
* @param card The card to unlock
Expand Down
8 changes: 4 additions & 4 deletions src/libopensc/pkcs15-pin.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ _sc_pkcs15_verify_pin(struct sc_pkcs15_card *p15card, struct sc_pkcs15_object *p
data.pin1.prompt = "Please enter PIN";
}

r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(ctx, r, "sc_lock() failed");

/* the path in the pin object is optional */
Expand Down Expand Up @@ -422,7 +422,7 @@ int sc_pkcs15_change_pin(struct sc_pkcs15_card *p15card,
LOG_TEST_RET(ctx, r, "New PIN value do not conform PIN policy");

card = p15card->card;
r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(ctx, r, "sc_lock() failed");
/* the path in the pin object is optional */
if ((auth_info->path.len > 0) || ((auth_info->path.aid.len > 0))) {
Expand Down Expand Up @@ -528,7 +528,7 @@ int sc_pkcs15_unblock_pin(struct sc_pkcs15_card *p15card,
r = _validate_pin(p15card, puk_info, puklen);
LOG_TEST_RET(ctx, r, "PIN do not conforms PIN policy");

r = sc_lock(card);
r = sc_lock(card, 0);
LOG_TEST_RET(ctx, r, "sc_lock() failed");

/* the path in the pin object is optional */
Expand Down Expand Up @@ -609,7 +609,7 @@ int sc_pkcs15_get_pin_info(struct sc_pkcs15_card *p15card,

LOG_FUNC_CALLED(ctx);

r = sc_lock(card);
r = sc_lock(card, 0);
if (r != SC_SUCCESS)
return r;

Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/pkcs15-sec.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ static int use_key(struct sc_pkcs15_card *p15card,
int revalidated_cached_pin = 0;
const struct sc_pkcs15_prkey_info *prkey = (const struct sc_pkcs15_prkey_info *) obj->data;

r = sc_lock(p15card->card);
r = sc_lock(p15card->card, 0);
LOG_TEST_RET(p15card->card->ctx, r, "sc_lock() failed");

do {
Expand Down
4 changes: 2 additions & 2 deletions src/libopensc/pkcs15.c
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ sc_pkcs15_bind(struct sc_card *card, struct sc_aid *aid,
p15card->opts.use_file_cache, p15card->opts.use_pin_cache,p15card->opts.pin_cache_counter,
p15card->opts.pin_cache_ignore_user_consent);

r = sc_lock(card);
r = sc_lock(card, 0);
if (r) {
sc_log(ctx, "sc_lock() failed: %s", sc_strerror(r));
sc_pkcs15_card_free(p15card);
Expand Down Expand Up @@ -2350,7 +2350,7 @@ sc_pkcs15_read_file(struct sc_pkcs15_card *p15card, const struct sc_path *in_pat
}

if (r) {
r = sc_lock(p15card->card);
r = sc_lock(p15card->card, 0);
LOG_TEST_RET(ctx, r, "sc_lock() failed");
r = sc_select_file(p15card->card, in_path, &file);
if (r)
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/reader-ctapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ static int ctapi_disconnect(sc_reader_t *reader)
return 0;
}

static int ctapi_lock(sc_reader_t *reader)
static int ctapi_lock(sc_reader_t *reader, int resetok)
{
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion src/libopensc/reader-openct.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ static int openct_reader_perform_verify(sc_reader_t *reader, struct sc_pin_cmd_d
return 0;
}

static int openct_reader_lock(sc_reader_t *reader)
static int openct_reader_lock(sc_reader_t *reader, int resetok)
{
struct driver_data *data = (struct driver_data *) reader->drv_data;
int rc;
Expand Down
Loading

0 comments on commit 7eff745

Please sign in to comment.