libykpiv/piv-tool 1.6.3

lib: promote get_serial to base API
lib: add ykpiv_get_serial to external API
tool: add serial number/version to status command
build: fix msvc build of case insensitive-reader (missing strncasecmp and cast warnings)
lib: consolidate neo/yk4 + yk5 serial number routines
lib: fix GCC 8 compilier warnings
lib: reimplement deauthenticate to select mgmt aid
build: disable -Waggregate-return
lib: fix warning differences between gcc and msvc
lib: add option to disable implicit card transactions
lib: remove application reselect prior to crypt operations
build: fix msvc warnings wrt length checking logic fixes
lib: fix error condition logic in untransacted internal functions
lib: create internal transactionless ykpiv_transfer_data
This commit is contained in:
Dave Pate
2018-09-14 14:29:39 -07:00
parent 311ba9b30c
commit cbd5ba5122
8 changed files with 396 additions and 163 deletions
+262 -34
View File
@@ -76,11 +76,36 @@
#define ENABLE_APPLICATION_RESELECTION 0
#endif
/**
* ENABLE_IMPLICIT_TRANSACTIONS - call SCardBeginTransaction for all public API calls
*
* If this is enabled, every public call (prefixed with \r ykpiv_) will call
* SCardBeginTransaction on entry and SCardEndTransaction on exit.
*
* For applications that do not do their own transaction management, like the piv tool
* itself, retaining the default setting of enabled can allow other applications and
* threads to make calls to CCID that can interfere with multi-block data sent to the
* card via SCardTransmitData.
*/
#ifndef ENABLE_IMPLICIT_TRANSACTIONS
#define ENABLE_IMPLICIT_TRANSACTIONS 1
#endif
/**
* Platform specific definitions
*/
#ifdef _MSC_VER
#define strncasecmp _strnicmp
#endif
#define YKPIV_MGM_DEFAULT "\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
static ykpiv_rc _cache_pin(ykpiv_state *state, const char *pin, size_t len);
static ykpiv_rc _ykpiv_get_serial(ykpiv_state *state, uint32_t *p_serial, bool force);
static ykpiv_rc _ykpiv_get_version(ykpiv_state *state, ykpiv_version_t *p_version);
unsigned const char aid[] = {
static unsigned const char aid[] = {
0xa0, 0x00, 0x00, 0x03, 0x08
};
@@ -130,7 +155,7 @@ static void dump_hex(const unsigned char *buf, unsigned int len) {
}
}
int _ykpiv_set_length(unsigned char *buffer, size_t length) {
unsigned int _ykpiv_set_length(unsigned char *buffer, size_t length) {
if(length < 0x80) {
*buffer++ = (unsigned char)length;
return 1;
@@ -146,7 +171,7 @@ int _ykpiv_set_length(unsigned char *buffer, size_t length) {
}
}
int _ykpiv_get_length(const unsigned char *buffer, size_t *len) {
unsigned int _ykpiv_get_length(const unsigned char *buffer, size_t *len) {
if(buffer[0] < 0x81) {
*len = buffer[0];
return 1;
@@ -193,7 +218,7 @@ ykpiv_rc ykpiv_init_with_allocator(ykpiv_state **state, int verbose, const ykpiv
s->pin = NULL;
s->allocator = *allocator;
s->verbose = verbose;
s->context = SCARD_E_INVALID_HANDLE;
s->context = (SCARDCONTEXT)-1;
*state = s;
return YKPIV_OK;
}
@@ -226,7 +251,7 @@ ykpiv_rc ykpiv_disconnect(ykpiv_state *state) {
if(SCardIsValidContext(state->context) == SCARD_S_SUCCESS) {
SCardReleaseContext(state->context);
state->context = SCARD_E_INVALID_HANDLE;
state->context = (SCARDCONTEXT)-1;
}
return YKPIV_OK;
@@ -239,8 +264,6 @@ ykpiv_rc _ykpiv_select_application(ykpiv_state *state) {
int sw;
ykpiv_rc res = YKPIV_OK;
_ykpiv_util_get_serial(state, NULL, false);
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = YKPIV_INS_SELECT_APPLICATION;
apdu.st.p1 = 0x04;
@@ -260,7 +283,16 @@ ykpiv_rc _ykpiv_select_application(ykpiv_state *state) {
return YKPIV_GENERIC_ERROR;
}
/* now that the PIV application is selected, retrieve the version
* and serial number. Previously the NEO/YK4 required switching
* to the yk applet to retrieve the serial, YK5 implements this
* as a PIV applet command. Unfortunately, this change requires
* that we retrieve the version number first, so that get_serial
* can determine how to get the serial number, which for the NEO/Yk4
* will result in another selection of the PIV applet. */
_ykpiv_get_version(state, NULL);
_ykpiv_get_serial(state, NULL, false);
return res;
}
@@ -284,6 +316,7 @@ ykpiv_rc _ykpiv_ensure_application_selected(ykpiv_state *state) {
return res;
#else
(void)state;
return res;
#endif
}
@@ -456,7 +489,7 @@ ykpiv_rc ykpiv_list_readers(ykpiv_state *state, char *readers, size_t *len) {
fprintf (stderr, "error: SCardListReaders failed, rc=%08lx\n", rc);
}
SCardReleaseContext(state->context);
state->context = SCARD_E_INVALID_HANDLE;
state->context = (SCARDCONTEXT)-1;
return YKPIV_PCSC_ERROR;
}
@@ -471,7 +504,7 @@ ykpiv_rc ykpiv_list_readers(ykpiv_state *state, char *readers, size_t *len) {
fprintf (stderr, "error: SCardListReaders failed, rc=%08lx\n", rc);
}
SCardReleaseContext(state->context);
state->context = SCARD_E_INVALID_HANDLE;
state->context = (SCARDCONTEXT)-1;
return YKPIV_PCSC_ERROR;
}
@@ -481,12 +514,13 @@ ykpiv_rc ykpiv_list_readers(ykpiv_state *state, char *readers, size_t *len) {
}
ykpiv_rc _ykpiv_begin_transaction(ykpiv_state *state) {
#if ENABLE_IMPLICIT_TRANSACTIONS
long rc;
ykpiv_rc res;
rc = SCardBeginTransaction(state->card);
if((rc & 0xFFFFFFFF) == SCARD_W_RESET_CARD) {
if((res = reconnect(state)) != YKPIV_OK) {
if((long)((unsigned long)rc & 0xFFFFFFFF) == SCARD_W_RESET_CARD) {
ykpiv_rc res = YKPIV_OK;
if((res = reconnect(state)) != YKPIV_OK) {
return res;
}
rc = SCardBeginTransaction(state->card);
@@ -497,27 +531,33 @@ ykpiv_rc _ykpiv_begin_transaction(ykpiv_state *state) {
}
return YKPIV_PCSC_ERROR;
}
#endif /* ENABLE_IMPLICIT_TRANSACTIONS */
return YKPIV_OK;
}
ykpiv_rc _ykpiv_end_transaction(ykpiv_state *state) {
#if ENABLE_IMPLICIT_TRANSACTIONS
long rc = SCardEndTransaction(state->card, SCARD_LEAVE_CARD);
if(rc != SCARD_S_SUCCESS && state->verbose) {
fprintf(stderr, "error: Failed to end pcsc transaction, rc=%08lx\n", rc);
return YKPIV_PCSC_ERROR;
}
#endif /* ENABLE_IMPLICIT_TRANSACTIONS */
return YKPIV_OK;
}
ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ,
const unsigned char *in_data, long in_len,
unsigned char *out_data, unsigned long *out_len, int *sw) {
ykpiv_rc _ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ,
const unsigned char *in_data,
long in_len,
unsigned char *out_data,
unsigned long *out_len,
int *sw) {
const unsigned char *in_ptr = in_data;
unsigned long max_out = *out_len;
ykpiv_rc res;
*out_len = 0;
if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR;
do {
size_t this_size = 0xff;
unsigned char data[261];
@@ -587,6 +627,19 @@ ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ,
}
}
Cleanup:
return res;
}
ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ,
const unsigned char *in_data, long in_len,
unsigned char *out_data, unsigned long *out_len, int *sw) {
ykpiv_rc res;
if ((res = _ykpiv_begin_transaction(state)) != YKPIV_OK) {
*out_len = 0;
return YKPIV_PCSC_ERROR;
}
res = _ykpiv_transfer_data(state, templ, in_data, in_len, out_data, out_len, sw);
_ykpiv_end_transaction(state);
return res;
}
@@ -838,24 +891,26 @@ static ykpiv_rc _general_authenticate(ykpiv_state *state,
switch(algorithm) {
case YKPIV_ALGO_RSA1024:
key_len = 128;
// fall through
case YKPIV_ALGO_RSA2048:
if(key_len == 0) {
key_len = 256;
key_len = 256;
}
if(in_len != key_len) {
return YKPIV_SIZE_ERROR;
return YKPIV_SIZE_ERROR;
}
break;
case YKPIV_ALGO_ECCP256:
key_len = 32;
// fall through
case YKPIV_ALGO_ECCP384:
if(key_len == 0) {
key_len = 48;
key_len = 48;
}
if(!decipher && in_len > key_len) {
return YKPIV_SIZE_ERROR;
return YKPIV_SIZE_ERROR;
} else if(decipher && in_len != (key_len * 2) + 1) {
return YKPIV_SIZE_ERROR;
return YKPIV_SIZE_ERROR;
}
break;
default:
@@ -932,11 +987,12 @@ ykpiv_rc ykpiv_sign_data(ykpiv_state *state,
if (NULL == state) return YKPIV_GENERIC_ERROR;
if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR;
if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;
/* don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS */
/*if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;*/
res = _general_authenticate(state, raw_in, in_len, sign_out, out_len,
algorithm, key, false);
Cleanup:
/* Cleanup: */
_ykpiv_end_transaction(state);
return res;
}
@@ -949,19 +1005,19 @@ ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *in,
if (NULL == state) return YKPIV_GENERIC_ERROR;
if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR;
if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;
/* don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS */
/*if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;*/
res = _general_authenticate(state, in, in_len, out, out_len,
algorithm, key, true);
Cleanup:
/* Cleanup: */
_ykpiv_end_transaction(state);
return res;
}
ykpiv_rc _ykpiv_get_version(ykpiv_state *state, ykpiv_version_t *p_version) {
static ykpiv_rc _ykpiv_get_version(ykpiv_state *state, ykpiv_version_t *p_version) {
APDU apdu;
unsigned char data[261];
uint32_t recv_len = sizeof(data);
@@ -1022,6 +1078,138 @@ Cleanup:
return res;
}
/* caller must make sure that this is wrapped in a transaction for synchronized operation */
static ykpiv_rc _ykpiv_get_serial(ykpiv_state *state, uint32_t *p_serial, bool f_force) {
ykpiv_rc res = YKPIV_OK;
APDU apdu;
const uint8_t yk_applet[] = { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01 };
unsigned char data[0xff];
uint32_t recv_len = sizeof(data);
int sw;
uint8_t *p_temp = NULL;
if (!state) {
return YKPIV_ARGUMENT_ERROR;
}
if (!f_force && (state->serial != 0)) {
if (p_serial) *p_serial = state->serial;
return YKPIV_OK;
}
if (state->ver.major < 5) {
/* get serial from neo/yk4 devices using the otp applet */
uint8_t temp[0xff];
recv_len = sizeof(temp);
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = YKPIV_INS_SELECT_APPLICATION;
apdu.st.p1 = 0x04;
apdu.st.lc = sizeof(yk_applet);
memcpy(apdu.st.data, yk_applet, sizeof(yk_applet));
if ((res = _send_data(state, &apdu, temp, &recv_len, &sw)) < YKPIV_OK) {
if (state->verbose) {
fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res));
}
goto Cleanup;
}
else if (sw != SW_SUCCESS) {
if (state->verbose) {
fprintf(stderr, "Failed selecting yk application: %04x\n", sw);
}
res = YKPIV_GENERIC_ERROR;
goto Cleanup;
}
recv_len = sizeof(data);
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = 0x01;
apdu.st.p1 = 0x10;
apdu.st.lc = 0x00;
if ((res = _send_data(state, &apdu, data, &recv_len, &sw)) < YKPIV_OK) {
if (state->verbose) {
fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res));
}
goto Cleanup;
}
else if (sw != SW_SUCCESS) {
if (state->verbose) {
fprintf(stderr, "Failed retrieving serial number: %04x\n", sw);
}
res = YKPIV_GENERIC_ERROR;
goto Cleanup;
}
recv_len = sizeof(temp);
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = YKPIV_INS_SELECT_APPLICATION;
apdu.st.p1 = 0x04;
apdu.st.lc = (unsigned char)sizeof(aid);
memcpy(apdu.st.data, aid, sizeof(aid));
if((res = _send_data(state, &apdu, temp, &recv_len, &sw)) < YKPIV_OK) {
if(state->verbose) {
fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res));
}
return res;
}
else if(sw != SW_SUCCESS) {
if(state->verbose) {
fprintf(stderr, "Failed selecting application: %04x\n", sw);
}
return YKPIV_GENERIC_ERROR;
}
}
else {
/* get serial from yk5 and later devices using the f8 command */
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = YKPIV_INS_GET_SERIAL;
if ((res = _send_data(state, &apdu, data, &recv_len, &sw) != YKPIV_OK)) {
if(state->verbose) {
fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res));
}
return res;
}
else if(sw != SW_SUCCESS) {
if(state->verbose) {
fprintf(stderr, "Failed retrieving serial number: %04x\n", sw);
}
return YKPIV_GENERIC_ERROR;
}
}
p_temp = (uint8_t*)(&state->serial);
*p_temp++ = data[3];
*p_temp++ = data[2];
*p_temp++ = data[1];
*p_temp++ = data[0];
if (p_serial) *p_serial = state->serial;
Cleanup:
return res;
}
ykpiv_rc ykpiv_get_serial(ykpiv_state *state, uint32_t *p_serial) {
ykpiv_rc res = YKPIV_OK;
if ((res = _ykpiv_begin_transaction(state)) != YKPIV_OK) return YKPIV_PCSC_ERROR;
if ((res = _ykpiv_ensure_application_selected(state)) != YKPIV_OK) goto Cleanup;
res = _ykpiv_get_serial(state, p_serial, false);
Cleanup:
_ykpiv_end_transaction(state);
return res;
}
static ykpiv_rc _cache_pin(ykpiv_state *state, const char *pin, size_t len) {
#if DISABLE_PIN_CACHE
// Some embedded applications of this library may not want to keep the PIN
@@ -1290,13 +1478,13 @@ ykpiv_rc _ykpiv_fetch_object(ykpiv_state *state, int object_id,
if(sw == SW_SUCCESS) {
size_t outlen;
int offs = _ykpiv_get_length(data + 1, &outlen);
unsigned int offs = _ykpiv_get_length(data + 1, &outlen);
if(offs == 0) {
return YKPIV_SIZE_ERROR;
}
if(outlen + offs + 1 != *len) {
if(state->verbose) {
fprintf(stderr, "Invalid length indicated in object, total objlen is %lu, indicated length is %lu.", *len, outlen);
fprintf(stderr, "Invalid length indicated in object, total objlen is %lu, indicated length is %lu.", *len, (unsigned long)outlen);
}
return YKPIV_SIZE_ERROR;
}
@@ -1322,8 +1510,11 @@ Cleanup:
return res;
}
ykpiv_rc _ykpiv_save_object(ykpiv_state *state, int object_id,
unsigned char *indata, size_t len) {
ykpiv_rc _ykpiv_save_object(
ykpiv_state *state,
int object_id,
unsigned char *indata,
size_t len) {
unsigned char data[CB_BUF_MAX];
unsigned char *dataptr = data;
unsigned char templ[] = {0, YKPIV_INS_PUT_DATA, 0x3f, 0xff};
@@ -1343,7 +1534,7 @@ ykpiv_rc _ykpiv_save_object(ykpiv_state *state, int object_id,
memcpy(dataptr, indata, len);
dataptr += len;
if((res = ykpiv_transfer_data(state, templ, data, (long)(dataptr - data), NULL, &outlen,
if((res = _ykpiv_transfer_data(state, templ, data, (long)(dataptr - data), NULL, &outlen,
&sw)) != YKPIV_OK) {
return res;
}
@@ -1436,10 +1627,13 @@ ykpiv_rc ykpiv_import_private_key(ykpiv_state *state, const unsigned char key, u
}
else if (algorithm == YKPIV_ALGO_ECCP256 || algorithm == YKPIV_ALGO_ECCP384) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
if ((size_t)ec_data_len >= sizeof(key_data)) {
// This can never be true, but check to be explicit.
/* This can never be true, but check to be explicit. */
return YKPIV_SIZE_ERROR;
}
#pragma GCC diagnostic pop
if (algorithm == YKPIV_ALGO_ECCP256)
elem_len = 32;
@@ -1621,11 +1815,45 @@ Cleanup:
return res;
}
static const uint8_t MGMT_AID[] = { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 };
/* deauthenticates the user pin and mgm key */
ykpiv_rc ykpiv_auth_deauthenticate(ykpiv_state *state) {
ykpiv_rc res = YKPIV_OK;
APDU apdu;
unsigned char data[0xff];
uint32_t recv_len = sizeof(data);
int sw;
if (!state) {
return YKPIV_ARGUMENT_ERROR;
}
/* this function does not use ykpiv_transfer_data because it selects a different app */
if ((res = _ykpiv_begin_transaction(state)) < YKPIV_OK) return res;
_ykpiv_util_get_serial(state, NULL, true);
memset(apdu.raw, 0, sizeof(apdu));
apdu.st.ins = YKPIV_INS_SELECT_APPLICATION;
apdu.st.p1 = 0x04;
apdu.st.lc = sizeof(MGMT_AID);
memcpy(apdu.st.data, MGMT_AID, sizeof(MGMT_AID));
if ((res = _send_data(state, &apdu, data, &recv_len, &sw)) < YKPIV_OK) {
if (state->verbose) {
fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res));
}
goto Cleanup;
}
else if (sw != SW_SUCCESS) {
if (state->verbose) {
fprintf(stderr, "Failed selecting mgmt application: %04x\n", sw);
}
res = YKPIV_GENERIC_ERROR;
goto Cleanup;
}
Cleanup:
_ykpiv_end_transaction(state);
return res;
}