PIV detection of AID using Discovery Object before doing select AID

Many OpenSC drivers try and detect during match if the card supports
their AID by doing a SELECT FILE for the AID.

But this can cause problems with cards such as Yubico that do not ignore
SELECT AID commands for applications they do not support. Other cards may
have the same problems. Selecting the wrong AID can also lose the security
state.

The card-piv.c will now uses the GET DATA to read the PIV Discovery Object '7E'
which is a ISO standard template that will contain the AID of the currently
active application. The driver will then double check that the template is
for the PIV application.

If the template contains the PIV AID, then no SELECT AID is done.
PIV standards say there can only be one PIV application on a card.
PIV standards also say PIV must be the the default application,
but Yubico does not follow this.

The command fails only then will a SELECT AID be done.

Thus this can avoid the Yubico problem.

This logic is used in both "match" and in the piv_card_reader_lock_obtained
routine.

Additional logic was in piv_card_reader_lock_obtained was added to handle
when the card reset was received by some other program. Multiple programs
may be trying to use the PIV application on the card, and thus multiple
programs will all receive that the card was reset. The first program to receive
the card was reset will do all of the above logic, and may leave the card in
a state will cause other programs to not  have to do much at all.

 The intent of all of this is to avoid sending extra commands to the card
 including SELECT AID that could change the card state when not needed.

 On branch piv-aid-discovery
 Changes to be committed:
	modified:   card-piv.c
This commit is contained in:
Doug Engert 2018-01-29 10:37:47 -06:00
parent 8cc0c3911a
commit 3fea6b7927
1 changed files with 160 additions and 51 deletions

View File

@ -141,6 +141,12 @@ typedef struct piv_obj_cache {
int flags;
} piv_obj_cache_t;
enum {
PIV_STATE_NORMAL = 0,
PIV_STATE_MATCH,
PIV_STATE_INIT
};
typedef struct piv_private_data {
sc_file_t *aid_file;
int enumtag;
@ -160,6 +166,7 @@ typedef struct piv_private_data {
char * offCardCertURL;
int pin_preference; /* set from Discovery object */
int logged_in;
int pstate;
int pin_cmd_verify;
int pin_cmd_noparse;
unsigned int pin_cmd_verify_sw1;
@ -180,7 +187,7 @@ struct piv_aid {
};
/*
* The Generic entry should be the "A0 00 00 03 08 00 00 01 00 "
* The Generic entry should be the "A0 00 00 03 08 00 00 10 00 "
* NIST published this on 10/6/2005
* 800-73-2 Part 1 now refers to version "02 00"
* i.e. "A0 00 00 03 08 00 00 01 00 02 00".
@ -2072,8 +2079,6 @@ piv_get_serial_nr_from_CHUI(sc_card_t* card, sc_serial_number_t* serial)
const u8 *fascn;
const u8 *guid;
size_t rbuflen = 0, bodylen, fascnlen, guidlen;
u8 temp[2000];
size_t templen = sizeof(temp);
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
if (card->serialnr.len) {
@ -2081,15 +2086,12 @@ piv_get_serial_nr_from_CHUI(sc_card_t* card, sc_serial_number_t* serial)
LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
}
/* ensure we've got the PIV selected, and nothing else is in process */
/* This fixes several problems due to previous incomplete APDUs during card detection */
/* Note: We need the temp because (some?) Oberthur cards don't like selecting an applet without response data */
/* 800-73-3 part1 draft, and CIO Council docs imply for PIV Compatible card
* The FASC-N Agency code should be 9999 and there should be a GUID
* based on RFC 4122. RIf so and the GUID is not all 0's
/*
* 800-73-3 Part 1 and CIO Council docs say for PIV Compatible cards
* the FASC-N Agency code should be 9999 and there should be a GUID
* based on RFC 4122. If GUID present and not zero
* we will use the GUID as the serial number.
*/
piv_select_aid(card, piv_aids[0].value, piv_aids[0].len_short, temp, &templen);
r = piv_get_cached_data(card, PIV_OBJ_CHUI, &rbuf, &rbuflen);
LOG_TEST_RET(card->ctx, r, "Failure retrieving CHUI");
@ -2577,12 +2579,10 @@ static int piv_select_file(sc_card_t *card, const sc_path_t *in_path,
}
static int piv_process_discovery(sc_card_t *card)
static int piv_parse_discovery(sc_card_t *card, u8 * rbuf, size_t rbuflen, int aid_only)
{
piv_private_data_t * priv = PIV_DATA(card);
int r;
u8 * rbuf = NULL;
size_t rbuflen = 0;
int r = 0;
const u8 * body;
size_t bodylen;
const u8 * aid;
@ -2592,20 +2592,6 @@ static int piv_process_discovery(sc_card_t *card)
unsigned int cla_out, tag_out;
r = piv_get_cached_data(card, PIV_OBJ_DISCOVERY, &rbuf, &rbuflen);
if (r <= 0) {
priv->obj_cache[PIV_OBJ_DISCOVERY].flags |= PIV_OBJ_CACHE_NOT_PRESENT;
/* Discovery object is only object that has 3 byte Lc= 50017E
* and pree 800-73-3 cards may treat this as a strange error.
* So treat any error as not present
*/
r = 0;
goto err;
}
sc_log(card->ctx, "Discovery = %p:%"SC_FORMAT_LEN_SIZE_T"u", rbuf,
rbuflen);
/* the object is now cached, see what we have */
if (rbuflen != 0) {
body = rbuf;
if ((r = sc_asn1_read_tag(&body, rbuflen, &cla_out, &tag_out, &bodylen)) != SC_SUCCESS) {
@ -2625,19 +2611,21 @@ static int piv_process_discovery(sc_card_t *card)
if (aid == NULL || aidlen < piv_aids[0].len_short ||
memcmp(aid,piv_aids[0].value,piv_aids[0].len_short) != 0) { /*TODO look at long */
sc_log(card->ctx, "Discovery object not PIV");
r = SC_SUCCESS; /* not an error could be some other appl */
r = SC_ERROR_INVALID_CARD; /* This is an error */
goto err;
}
pinp = sc_asn1_find_tag(card->ctx, body, bodylen, 0x5F2F, &pinplen);
sc_log(card->ctx,
"Discovery pinp=%p:%"SC_FORMAT_LEN_SIZE_T"u",
pinp, pinplen);
if (pinp && pinplen == 2) {
sc_log(card->ctx, "Discovery pinp flags=0x%2.2x 0x%2.2x",*pinp, *(pinp+1));
r = SC_SUCCESS;
if (*pinp == 0x60 && *(pinp+1) == 0x20) { /* use Global pin */
sc_log(card->ctx, "Pin Preference - Global");
priv->pin_preference = 0x00;
if (aid_only == 0) {
pinp = sc_asn1_find_tag(card->ctx, body, bodylen, 0x5F2F, &pinplen);
sc_log(card->ctx,
"Discovery pinp=%p:%"SC_FORMAT_LEN_SIZE_T"u",
pinp, pinplen);
if (pinp && pinplen == 2) {
sc_log(card->ctx, "Discovery pinp flags=0x%2.2x 0x%2.2x",*pinp, *(pinp+1));
r = SC_SUCCESS;
if (*pinp == 0x60 && *(pinp+1) == 0x20) { /* use Global pin */
sc_log(card->ctx, "Pin Preference - Global");
priv->pin_preference = 0x00;
}
}
}
}
@ -2647,6 +2635,61 @@ err:
LOG_FUNC_RETURN(card->ctx, r);
}
/* normal way to get the discovery object via cache */
static int piv_process_discovery(sc_card_t *card)
{
int r;
u8 * rbuf = NULL;
size_t rbuflen = 0;
r = piv_get_cached_data(card, PIV_OBJ_DISCOVERY, &rbuf, &rbuflen);
/* Note rbuf and rbuflen are now pointers into cache */
if (r < 0)
goto err;
sc_log(card->ctx, "Discovery = %p:%"SC_FORMAT_LEN_SIZE_T"u", rbuf,
rbuflen);
/* the object is now cached, see what we have */
r = piv_parse_discovery(card, rbuf, rbuflen, 0);
err:
LOG_FUNC_RETURN(card->ctx, r);
}
/* Do not use the cache value but read every time */
static int piv_find_discovery(sc_card_t *card)
{
int r = 0;
u8 rbuf[256];
size_t rbuflen = sizeof(rbuf);
u8 * arbuf = rbuf;
piv_private_data_t * priv = PIV_DATA(card);
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
/*
* During piv_match or piv_card_reader_lock_obtained,
* we use the discovery object to test if card present, and
* if PIV AID is active. So we can not use the cache
*/
/* If not valid, read, cache and test */
if (!(priv->obj_cache[PIV_OBJ_DISCOVERY].flags & PIV_OBJ_CACHE_VALID)) {
r = piv_process_discovery(card);
} else {
/* if already in cache,force read */
r = piv_get_data(card, PIV_OBJ_DISCOVERY, &arbuf, &rbuflen);
if (r >= 0)
/* make sure it is PIV AID */
r = piv_parse_discovery(card, rbuf, rbuflen, 1);
}
LOG_FUNC_RETURN(card->ctx, r);
}
/*
* The history object lists what retired keys and certs are on the card
* or listed in the offCardCertURL. The user may have read the offCardURL file,
@ -2902,6 +2945,7 @@ piv_finish(sc_card_t *card)
free(priv->obj_cache[i].internal_obj_data);
}
free(priv);
card->drv_data = NULL; /* priv */
}
return 0;
}
@ -2914,7 +2958,7 @@ static int piv_match_card(sc_card_t *card)
u8 *p, *pe;
sc_file_t aidfile;
int type = -1;
piv_private_data_t *priv;
piv_private_data_t *priv = NULL;
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
@ -2978,11 +3022,7 @@ static int piv_match_card(sc_card_t *card)
type = SC_CARD_TYPE_PIV_II_GENERIC;
}
/* Detect by selecting applet */
i = piv_find_aid(card, &aidfile);
if (i < 0)
return 0; /* don't match. Does not have a PIV applet. */
/* allocate and init basic fields */
priv = calloc(1, sizeof(piv_private_data_t));
@ -2992,19 +3032,47 @@ static int piv_match_card(sc_card_t *card)
if (card->type == -1)
card->type = type;
card->drv_data = priv;
card->drv_data = priv; /* will frre if no match, or pass on to piv_init */
priv->aid_file = sc_file_new();
priv->selected_obj = -1;
priv->pin_preference = 0x80; /* 800-73-3 part 1, table 3 */
priv->logged_in = SC_PIN_STATE_UNKNOWN;
priv->tries_left = 10; /* will assume OK at start */
priv->pstate = PIV_STATE_MATCH;
/* Some objects will only be present if Histroy object says so */
for (i=0; i < PIV_OBJ_LAST_ENUM -1; i++)
if(piv_objects[i].flags & PIV_OBJECT_NOT_PRESENT)
priv->obj_cache[i].flags |= PIV_OBJ_CACHE_NOT_PRESENT;
sc_lock(card);
/*
* detect if active AID is PIV. NIST 800-73 says Only one PIV application per card
* and PIV must be the default application
* This can avoid doing doing a select_aid and losing the login state on some cards
* We may get interference on some cards by other drivers trying SELECT_AID before
* we get to see if PIV application is still active.
* putting PIV driver first might help.
* TODO could be cached too
*/
i = piv_find_discovery(card);
if (i < 0) {
/* Detect by selecting applet */
i = piv_find_aid(card, &aidfile);
}
if (i < 0) {
piv_finish(card);
/* don't match. Does not have a PIV applet. */
sc_unlock(card);
return 0;
}
/* Matched, and priv is being passed to piv_init */
/* hold the lock and pass to piv_init */
priv->pstate=PIV_STATE_INIT;
return 1; /* match */
}
@ -3131,6 +3199,8 @@ static int piv_init(sc_card_t *card)
if (r > 0)
r = 0;
priv->pstate=PIV_STATE_NORMAL;
sc_unlock(card) ; /* obtained in piv_match */
LOG_FUNC_RETURN(card->ctx, r);
}
@ -3358,21 +3428,60 @@ static int piv_logout(sc_card_t *card)
}
/*
* Called when a sc_lock gets a reader lock and PCSC SCardBeginTransaction
* If SCardBeginTransaction may pass back tha a card reset was seen since
* the last transaction completed.
* There may have been one or more resets, by other card drivers in different
* processes, and they may have taken action already
* and changed the AID and or may have sent a VERIFY with PIN
* So test the state of the card.
* this is very similiar to what the piv_match routtine does,
*/
static int piv_card_reader_lock_obtained(sc_card_t *card, int was_reset)
{
int r = 0;
u8 temp[2000];
u8 temp[256];
size_t templen = sizeof(temp);
struct sc_pin_cmd_data data;
piv_private_data_t * priv = PIV_DATA(card); /* may be null */
SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
if (was_reset > 0) {
if (priv)
priv->logged_in = SC_PIN_STATE_UNKNOWN;
r = piv_select_aid(card, piv_aids[0].value, piv_aids[0].len_short, temp, &templen);
/* We have a PCSC transaction and sc_lock */
if (priv == NULL || priv->pstate == PIV_STATE_MATCH) {
sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE,
priv ? "PIV_STATE_MATCH" : "priv==NULL");
r = 0; /* do nothing, piv_match will take care of it */
goto err;
}
/* make sure our application is active */
/* first see if AID is active AID be reading discovery obkect '7E' */
/* If not try selecting AID */
r = piv_find_discovery(card);
if (r < 0)
r = piv_select_aid(card, piv_aids[0].value, piv_aids[0].len_short, temp, &templen);
if (r < 0) /* bad error return will show up in sc_lock as error*/
goto err;
if (was_reset > 0)
priv->logged_in = SC_PIN_STATE_UNKNOWN;
/* See if VERIFY Lc=empty will tell us the state */
memset(&data, 0, sizeof(data));
data.cmd = SC_PIN_CMD_GET_INFO;
data.pin_type = SC_AC_CHV;
data.pin_reference = priv->pin_preference;
/* will try our best to see if logged_in or not */
r = piv_pin_cmd(card, &data, NULL);
r = 0; /* ignore return from piv_pin_cmd */
err:
LOG_FUNC_RETURN(card->ctx, r);
}