Skip to content

Commit

Permalink
Detect Yubikey, get version, and use it to select proper SELECT DATA …
Browse files Browse the repository at this point in the history
…function
  • Loading branch information
ya-isakov committed Jan 4, 2024
1 parent fc4886c commit 99823b2
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 34 deletions.
105 changes: 71 additions & 34 deletions src/libopensc/card-openpgp.c
Expand Up @@ -71,7 +71,7 @@ static const struct sc_atr_table pgp_atrs[] = {
},
{ "3b:fc:13:00:00:81:31:fe:15:59:75:62:69:6b:65:79:4e:45:4f:72:33:e1", NULL, "Yubikey NEO (OpenPGP v2.0)", SC_CARD_TYPE_OPENPGP_V2, 0, NULL },
{ "3b:f8:13:00:00:81:31:fe:15:59:75:62:69:6b:65:79:34:d4", NULL, "Yubikey 4 (OpenPGP v2.1)", SC_CARD_TYPE_OPENPGP_V2, 0, NULL },
{ "3b:fd:13:00:00:81:31:fe:15:80:73:c0:21:c0:57:59:75:62:69:4b:65:79:40", NULL, "Yubikey 5 (OpenPGP v3.4)", SC_CARD_TYPE_OPENPGP_V3, 0, NULL },
{ "3b:fd:13:00:00:81:31:fe:15:80:73:c0:21:c0:57:59:75:62:69:4b:65:79:40", NULL, "Yubikey 5 (OpenPGP v3.4)", SC_CARD_TYPE_OPENPGP_V3, SC_CARD_FLAG_YUBIKEY_SELECT, NULL },
{ "3b:da:18:ff:81:b1:fe:75:1f:03:00:31:f5:73:c0:01:60:00:90:00:1c", NULL, default_cardname_v3, SC_CARD_TYPE_OPENPGP_V3, 0, NULL },
{ NULL, NULL, NULL, 0, 0, NULL }
};
Expand Down Expand Up @@ -311,6 +311,7 @@ pgp_match_card(sc_card_t *card)
i = _sc_match_atr(card, pgp_atrs, &card->type);
if (i >= 0) {
card->name = pgp_atrs[i].name;
card->flags = pgp_atrs[i].flags;
LOG_FUNC_RETURN(card->ctx, 1);
}
else {
Expand Down Expand Up @@ -1772,30 +1773,34 @@ pgp_get_pubkey_pem(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
}

/**
* Internal: Same as pgp_select_data, but with Yubikey workaround
* https://github.com/Yubico/yubikey-manager/issues/403
* Internal: SELECT DATA - selects a DO within a DO tag with several instances
* (supported since OpenPGP Card v3 for DO 7F21 only, see section 7.2.5 of the specification;
* this enables us to store multiple Card holder certificates in DO 7F21)
*
* p1: number of an instance (DO 7F21: 0x00 for AUT, 0x01 for DEC and 0x02 for SIG)
*/
static int
pgp_select_data_yubikey(sc_card_t *card, u8 p1)
pgp_select_data(sc_card_t *card, u8 p1)
{
sc_apdu_t apdu;
u8 apdu_data[7];
u8 apdu_data[6];
int r;
struct pgp_priv_data *priv = DRVDATA(card);

LOG_FUNC_CALLED(card->ctx);

sc_log(card->ctx, "select data with: %u (Yubikey workaround)", p1);
// Yubikey wants another length
apdu_data[0] = 0x06;
if (priv->bcd_version < OPENPGP_CARD_3_0)
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);

sc_log(card->ctx, "select data with: %u", p1);

// create apdu data (taken from spec: SELECT DATA 7.2.5.)
apdu_data[1] = 0x60;
apdu_data[2] = 0x04;
apdu_data[3] = 0x5c;
apdu_data[4] = 0x02;
apdu_data[5] = 0x7f;
apdu_data[6] = 0x21;
apdu_data[0] = 0x60;
apdu_data[1] = 0x04;
apdu_data[2] = 0x5c;
apdu_data[3] = 0x02;
apdu_data[4] = 0x7f;
apdu_data[5] = 0x21;

// apdu, cla, ins, p1, p2, data, datalen, resp, resplen
sc_format_apdu_ex(&apdu, 0x00, 0xA5, p1, 0x04, apdu_data, sizeof(apdu_data), NULL, 0);
Expand All @@ -1809,34 +1814,67 @@ pgp_select_data_yubikey(sc_card_t *card, u8 p1)
}

/**
* Internal: SELECT DATA - selects a DO within a DO tag with several instances
* (supported since OpenPGP Card v3 for DO 7F21 only, see section 7.2.5 of the specification;
* this enables us to store multiple Card holder certificates in DO 7F21)
* Internal: Get Yubikey firmware version
*
*/
static int
pgp_yubikey_get_version(sc_card_t *card, unsigned long* version)
{
sc_apdu_t apdu;
u8 resp_buf[3];
u8 apdu_data[1];
int r;

LOG_FUNC_CALLED(card->ctx);

apdu_data[0] = 0x03;

// apdu, cla, ins, p1, p2, data, datalen, resp, resplen
sc_format_apdu_ex(&apdu, 0x00, 0xF1, 0x00, 0x00, apdu_data, sizeof(apdu_data), resp_buf, sizeof(resp_buf));
r = sc_transmit_apdu(card, &apdu);
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
LOG_TEST_RET(card->ctx, r, "Card returned error");
sc_log(card->ctx, "detected Yubikey version %d.%d.%d", resp_buf[0], resp_buf[1], resp_buf[2]);
*version = (unsigned long)resp_buf[0] << 16
| (unsigned long)resp_buf[1] << 8
| (unsigned long)resp_buf[2];
LOG_FUNC_RETURN(card->ctx, r);
}

/**
* Internal: Same as pgp_select_data, but with Yubikey workaround
* https://github.com/Yubico/yubikey-manager/issues/403
*
* p1: number of an instance (DO 7F21: 0x00 for AUT, 0x01 for DEC and 0x02 for SIG)
*/
static int
pgp_select_data(sc_card_t *card, u8 p1)
pgp_select_data_yubikey(sc_card_t *card, u8 p1)
{
unsigned long version;
sc_apdu_t apdu;
u8 apdu_data[6];
u8 apdu_data[7];
int r;
struct pgp_priv_data *priv = DRVDATA(card);

LOG_FUNC_CALLED(card->ctx);

if (priv->bcd_version < OPENPGP_CARD_3_0)
LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED);
r = pgp_yubikey_get_version(card, &version);
LOG_TEST_RET(card->ctx, r, "Cannot read Yubikey version");

sc_log(card->ctx, "select data with: %u", p1);
// only versions of firmware 5.4.3 and earlier are affected
if (version > 0x050403)
LOG_FUNC_RETURN(card->ctx, pgp_select_data(card, p1));

sc_log(card->ctx, "select data with: %u (Yubikey workaround)", p1);
// Yubikey wants another length
apdu_data[0] = 0x06;
// create apdu data (taken from spec: SELECT DATA 7.2.5.)
apdu_data[0] = 0x60;
apdu_data[1] = 0x04;
apdu_data[2] = 0x5c;
apdu_data[3] = 0x02;
apdu_data[4] = 0x7f;
apdu_data[5] = 0x21;
apdu_data[1] = 0x60;
apdu_data[2] = 0x04;
apdu_data[3] = 0x5c;
apdu_data[4] = 0x02;
apdu_data[5] = 0x7f;
apdu_data[6] = 0x21;

// apdu, cla, ins, p1, p2, data, datalen, resp, resplen
sc_format_apdu_ex(&apdu, 0x00, 0xA5, p1, 0x04, apdu_data, sizeof(apdu_data), NULL, 0);
Expand All @@ -1845,14 +1883,10 @@ pgp_select_data(sc_card_t *card, u8 p1)
r = sc_transmit_apdu(card, &apdu);
LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
r = sc_check_sw(card, apdu.sw1, apdu.sw2);
// If parameters are incorrect, let's try to read it again, with Yubikey parameters
if (r == SC_ERROR_INCORRECT_PARAMETERS)
return pgp_select_data_yubikey(card, p1);
LOG_TEST_RET(card->ctx, r, "Card returned error");
LOG_FUNC_RETURN(card->ctx, r);
}


/**
* ABI: ISO 7816-4 GET DATA - get contents of a DO.
*/
Expand Down Expand Up @@ -3609,7 +3643,10 @@ pgp_card_ctl(sc_card_t *card, unsigned long cmd, void *ptr)
LOG_FUNC_RETURN(card->ctx, SC_SUCCESS);
break;
case SC_CARDCTL_OPENPGP_SELECT_DATA:
r = pgp_select_data(card, *((u8 *) ptr));
if (card->flags & SC_CARD_FLAG_YUBIKEY_SELECT)
r = pgp_select_data_yubikey(card, *((u8 *) ptr));
else
r = pgp_select_data(card, *((u8 *) ptr));
LOG_FUNC_RETURN(card->ctx, r);
break;
#ifdef ENABLE_OPENSSL
Expand Down
2 changes: 2 additions & 0 deletions src/libopensc/opensc.h
Expand Up @@ -539,6 +539,8 @@ struct sc_reader_operations {
#define SC_CARD_FLAG_RNG 0x00000002
#define SC_CARD_FLAG_KEEP_ALIVE 0x00000004

#define SC_CARD_FLAG_YUBIKEY_SELECT 0x00010000

/*
* Card capabilities
*/
Expand Down

0 comments on commit 99823b2

Please sign in to comment.