@@ -154,17 +154,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 @@ -694,18 +716,35 @@ # 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 +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; @@ -834,10 +873,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) { @@ -1203,10 +1247,12 @@ slot->transaction_depth = 0; slot->transaction_need_hw_lock = 0; slot->protocol = protocol; } + CACKEY_DEBUG_PRINTF("Returning in success"); + return(CACKEY_PCSC_S_OK); } /* * SYNPOSIS @@ -1389,11 +1435,11 @@ * 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; @@ -1418,14 +1464,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."); @@ -1438,24 +1488,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); } @@ -1645,10 +1713,181 @@ 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; + + 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); * @@ -2135,12 +2374,24 @@ if (dest == NULL) { dest = malloc(sizeof(*dest) * count); } for (idx = 0; idx < count; idx++) { - memcpy(dest[idx].applet, start[idx].applet, sizeof(dest[idx].applet)); - dest[idx].file = start[idx].file; + 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); @@ -2164,16 +2415,23 @@ * */ 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; unsigned long outidx = 0; + char *piv_label; cackey_ret transaction_ret; + ssize_t read_ret; + size_t buffer_len; int certs_resizable; int send_ret, select_ret; + int piv_key, piv = 0; + int idx; CACKEY_DEBUG_PRINTF("Called."); if (count == NULL) { CACKEY_DEBUG_PRINTF("count is NULL, returning in failure"); @@ -2192,11 +2450,10 @@ 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; } } @@ -2230,105 +2487,165 @@ } /* 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); + + outidx--; + + continue; + } + } + } 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; + certs = realloc(certs, sizeof(*certs) * (*count)); + } 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)); @@ -2356,17 +2673,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]; + 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."); @@ -2397,10 +2716,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); } @@ -2453,32 +2789,75 @@ /* 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; + + send_ret = cackey_send_apdu(slot, 0x10, NISTSP800_73_3_INSTR_GENAUTH, NISTSP800_78_3_ALGO_RSA2048, identity->pcsc_identity->card.piv.key_id, sizeof(dyn_auth_template), dyn_auth_template, 0x00, NULL, NULL, NULL); + 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; + } + + 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); + 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; } - tmpoutbuflen = outbuflen; - - send_ret = cackey_send_apdu(slot, GSCIS_CLASS_GLOBAL_PLATFORM, GSCIS_INSTR_SIGNDECRYPT, p1, 0x00, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen); if (send_ret != CACKEY_PCSC_S_OK) { CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- returning in error."); if (free_tmpbuf) { if (tmpbuf_s) { @@ -2487,12 +2866,21 @@ } /* End transaction */ cackey_end_transaction(slot); - if (respcode == 0x6982) { - CACKEY_DEBUG_PRINTF("Security status not satisified. Returning NEEDLOGIN"); + if (respcode == 0x6982 || respcode == 0x6e00) { + CACKEY_DEBUG_PRINTF("Security status not satisified (respcode = 0x%04x). Returning NEEDLOGIN", (int) respcode); + + cackey_mark_slot_reset(slot); + slot->token_flags = CKF_LOGIN_REQUIRED; + + return(CACKEY_PCSC_E_NEEDLOGIN); + } + + 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"); cackey_mark_slot_reset(slot); slot->token_flags = CKF_LOGIN_REQUIRED; return(CACKEY_PCSC_E_NEEDLOGIN); @@ -2535,10 +2923,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 with then 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!"); @@ -2631,14 +3048,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; } @@ -2647,13 +3067,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); @@ -2712,10 +3156,12 @@ 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) { CACKEY_DEBUG_PRINTF("SCardStatus() returned SCARD_E_INVALID_HANDLE, marking is not already connected and trying again"); @@ -2725,10 +3171,12 @@ 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() again"); atr_len = sizeof(atr); status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len); } @@ -3070,17 +3518,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; @@ -3614,11 +4066,11 @@ id_idx++; } } if (include_extra_certs) { - CACKEY_DEBUG_PRINTF("Including DoD Certificates on hardware slot"); + 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); @@ -3635,10 +4087,11 @@ CK_DEFINE_FUNCTION(CK_RV, C_Initialize)(CK_VOID_PTR pInitArgs) { CK_C_INITIALIZE_ARGS CK_PTR args; 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."); @@ -3678,20 +4131,34 @@ 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 *) "DoD Certificates"; + 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; @@ -3705,10 +4172,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); } @@ -4237,10 +4723,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; @@ -4700,13 +5190,16 @@ 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_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."); @@ -4754,19 +5247,74 @@ 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); + + 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 != 0) { + CACKEY_DEBUG_PRINTF("Error. %s: exited with non-zero status of %i", pincmd, pclose_ret); + + 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); + + 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 (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."); @@ -4773,10 +5321,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);