smartcard: PIN pad support
Nils Faerber
nils.faerber at kernelconcepts.de
Wed Jan 12 09:53:41 CET 2011
Hi!
I cannot say much about the patch - I just wanted to encourage you to:
Great thing that you did this! This is something that I have been
waiting for for a long time. I know that Werner is very busy with other
things so I am happy to see that someone else picked up the ball.
Thank you!
And I hope that this will become mainline GnuPG as soon as possible.
PS: But please check your email client... that patch that was attached
to your post is nice for reading but was not a proper attachment that
one could easily extract and apply...
Cheers
nils
Am 12.01.2011 06:40, schrieb NIIBE Yutaka:
> 2011-01-05 17:10, Werner Koch wrote:
>> On Wed, 5 Jan 2011 05:10, gniibe at fsij.org said:
>>
>>> * It is only used for VERIFY command.
>>>
>>> Yes, we have the functions iso7816_change_reference_data_kp and
>>> iso7816_reset_retry_counter_kp, but callers are not yet
>>> implemented to support PIN pad.
>>
>> Right. My fear is that a little bug in the code or one of the readers
>> turns the card into a brick (v1 cards) or renders the keys unusable (v2).
>> Thus this support would need extensive testing.
>
> Thanks for your reply. I understand the current situation.
>
> I think that iso7816_reset_retry_counter_kp is not needed
> (iso7816_verify_kp and iso7816_change_reference_data_kp only) because
> CCID protocol only defines PIN Verification and PIN Modification
> operations.
>
> I am testing GnuPG version 2 on Debian with pcsc-lite. Attached is a
> patch to enable pcsc-lite backend to support PINPAD input. Note that
> I don't think this is ready to merge (yet), because it's not that
> clean and there are code duplicates and interface mismatches, etc.
> This is to show it's not that far for pcsc-lite.
>
> For pcsc-lite (I guess that it's same for PCSC on Windows), the CCID
> message of PC_to_RDR_Secure (= 0x69) can be composed by SCardControl
> API.
>
>
> 8<-------------------------------------------------
>
> diff --git a/scd/Makefile.am b/scd/Makefile.am
> index 923ebfe..b2aab01 100644
> --- a/scd/Makefile.am
> +++ b/scd/Makefile.am
> @@ -22,7 +22,7 @@ if ! HAVE_W32_SYSTEM
> libexec_PROGRAMS = gnupg-pcsc-wrapper
> endif
>
> -AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common
> +AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common -I/usr/include/PCSC
>
> include $(top_srcdir)/am/cmacros.am
>
> diff --git a/scd/apdu.c b/scd/apdu.c
> index 80c933e..656801d 100644
> --- a/scd/apdu.c
> +++ b/scd/apdu.c
> @@ -33,6 +33,8 @@
> # include <fcntl.h>
> # include <pth.h>
> #endif
> +#include <winscard.h>
> +#include <reader.h>
>
>
> /* If requested include the definitions for the remote APDU protocol
> @@ -121,6 +123,9 @@ struct reader_table_s {
> int req_fd;
> int rsp_fd;
> pid_t pid;
> +#else
> + unsigned long verify_ioctl;
> + unsigned long modify_ioctl;
> #endif /*NEED_PCSC_WRAPPER*/
> } pcsc;
> #ifdef USE_G10CODE_RAPDU
> @@ -303,6 +308,13 @@ long (* DLSTDCALL pcsc_transmit) (unsigned long card,
> unsigned long *recv_len);
> long (* DLSTDCALL pcsc_set_timeout) (unsigned long context,
> unsigned long timeout);
> +long (* DLSTDCALL pcsc_control) (unsigned long card,
> + unsigned long control_code,
> + const void *send_buffer,
> + unsigned long send_len,
> + void *recv_buffer,
> + unsigned long recv_len,
> + unsigned long *bytes_returned);
>
>
> /* Prototypes. */
> @@ -311,6 +323,8 @@ static int reset_pcsc_reader (int slot);
> static int apdu_get_status_internal (int slot, int hang, int no_atr_reset,
> unsigned int *status,
> unsigned int *changed);
> +static int pcsc_check_keypad (int slot, int command, int pin_mode,
> + int pinlen_min, int pinlen_max, int pin_padlen);
>
>
>
> @@ -354,7 +368,7 @@ new_reader_slot (void)
> reader_table[reader].reset_reader = NULL;
> reader_table[reader].get_status_reader = NULL;
> reader_table[reader].send_apdu_reader = NULL;
> - reader_table[reader].check_keypad = NULL;
> + reader_table[reader].check_keypad = pcsc_check_keypad;
> reader_table[reader].dump_status_reader = NULL;
> reader_table[reader].set_progress_cb = NULL;
>
> @@ -1165,6 +1179,170 @@ pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
> #endif
> }
>
> +#ifdef NEED_PCSC_WRAPPER
> +static int
> +pcsc_control_wrapped (int slot, const unsigned char *controlbuf, size_t len,
> + unsigned char *buffer, size_t *buflen)
> +{
> + long err = PCSC_E_NOT_TRANSACTED;
> + reader_table_t slotp;
> + unsigned char msgbuf[9];
> + int i, n;
> + size_t full_len;
> +
> + slotp = reader_table + slot;
> +
> + msgbuf[0] = 0x06; /* CONTROL command. */
> + msgbuf[1] = (len >> 24);
> + msgbuf[2] = (len >> 16);
> + msgbuf[3] = (len >> 8);
> + msgbuf[4] = (len );
> + if ( writen (slotp->pcsc.req_fd, msgbuf, 5)
> + || writen (slotp->pcsc.req_fd, controlbuf, len))
> + {
> + log_error ("error sending PC/SC CONTROL request: %s\n",
> + strerror (errno));
> + goto command_failed;
> + }
> +
> + /* Read the response. */
> + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9)
> + {
> + log_error ("error receiving PC/SC CONTROL response: %s\n",
> + i? strerror (errno) : "premature EOF");
> + goto command_failed;
> + }
> + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4];
> + if (msgbuf[0] != 0x81 || len < 4)
> + {
> + log_error ("invalid response header from PC/SC received\n");
> + goto command_failed;
> + }
> + len -= 4; /* Already read the error code. */
> + err = PCSC_ERR_MASK ((msgbuf[5] << 24) | (msgbuf[6] << 16)
> + | (msgbuf[7] << 8 ) | msgbuf[8]);
> + if (err)
> + {
> + log_error ("pcsc_control failed: %s (0x%lx)\n",
> + pcsc_error_string (err), err);
> + return err;
> + }
> +
> + full_len = len;
> +
> + n = *buflen < len ? *buflen : len;
> + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != n)
> + {
> + log_error ("error receiving PC/SC CONTROL response: %s\n",
> + i? strerror (errno) : "premature EOF");
> + goto command_failed;
> + }
> + *buflen = n;
> +
> + full_len -= len;
> + if (full_len)
> + {
> + log_error ("pcsc_send_apdu: provided buffer too short - truncated\n");
> + err = PCSC_E_INVALID_VALUE;
> + }
> + /* We need to read any rest of the response, to keep the
> + protocol running. */
> + while (full_len)
> + {
> + unsigned char dummybuf[128];
> +
> + n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf);
> + if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n)
> + {
> + log_error ("error receiving PC/SC CONTROL response: %s\n",
> + i? strerror (errno) : "premature EOF");
> + goto command_failed;
> + }
> + full_len -= n;
> + }
> +
> + if (!err)
> + return 0;
> +
> + command_failed:
> + close (slotp->pcsc.req_fd);
> + close (slotp->pcsc.rsp_fd);
> + slotp->pcsc.req_fd = -1;
> + slotp->pcsc.rsp_fd = -1;
> + kill (slotp->pcsc.pid, SIGTERM);
> + slotp->pcsc.pid = (pid_t)(-1);
> + slotp->used = 0;
> + return err;
> +}
> +#endif /*NEED_PCSC_WRAPPER*/
> +
> +static int
> +pcsc_pinpad_verify (int slot, unsigned char *apdu, size_t apdulen,
> + unsigned char *buffer, size_t *buflen,
> + struct pininfo_s *pininfo)
> +{
> + long err;
> + PIN_VERIFY_STRUCTURE *pin_verify;
> + unsigned long len = sizeof (PIN_VERIFY_STRUCTURE) + apdulen - 1;
> +
> + if (!reader_table[slot].atrlen
> + && (err = reset_pcsc_reader (slot)))
> + return err;
> +
> + pin_verify = xtrymalloc (len);
> + if (!pin_verify)
> + return SW_HOST_OUT_OF_CORE;
> +
> + pin_verify->bTimerOut = 0x00;
> + pin_verify->bTimerOut2 = 0x00;
> + pin_verify->bmFormatString = 0x82; /* Byte, pos=0, left, ASCII. */
> + pin_verify->bmPINBlockString = 0x00;
> + pin_verify->bmPINLengthFormat = 0x00;
> + ((unsigned char *)&pin_verify->wPINMaxExtraDigit)[0] = pininfo->maxlen;
> + ((unsigned char *)&pin_verify->wPINMaxExtraDigit)[1] = pininfo->minlen;
> + pin_verify->bEntryValidationCondition = 0x02; /* Validation key pressed */
> + pin_verify->bNumberMessage = 0xff; /* Default */
> + /* LangId = 0x0409: US English */
> + ((unsigned char *)&pin_verify->wLangId)[0] = 0x09;
> + ((unsigned char *)&pin_verify->wLangId)[1] = 0x04;
> + pin_verify->bMsgIndex = 0x00;
> + pin_verify->bTeoPrologue[0] = 0x00;
> + pin_verify->bTeoPrologue[1] = 0x00;
> + pin_verify->bTeoPrologue[2] = 0x00;
> + ((unsigned char *)&pin_verify->ulDataLength)[0] = apdulen & 0xff;
> + ((unsigned char *)&pin_verify->ulDataLength)[1] = (apdulen >> 8) & 0xff;
> + ((unsigned char *)&pin_verify->ulDataLength)[2] = (apdulen >> 16) & 0xff;
> + ((unsigned char *)&pin_verify->ulDataLength)[3] = apdulen >> 24;
> + memcpy (pin_verify->abData, apdu, apdulen);
> +
> +#ifdef NEED_PCSC_WRAPPER
> + err = pcsc_control_wrapped (slot, (const unsigned char *)pin_verify,
> + len, buffer, buflen);
> +#else
> + err = pcsc_control (reader_table[slot].pcsc.card,
> + reader_table[slot].pcsc.verify_ioctl,
> + pin_verify, len, buffer, *buflen, buflen);
> +#endif
> +
> + if (err)
> + log_error ("pcsc_control failed: %s (0x%lx)\n",
> + pcsc_error_string (err), err);
> + xfree (pin_verify);
> + return pcsc_error_to_sw (err);
> +}
> +
> +static int
> +pcsc_check_keypad (int slot, int command, int pin_mode,
> + int pinlen_min, int pinlen_max, int pin_padlen)
> +{
> + (void)slot;
> + (void)command;
> + (void)pin_mode;
> + (void)pinlen_min;
> + (void)pinlen_max;
> + (void)pin_padlen;
> + return 0; /* Success */
> +}
>
> #ifndef NEED_PCSC_WRAPPER
> static int
> @@ -1261,6 +1439,14 @@ close_pcsc_reader (int slot)
>
> /* Connect a PC/SC card. */
> #ifndef NEED_PCSC_WRAPPER
> +/* Convert a big endian stored 4 byte value into an unsigned
> + integer. */
> +static unsigned int
> +convert_be_u32 (const unsigned char *buf)
> +{
> + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
> +}
> +
> static int
> connect_pcsc_card (int slot)
> {
> @@ -1314,6 +1500,28 @@ connect_pcsc_card (int slot)
> | APDU_CARD_PRESENT
> | APDU_CARD_ACTIVE);
> reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
> + reader_table[slot].pcsc.verify_ioctl = 0;
> + reader_table[slot].pcsc.modify_ioctl = 0;
> + err = pcsc_control (reader_table[slot].pcsc.card,
> + CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
> + reader, sizeof reader, &readerlen);
> + if (err)
> + log_error ("pcsc_control failed: %s (0x%lx)\n",
> + pcsc_error_string (err), err);
> + else
> + {
> + PCSC_TLV_STRUCTURE *pcsc_tlv;
> + int i;
> +
> + pcsc_tlv = (PCSC_TLV_STRUCTURE *)reader;
> + for (i = 0; i < readerlen / sizeof (PCSC_TLV_STRUCTURE); i++)
> + if (pcsc_tlv[i].tag == FEATURE_VERIFY_PIN_DIRECT)
> + reader_table[slot].pcsc.verify_ioctl
> + = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
> + else if (pcsc_tlv[i].tag == FEATURE_MODIFY_PIN_DIRECT)
> + reader_table[slot].pcsc.modify_ioctl
> + = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
> + }
> }
> }
>
> @@ -2438,6 +2646,7 @@ apdu_open_reader (const char *portstr)
> pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
> pcsc_transmit = dlsym (handle, "SCardTransmit");
> pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
> + pcsc_control = dlsym (handle, "SCardControl");
>
> if (!pcsc_establish_context
> || !pcsc_release_context
> @@ -2450,12 +2659,13 @@ apdu_open_reader (const char *portstr)
> || !pcsc_begin_transaction
> || !pcsc_end_transaction
> || !pcsc_transmit
> + || !pcsc_control
> /* || !pcsc_set_timeout */)
> {
> /* Note that set_timeout is currently not used and also not
> available under Windows. */
> log_error ("apdu_open_reader: invalid PC/SC driver "
> - "(%d%d%d%d%d%d%d%d%d%d%d%d)\n",
> + "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
> !!pcsc_establish_context,
> !!pcsc_release_context,
> !!pcsc_list_readers,
> @@ -2467,7 +2677,8 @@ apdu_open_reader (const char *portstr)
> !!pcsc_begin_transaction,
> !!pcsc_end_transaction,
> !!pcsc_transmit,
> - !!pcsc_set_timeout );
> + !!pcsc_set_timeout,
> + !!pcsc_control );
> dlclose (handle);
> return -1;
> }
> @@ -3301,6 +3512,7 @@ apdu_send_simple (int slot, int extended_mode,
>
>
> /* Same as apdu_send_simple but uses the keypad of the reader. */
> +#if 0
> int
> apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
> int lc, const char *data,
> @@ -3316,7 +3528,49 @@ apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
> return send_le (slot, class, ins, p0, p1, lc, data, -1,
> NULL, NULL, &pininfo, 0);
> }
> +#else
> +int
> +apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
> + int lc, const char *data,
> + int pin_mode,
> + int pinlen_min, int pinlen_max, int pin_padlen)
> +{
> + unsigned char apdu[4];
> + size_t apdulen;
> + struct pininfo_s pininfo;
> + unsigned char result[2];
> + size_t resultlen = 2;
> + long rc;
> + int sw;
> +
> + (void)data;
> + (void)lc;
> + apdulen = 0;
> + apdu[apdulen++] = class;
> + apdu[apdulen++] = ins;
> + apdu[apdulen++] = p0;
> + apdu[apdulen++] = p1;
> + pininfo.mode = pin_mode;
> + pininfo.minlen = pinlen_min;
> + pininfo.maxlen = pinlen_max;
> + pininfo.padlen = pin_padlen;
> +
> + if ((sw = lock_slot (slot)))
> + return sw;
>
> + rc = pcsc_pinpad_verify (slot, apdu, apdulen, result, &resultlen, &pininfo);
> + if (rc || resultlen < 2)
> + {
> + log_info ("apdu_send_simple_kp(%d) failed: %s\n",
> + slot, apdu_strerror (rc));
> + unlock_slot (slot);
> + return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
> + }
> + sw = (result[resultlen-2] << 8) | result[resultlen-1];
> + unlock_slot (slot);
> + return sw;
> +}
> +#endif
>
> /* This is a more generic version of the apdu sending routine. It
> takes an already formatted APDU in APDUDATA or length APDUDATALEN
> diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c
> index a7b2198..53ecb4e 100644
> --- a/scd/pcsc-wrapper.c
> +++ b/scd/pcsc-wrapper.c
> @@ -47,6 +47,8 @@
> #include <stdarg.h>
> #include <assert.h>
> #include <dlfcn.h>
> +#include <winscard.h>
> +#include <reader.h>
>
>
> #define PGM "pcsc-wrapper"
> @@ -133,6 +135,8 @@ static unsigned long pcsc_context; /* The current PC/CS context. */
> static char *current_rdrname;
> static unsigned long pcsc_card;
> static unsigned long pcsc_protocol;
> +static unsigned long verify_ioctl;
> +static unsigned long modify_ioctl;
> static unsigned char current_atr[33];
> static size_t current_atrlen;
>
> @@ -178,6 +182,13 @@ long (* pcsc_transmit) (unsigned long card,
> unsigned long *recv_len);
> long (* pcsc_set_timeout) (unsigned long context,
> unsigned long timeout);
> +long (* pcsc_control) (unsigned long card,
> + unsigned long control_code,
> + const void *send_buffer,
> + unsigned long send_len,
> + void *recv_buffer,
> + unsigned long recv_len,
> + unsigned long *bytes_returned);
>
>
>
> @@ -335,6 +346,7 @@ load_pcsc_driver (const char *libname)
> pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
> pcsc_transmit = dlsym (handle, "SCardTransmit");
> pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
> + pcsc_control = dlsym (handle, "SCardControl");
>
> if (!pcsc_establish_context
> || !pcsc_release_context
> @@ -347,13 +359,14 @@ load_pcsc_driver (const char *libname)
> || !pcsc_begin_transaction
> || !pcsc_end_transaction
> || !pcsc_transmit
> + || !pcsc_control
> /* || !pcsc_set_timeout */)
> {
> /* Note that set_timeout is currently not used and also not
> available under Windows. */
> fprintf (stderr,
> "apdu_open_reader: invalid PC/SC driver "
> - "(%d%d%d%d%d%d%d%d%d%d%d%d)\n",
> + "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
> !!pcsc_establish_context,
> !!pcsc_release_context,
> !!pcsc_list_readers,
> @@ -365,7 +378,8 @@ load_pcsc_driver (const char *libname)
> !!pcsc_begin_transaction,
> !!pcsc_end_transaction,
> !!pcsc_transmit,
> - !!pcsc_set_timeout );
> + !!pcsc_set_timeout,
> + !!pcsc_control);
> dlclose (handle);
> exit (1);
> }
> @@ -373,6 +387,14 @@ load_pcsc_driver (const char *libname)
>
>
>
> +/* Convert a big endian stored 4 byte value into an unsigned
> + integer. */
> +static unsigned int
> +convert_be_u32 (const unsigned char *buf)
> +{
> + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
> +}
> +
>
> /* Handle a open request. The argument is expected to be a string
> with the port identification. ARGBUF is always guaranteed to be
> @@ -504,6 +526,30 @@ handle_open (unsigned char *argbuf, size_t arglen)
> }
> memcpy (current_atr, atr, atrlen);
> current_atrlen = atrlen;
> +
> + verify_ioctl = 0;
> + modify_ioctl = 0;
> + err = pcsc_control (pcsc_card,
> + CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
> + reader, sizeof reader, &readerlen);
> + if (err)
> + {
> + if (verbose)
> + fprintf (stderr, PGM": pcsc_control failed: %s (0x%lx)\n",
> + pcsc_error_string (err), err);
> + }
> + else
> + {
> + PCSC_TLV_STRUCTURE *pcsc_tlv;
> + int i;
> +
> + pcsc_tlv = (PCSC_TLV_STRUCTURE *)reader;
> + for (i = 0; i < readerlen / sizeof (PCSC_TLV_STRUCTURE); i++)
> + if (pcsc_tlv[i].tag == FEATURE_VERIFY_PIN_DIRECT)
> + verify_ioctl = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
> + else if (pcsc_tlv[i].tag == FEATURE_MODIFY_PIN_DIRECT)
> + modify_ioctl = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
> + }
> }
> }
>
> @@ -723,6 +769,29 @@ handle_transmit (unsigned char *argbuf, size_t arglen)
>
>
> static void
> +handle_control (unsigned char *argbuf, size_t arglen)
> +{
> + long err;
> + unsigned long recv_len = 1024;
> + unsigned char buffer[1024];
> +
> + recv_len = sizeof (buffer);
> + err = pcsc_control (pcsc_card, verify_ioctl, argbuf, arglen,
> + buffer, recv_len, &recv_len);
> + if (err)
> + {
> + if (verbose)
> + fprintf (stderr, PGM": pcsc_control failed: %s (0x%lx)\n",
> + pcsc_error_string (err), err);
> + request_failed (err);
> + return;
> + }
> + request_succeeded (buffer, recv_len);
> +}
> +
> +
> +
> +static void
> print_version (int with_help)
> {
> fputs (MYVERSION_LINE "\n"
> @@ -832,6 +901,10 @@ main (int argc, char **argv)
> handle_reset (argbuffer, arglen);
> break;
>
> + case 6:
> + handle_control (argbuffer, arglen);
> + break;
> +
> default:
> fprintf (stderr, PGM ": invalid request 0x%02X\n", c);
> exit (1);
>
--
kernel concepts GbR Tel: +49-271-771091-12
Sieghuetter Hauptweg 48
D-57072 Siegen Mob: +49-176-21024535
http://www.kernelconcepts.de
More information about the Gnupg-devel
mailing list