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