diff options
Diffstat (limited to 'g10/iso7816.c')
-rw-r--r-- | g10/iso7816.c | 326 |
1 files changed, 256 insertions, 70 deletions
diff --git a/g10/iso7816.c b/g10/iso7816.c index 8c6af044f..f1ee0daef 100644 --- a/g10/iso7816.c +++ b/g10/iso7816.c @@ -1,5 +1,5 @@ /* iso7816.c - ISO 7816 commands - * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -45,9 +45,9 @@ #define CMD_SELECT_FILE 0xA4 -#define CMD_VERIFY 0x20 -#define CMD_CHANGE_REFERENCE_DATA 0x24 -#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_VERIFY ISO7816_VERIFY +#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA +#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 @@ -66,7 +66,10 @@ map_sw (int sw) switch (sw) { case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; + case SW_TERM_STATE: ec = GPG_ERR_CARD; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; @@ -93,6 +96,7 @@ map_sw (int sw) case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break; case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break; case SW_HOST_ABORTED: ec = GPG_ERR_CANCELED; break; + case SW_HOST_NO_KEYPAD: ec = GPG_ERR_NOT_SUPPORTED; break; default: if ((sw & 0x010000)) @@ -122,12 +126,15 @@ iso7816_map_sw (int sw) requested application ID. The function can't be used to enumerate AIDs and won't return the AID on success. The return value is 0 for okay or a GPG error code. Note that ISO error codes are - internally mapped. */ + internally mapped. Bit 0 of FLAGS should be set if the card does + not understand P2=0xC0. */ gpg_error_t -iso7816_select_application (int slot, const char *aid, size_t aidlen) +iso7816_select_application (int slot, const char *aid, size_t aidlen, + unsigned int flags) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0 :0x0c, aidlen, aid); return map_sw (sw); } @@ -152,7 +159,7 @@ iso7816_select_file (int slot, int tag, int is_dir, { p0 = (tag == 0x3F00)? 0: is_dir? 1:2; p1 = 0x0c; /* No FC return. */ - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, 2, (char*)tagbuf ); return map_sw (sw); } @@ -188,7 +195,7 @@ iso7816_select_path (int slot, const unsigned short *path, size_t pathlen, p0 = 0x08; p1 = 0x0c; /* No FC return. */ - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, buflen, (char*)buffer ); return map_sw (sw); } @@ -206,7 +213,7 @@ iso7816_list_directory (int slot, int list_dirs, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL, + sw = apdu_send (slot, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL, result, resultlen); if (sw != SW_SUCCESS) { @@ -219,27 +226,101 @@ iso7816_list_directory (int slot, int list_dirs, } +/* This funcion sends an already formatted APDU to the card. With + HANDLE_MORE set to true a MORE DATA status will be handled + internally. The return value is a gpg error code (i.e. a mapped + status word). This is basically the same as apdu_send_direct but + it maps the status word and does not return it in the result + buffer. */ +gpg_error_t +iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, + int handle_more, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, + result, resultlen); + if (!sw) + { + if (*resultlen < 2) + sw = SW_HOST_GENERAL_ERROR; + else + { + sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1]; + (*resultlen)--; + (*resultlen)--; + } + } + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + return map_sw (sw); +} + + +/* Check whether the reader supports the ISO command code COMMAND on + the keypad. Returns 0 on success. */ +gpg_error_t +iso7816_check_keypad (int slot, int command, iso7816_pininfo_t *pininfo) +{ + int sw; + + sw = apdu_check_keypad (slot, command, + pininfo->mode, pininfo->minlen, pininfo->maxlen, + pininfo->padlen); + return iso7816_map_sw (sw); +} + /* Perform a VERIFY command on SLOT using the card holder verification - vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ + vector CHVNO with a CHV of lenght CHVLEN. With PININFO non-NULL + the keypad of the reader will be used. Returns 0 on success. */ gpg_error_t -iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +iso7816_verify_kp (int slot, int chvno, const char *chv, size_t chvlen, + iso7816_pininfo_t *pininfo) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); return map_sw (sw); } +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ +gpg_error_t +iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +{ + return iso7816_verify_kp (slot, chvno, chv, chvlen, NULL); +} + /* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN 0), a "change reference data" is done, otherwise an "exchange reference data". The new reference data is expected in NEWCHV of - length NEWCHVLEN. */ + length NEWCHVLEN. With PININFO non-NULL the keypad of the reader + will be used. */ gpg_error_t -iso7816_change_reference_data (int slot, int chvno, - const char *oldchv, size_t oldchvlen, - const char *newchv, size_t newchvlen) +iso7816_change_reference_data_kp (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; char *buf; @@ -256,45 +337,110 @@ iso7816_change_reference_data (int slot, int chvno, memcpy (buf, oldchv, oldchvlen); memcpy (buf+oldchvlen, newchv, newchvlen); - sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, - oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); xfree (buf); return map_sw (sw); } +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ gpg_error_t -iso7816_reset_retry_counter (int slot, int chvno, - const char *newchv, size_t newchvlen) +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + return iso7816_change_reference_data_kp (slot, chvno, oldchv, oldchvlen, + newchv, newchvlen, NULL); +} + + +gpg_error_t +iso7816_reset_retry_counter_kp (int slot, int chvno, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; if (!newchv || !newchvlen ) return gpg_error (GPG_ERR_INV_VALUE); - sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, - 2, chvno, newchvlen, newchv); + /* FIXME: The keypad mode has not yet been tested. */ + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); + return map_sw (sw); +} + + +gpg_error_t +iso7816_reset_retry_counter_with_rc (int slot, int chvno, + const char *data, size_t datalen) +{ + int sw; + + if (!data || !datalen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, + 0, chvno, datalen, data); return map_sw (sw); } +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + return iso7816_reset_retry_counter_kp (slot, chvno, newchv, newchvlen, NULL); +} + + + /* Perform a GET DATA command requesting TAG and storing the result in a newly allocated buffer at the address passed by RESULT. Return the length of this data at the address of RESULTLEN. */ gpg_error_t -iso7816_get_data (int slot, int tag, +iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen) { int sw; + int le; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_GET_DATA, - ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, - result, resultlen); + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -309,14 +455,29 @@ iso7816_get_data (int slot, int tag, /* Perform a PUT DATA command on card in SLOT. Write DATA of length - DATALEN to TAG. */ + DATALEN to TAG. EXTENDED_MODE controls whether extended length + headers or command chaining is used instead of single length + bytes. */ gpg_error_t -iso7816_put_data (int slot, int tag, +iso7816_put_data (int slot, int extended_mode, int tag, const unsigned char *data, size_t datalen) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, (const char*)data); + return map_sw (sw); +} + +/* Same as iso7816_put_data but uses an odd instruction byte. */ +gpg_error_t +iso7816_put_data_odd (int slot, int extended_mode, int tag, + const unsigned char *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1, ((tag >> 8) & 0xff), (tag & 0xff), datalen, (const char*)data); return map_sw (sw); @@ -335,7 +496,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 ) return gpg_error (GPG_ERR_INV_VALUE); - sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, + sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2, data? datalen : -1, (const char*)data); return map_sw (sw); } @@ -344,9 +505,10 @@ iso7816_manage_security_env (int slot, int p1, int p2, /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On success 0 is returned and the data is availavle in a newly allocated buffer stored at RESULT with its length stored at - RESULTLEN. */ + RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t -iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, +iso7816_compute_ds (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { int sw; @@ -356,8 +518,16 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data, - result, resultlen); + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x9E, 0x9A, + datalen, (const char*)data, + le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -377,7 +547,8 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, and the plaintext is available in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. */ gpg_error_t -iso7816_decipher (int slot, const unsigned char *data, size_t datalen, +iso7816_decipher (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int padind, unsigned char **result, size_t *resultlen) { int sw; @@ -394,17 +565,19 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, buf = xtrymalloc (datalen + 1); if (!buf) return gpg_error (gpg_err_code_from_errno (errno)); - + *buf = padind; /* Padding indicator. */ memcpy (buf+1, data, datalen); - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + sw = apdu_send (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, datalen+1, (char*)buf, result, resultlen); xfree (buf); } else { - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + sw = apdu_send (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, datalen, (const char *)data, result, resultlen); } @@ -421,9 +594,11 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, } +/* For LE see do_generate_keypair. */ gpg_error_t -iso7816_internal_authenticate (int slot, +iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen) { int sw; @@ -433,8 +608,16 @@ iso7816_internal_authenticate (int slot, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, - datalen, (const char*)data, result, resultlen); + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, (const char*)data, + le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -448,10 +631,15 @@ iso7816_internal_authenticate (int slot, } +/* LE is the expected return length. This is usually 0 except if + extended length mode is used and more than 256 byte will be + returned. In that case a value of -1 uses a large default + (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t -do_generate_keypair (int slot, int readonly, - const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen) +do_generate_keypair (int slot, int extended_mode, int readonly, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) { int sw; @@ -460,8 +648,11 @@ do_generate_keypair (int slot, int readonly, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, - datalen, (const char*)data, result, resultlen); + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, + datalen, (const char*)data, + le >= 0 && le < 256? 256:le, + result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -476,20 +667,24 @@ do_generate_keypair (int slot, int readonly, gpg_error_t -iso7816_generate_keypair (int slot, +iso7816_generate_keypair (int slot, int extended_mode, const unsigned char *data, size_t datalen, + int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, 0, data, datalen, result, resultlen); + return do_generate_keypair (slot, extended_mode, 0, + data, datalen, le, result, resultlen); } gpg_error_t -iso7816_read_public_key (int slot, - const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen) +iso7816_read_public_key (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, 1, data, datalen, result, resultlen); + return do_generate_keypair (slot, extended_mode, 1, + data, datalen, le, result, resultlen); } @@ -508,8 +703,8 @@ iso7816_get_challenge (int slot, int length, unsigned char *buffer) { result = NULL; n = length > 254? 254 : length; - sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, - n, + sw = apdu_send_le (slot, 0, + 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, n, &result, &resultlen); if (sw != SW_SUCCESS) { @@ -557,21 +752,14 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, { buffer = NULL; bufferlen = 0; - /* Note, that we to set N to 254 due to problems either with the - ccid driver or some TCOS cards. It actually should be 0 - which is the official ISO value to read a variable length - object. */ - if (read_all || nmax > 254) - n = 254; - else - n = nmax; - sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + n = read_all? 0 : nmax; + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); if ( SW_EXACT_LENGTH_P(sw) ) { n = (sw & 0x00ff); - sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); } @@ -598,7 +786,7 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen); if (!p) { - gpg_error_t err = gpg_error_from_errno (errno); + gpg_error_t err = gpg_error_from_syserror (); xfree (buffer); xfree (*result); *result = NULL; @@ -658,13 +846,11 @@ iso7816_read_record (int slot, int recno, int reccount, int short_ef, buffer = NULL; bufferlen = 0; - /* Fixme: Either the ccid driver or the TCOS cards have problems - with an Le of 0. */ - sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD, + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD, recno, short_ef? short_ef : 0x04, -1, NULL, - 254, &buffer, &bufferlen); + 0, &buffer, &bufferlen); if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) { |