@@ -33,18 +33,25 @@ # include #endif #ifdef HAVE_STDIO_H # include #endif +#define HAVE_ERRNO_H 1 +#ifdef HAVE_ERRNO_H +# include +#endif #ifdef HAVE_ZLIB_H # ifdef HAVE_LIBZ # include # endif #else # ifdef HAVE_LIBZ # undef HAVE_LIBZ # endif +#endif +#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST +# include #endif #define CK_PTR * #define CK_DEFINE_FUNCTION(returnType, name) returnType name #define CK_DECLARE_FUNCTION(returnType, name) returnType name @@ -58,18 +65,10 @@ #include "pkcs11n.h" #include "asn1-x509.h" #include "sha1.h" #include "md5.h" -/* - * Include these source files in this translation unit so that we can bind to - * functions and not include any symbols in the output shared object. - */ -#include "asn1-x509.c" -#include "sha1.c" -#include "md5.c" - #ifndef CACKEY_CRYPTOKI_VERSION_CODE # define CACKEY_CRYPTOKI_VERSION_CODE 0x021e00 #endif /* GSC-IS v2.1 Definitions */ @@ -151,17 +150,39 @@ #define GSCIS_TAG_EXPIRE_DATE 0x53 #define GSCIS_TAG_CARD_TYPE 0x54 #define GSCIS_TAG_SECURITY_CODE 0x57 #define GSCIS_TAG_CARDID_AID 0x58 +/*** PIV Codes ***/ +#define NISTSP800_73_3_INSTR_GET_DATA 0xCB +#define NISTSP800_73_3_INSTR_GENAUTH 0x87 + /*** PKI Information - EF 7000 ***/ #define GSCIS_TAG_CERTIFICATE 0x70 #define GSCIS_TAG_CERT_ISSUE_DATE 0x71 #define GSCIS_TAG_CERT_EXPIRE_DATE 0x72 /** Applet IDs **/ #define GSCIS_AID_CCC 0xA0, 0x00, 0x00, 0x01, 0x16, 0xDB, 0x00 +#define NISTSP800_73_3_PIV_AID 0xA0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00 + +/* PIV IDs */ +/** Key Identifiers (NIST SP 800-78-3, Table 6-1 **/ +#define NISTSP800_78_3_KEY_PIVAUTH 0x9A +#define NISTSP800_78_3_KEY_SIGNATURE 0x9C +#define NISTSP800_78_3_KEY_KEYMGT 0x9D +#define NISTSP800_78_3_KEY_CARDAUTH 0x9E + +/** Algorithm Identifiers (NIST SP 800-78-3, Table 6-2 **/ +#define NISTSP800_78_3_ALGO_RSA1024 0x06 +#define NISTSP800_78_3_ALGO_RSA2048 0x07 + +/** Object Identifiers (NIST SP 800-73-3 Part 1, Table 2) **/ +#define NISTSP800_73_3_OID_PIVAUTH 0x5F, 0xC1, 0x05 +#define NISTSP800_73_3_OID_SIGNATURE 0x5F, 0xC1, 0x0A +#define NISTSP800_73_3_OID_KEYMGT 0x5F, 0xC1, 0x0B +#define NISTSP800_73_3_OID_CARDAUTH 0x5F, 0xC1, 0x01 /* Maximum size of data portion of APDUs */ /** Do not set this above 250 **/ #define CACKEY_APDU_MTU 250 @@ -172,18 +193,55 @@ #ifdef CACKEY_DEBUG # ifdef HAVE_TIME_H # include static time_t cackey_debug_start_time = 0; -# define CACKEY_DEBUG_PRINTTIME { if (cackey_debug_start_time == 0) { cackey_debug_start_time = time(NULL); }; fprintf(cackey_debug_fd(), "[%lu]: ", (unsigned long) (time(NULL) - cackey_debug_start_time)); } +static unsigned long CACKEY_DEBUG_GETTIME(void) { + if (cackey_debug_start_time == 0) { + cackey_debug_start_time = time(NULL); + } + + return(time(NULL) - cackey_debug_start_time); +} # else -# define CACKEY_DEBUG_PRINTTIME /**/ +static unsigned long CACKEY_DEBUG_GETTIME(void) { + return(0); +} # endif -# define CACKEY_DEBUG_PRINTF(x...) { CACKEY_DEBUG_PRINTTIME; fprintf(cackey_debug_fd(), "%s():%i: ", __func__, __LINE__); fprintf(cackey_debug_fd(), x); fprintf(cackey_debug_fd(), "\n"); fflush(cackey_debug_fd()); } -# define CACKEY_DEBUG_PRINTBUF(f, x, y) { unsigned char *TMPBUF; unsigned long idx; TMPBUF = (unsigned char *) (x); CACKEY_DEBUG_PRINTTIME; fprintf(cackey_debug_fd(), "%s():%i: %s (%s/%lu = {%02x", __func__, __LINE__, f, #x, (unsigned long) (y), TMPBUF[0]); for (idx = 1; idx < (y); idx++) { fprintf(cackey_debug_fd(), ", %02x", TMPBUF[idx]); }; fprintf(cackey_debug_fd(), "})\n"); fflush(cackey_debug_fd()); } -# define CACKEY_DEBUG_PERROR(x) { fprintf(cackey_debug_fd(), "%s():%i: ", __func__, __LINE__); CACKEY_DEBUG_PRINTTIME; perror(x); fflush(cackey_debug_fd()); } +# define CACKEY_DEBUG_PRINTF(x...) { \ + static char buf_user[4096] = {0}; \ + snprintf(buf_user, sizeof(buf_user), x); \ + buf_user[sizeof(buf_user) - 1] = '\0'; \ + fprintf(cackey_debug_fd(), "[%lu]: %s():%i: %s\n", CACKEY_DEBUG_GETTIME(), __func__, __LINE__, buf_user); \ + fflush(cackey_debug_fd()); \ +} +# define CACKEY_DEBUG_PRINTBUF(f, x, y) { \ + static char buf_user[4096] = {0}, *buf_user_p; \ + unsigned long buf_user_size; \ + unsigned char *TMPBUF; \ + unsigned long idx; \ + int snprintf_ret; \ + TMPBUF = (unsigned char *) (x); \ + buf_user[0] = 0; \ + buf_user_p = buf_user; \ + buf_user_size = sizeof(buf_user); \ + for (idx = 1; idx < (y); idx++) { \ + if (buf_user_size <= 0) { \ + break; \ + }; \ + snprintf_ret = snprintf(buf_user_p, buf_user_size, ", %02x", TMPBUF[idx]); \ + if (snprintf_ret <= 0) { \ + break; \ + }; \ + buf_user_p += snprintf_ret; \ + buf_user_size -= snprintf_ret; \ + }; \ + buf_user[sizeof(buf_user) - 1] = '\0'; \ + fprintf(cackey_debug_fd(), "[%lu]: %s():%i: %s (%s/%lu = {%02x%s})\n", CACKEY_DEBUG_GETTIME(), __func__, __LINE__, f, #x, (unsigned long) (y), TMPBUF[0], buf_user); \ + fflush(cackey_debug_fd()); \ +} # define free(x) { CACKEY_DEBUG_PRINTF("FREE(%p) (%s)", (void *) x, #x); free(x); } static FILE *cackey_debug_fd(void) { static FILE *fd = NULL; char *logfile; @@ -207,10 +265,16 @@ logfile = getenv("CACKEY_DEBUG_LOGFILE"); } else { logfile++; } } + +#ifdef CACKEY_DEBUG_LOGFILE + if (logfile == NULL) { + logfile = CACKEY_DEBUG_LOGFILE; + } +#endif if (logfile != NULL) { CACKEY_DEBUG_PRINTF("Found log file: %s", logfile); fd = fopen(logfile, "a"); @@ -232,14 +296,11 @@ static void *CACKEY_DEBUG_FUNC_MALLOC(size_t size, const char *func, int line) { void *retval; retval = malloc(size); - CACKEY_DEBUG_PRINTTIME; - fprintf(cackey_debug_fd(), "%s():%i: ", func, line); - fprintf(cackey_debug_fd(), "MALLOC() = %p", retval); - fprintf(cackey_debug_fd(), "\n"); + fprintf(cackey_debug_fd(), "[%lu]: %s():%i: MALLOC() = %p\n", CACKEY_DEBUG_GETTIME(), func, line, retval); fflush(cackey_debug_fd()); return(retval); } @@ -247,14 +308,11 @@ void *retval; retval = realloc(ptr, size); if (retval != ptr) { - CACKEY_DEBUG_PRINTTIME; - fprintf(cackey_debug_fd(), "%s():%i: ", func, line); - fprintf(cackey_debug_fd(), "REALLOC(%p) = %p", ptr, retval); - fprintf(cackey_debug_fd(), "\n"); + fprintf(cackey_debug_fd(), "[%lu]: %s():%i: REALLOC(%p) = %p\n", CACKEY_DEBUG_GETTIME(), func, line, ptr, retval); fflush(cackey_debug_fd()); } if (retval == NULL) { CACKEY_DEBUG_PRINTF(" *** ERROR *** realloc returned NULL (size = %lu)", (unsigned long) size); @@ -266,14 +324,11 @@ static char *CACKEY_DEBUG_FUNC_STRDUP(const char *ptr, const char *func, int line) { char *retval; retval = strdup(ptr); - CACKEY_DEBUG_PRINTTIME; - fprintf(cackey_debug_fd(), "%s():%i: ", func, line); - fprintf(cackey_debug_fd(), "STRDUP_MALLOC() = %p", retval); - fprintf(cackey_debug_fd(), "\n"); + fprintf(cackey_debug_fd(), "[%lu]: %s():%i: STRDUP_MALLOC() = %p\n", CACKEY_DEBUG_GETTIME(), func, line, retval); fflush(cackey_debug_fd()); return(retval); } @@ -651,26 +706,50 @@ # endif # define strdup(x) CACKEY_DEBUG_FUNC_STRDUP(x, __func__, __LINE__) #else # define CACKEY_DEBUG_PRINTF(x...) /**/ # define CACKEY_DEBUG_PRINTBUF(f, x, y) /**/ -# define CACKEY_DEBUG_PERROR(x) /**/ # define CACKEY_DEBUG_FUNC_TAG_TO_STR(x) "DEBUG_DISABLED" # define CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(x) "DEBUG_DISABLED" # define CACKEY_DEBUG_FUNC_OBJID_TO_STR(x) "DEBUG_DISABLED" # define CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(x) "DEBUG_DISABLED" # define CACKEY_DEBUG_FUNC_ATTRIBUTE_TO_STR(x) "DEBUG_DISABLED" #endif +/* + * Include these source files in this translation unit so that we can bind to + * functions and not include any symbols in the output shared object. + */ +#include "asn1-x509.c" +#include "sha1.c" +#include "md5.c" + +typedef enum { + CACKEY_ID_TYPE_CAC, + CACKEY_ID_TYPE_PIV, + CACKEY_ID_TYPE_CERT_ONLY +} cackey_pcsc_id_type; + struct cackey_pcsc_identity { - unsigned char applet[7]; - uint16_t file; + cackey_pcsc_id_type id_type; size_t certificate_len; unsigned char *certificate; ssize_t keysize; + + union { + struct { + unsigned char applet[7]; + uint16_t file; + } cac; + + struct { + unsigned char key_id; + char label[32]; + } piv; + } card; }; struct cackey_identity { struct cackey_pcsc_identity *pcsc_identity; @@ -711,10 +790,11 @@ struct cackey_identity *decrypt_identity; }; struct cackey_slot { int active; + int internal; char *pcsc_reader; int pcsc_card_connected; SCARDHANDLE pcsc_card; @@ -727,10 +807,13 @@ CK_FLAGS token_flags; unsigned char *label; DWORD protocol; + + unsigned int cached_certs_count; + struct cackey_pcsc_identity *cached_certs; }; typedef enum { CACKEY_TLV_APP_GENERIC = 0x01, CACKEY_TLV_APP_SKI = 0x02, @@ -796,10 +879,15 @@ /** Extra certificates to include in token **/ struct cackey_pcsc_identity extra_certs[] = { #include "cackey_builtin_certs.h" }; +/* Protected Authentication Path command */ +#define CACKEY_PIN_COMMAND_DEFAULT_XSTR(str) CACKEY_PIN_COMMAND_DEFAULT_STR(str) +#define CACKEY_PIN_COMMAND_DEFAULT_STR(str) #str +static char *cackey_pin_command = NULL; + /* PCSC Global Handles */ static LPSCARDCONTEXT cackey_pcsc_handle = NULL; static unsigned long cackey_getversion(void) { static unsigned long retval = 255; @@ -859,10 +947,15 @@ uint32_t idx; CACKEY_DEBUG_PRINTF("Called."); for (idx = 0; idx < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); idx++) { + if (cackey_slots[idx].internal) { + /* Skip internal slots */ + continue; + } + if (cackey_slots[idx].pcsc_card_connected) { CACKEY_DEBUG_PRINTF("SCardDisconnect(%lu) called", (unsigned long) idx); SCardDisconnect(cackey_slots[idx].pcsc_card, SCARD_LEAVE_CARD); } @@ -1034,10 +1127,14 @@ } slot->slot_reset = 1; slot->pcsc_card_connected = 0; slot->token_flags = 0; + + if (cackey_pin_command == NULL) { + login_required = 0; + } if (login_required == -1) { if (cackey_login_required(slot) != CACKEY_PCSC_S_OK) { login_required = 1; } else { @@ -1054,22 +1151,19 @@ return; } /* * SYNPOSIS - * LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol, LPDWORD selected_protocol); + * LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol); * * ARGUMENTS * cackey_slot *slot * Slot to send commands to * * DWORD default_protocol * Protocol to attempt first * - * LPDWORD selected_protocol - * [OUT] Protocol selected - * * RETURN VALUE * The return value from SCardReconnect() * * NOTES * This function is a wrapper around SCardReconnect() @@ -1078,24 +1172,31 @@ * dwPreferredProtocols of "default_protocol". If that call returns * SCARD_E_PROTO_MISMATCH try again with a protocol of T=0, and failing * that T=1. * */ -static LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol, LPDWORD selected_protocol) { +static LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol) { + DWORD selected_protocol; LONG scard_conn_ret; - scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, default_protocol, SCARD_RESET_CARD, selected_protocol); + selected_protocol = 0; + + scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, default_protocol, SCARD_RESET_CARD, &selected_protocol); if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) { CACKEY_DEBUG_PRINTF("SCardReconnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=0") - scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, SCARD_RESET_CARD, selected_protocol); + scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, SCARD_RESET_CARD, &selected_protocol); if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) { CACKEY_DEBUG_PRINTF("SCardReconnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=1") - scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, SCARD_RESET_CARD, selected_protocol); + scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, SCARD_RESET_CARD, &selected_protocol); } } + + if (scard_conn_ret == SCARD_S_SUCCESS) { + slot->protocol = selected_protocol; + } return(scard_conn_ret); } /* @@ -1134,11 +1235,11 @@ return(CACKEY_PCSC_E_GENERIC); } /* Connect to reader, if needed */ if (!slot->pcsc_card_connected) { - CACKEY_DEBUG_PRINTF("SCardConnect(%s) called", slot->pcsc_reader); + CACKEY_DEBUG_PRINTF("SCardConnect(%s) called for slot %p", slot->pcsc_reader, slot); scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol); if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) { CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=0") scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, &slot->pcsc_card, &protocol); @@ -1162,11 +1263,11 @@ CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=1") scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol); } } - scard_conn_ret = cackey_reconnect_card(slot, protocol, &protocol); + scard_conn_ret = cackey_reconnect_card(slot, protocol); } if (scard_conn_ret != SCARD_S_SUCCESS) { CACKEY_DEBUG_PRINTF("Connection to card failed, returning in failure (SCardConnect() = %s/%li)", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_conn_ret), (long) scard_conn_ret); @@ -1176,10 +1277,12 @@ slot->pcsc_card_connected = 1; slot->transaction_depth = 0; slot->transaction_need_hw_lock = 0; slot->protocol = protocol; } + + CACKEY_DEBUG_PRINTF("Returning in success"); return(CACKEY_PCSC_S_OK); } /* @@ -1363,15 +1466,14 @@ * It will connect to the card in the reader attached to the slot * specified. It will reconnect to the card if the connection * goes away. * */ -static cackey_ret cackey_send_apdu(struct cackey_slot *slot, unsigned char class, unsigned char instruction, unsigned char p1, unsigned char p2, unsigned char lc, unsigned char *data, unsigned char le, uint16_t *respcode, unsigned char *respdata, size_t *respdata_len) { +static cackey_ret cackey_send_apdu(struct cackey_slot *slot, unsigned char class, unsigned char instruction, unsigned char p1, unsigned char p2, unsigned int lc, unsigned char *data, unsigned int le, uint16_t *respcode, unsigned char *respdata, size_t *respdata_len) { uint8_t major_rc, minor_rc; size_t bytes_to_copy, tmp_respdata_len; LPCSCARD_IO_REQUEST pioSendPci; - DWORD protocol; DWORD xmit_len, recv_len; LONG scard_xmit_ret, scard_reconn_ret; BYTE xmit_buf[1024], recv_buf[1024]; int pcsc_connect_ret, pcsc_getresp_ret; int idx; @@ -1392,14 +1494,18 @@ } /* Determine which protocol to send using */ switch (slot->protocol) { case SCARD_PROTOCOL_T0: + CACKEY_DEBUG_PRINTF("Protocol to send datagram is T=0"); + pioSendPci = SCARD_PCI_T0; break; case SCARD_PROTOCOL_T1: + CACKEY_DEBUG_PRINTF("Protocol to send datagram is T=1"); + pioSendPci = SCARD_PCI_T1; break; default: CACKEY_DEBUG_PRINTF("Invalid protocol found, aborting."); @@ -1412,24 +1518,42 @@ xmit_buf[xmit_len++] = class; xmit_buf[xmit_len++] = instruction; xmit_buf[xmit_len++] = p1; xmit_buf[xmit_len++] = p2; if (data) { - xmit_buf[xmit_len++] = lc; + if (lc > 255) { + CACKEY_DEBUG_PRINTF("CAUTION! Using an Lc greater than 255 is untested. Lc = %u", lc); + + xmit_buf[xmit_len++] = 0x82; /* XXX UNTESTED */ + xmit_buf[xmit_len++] = (lc & 0xff00) >> 8; + xmit_buf[xmit_len++] = lc & 0xff; + } else { + xmit_buf[xmit_len++] = lc; + } for (idx = 0; idx < lc; idx++) { xmit_buf[xmit_len++] = data[idx]; } } if (le != 0x00) { - xmit_buf[xmit_len++] = le; + if (le > 256) { + CACKEY_DEBUG_PRINTF("CAUTION! Using an Le greater than 256 is untested. Le = %u", le); + + xmit_buf[xmit_len++] = 0x82; /* XXX UNTESTED */ + xmit_buf[xmit_len++] = (le & 0xff00) >> 8; + xmit_buf[xmit_len++] = le & 0xff; + } else if (le == 256) { + xmit_buf[xmit_len++] = 0x00; + } else { + xmit_buf[xmit_len++] = le; + } } /* Begin Smartcard Transaction */ cackey_begin_transaction(slot); - if (class == GSCIS_CLASS_ISO7816 && instruction == GSCIS_INSTR_VERIFY && p1 == 0x00 && p2 == 0x00) { + if (class == GSCIS_CLASS_ISO7816 && instruction == GSCIS_INSTR_VERIFY && p1 == 0x00) { CACKEY_DEBUG_PRINTF("Sending APDU: <>"); } else { CACKEY_DEBUG_PRINTBUF("Sending APDU:", xmit_buf, xmit_len); } @@ -1440,10 +1564,12 @@ CACKEY_DEBUG_PRINTF("Failed to send APDU to card (SCardTransmit() = %s/%lx), will ask calling function to retry (not resetting card)...", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret); /* Begin Smartcard Transaction */ cackey_end_transaction(slot); + cackey_reconnect_card(slot, slot->protocol); + return(CACKEY_PCSC_E_RETRY); } if (scard_xmit_ret != SCARD_S_SUCCESS) { CACKEY_DEBUG_PRINTF("Failed to send APDU to card (SCardTransmit() = %s/%lx)", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret); @@ -1452,15 +1578,13 @@ cackey_mark_slot_reset(slot); if (scard_xmit_ret == SCARD_W_RESET_CARD) { CACKEY_DEBUG_PRINTF("Reset required, please hold..."); - scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &protocol); + scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1); if (scard_reconn_ret == SCARD_S_SUCCESS) { - /* Update protocol */ - slot->protocol = protocol; switch (slot->protocol) { case SCARD_PROTOCOL_T0: pioSendPci = SCARD_PCI_T0; break; @@ -1619,10 +1743,187 @@ CACKEY_DEBUG_PRINTF("APDU Returned an error, returning in failure"); return(CACKEY_PCSC_E_GENERIC); } + +static unsigned char *cackey_read_bertlv_tag(unsigned char *buffer, size_t *buffer_len_p, unsigned char tag, unsigned char *outbuffer, size_t *outbuffer_len_p) { + unsigned char *buffer_p; + size_t outbuffer_len, buffer_len; + size_t size; + int idx; + + CACKEY_DEBUG_PRINTF("Called."); + + if (buffer_len_p == NULL) { + CACKEY_DEBUG_PRINTF("buffer_len_p is NULL. Returning in failure."); + + return(NULL); + } + + if (outbuffer_len_p == NULL) { + CACKEY_DEBUG_PRINTF("outbuffer_len_p is NULL. Returning in failure."); + + return(NULL); + } + + buffer_len = *outbuffer_len_p; + outbuffer_len = *outbuffer_len_p; + + if (buffer_len < 2) { + CACKEY_DEBUG_PRINTF("buffer_len is less than 2, so we can't read any tag. Returning in failure."); + + return(NULL); + } + + buffer_p = buffer; + if (buffer_p[0] != tag) { + CACKEY_DEBUG_PRINTF("Tag found was not tag expected. Tag = %02x, Expected = %02x. Returning in failure.", (unsigned int) buffer_p[0], tag); + + return(NULL); + } + + buffer_p++; + buffer_len--; + + if ((buffer_p[0] & 0x80) == 0x80) { + size = 0; + idx = (buffer_p[0] & 0x7f); + + if (idx > buffer_len) { + CACKEY_DEBUG_PRINTF("Malformed BER value -- not enough bytes available to read length (idx = %i, buffer_len = %lu)", idx, (unsigned long) buffer_len); + + return(NULL); + } + + for (; idx > 0; idx--) { + buffer_p++; + buffer_len--; + + size <<= 8; + size |= buffer_p[0]; + } + } else { + size = buffer_p[0]; + } + + buffer_p++; + buffer_len--; + + if (size > outbuffer_len) { + CACKEY_DEBUG_PRINTF("Unable to copy value buffer to outbuffer, not enough room. Value buffer length = %lu, out buffer length = %lu", (unsigned long) size, (unsigned long) outbuffer_len); + + return(NULL); + } + + *outbuffer_len_p = size; + if (outbuffer) { + memcpy(outbuffer, buffer_p, size); + buffer_p += size; + buffer_len -= size; + + *buffer_len_p = buffer_len; + + CACKEY_DEBUG_PRINTBUF("BER-TLV results:", outbuffer, size); + } else { + memmove(buffer, buffer_p, size); + buffer_p = buffer; + + CACKEY_DEBUG_PRINTBUF("BER-TLV results:", buffer, size); + } + + CACKEY_DEBUG_PRINTF("Returning in success. Size of contents for tag %02x is %lu", (unsigned int) tag, (unsigned long) size); + + return(buffer_p); +} + +/* + * SYNPOSIS + * ssize_t cackey_get_data(struct cackey_slot *slot, unsigned char *buffer, size_t buffer_len, unsigned char oid[3]); + * + * ARGUMENTS + * struct cackey_slot *slot + * Slot to send commands to + * + * unsigned char *buffer + * [OUT] Buffer + * + * size_t buffer_len + * Number of bytes to attempt to read + * + * unsigned char oid[3] + * 3-byte OID to read + * + * + * RETURN VALUE + * This function returns the number of bytes actually read, or -1 on error. + * + * NOTES + * None + * + */ +static ssize_t cackey_get_data(struct cackey_slot *slot, unsigned char *buffer, size_t buffer_len, unsigned char oid[3]) { + unsigned char cmd[] = {0x5C, 0x03, 0x00, 0x00, 0x00}; + unsigned char *buffer_p; + size_t init_buffer_len, size; + uint16_t respcode; + int send_ret; + + CACKEY_DEBUG_PRINTF("Called."); + + init_buffer_len = buffer_len; + + cmd[2] = oid[0]; + cmd[3] = oid[1]; + cmd[4] = oid[2]; + + /* 256 to indicate the largest message size -- not clear if this will work with all messages */ + send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, NISTSP800_73_3_INSTR_GET_DATA, 0x3F, 0xFF, sizeof(cmd), cmd, 256, &respcode, buffer, &buffer_len); + + if (send_ret == CACKEY_PCSC_E_RETRY) { + CACKEY_DEBUG_PRINTF("ADPU Sending failed, retrying read buffer"); + + return(cackey_get_data(slot, buffer, init_buffer_len, oid)); + } + + if (send_ret != CACKEY_PCSC_S_OK) { + CACKEY_DEBUG_PRINTF("cackey_send_apdu() failed, returning in failure"); + + return(-1); + } + +#ifdef CACKEY_PARANOID +# ifdef _POSIX_SSIZE_MAX + if (buffer_len > _POSIX_SSIZE_MAX) { + CACKEY_DEBUG_PRINTF("Read bytes (buffer_len) exceeds maximum value, returning in failure. (max = %li, buffer_len = %lu)", (long) _POSIX_SSIZE_MAX, (unsigned long) buffer_len); + + return(-1); + } +# endif +#endif + + if (buffer_len < 2) { + CACKEY_DEBUG_PRINTF("APDU GET DATA returned %lu bytes, which is too short for a BER-TLV response", (unsigned long) buffer_len); + + return(-1); + } + + size = buffer_len; + buffer_p = cackey_read_bertlv_tag(buffer, &buffer_len, 0x53, NULL, &size); + + if (buffer_p == NULL) { + CACKEY_DEBUG_PRINTF("Tag decoding failed, returning in error."); + + return(-1); + } + + CACKEY_DEBUG_PRINTBUF("GET DATA result", buffer, size); + + CACKEY_DEBUG_PRINTF("Returning in success, read %lu bytes", (unsigned long) size); + + return(size); +} /* * SYNPOSIS * ssize_t cackey_read_buffer(struct cackey_slot *slot, unsigned char *buffer, size_t count, unsigned char t_or_v, size_t initial_offset); * @@ -2096,10 +2397,46 @@ free(start); } return; } + +static struct cackey_pcsc_identity *cackey_copy_certs(struct cackey_pcsc_identity *dest, struct cackey_pcsc_identity *start, size_t count) { + size_t idx; + + if (start == NULL) { + return(NULL); + } + + if (dest == NULL) { + dest = malloc(sizeof(*dest) * count); + } + + for (idx = 0; idx < count; idx++) { + dest[idx].id_type = start[idx].id_type; + + switch (dest[idx].id_type) { + case CACKEY_ID_TYPE_CAC: + memcpy(dest[idx].card.cac.applet, start[idx].card.cac.applet, sizeof(dest[idx].card.cac.applet)); + dest[idx].card.cac.file = start[idx].card.cac.file; + break; + case CACKEY_ID_TYPE_PIV: + dest[idx].card.piv.key_id = start[idx].card.piv.key_id; + memcpy(dest[idx].card.piv.label, start[idx].card.piv.label, sizeof(dest[idx].card.piv.label)); + break; + case CACKEY_ID_TYPE_CERT_ONLY: + break; + } + dest[idx].certificate_len = start[idx].certificate_len; + dest[idx].keysize = start[idx].keysize; + + dest[idx].certificate = malloc(dest[idx].certificate_len); + memcpy(dest[idx].certificate, start[idx].certificate, dest[idx].certificate_len); + } + + return(dest); +} /* * SYNPOSIS * ... * @@ -2114,16 +2451,27 @@ * */ static struct cackey_pcsc_identity *cackey_read_certs(struct cackey_slot *slot, struct cackey_pcsc_identity *certs, unsigned long *count) { struct cackey_pcsc_identity *curr_id; struct cackey_tlv_entity *ccc_tlv, *ccc_curr, *app_tlv, *app_curr; - unsigned char ccc_aid[] = {GSCIS_AID_CCC}; + unsigned char ccc_aid[] = {GSCIS_AID_CCC}, piv_aid[] = {NISTSP800_73_3_PIV_AID}; + unsigned char *piv_oid, piv_oid_pivauth[] = {NISTSP800_73_3_OID_PIVAUTH}, piv_oid_signature[] = {NISTSP800_73_3_OID_SIGNATURE}, piv_oid_keymgt[] = {NISTSP800_73_3_OID_KEYMGT}; unsigned char curr_aid[7]; + unsigned char buffer[8192], *buffer_p, *tmpbuf; unsigned long outidx = 0; + char *piv_label; cackey_ret transaction_ret; + ssize_t read_ret; + size_t buffer_len, tmpbuflen; int certs_resizable; int send_ret, select_ret; + int piv_key, piv = 0; + int idx; +#ifdef HAVE_LIBZ + int uncompress_ret; + z_stream gzip_stream; +#endif CACKEY_DEBUG_PRINTF("Called."); if (count == NULL) { CACKEY_DEBUG_PRINTF("count is NULL, returning in failure"); @@ -2136,10 +2484,33 @@ CACKEY_DEBUG_PRINTF("Requested we return 0 objects, short-circuit"); return(certs); } } + + if (!slot->slot_reset) { + if (slot->cached_certs) { + if (certs == NULL) { + certs = malloc(sizeof(*certs) * slot->cached_certs_count); + *count = slot->cached_certs_count; + } else { + if (*count > slot->cached_certs_count) { + *count = slot->cached_certs_count; + } + } + + cackey_copy_certs(certs, slot->cached_certs, *count); + + return(certs); + } + } + + if (slot->cached_certs) { + cackey_free_certs(slot->cached_certs, slot->cached_certs_count, 1); + + slot->cached_certs = NULL; + } /* Begin a SmartCard transaction */ transaction_ret = cackey_begin_transaction(slot); if (transaction_ret != CACKEY_PCSC_S_OK) { CACKEY_DEBUG_PRINTF("Unable begin transaction, returning in failure"); @@ -2156,111 +2527,232 @@ } /* Select the CCC Applet */ send_ret = cackey_select_applet(slot, ccc_aid, sizeof(ccc_aid)); if (send_ret != CACKEY_PCSC_S_OK) { - CACKEY_DEBUG_PRINTF("Unable to select CCC Applet, returning in failure"); - - /* Terminate SmartCard Transaction */ - cackey_end_transaction(slot); - - return(NULL); - } - - /* Read all the applets from the CCC's TLV */ - ccc_tlv = cackey_read_tlv(slot); - - /* Look for CARDURLs that coorespond to PKI applets */ - for (ccc_curr = ccc_tlv; ccc_curr; ccc_curr = ccc_curr->_next) { - CACKEY_DEBUG_PRINTF("Found tag: %s ... ", CACKEY_DEBUG_FUNC_TAG_TO_STR(ccc_curr->tag)); - - if (ccc_curr->tag != GSCIS_TAG_CARDURL) { - CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about CARDURLs)"); - - continue; - } - - if ((ccc_curr->value_cardurl->apptype & CACKEY_TLV_APP_PKI) != CACKEY_TLV_APP_PKI) { - CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about PKI applets, this applet supports: %s/%02x)", CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(ccc_curr->value_cardurl->apptype), (unsigned int) ccc_curr->value_cardurl->apptype); - - continue; - } - - CACKEY_DEBUG_PRINTBUF("RID:", ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid)); - CACKEY_DEBUG_PRINTF("AppID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->appid), (unsigned long) ccc_curr->value_cardurl->appid); - CACKEY_DEBUG_PRINTF("ObjectID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->objectid), (unsigned long) ccc_curr->value_cardurl->objectid); - - memcpy(curr_aid, ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid)); - curr_aid[sizeof(curr_aid) - 2] = (ccc_curr->value_cardurl->appid >> 8) & 0xff; - curr_aid[sizeof(curr_aid) - 1] = ccc_curr->value_cardurl->appid & 0xff; - - /* Select found applet ... */ - select_ret = cackey_select_applet(slot, curr_aid, sizeof(curr_aid)); - if (select_ret != CACKEY_PCSC_S_OK) { - CACKEY_DEBUG_PRINTF("Failed to select applet, skipping processing of this object"); - - continue; - } - - /* ... and object (file) */ - select_ret = cackey_select_file(slot, ccc_curr->value_cardurl->objectid); - if (select_ret != CACKEY_PCSC_S_OK) { - CACKEY_DEBUG_PRINTF("Failed to select file, skipping processing of this object"); - - continue; - } - - /* Process this file's TLV looking for certificates */ - app_tlv = cackey_read_tlv(slot); - - for (app_curr = app_tlv; app_curr; app_curr = app_curr->_next) { - CACKEY_DEBUG_PRINTF("Found tag: %s", CACKEY_DEBUG_FUNC_TAG_TO_STR(app_curr->tag)); - if (app_curr->tag != GSCIS_TAG_CERTIFICATE) { - CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about CERTIFICATEs)"); - + /* Try PIV application */ + send_ret = cackey_select_applet(slot, piv_aid, sizeof(piv_aid)); + if (send_ret == CACKEY_PCSC_S_OK) { + CACKEY_DEBUG_PRINTF("We have a PIV card -- not using the CCC, pulling pre-selected keys"); + + piv = 1; + } else { + CACKEY_DEBUG_PRINTF("Unable to select CCC Applet, returning in failure"); + + /* Terminate SmartCard Transaction */ + cackey_end_transaction(slot); + + return(NULL); + } + } + + if (piv) { + for (idx = 0; idx < 3; idx++) { + switch (idx) { + case 0: + piv_oid = piv_oid_pivauth; + piv_key = NISTSP800_78_3_KEY_PIVAUTH; + piv_label = "Authentication"; + break; + case 1: + piv_oid = piv_oid_signature; + piv_key = NISTSP800_78_3_KEY_SIGNATURE; + piv_label = "Signature"; + break; + case 2: + piv_oid = piv_oid_keymgt; + piv_key = NISTSP800_78_3_KEY_KEYMGT; + piv_label = "Key Management"; + break; + } + + read_ret = cackey_get_data(slot, buffer, sizeof(buffer), piv_oid); + + if (read_ret <= 0) { continue; } curr_id = &certs[outidx]; outidx++; - memcpy(curr_id->applet, curr_aid, sizeof(curr_id->applet)); - curr_id->file = ccc_curr->value_cardurl->objectid; - curr_id->keysize = -1; - - CACKEY_DEBUG_PRINTF("Filling curr_id->applet (%p) with %lu bytes:", curr_id->applet, (unsigned long) sizeof(curr_id->applet)); - CACKEY_DEBUG_PRINTBUF("VAL:", curr_id->applet, sizeof(curr_id->applet)); - - curr_id->certificate_len = app_curr->length; - - curr_id->certificate = malloc(curr_id->certificate_len); - memcpy(curr_id->certificate, app_curr->value, curr_id->certificate_len); - - if (outidx >= *count) { - if (certs_resizable) { - *count *= 2; - certs = realloc(certs, sizeof(*certs) * (*count)); - } else { - break; - } - } - } - - cackey_free_tlv(app_tlv); - - if (outidx >= *count) { - break; - } - } - - cackey_free_tlv(ccc_tlv); + curr_id->keysize = -1; + curr_id->id_type = CACKEY_ID_TYPE_PIV; + curr_id->card.piv.key_id = piv_key; + memcpy(curr_id->card.piv.label, piv_label, strlen(piv_label) + 1); + + curr_id->certificate_len = read_ret; + curr_id->certificate = malloc(curr_id->certificate_len); + + buffer_len = sizeof(buffer); + buffer_p = cackey_read_bertlv_tag(buffer, &buffer_len, 0x70, curr_id->certificate, &curr_id->certificate_len); + + if (buffer_p == NULL) { + CACKEY_DEBUG_PRINTF("Reading certificate from BER-TLV response failed, skipping key %i", idx); + + free(curr_id->certificate); + + curr_id->certificate = NULL; + + outidx--; + + continue; + } + +#ifdef HAVE_LIBZ + if (curr_id->certificate_len > 4) { + if (memcmp(curr_id->certificate, "\x1f\x8b\x08\x00", 4) == 0) { + tmpbuflen = curr_id->certificate_len * 2; + tmpbuf = malloc(tmpbuflen); + + CACKEY_DEBUG_PRINTBUF("Attempting to decompress:", curr_id->certificate, curr_id->certificate_len); + + gzip_stream.zalloc = NULL; + gzip_stream.zfree = NULL; + gzip_stream.opaque = NULL; + + gzip_stream.next_in = curr_id->certificate; + gzip_stream.avail_in = curr_id->certificate_len; + gzip_stream.next_out = tmpbuf; + gzip_stream.avail_out = tmpbuflen; + + uncompress_ret = inflateInit(&gzip_stream); + if (uncompress_ret == Z_OK) { + uncompress_ret = inflateReset2(&gzip_stream, 31); + } + if (uncompress_ret == Z_OK) { + uncompress_ret = inflate(&gzip_stream, 0); + } + if (uncompress_ret == Z_STREAM_END) { + uncompress_ret = inflateEnd(&gzip_stream); + } else { + uncompress_ret = Z_DATA_ERROR; + } + if (uncompress_ret == Z_OK) { + tmpbuflen = gzip_stream.total_out; + + CACKEY_DEBUG_PRINTBUF("Decompressed to:", tmpbuf, tmpbuflen); + + free(curr_id->certificate); + + curr_id->certificate = tmpbuf; + curr_id->certificate_len = tmpbuflen; + } else { + CACKEY_DEBUG_PRINTF("Decompressing failed! uncompress() returned %i", uncompress_ret); + + free(tmpbuf); + } + } + } +#endif + } + } else { + /* Read all the applets from the CCC's TLV */ + ccc_tlv = cackey_read_tlv(slot); + + /* Look for CARDURLs that coorespond to PKI applets */ + for (ccc_curr = ccc_tlv; ccc_curr; ccc_curr = ccc_curr->_next) { + CACKEY_DEBUG_PRINTF("Found tag: %s ... ", CACKEY_DEBUG_FUNC_TAG_TO_STR(ccc_curr->tag)); + + if (ccc_curr->tag != GSCIS_TAG_CARDURL) { + CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about CARDURLs)"); + + continue; + } + + if ((ccc_curr->value_cardurl->apptype & CACKEY_TLV_APP_PKI) != CACKEY_TLV_APP_PKI) { + CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about PKI applets, this applet supports: %s/%02x)", CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(ccc_curr->value_cardurl->apptype), (unsigned int) ccc_curr->value_cardurl->apptype); + + continue; + } + + CACKEY_DEBUG_PRINTBUF("RID:", ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid)); + CACKEY_DEBUG_PRINTF("AppID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->appid), (unsigned long) ccc_curr->value_cardurl->appid); + CACKEY_DEBUG_PRINTF("ObjectID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->objectid), (unsigned long) ccc_curr->value_cardurl->objectid); + + memcpy(curr_aid, ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid)); + curr_aid[sizeof(curr_aid) - 2] = (ccc_curr->value_cardurl->appid >> 8) & 0xff; + curr_aid[sizeof(curr_aid) - 1] = ccc_curr->value_cardurl->appid & 0xff; + + /* Select found applet ... */ + select_ret = cackey_select_applet(slot, curr_aid, sizeof(curr_aid)); + if (select_ret != CACKEY_PCSC_S_OK) { + CACKEY_DEBUG_PRINTF("Failed to select applet, skipping processing of this object"); + + continue; + } + + /* ... and object (file) */ + select_ret = cackey_select_file(slot, ccc_curr->value_cardurl->objectid); + if (select_ret != CACKEY_PCSC_S_OK) { + CACKEY_DEBUG_PRINTF("Failed to select file, skipping processing of this object"); + + continue; + } + + /* Process this file's TLV looking for certificates */ + app_tlv = cackey_read_tlv(slot); + + for (app_curr = app_tlv; app_curr; app_curr = app_curr->_next) { + CACKEY_DEBUG_PRINTF("Found tag: %s", CACKEY_DEBUG_FUNC_TAG_TO_STR(app_curr->tag)); + if (app_curr->tag != GSCIS_TAG_CERTIFICATE) { + CACKEY_DEBUG_PRINTF(" ... skipping it (we only care about CERTIFICATEs)"); + + continue; + } + + curr_id = &certs[outidx]; + outidx++; + + curr_id->id_type = CACKEY_ID_TYPE_CAC; + memcpy(curr_id->card.cac.applet, curr_aid, sizeof(curr_id->card.cac.applet)); + curr_id->card.cac.file = ccc_curr->value_cardurl->objectid; + curr_id->keysize = -1; + + CACKEY_DEBUG_PRINTF("Filling curr_id->card.cac.applet (%p) with %lu bytes:", curr_id->card.cac.applet, (unsigned long) sizeof(curr_id->card.cac.applet)); + CACKEY_DEBUG_PRINTBUF("VAL:", curr_id->card.cac.applet, sizeof(curr_id->card.cac.applet)); + + curr_id->certificate_len = app_curr->length; + + curr_id->certificate = malloc(curr_id->certificate_len); + memcpy(curr_id->certificate, app_curr->value, curr_id->certificate_len); + + if (outidx >= *count) { + if (certs_resizable) { + *count *= 2; + if (*count != 0) { + certs = realloc(certs, sizeof(*certs) * (*count)); + } else { + certs = NULL; + } + } else { + break; + } + } + } + + cackey_free_tlv(app_tlv); + + if (outidx >= *count) { + break; + } + } + + cackey_free_tlv(ccc_tlv); + } *count = outidx; if (certs_resizable) { - certs = realloc(certs, sizeof(*certs) * (*count)); + if (*count != 0) { + certs = realloc(certs, sizeof(*certs) * (*count)); + } else { + certs = NULL; + } } + + slot->cached_certs = cackey_copy_certs(NULL, certs, *count); + slot->cached_certs_count = *count; /* Terminate SmartCard Transaction */ cackey_end_transaction(slot); return(certs); @@ -2279,17 +2771,19 @@ * NOTES * ... * */ static ssize_t cackey_signdecrypt(struct cackey_slot *slot, struct cackey_identity *identity, unsigned char *buf, size_t buflen, unsigned char *outbuf, size_t outbuflen, int padInput, int unpadOutput) { - unsigned char *tmpbuf, *tmpbuf_s, *outbuf_s; - unsigned char bytes_to_send, p1; + cackey_pcsc_id_type id_type; + unsigned char dyn_auth_template[10], *dyn_auth_tmpbuf; + unsigned char *tmpbuf, *tmpbuf_s, *outbuf_s, *outbuf_p; + unsigned char bytes_to_send, p1, class; unsigned char blocktype; cackey_ret send_ret; uint16_t respcode; ssize_t retval = 0, unpadoffset; - size_t tmpbuflen, padlen, tmpoutbuflen; + size_t tmpbuflen, padlen, tmpoutbuflen, outbuf_len; int free_tmpbuf = 0; int le; CACKEY_DEBUG_PRINTF("Called."); @@ -2320,10 +2814,27 @@ if (identity->pcsc_identity == NULL) { CACKEY_DEBUG_PRINTF("Error. identity->pcsc_identity is NULL"); return(-1); } + + id_type = identity->pcsc_identity->id_type; + if (id_type == CACKEY_ID_TYPE_CERT_ONLY) { + CACKEY_DEBUG_PRINTF("Error. identity->pcsc_identity is CACKEY_ID_TYPE_CERT_ONLY, which cannot be used for sign/decrypt"); + + return(-1); + } + + switch (id_type) { + case CACKEY_ID_TYPE_PIV: + case CACKEY_ID_TYPE_CAC: + break; + default: + CACKEY_DEBUG_PRINTF("Error. identity->pcsc_identity is not a supported value. Type is: 0x%lx (PIV = 0x%lx, CAC = 0x%lx)", (unsigned long) id_type, (unsigned long) CACKEY_ID_TYPE_PIV, (unsigned long) CACKEY_ID_TYPE_CAC); + + return(-1); + } /* Determine identity Key size */ if (identity->pcsc_identity->keysize < 0) { identity->pcsc_identity->keysize = x509_to_keysize(identity->pcsc_identity->certificate, identity->pcsc_identity->certificate_len); } @@ -2376,46 +2887,110 @@ /* Begin transaction */ cackey_begin_transaction(slot); /* Select correct applet */ - CACKEY_DEBUG_PRINTF("Selecting applet found at %p ...", identity->pcsc_identity->applet); - cackey_select_applet(slot, identity->pcsc_identity->applet, sizeof(identity->pcsc_identity->applet)); + switch (id_type) { + case CACKEY_ID_TYPE_CAC: + CACKEY_DEBUG_PRINTF("Selecting applet found at %p ...", identity->pcsc_identity->card.cac.applet); + cackey_select_applet(slot, identity->pcsc_identity->card.cac.applet, sizeof(identity->pcsc_identity->card.cac.applet)); - /* Select correct file */ - cackey_select_file(slot, identity->pcsc_identity->file); + /* Select correct file */ + cackey_select_file(slot, identity->pcsc_identity->card.cac.file); + break; + case CACKEY_ID_TYPE_PIV: + dyn_auth_template[0] = 0x7C; + dyn_auth_template[1] = 0x82; + dyn_auth_template[2] = ((tmpbuflen + 6) & 0xff00) >> 8; + dyn_auth_template[3] = (tmpbuflen + 6) & 0x00ff; + dyn_auth_template[4] = 0x82; + dyn_auth_template[5] = 0x00; + dyn_auth_template[6] = 0x81; + dyn_auth_template[7] = 0x82; + dyn_auth_template[8] = (tmpbuflen & 0xff00) >> 8; + dyn_auth_template[9] = tmpbuflen & 0x00ff; + + dyn_auth_tmpbuf = malloc(tmpbuflen + sizeof(dyn_auth_template)); + memcpy(dyn_auth_tmpbuf, dyn_auth_template, sizeof(dyn_auth_template)); + memcpy(dyn_auth_tmpbuf + sizeof(dyn_auth_template), tmpbuf, tmpbuflen); + + if (free_tmpbuf) { + free(tmpbuf); + } + + tmpbuflen += sizeof(dyn_auth_template); + tmpbuf = dyn_auth_tmpbuf; + free_tmpbuf = 1; + + break; + case CACKEY_ID_TYPE_CERT_ONLY: + break; + } tmpbuf_s = tmpbuf; outbuf_s = outbuf; while (tmpbuflen) { - if (tmpbuflen > 245) { - bytes_to_send = 245; - p1 = 0x80; - le = 0x00; + tmpoutbuflen = outbuflen; + + if (tmpbuflen > CACKEY_APDU_MTU) { + bytes_to_send = CACKEY_APDU_MTU; } else { bytes_to_send = tmpbuflen; - p1 = 0x00; - le = 0x00; } - tmpoutbuflen = outbuflen; + send_ret = CACKEY_PCSC_E_GENERIC; + switch (id_type) { + case CACKEY_ID_TYPE_CAC: + if (tmpbuflen > CACKEY_APDU_MTU) { + p1 = 0x80; + le = 0x00; + } else { + p1 = 0x00; + le = 0x00; + } - send_ret = cackey_send_apdu(slot, GSCIS_CLASS_GLOBAL_PLATFORM, GSCIS_INSTR_SIGNDECRYPT, p1, 0x00, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen); + send_ret = cackey_send_apdu(slot, GSCIS_CLASS_GLOBAL_PLATFORM, GSCIS_INSTR_SIGNDECRYPT, p1, 0x00, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen); + break; + case CACKEY_ID_TYPE_PIV: + if (tmpbuflen > CACKEY_APDU_MTU) { + class = 0x10; + le = 0x00; + } else { + class = GSCIS_CLASS_ISO7816; + le = 256; + } + + send_ret = cackey_send_apdu(slot, class, NISTSP800_73_3_INSTR_GENAUTH, NISTSP800_78_3_ALGO_RSA2048, identity->pcsc_identity->card.piv.key_id, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen); + break; + case CACKEY_ID_TYPE_CERT_ONLY: + break; + } + if (send_ret != CACKEY_PCSC_S_OK) { - CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- returning in error."); - if (free_tmpbuf) { if (tmpbuf_s) { free(tmpbuf_s); } } /* End transaction */ cackey_end_transaction(slot); - if (respcode == 0x6982) { - CACKEY_DEBUG_PRINTF("Security status not satisified. Returning NEEDLOGIN"); + if (send_ret == CACKEY_PCSC_E_RETRY) { + CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- retrying."); + + return(cackey_signdecrypt(slot, identity, buf, buflen, outbuf, outbuflen, padInput, unpadOutput)); + } + + CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- returning in error."); + + if (respcode == 0x6982 || respcode == 0x6e00) { + if (respcode == 0x6E00) { + CACKEY_DEBUG_PRINTF("Got \"WRONG CLASS\", this means we are talking to the wrong object (likely because the card went away) -- resetting"); + } else { + CACKEY_DEBUG_PRINTF("Security status not satisified (respcode = 0x%04x). Returning NEEDLOGIN", (int) respcode); + } cackey_mark_slot_reset(slot); return(CACKEY_PCSC_E_NEEDLOGIN); } @@ -2457,10 +3032,39 @@ return(-1); } # endif #endif + + /* We must remove the "7C" tag to get to the signature */ + switch (id_type) { + case CACKEY_ID_TYPE_PIV: + outbuf_len = retval; + outbuf_p = cackey_read_bertlv_tag(outbuf, &outbuf_len, 0x7C, NULL, &outbuf_len); + if (outbuf_p == NULL) { + CACKEY_DEBUG_PRINTF("Response from PIV for GENERATE AUTHENTICATION was not a 0x7C tag, returning in failure"); + + return(-1); + } + + retval = outbuf_len; + + outbuf_len = retval; + outbuf_p = cackey_read_bertlv_tag(outbuf, &outbuf_len, 0x82, NULL, &outbuf_len); + if (outbuf_p == NULL) { + CACKEY_DEBUG_PRINTF("Response from PIV for GENERATE AUTHENTICATION was not a 0x82 within a 0x7C tag, returning in failure"); + + return(-1); + } + + retval = outbuf_len; + + break; + case CACKEY_ID_TYPE_CAC: + case CACKEY_ID_TYPE_CERT_ONLY: + break; + } /* Unpad reply */ if (unpadOutput) { if (retval < 3) { CACKEY_DEBUG_PRINTF("Reply is too small, we are not able to unpad -- passing back and hoping for the best!"); @@ -2553,14 +3157,17 @@ * NOTES * ... * */ static cackey_ret cackey_login(struct cackey_slot *slot, unsigned char *pin, unsigned long pin_len, int *tries_remaining_p) { + struct cackey_pcsc_identity *pcsc_identities; unsigned char cac_pin[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + unsigned long num_certs; uint16_t response_code; int tries_remaining; int send_ret; + int key_reference = 0x00; /* Indicate that we do not know about how many tries are remaining */ if (tries_remaining_p) { *tries_remaining_p = -1; } @@ -2569,13 +3176,37 @@ if (pin_len >= 8) { memcpy(cac_pin, pin, 8); } else { memcpy(cac_pin, pin, pin_len); } + + /* Reject PINs which are too short */ + if (pin_len < 5) { + CACKEY_DEBUG_PRINTF("Rejecting PIN which is too short (length = %lu, must be atleast 5)", pin_len); + + return(CACKEY_PCSC_E_BADPIN); + } + + /* PIV authentication uses a "key_reference" of 0x80 */ + pcsc_identities = cackey_read_certs(slot, NULL, &num_certs); + if (num_certs > 0 && pcsc_identities != NULL) { + switch (pcsc_identities[0].id_type) { + case CACKEY_ID_TYPE_PIV: + CACKEY_DEBUG_PRINTF("We have PIV card, so we will attempt to authenticate using the PIV Application key reference"); + + key_reference = 0x80; + break; + default: + break; + } + + cackey_free_certs(pcsc_identities, num_certs, 1); + } /* Issue PIN Verify */ - send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_VERIFY, 0x00, 0x00, sizeof(cac_pin), cac_pin, 0x00, &response_code, NULL, NULL); + send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_VERIFY, 0x00, key_reference, sizeof(cac_pin), cac_pin, 0x00, &response_code, NULL, NULL); + if (send_ret != CACKEY_PCSC_S_OK) { if ((response_code & 0x63C0) == 0x63C0) { tries_remaining = (response_code & 0xF); CACKEY_DEBUG_PRINTF("PIN Verification failed, %i tries remaining", tries_remaining); @@ -2615,22 +3246,30 @@ * ... * */ static cackey_ret cackey_token_present(struct cackey_slot *slot) { cackey_ret pcsc_connect_ret; - DWORD reader_len, state, protocol, atr_len; + DWORD reader_len = 0, state = 0, protocol = 0, atr_len; BYTE atr[MAX_ATR_SIZE]; LONG status_ret, scard_reconn_ret; CACKEY_DEBUG_PRINTF("Called."); + + if (slot->internal) { + CACKEY_DEBUG_PRINTF("Returning token present (internal token)"); + + return(CACKEY_PCSC_S_TOKENPRESENT); + } pcsc_connect_ret = cackey_connect_card(slot); if (pcsc_connect_ret != CACKEY_PCSC_S_OK) { CACKEY_DEBUG_PRINTF("Unable to connect to card, returning token absent"); return(CACKEY_PCSC_E_TOKENABSENT); } + + CACKEY_DEBUG_PRINTF("Calling SCardStatus() to determine card status"); atr_len = sizeof(atr); status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len); if (status_ret == SCARD_E_INVALID_HANDLE) { @@ -2642,10 +3281,12 @@ CACKEY_DEBUG_PRINTF("Unable to connect to card, returning token absent"); return(CACKEY_PCSC_E_TOKENABSENT); } + CACKEY_DEBUG_PRINTF("Calling SCardStatus() again"); + atr_len = sizeof(atr); status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len); } if (status_ret != SCARD_S_SUCCESS) { @@ -2652,15 +3293,12 @@ cackey_mark_slot_reset(slot); if (status_ret == SCARD_W_RESET_CARD) { CACKEY_DEBUG_PRINTF("Reset required, please hold..."); - scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &protocol); + scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1); if (scard_reconn_ret == SCARD_S_SUCCESS) { - /* Update protocol */ - slot->protocol = protocol; - /* Re-establish transaction, if it was present */ if (slot->transaction_depth > 0) { slot->transaction_depth--; slot->transaction_need_hw_lock = 1; cackey_begin_transaction(slot); @@ -2986,17 +3624,21 @@ break; case CKA_LABEL: CACKEY_DEBUG_PRINTF("Requesting attribute CKA_LABEL (0x%08lx) ...", (unsigned long) curr_attr_type); - /* XXX: Determine name */ - ulValueLen = snprintf((char *) ucTmpBuf, sizeof(ucTmpBuf), "Identity #%lu", (unsigned long) identity_num); - pValue = ucTmpBuf; + if (identity->id_type == CACKEY_ID_TYPE_PIV) { + pValue = identity->card.piv.label; + ulValueLen = strlen(pValue); + } else { + ulValueLen = snprintf((char *) ucTmpBuf, sizeof(ucTmpBuf), "Identity #%lu", (unsigned long) identity_num); + pValue = ucTmpBuf; - if (ulValueLen >= sizeof(ucTmpBuf)) { - ulValueLen = 0; - pValue = NULL; + if (ulValueLen >= sizeof(ucTmpBuf)) { + ulValueLen = 0; + pValue = NULL; + } } CACKEY_DEBUG_PRINTF(" ... returning (%p/%lu)", pValue, (unsigned long) ulValueLen); break; @@ -3414,45 +4056,106 @@ } } free(identities); } + +static unsigned long cackey_read_dod_identities(struct cackey_identity *identities, unsigned long num_dod_certs) { + unsigned long cert_idx, id_idx = 0; + + if (identities == NULL) { + return(num_dod_certs * 3); + } + + for (cert_idx = 0; cert_idx < num_dod_certs; cert_idx++) { + identities[id_idx].pcsc_identity = NULL; + identities[id_idx].attributes = cackey_get_attributes(CKO_CERTIFICATE, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); + id_idx++; + + identities[id_idx].pcsc_identity = NULL; + identities[id_idx].attributes = cackey_get_attributes(CKO_PUBLIC_KEY, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); + id_idx++; + + identities[id_idx].pcsc_identity = NULL; + identities[id_idx].attributes = cackey_get_attributes(CKO_NETSCAPE_TRUST, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); + id_idx++; + } + + return(id_idx); +} static struct cackey_identity *cackey_read_identities(struct cackey_slot *slot, unsigned long *ids_found) { struct cackey_pcsc_identity *pcsc_identities; struct cackey_identity *identities; unsigned long num_ids, id_idx, curr_id_type; - unsigned long num_certs, num_extra_certs, cert_idx; - int include_extra_certs = 1; + unsigned long num_certs, num_dod_certs, cert_idx; + int include_extra_certs = 0, include_dod_certs; CACKEY_DEBUG_PRINTF("Called."); - if (getenv("CACKEY_NO_EXTRA_CERTS") != NULL) { - CACKEY_DEBUG_PRINTF("Asked not to include extra (DoD) certificates"); - - include_extra_certs = 0; - } - - if (include_extra_certs) { - num_extra_certs = sizeof(extra_certs) / sizeof(extra_certs[0]); - - CACKEY_DEBUG_PRINTF("Including %li DoD Certificates as objects on this token", num_extra_certs); - } else { - num_extra_certs = 0; - } - if (ids_found == NULL) { CACKEY_DEBUG_PRINTF("Error. ids_found is NULL"); return(NULL); } + +#ifdef CACKEY_CARD_SLOT_INCLUDE_EXTRA_CERTS + include_extra_certs = 1; +#endif + + if (getenv("CACKEY_DOD_CERTS_ON_HW_SLOTS") != NULL) { + include_extra_certs = 1; + } + + if (getenv("CACKEY_NO_DOD_CERTS_ON_HW_SLOTS") != NULL) { + include_extra_certs = 0; + } + +#ifdef CACKEY_NO_EXTRA_CERTS + if (getenv("CACKEY_EXTRA_CERTS") != NULL) { + include_dod_certs = 1; + } else { + include_dod_certs = 0; + } +#else + if (getenv("CACKEY_NO_EXTRA_CERTS") != NULL) { + include_dod_certs = 0; + } else { + include_dod_certs = 1; + } +#endif + + if (include_dod_certs) { + num_dod_certs = sizeof(extra_certs) / sizeof(extra_certs[0]); + } else { + num_dod_certs = 0; + } + + if (slot->internal) { + num_ids = cackey_read_dod_identities(NULL, num_dod_certs); + + if (num_ids != 0) { + identities = malloc(num_ids * sizeof(*identities)); + + cackey_read_dod_identities(identities, num_dod_certs); + } else { + identities = NULL; + } + + *ids_found = num_ids; + + return(identities); + } pcsc_identities = cackey_read_certs(slot, NULL, &num_certs); if (pcsc_identities != NULL) { /* Convert number of Certs to number of objects */ num_ids = (CKO_PRIVATE_KEY - CKO_CERTIFICATE + 1) * num_certs; - num_ids += num_extra_certs * 3; + + if (include_extra_certs) { + num_ids += cackey_read_dod_identities(NULL, num_dod_certs); + } identities = malloc(num_ids * sizeof(*identities)); /* Add certificates, public keys, and private keys from the smartcard */ id_idx = 0; @@ -3467,40 +4170,34 @@ memcpy(identities[id_idx].pcsc_identity->certificate, pcsc_identities[cert_idx].certificate, pcsc_identities[cert_idx].certificate_len); id_idx++; } } + + if (include_extra_certs) { + CACKEY_DEBUG_PRINTF("Including US Government Certificates on hardware slot"); + + cackey_read_dod_identities(identities + id_idx, num_dod_certs); + } cackey_free_certs(pcsc_identities, num_certs, 1); - /* Add DoD Certificates and Netscape Trust Objects */ - for (cert_idx = 0; cert_idx < num_extra_certs; cert_idx++) { - identities[id_idx].pcsc_identity = NULL; - identities[id_idx].attributes = cackey_get_attributes(CKO_CERTIFICATE, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); - id_idx++; - - identities[id_idx].pcsc_identity = NULL; - identities[id_idx].attributes = cackey_get_attributes(CKO_PUBLIC_KEY, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); - id_idx++; - - identities[id_idx].pcsc_identity = NULL; - identities[id_idx].attributes = cackey_get_attributes(CKO_NETSCAPE_TRUST, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count); - id_idx++; - } - *ids_found = num_ids; + return(identities); } + *ids_found = 0; return(NULL); } CK_DEFINE_FUNCTION(CK_RV, C_Initialize)(CK_VOID_PTR pInitArgs) { CK_C_INITIALIZE_ARGS CK_PTR args; - uint32_t idx; + uint32_t idx, highest_slot; int mutex_init_ret; + int include_dod_certs; CACKEY_DEBUG_PRINTF("Called."); if (cackey_initialized) { CACKEY_DEBUG_PRINTF("Error. Already initialized."); @@ -3537,10 +4234,39 @@ cackey_slots[idx].transaction_depth = 0; cackey_slots[idx].transaction_need_hw_lock = 0; cackey_slots[idx].slot_reset = 0; cackey_slots[idx].token_flags = 0; cackey_slots[idx].label = NULL; + cackey_slots[idx].internal = 0; + } + +#ifdef CACKEY_NO_EXTRA_CERTS + if (getenv("CACKEY_EXTRA_CERTS") != NULL) { + include_dod_certs = 1; + } else { + include_dod_certs = 0; + } +#else + if (getenv("CACKEY_NO_EXTRA_CERTS") != NULL) { + include_dod_certs = 0; + } else { + include_dod_certs = 1; + } +#endif + + if (include_dod_certs == 0) { + CACKEY_DEBUG_PRINTF("Asked not to include DoD certificates"); + } else { + highest_slot = (sizeof(cackey_slots) / sizeof(cackey_slots[0])) - 1; + + CACKEY_DEBUG_PRINTF("Including DoD certs in slot %lu", (unsigned long) highest_slot); + + cackey_slots[highest_slot].active = 1; + cackey_slots[highest_slot].internal = 1; + cackey_slots[highest_slot].label = (unsigned char *) "US Government Certificates"; + cackey_slots[highest_slot].pcsc_reader = "CACKey"; + cackey_slots[highest_slot].token_flags = 0; } cackey_initialized = 1; if (!cackey_biglock_init) { @@ -3552,10 +4278,29 @@ return(CKR_CANT_LOCK); } cackey_biglock_init = 1; } + + /* Define a command to prompt user for a PIN */ +#ifdef CACKEY_PIN_COMMAND_DEFAULT + cackey_pin_command = CACKEY_PIN_COMMAND_DEFAULT_XSTR(CACKEY_PIN_COMMAND_DEFAULT); +#endif + +#ifdef CACKEY_PIN_COMMAND_XONLY_DEFAULT + if (getenv("DISPLAY") != NULL) { + cackey_pin_command = CACKEY_PIN_COMMAND_DEFAULT_XSTR(CACKEY_PIN_COMMAND_XONLY_DEFAULT); + } +#endif + + if (getenv("CACKEY_PIN_COMMAND") != NULL) { + cackey_pin_command = getenv("CACKEY_PIN_COMMAND"); + } + + if (getenv("CACKEY_PIN_COMMAND_XONLY") != NULL && getenv("DISPLAY") != NULL) { + cackey_pin_command = getenv("CACKEY_PIN_COMMAND_XONLY"); + } CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK); return(CKR_OK); } @@ -3584,13 +4329,23 @@ } cackey_slots_disconnect_all(); for (idx = 0; idx < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); idx++) { + if (cackey_slots[idx].internal) { + continue; + } + if (cackey_slots[idx].pcsc_reader) { free(cackey_slots[idx].pcsc_reader); } + + if (cackey_slots[idx].cached_certs) { + cackey_free_certs(cackey_slots[idx].cached_certs, cackey_slots[idx].cached_certs_count, 1); + + cackey_slots[idx].cached_certs = NULL; + } } cackey_pcsc_disconnect(); cackey_initialized = 0; @@ -3639,17 +4394,19 @@ /* * Process list of readers, and create mapping between reader name and slot ID */ CK_DEFINE_FUNCTION(CK_RV, C_GetSlotList)(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, CK_ULONG_PTR pulCount) { + static int first_call = 1; int mutex_retval; int pcsc_connect_ret; - CK_ULONG count, slot_count = 0, currslot; + CK_ULONG count, slot_count = 0, currslot, slot_idx; char *pcsc_readers, *pcsc_readers_s, *pcsc_readers_e; DWORD pcsc_readers_len; LONG scard_listreaders_ret; size_t curr_reader_len; + int slot_reset; CACKEY_DEBUG_PRINTF("Called."); if (pulCount == NULL) { CACKEY_DEBUG_PRINTF("Error. pulCount is NULL."); @@ -3669,35 +4426,68 @@ return(CKR_GENERAL_ERROR); } /* Clear list of slots */ + slot_reset = 0; if (pSlotList) { - CACKEY_DEBUG_PRINTF("Purging all slot information."); + if (first_call) { + first_call = 0; - /* Only update the list of slots if we are actually being supply the slot information */ - cackey_slots_disconnect_all(); + slot_reset = 1; + } + /* If any of the slots have been reset then purge all information and check again */ for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) { - if (cackey_slots[currslot].pcsc_reader) { - free(cackey_slots[currslot].pcsc_reader); - - cackey_slots[currslot].pcsc_reader = NULL; + if (cackey_slots[currslot].internal) { + continue; } - if (cackey_slots[currslot].label) { - free(cackey_slots[currslot].label); - - cackey_slots[currslot].label = NULL; + if (!cackey_slots[currslot].active) { + continue; } - cackey_slots[currslot].active = 0; + if (cackey_slots[currslot].slot_reset) { + slot_reset = 1; + + break; + } + } + + if (slot_reset) { + CACKEY_DEBUG_PRINTF("Purging all slot information."); + + /* Only update the list of slots if we are actually being supply the slot information */ + cackey_slots_disconnect_all(); + + for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) { + if (cackey_slots[currslot].internal) { + continue; + } + + if (cackey_slots[currslot].pcsc_reader) { + free(cackey_slots[currslot].pcsc_reader); + + cackey_slots[currslot].pcsc_reader = NULL; + } + + if (cackey_slots[currslot].label) { + free(cackey_slots[currslot].label); + + cackey_slots[currslot].label = NULL; + } + + cackey_slots[currslot].active = 0; + } + } else { + } } /* Determine list of readers */ pcsc_connect_ret = cackey_pcsc_connect(); +/* XXX: CAN HANG HERE ! */ if (pcsc_connect_ret != CACKEY_PCSC_S_OK) { CACKEY_DEBUG_PRINTF("Connection to PC/SC failed, assuming no slots"); slot_count = 0; } else { @@ -3724,11 +4514,19 @@ pcsc_readers_e = pcsc_readers + pcsc_readers_len; /* Start with Slot ID 1, to avoid a bug in GDM on RHEL */ /* Bug 594911: https://bugzilla.redhat.com/show_bug.cgi?id=594911 */ currslot = 1; + slot_count = 0; while (pcsc_readers < pcsc_readers_e) { + /* Find next available slot */ + for (; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) { + if (!cackey_slots[currslot].active) { + break; + } + } + curr_reader_len = strlen(pcsc_readers); if ((pcsc_readers + curr_reader_len) > pcsc_readers_e) { break; } @@ -3741,35 +4539,45 @@ CACKEY_DEBUG_PRINTF("Found more readers than slots are available!"); break; } - CACKEY_DEBUG_PRINTF("Found reader: %s", pcsc_readers); + CACKEY_DEBUG_PRINTF("Found reader: %s (currslot = %lu)", pcsc_readers, (unsigned long) currslot); /* Only update the list of slots if we are actually being asked supply the slot information */ if (pSlotList) { - cackey_slots[currslot].active = 1; - cackey_slots[currslot].pcsc_reader = strdup(pcsc_readers); - cackey_slots[currslot].pcsc_card_connected = 0; - cackey_slots[currslot].transaction_depth = 0; - cackey_slots[currslot].transaction_need_hw_lock = 0; - cackey_slots[currslot].token_flags = 0; - cackey_slots[currslot].label = NULL; - - cackey_mark_slot_reset(&cackey_slots[currslot]); + if (slot_reset) { + cackey_slots[currslot].active = 1; + cackey_slots[currslot].internal = 0; + cackey_slots[currslot].pcsc_reader = strdup(pcsc_readers); + cackey_slots[currslot].pcsc_card_connected = 0; + cackey_slots[currslot].transaction_depth = 0; + cackey_slots[currslot].transaction_need_hw_lock = 0; + cackey_slots[currslot].token_flags = 0; + cackey_slots[currslot].label = NULL; + + cackey_mark_slot_reset(&cackey_slots[currslot]); + } + } else { + if (!cackey_slots[currslot].active) { + /* Artificially increase the number of active slots by what will become active */ + CACKEY_DEBUG_PRINTF("Found in-active slot %lu, but it will be active after a reset -- marking as active for accounting purposes", (unsigned long) currslot); + + slot_count++; + } } currslot++; pcsc_readers += curr_reader_len + 1; } - /* Start with Slot ID 1, to avoid a bug in GDM on RHEL */ - /* Bug 594911: https://bugzilla.redhat.com/show_bug.cgi?id=594911 */ - if (currslot > 1) { - /* Start with Slot ID 1, to avoid a bug in GDM on RHEL */ - /* Bug 594911: https://bugzilla.redhat.com/show_bug.cgi?id=594911 */ - slot_count = currslot - 1; + for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) { + if (cackey_slots[currslot].active) { + CACKEY_DEBUG_PRINTF("Found active slot %lu, reader = %s", (unsigned long) currslot, cackey_slots[currslot].pcsc_reader); + + slot_count++; + } } } else { CACKEY_DEBUG_PRINTF("Second call to SCardListReaders failed, return %s/%li", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_listreaders_ret), (long) scard_listreaders_ret); } @@ -3796,17 +4604,43 @@ count = *pulCount; if (count < slot_count) { CACKEY_DEBUG_PRINTF("Error. User allocated %lu entries, but we have %lu entries.", count, slot_count); + CACKEY_DEBUG_PRINTF("Returning CKR_BUFFER_TOO_SMALL"); + return(CKR_BUFFER_TOO_SMALL); } - for (currslot = 0; currslot < slot_count; currslot++) { - /* Start with Slot ID 1, to avoid a bug in GDM on RHEL */ - /* Bug 594911: https://bugzilla.redhat.com/show_bug.cgi?id=594911 */ - pSlotList[currslot] = currslot + 1; + mutex_retval = cackey_mutex_lock(cackey_biglock); + if (mutex_retval != 0) { + CACKEY_DEBUG_PRINTF("Error. Locking failed."); + + return(CKR_GENERAL_ERROR); + } + + slot_idx = 0; + for (currslot = 0; (currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0]))); currslot++) { + if (!cackey_slots[currslot].active) { + continue; + } + + if (slot_idx >= count) { + CACKEY_DEBUG_PRINTF("Error. User allocated %lu entries, but we just tried to write to the %lu index -- ignoring", count, slot_idx); + + continue; + } + + pSlotList[slot_idx] = currslot; + slot_idx++; + } + + mutex_retval = cackey_mutex_unlock(cackey_biglock); + if (mutex_retval != 0) { + CACKEY_DEBUG_PRINTF("Error. Unlocking failed."); + + return(CKR_GENERAL_ERROR); } *pulCount = slot_count; CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i). Found %lu readers.", CKR_OK, (unsigned long) slot_count); @@ -3854,11 +4688,15 @@ cackey_mutex_unlock(cackey_biglock); return(CKR_SLOT_ID_INVALID); } - pInfo->flags = CKF_REMOVABLE_DEVICE | CKF_HW_SLOT; + pInfo->flags = CKF_HW_SLOT; + + if (!cackey_slots[slotID].internal) { + pInfo->flags |= CKF_REMOVABLE_DEVICE; + } if (cackey_token_present(&cackey_slots[slotID]) == CACKEY_PCSC_S_TOKENPRESENT) { pInfo->flags |= CKF_TOKEN_PRESENT; } @@ -3996,10 +4834,14 @@ pInfo->firmwareVersion.major = 0x00; pInfo->firmwareVersion.minor = 0x00; pInfo->flags = CKF_WRITE_PROTECTED | CKF_USER_PIN_INITIALIZED | CKF_TOKEN_INITIALIZED | cackey_slots[slotID].token_flags; + + if (cackey_pin_command != NULL) { + pInfo->flags |= CKF_PROTECTED_AUTHENTICATION_PATH; + } pInfo->ulMaxSessionCount = (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])) - 1; pInfo->ulSessionCount = CK_UNAVAILABLE_INFORMATION; pInfo->ulMaxRwSessionCount = 0; pInfo->ulRwSessionCount = CK_UNAVAILABLE_INFORMATION; @@ -4457,15 +5299,18 @@ CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED); return(CKR_FUNCTION_NOT_SUPPORTED); } -CK_DEFINE_FUNCTION(CK_RV, C_Login)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) { +CK_DEFINE_FUNCTION(CK_RV, _C_LoginMutexArg)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen, int lock_mutex) { CK_SLOT_ID slotID; + FILE *pinfd; + char *pincmd, pinbuf[64], *fgets_ret; int mutex_retval; int tries_remaining; int login_ret; + int pclose_ret; CACKEY_DEBUG_PRINTF("Called."); if (!cackey_initialized) { CACKEY_DEBUG_PRINTF("Error. Not initialized."); @@ -4483,19 +5328,23 @@ CACKEY_DEBUG_PRINTF("Error. We only support USER mode, asked for %lu mode.", (unsigned long) userType) return(CKR_USER_TYPE_INVALID); } - mutex_retval = cackey_mutex_lock(cackey_biglock); - if (mutex_retval != 0) { - CACKEY_DEBUG_PRINTF("Error. Locking failed."); + if (lock_mutex) { + mutex_retval = cackey_mutex_lock(cackey_biglock); + if (mutex_retval != 0) { + CACKEY_DEBUG_PRINTF("Error. Locking failed."); - return(CKR_GENERAL_ERROR); + return(CKR_GENERAL_ERROR); + } } if (!cackey_sessions[hSession].active) { - cackey_mutex_unlock(cackey_biglock); + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } CACKEY_DEBUG_PRINTF("Error. Session not active."); return(CKR_SESSION_HANDLE_INVALID); } @@ -4502,30 +5351,105 @@ slotID = cackey_sessions[hSession].slotID; if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) { CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID); + + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } return(CKR_GENERAL_ERROR); } if (cackey_slots[slotID].active == 0) { CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID); - cackey_mutex_unlock(cackey_biglock); + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } return(CKR_GENERAL_ERROR); } + + pincmd = cackey_pin_command; + if (pincmd != NULL) { + CACKEY_DEBUG_PRINTF("CACKEY_PIN_COMMAND = %s", pincmd); + + if (pPin != NULL) { + CACKEY_DEBUG_PRINTF("Protected authentication path in effect and PIN provided !?"); + } + + pinfd = popen(pincmd, "r"); + if (pinfd == NULL) { + CACKEY_DEBUG_PRINTF("Error. %s: Unable to run", pincmd); + + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } + + CACKEY_DEBUG_PRINTF("Returning CKR_PIN_INCORRECT (%i)", (int) CKR_PIN_INCORRECT); + + return(CKR_PIN_INCORRECT); + } + + fgets_ret = fgets(pinbuf, sizeof(pinbuf), pinfd); + if (fgets_ret == NULL) { + pinbuf[0] = '\0'; + } + + pclose_ret = pclose(pinfd); + if (pclose_ret == -1 && errno == ECHILD) { + CACKEY_DEBUG_PRINTF("Notice. pclose() indicated it could not get the status of the child, assuming it succeeeded !"); + + pclose_ret = 0; + } + + if (pclose_ret != 0) { + CACKEY_DEBUG_PRINTF("Error. %s: exited with non-zero status of %i", pincmd, pclose_ret); + + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } + + CACKEY_DEBUG_PRINTF("Returning CKR_PIN_INCORRECT (%i)", (int) CKR_PIN_INCORRECT); + + return(CKR_PIN_INCORRECT); + } + + if (strlen(pinbuf) < 1) { + CACKEY_DEBUG_PRINTF("Error. %s: returned no data", pincmd); + + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } + + CACKEY_DEBUG_PRINTF("Returning CKR_PIN_INCORRECT (%i)", (int) CKR_PIN_INCORRECT); + + return(CKR_PIN_INCORRECT); + } + + if (pinbuf[strlen(pinbuf) - 1] == '\n') { + pinbuf[strlen(pinbuf) - 1] = '\0'; + } + + pPin = (CK_UTF8CHAR_PTR) pinbuf; + ulPinLen = strlen(pinbuf); + } login_ret = cackey_login(&cackey_slots[slotID], pPin, ulPinLen, &tries_remaining); if (login_ret != CACKEY_PCSC_S_OK) { - cackey_mutex_unlock(cackey_biglock); + if (lock_mutex) { + cackey_mutex_unlock(cackey_biglock); + } if (login_ret == CACKEY_PCSC_E_LOCKED) { CACKEY_DEBUG_PRINTF("Error. Token is locked."); cackey_slots[slotID].token_flags |= CKF_USER_PIN_LOCKED; + + CACKEY_DEBUG_PRINTF("Returning CKR_PIN_LOCKED (%i)", (int) CKR_PIN_LOCKED); return(CKR_PIN_LOCKED); } else if (login_ret == CACKEY_PCSC_E_BADPIN) { CACKEY_DEBUG_PRINTF("Error. Invalid PIN."); @@ -4532,10 +5456,12 @@ cackey_slots[slotID].token_flags |= CKF_USER_PIN_COUNT_LOW; if (tries_remaining == 1) { cackey_slots[slotID].token_flags |= CKF_USER_PIN_FINAL_TRY; } + + CACKEY_DEBUG_PRINTF("Returning CKR_PIN_INCORRECT (%i)", (int) CKR_PIN_INCORRECT); return(CKR_PIN_INCORRECT); } CACKEY_DEBUG_PRINTF("Error. Unknown error returned from cackey_login() (%i)", login_ret); @@ -4545,21 +5471,27 @@ cackey_slots[slotID].token_flags &= ~(CKF_USER_PIN_LOCKED | CKF_USER_PIN_COUNT_LOW | CKF_LOGIN_REQUIRED | CKF_USER_PIN_FINAL_TRY); cackey_sessions[hSession].state = CKS_RO_USER_FUNCTIONS; - mutex_retval = cackey_mutex_unlock(cackey_biglock); - if (mutex_retval != 0) { - CACKEY_DEBUG_PRINTF("Error. Unlocking failed."); + if (lock_mutex) { + mutex_retval = cackey_mutex_unlock(cackey_biglock); + if (mutex_retval != 0) { + CACKEY_DEBUG_PRINTF("Error. Unlocking failed."); - return(CKR_GENERAL_ERROR); + return(CKR_GENERAL_ERROR); + } } CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK); return(CKR_OK); } + +CK_DEFINE_FUNCTION(CK_RV, C_Login)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) { + return(_C_LoginMutexArg(hSession, userType, pPin, ulPinLen, 1)); +} CK_DEFINE_FUNCTION(CK_RV, C_Logout)(CK_SESSION_HANDLE hSession) { CK_SLOT_ID slotID; int mutex_retval; @@ -4607,11 +5539,10 @@ return(CKR_GENERAL_ERROR); } cackey_sessions[hSession].state = CKS_RO_PUBLIC_SESSION; - cackey_slots[slotID].token_flags = CKF_LOGIN_REQUIRED; mutex_retval = cackey_mutex_unlock(cackey_biglock); if (mutex_retval != 0) { CACKEY_DEBUG_PRINTF("Error. Unlocking failed."); @@ -5020,10 +5951,14 @@ struct cackey_identity *curr_id; CK_ATTRIBUTE *curr_attr; CK_ULONG curr_id_idx, curr_out_id_idx, curr_attr_idx, sess_attr_idx; CK_ULONG matched_count, prev_matched_count; int mutex_retval; +#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST + struct timeval start, end; + uint64_t start_int, end_int; +#endif CACKEY_DEBUG_PRINTF("Called."); if (!cackey_initialized) { CACKEY_DEBUG_PRINTF("Error. Not initialized."); @@ -5084,10 +6019,14 @@ CACKEY_DEBUG_PRINTF("Error. Search not active."); return(CKR_OPERATION_NOT_INITIALIZED); } + +#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST + gettimeofday(&start, NULL); +#endif curr_out_id_idx = 0; for (curr_id_idx = cackey_sessions[hSession].search_curr_id; curr_id_idx < cackey_sessions[hSession].identities_count && ulMaxObjectCount; curr_id_idx++) { curr_id = &cackey_sessions[hSession].identities[curr_id_idx]; @@ -5129,10 +6068,17 @@ CACKEY_DEBUG_PRINTF(" ... Not all %i (only found %i) attributes checked for found, not adding identity:%i", (int) cackey_sessions[hSession].search_query_count, (int) matched_count, (int) curr_id_idx); } } cackey_sessions[hSession].search_curr_id = curr_id_idx; *pulObjectCount = curr_out_id_idx; + +#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST + gettimeofday(&end, NULL); + start_int = (start.tv_sec * 1000000) + start.tv_usec; + end_int = (end.tv_sec * 1000000) + end.tv_usec; + fprintf(stderr, "Search took %lu microseconds\n", (unsigned long) (end_int - start_int)); +#endif mutex_retval = cackey_mutex_unlock(cackey_biglock); if (mutex_retval != 0) { CACKEY_DEBUG_PRINTF("Error. Unlocking failed."); @@ -5516,18 +6462,26 @@ switch (cackey_sessions[hSession].decrypt_mechanism) { case CKM_RSA_PKCS: /* Ask card to decrypt */ buflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].decrypt_identity, pEncryptedPart, ulEncryptedPartLen, buf, sizeof(buf), 0, 1); + + if (buflen == CACKEY_PCSC_E_NEEDLOGIN && cackey_pin_command != NULL) { + if (_C_LoginMutexArg(hSession, CKU_USER, NULL, 0, 0) == CKR_OK) { + buflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].decrypt_identity, pEncryptedPart, ulEncryptedPartLen, buf, sizeof(buf), 0, 1); + } + } if (buflen < 0) { /* Decryption failed. */ if (buflen == CACKEY_PCSC_E_NEEDLOGIN) { retval = CKR_USER_NOT_LOGGED_IN; } else if (buflen == CACKEY_PCSC_E_TOKENABSENT) { retval = CKR_DEVICE_REMOVED; } else { + CACKEY_DEBUG_PRINTF("Failed to send APDU, error = %li", (long int) buflen); + retval = CKR_GENERAL_ERROR; } } else if (((unsigned long) buflen) > *pulPartLen && pPart) { /* Decrypted data too large */ retval = CKR_BUFFER_TOO_SMALL; @@ -6027,10 +6981,16 @@ switch (cackey_sessions[hSession].sign_mechanism) { case CKM_RSA_PKCS: /* Ask card to sign */ CACKEY_DEBUG_PRINTF("Asking to sign from identity %p in session %lu", (void *) cackey_sessions[hSession].sign_identity, (unsigned long) hSession); sigbuflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].sign_identity, cackey_sessions[hSession].sign_buf, cackey_sessions[hSession].sign_bufused, sigbuf, sizeof(sigbuf), 1, 0); + + if (sigbuflen == CACKEY_PCSC_E_NEEDLOGIN && cackey_pin_command != NULL) { + if (_C_LoginMutexArg(hSession, CKU_USER, NULL, 0, 0) == CKR_OK) { + sigbuflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].sign_identity, cackey_sessions[hSession].sign_buf, cackey_sessions[hSession].sign_bufused, sigbuf, sizeof(sigbuf), 1, 0); + } + } if (sigbuflen < 0) { /* Signing failed. */ if (sigbuflen == CACKEY_PCSC_E_NEEDLOGIN) { retval = CKR_USER_NOT_LOGGED_IN;