From 59d7a54e72aec2a64ec58ac66d550c17be91b27c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 5 Mar 2009 19:19:37 +0000 Subject: [PATCH] New PIN Callback attributes in gpg-agent. Common prompts for keypad and simple card reader. More support for Netkey cards; PIN management works now. --- NEWS | 4 + agent/ChangeLog | 6 + agent/command.c | 2 +- agent/divert-scd.c | 40 +++- agent/findkey.c | 2 +- configure.ac | 4 +- doc/ChangeLog | 9 + doc/gpg.texi | 2 +- doc/help.txt | 8 +- scd/ChangeLog | 27 +++ scd/app-nks.c | 586 +++++++++++++++++++++++++++++++++++++++------ scd/app-openpgp.c | 125 +++++----- scd/command.c | 35 ++- 13 files changed, 687 insertions(+), 163 deletions(-) diff --git a/NEWS b/NEWS index a733b0a9e..afeba6861 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +Noteworthy changes in version 2.0.12 +------------------------------------------------- + + Noteworthy changes in version 2.0.11 (2009-03-03) ------------------------------------------------- diff --git a/agent/ChangeLog b/agent/ChangeLog index 9e8bf6133..e016377a1 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,9 @@ +2009-03-05 Werner Koch + + * divert-scd.c (getpin_cb): Support flag 'P'. Change max_digits + from 8 to 16. Append a message about keypads. + * findkey.c (unprotect): Change max digits to 16. + 2009-03-02 Werner Koch * command.c (cmd_getinfo): Add subcommand "scd_running". diff --git a/agent/command.c b/agent/command.c index bf2db1c76..9ebcd091f 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1061,7 +1061,7 @@ cmd_learn (assuan_context_t ctx, char *line) /* PASSWD - Change the passphrase/PID for the key identified by keygrip in LINE. */ + Change the passphrase/PIN for the key identified by keygrip in LINE. */ static int cmd_passwd (assuan_context_t ctx, char *line) { diff --git a/agent/divert-scd.c b/agent/divert-scd.c index fc96fd1ca..a583f1a61 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -1,5 +1,5 @@ /* divert-scd.c - divert operations to the scdaemon - * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2002, 2003, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -181,10 +181,11 @@ encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo, Flags: - 'N' = New PIN, this requests a second prompt to repeat the the + 'N' = New PIN, this requests a second prompt to repeat the PIN. If the PIN is not correctly repeated it starts from all over. - 'A' = The PIN is an Admin PIN, SO-PIN, PUK or alike. + 'A' = The PIN is an Admin PIN, SO-PIN or alike. + 'P' = The PIN is a PUK (Personal Unblocking Key). 'R' = The PIN is a Reset Code. Example: @@ -204,6 +205,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) int any_flags = 0; int newpin = 0; int resetcode = 0; + int is_puk = 0; const char *again_text = NULL; const char *prompt = "PIN"; @@ -217,6 +219,13 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) { if (*s == 'A') prompt = _("Admin PIN"); + else if (*s == 'P') + { + /* TRANSLATORS: A PUK is the Personal Unblocking Code + used to unblock a PIN. */ + prompt = _("PUK"); + is_puk = 1; + } else if (*s == 'N') newpin = 1; else if (*s == 'R') @@ -242,7 +251,22 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) } else if (maxbuf == 1) /* Open the pinentry. */ { - rc = agent_popup_message_start (ctrl, info, NULL); + if (info) + { + char *desc; + + if ( asprintf (&desc, + _("%s%%0A%%0AUse the reader's keypad for input."), + info) < 0 ) + rc = gpg_error_from_syserror (); + else + { + rc = agent_popup_message_start (ctrl, desc, NULL); + xfree (desc); + } + } + else + rc = agent_popup_message_start (ctrl, NULL, NULL); } else rc = gpg_error (GPG_ERR_INV_VALUE); @@ -258,7 +282,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) return gpg_error_from_syserror (); pi->max_length = maxbuf-1; pi->min_digits = 0; /* we want a real passphrase */ - pi->max_digits = 8; + pi->max_digits = 16; pi->max_tries = 3; if (any_flags) @@ -277,17 +301,21 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) } pi2->max_length = maxbuf-1; pi2->min_digits = 0; - pi2->max_digits = 8; + pi2->max_digits = 16; pi2->max_tries = 1; rc = agent_askpin (ctrl, (resetcode? _("Repeat this Reset Code"): + is_puk? + _("Repeat this PUK"): _("Repeat this PIN")), prompt, NULL, pi2); if (!rc && strcmp (pi->pin, pi2->pin)) { again_text = (resetcode? N_("Reset Code not correctly repeated; try again"): + is_puk? + N_("PUK not correctly repeated; try again"): N_("PIN not correctly repeated; try again")); xfree (pi2); xfree (pi); diff --git a/agent/findkey.c b/agent/findkey.c index ddcd26267..0bb6afdcb 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -367,7 +367,7 @@ unprotect (ctrl_t ctrl, const char *desc_text, return gpg_error_from_syserror (); pi->max_length = 100; pi->min_digits = 0; /* we want a real passphrase */ - pi->max_digits = 8; + pi->max_digits = 16; pi->max_tries = 3; pi->check_cb = try_unprotect_cb; arg.ctrl = ctrl; diff --git a/configure.ac b/configure.ac index 8afadf7a1..0b04c5d10 100644 --- a/configure.ac +++ b/configure.ac @@ -24,8 +24,8 @@ min_automake_version="1.10" # Remember to change the version number immediately *after* a release. # Set my_issvn to "yes" for non-released code. Remember to run an # "svn up" and "autogen.sh" right before creating a distribution. -m4_define([my_version], [2.0.11]) -m4_define([my_issvn], [no]) +m4_define([my_version], [2.0.12]) +m4_define([my_issvn], [yes]) m4_define([svn_revision], m4_esyscmd([printf "%d" $(svn info 2>/dev/null \ | sed -n '/^Revision:/ s/[^0-9]//gp'|head -1)])) diff --git a/doc/ChangeLog b/doc/ChangeLog index 780d71377..09acabcf8 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,12 @@ +2009-03-04 Werner Koch + + * help.txt (gpg.keygen.size): Add a link to web page. + +2009-03-03 Werner Koch + + * gpg.texi (Operational GPG Commands): "merge-only" is an + import-option. Reported by Joseph Oreste Bruni. + 2009-03-02 Werner Koch * gpg-agent.texi (Invoking GPG-AGENT): Modernized instructions. diff --git a/doc/gpg.texi b/doc/gpg.texi index d514c5631..c66b3df6e 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -425,7 +425,7 @@ Import/merge keys. This adds the given keys to the keyring. The fast version is currently just a synonym. There are a few other options which control how this command works. -Most notable here is the @option{--keyserver-options merge-only} option +Most notable here is the @option{--import-options merge-only} option which does not insert new keys but does only the merging of new signatures, user-IDs and subkeys. diff --git a/doc/help.txt b/doc/help.txt index a3322ee67..c4cc3d11a 100644 --- a/doc/help.txt +++ b/doc/help.txt @@ -135,7 +135,13 @@ Please consult your security expert first. .gpg.keygen.size -Enter the size of the key. +Enter the size of the key. + +The suggested default is usually a good choice. + +If you want to use a large key size, for example 4096 bit, please +think again whether it really makes sense for you. You may want +to view the web page http://www.xkcd.com/538/ . . .gpg.keygen.size.huge.okay diff --git a/scd/ChangeLog b/scd/ChangeLog index bcfd65b4a..b1ec7479e 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,30 @@ +2009-03-05 Werner Koch + + * app-openpgp.c (verify_a_chv): Remove special case for keypads. + (verify_chv3): Ditto. + + * app-nks.c (get_chv_status): New. + (parse_pwidstr): New. + (verify_pin): Add args PWID and DESC and use them. Remove the + CHV1 caching. + (do_change_pin): Allow PIN selection and add reset mode. + (do_learn_status): Use NKS-NKS3 tag for TCOS 3 cards. + (do_readcert, do_sign): Allow NKS-NKS3 tag. + +2009-03-04 Werner Koch + + * app-nks.c (do_getattr): New. + (app_select_nks): Register it. + (verify_pin): Factor some code out to... + (basic_pin_checks): New. + (do_change_pin): Call the basic check. + (app_select_nks): Move AID to .. + (aid_nks): .. new. + (aid_sigg): New. + (switch_application): New. + (do_getattr, do_learn_status, do_readcert, do_sign, do_decipher) + (do_change_pin, do_check_pin): Make sure we are in NKS mode. + 2009-03-03 Werner Koch * command.c (scd_command_handler): Remove dereference of STOPME diff --git a/scd/app-nks.c b/scd/app-nks.c index edc179b5f..6aa205697 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -17,6 +17,27 @@ * along with this program; if not, see . */ +/* Notes: + + - This is still work in progress. We are now targeting TCOS 3 cards + but try to keep compatibility to TCOS 2. Both are not fully + working as of now. TCOS 3 PIN management seems to work. Use GPA + from SVN trunk to test it. + + - If required, we automagically switch between the NKS application + and the SigG application. This avoids to use the DINSIG + application which is somewhat limited, has no support for Secure + Messaging as required by TCOS 3 and has no way to change the PIN + or even set the NullPIN. + + - We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer + cards. This is because the NKS application has moved to DF02 with + TCOS 3 and thus we better use a DF independent tag. + + - We use only the global PINs for the NKS application. + + */ + #include #include #include @@ -30,9 +51,15 @@ #include "iso7816.h" #include "app-common.h" #include "tlv.h" +#include "apdu.h" + +static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; +static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; + static struct { + int is_sigg; /* Valid for SigG application. */ int fid; /* File ID. */ int nks_ver; /* 0 for NKS version 2, 3 for version 3. */ int certtype; /* Type of certificate or 0 if it is not a certificate. */ @@ -40,20 +67,23 @@ static struct int issignkey; /* True if file is a key usable for signing. */ int isenckey; /* True if file is a key usable for decryption. */ } filelist[] = { - { 0x4531, 0, 0, 0xC000, 1, 0 }, /* EF_PK.NKS.SIG */ - { 0xC000, 0, 101 }, /* EF_C.NKS.SIG */ - { 0x4331, 0, 100 }, - { 0x4332, 0, 100 }, - { 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */ - { 0x45B1, 0, 0, 0xC200, 0, 1 }, /* EF_PK.NKS.ENC */ - { 0xC200, 0, 101 }, /* EF_C.NKS.ENC */ - { 0x43B1, 0, 100 }, - { 0x43B2, 0, 100 }, - { 0x4571, 3, 0, 0xc500, 0, 0 }, /* EF_PK.NKS.AUT */ - { 0xC500, 3, 101 }, /* EF_C.NKS.AUT */ - { 0x45B2, 3, 0, 0xC201, 0, 1 }, /* EF_PK.NKS.ENC1024 */ - { 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */ - { 0 } + { 0, 0x4531, 0, 0, 0xC000, 1, 0 }, /* EF_PK.NKS.SIG */ + { 1, 0x4531, 3, 0, 0x0000, 1, 1 }, /* EF_PK.CH.SIG */ + { 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */ + { 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */ + { 0, 0x4331, 0, 100 }, + { 0, 0x4332, 0, 100 }, + { 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */ + { 0, 0x45B1, 0, 0, 0xC200, 0, 1 }, /* EF_PK.NKS.ENC */ + { 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */ + { 0, 0x43B1, 0, 100 }, + { 0, 0x43B2, 0, 100 }, + { 0, 0x4571, 3, 0, 0xc500, 0, 0 }, /* EF_PK.NKS.AUT */ + { 0, 0xC500, 3, 101 }, /* EF_C.NKS.AUT */ + { 0, 0x45B2, 3, 0, 0xC201, 0, 1 }, /* EF_PK.NKS.ENC1024 */ + { 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */ +/* { 1, 0xB000, 3, ... */ + { 0, 0 } }; @@ -62,9 +92,13 @@ static struct struct app_local_s { int nks_version; /* NKS version. */ + int sigg_active; /* True if switched to the SigG application. */ }; + +static gpg_error_t switch_application (app_t app, int enable_sigg); + /* Release local data. */ @@ -146,6 +180,134 @@ keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr) } +/* TCOS responds to a verify with empty data (i.e. without the Lc + byte) with the status of the PIN. PWID is the PIN ID, If SIGG is + true, the application is switched into SigG mode. + Returns: + -1 = Error retrieving the data, + -2 = No such PIN, + -3 = PIN blocked, + -4 = NullPIN activ, + n >= 0 = Number of verification attempts left. */ +static int +get_chv_status (app_t app, int sigg, int pwid) +{ + unsigned char *result = NULL; + size_t resultlen; + char command[4]; + int rc; + + if (switch_application (app, sigg)) + return sigg? -2 : -1; /* No such PIN / General error. */ + + command[0] = 0x00; + command[1] = 0x20; + command[2] = 0x00; + command[3] = pwid; + + if (apdu_send_direct (app->slot, command, 4, 0, &result, &resultlen)) + rc = -1; /* Error. */ + else if (resultlen < 2) + rc = -1; /* Error. */ + else + { + unsigned int sw = ((result[resultlen-2] << 8) | result[resultlen-1]); + + if (sw == 0x6a88) + rc = -2; /* No such PIN. */ + else if (sw == 0x6983) + rc = -3; /* PIN is blocked. */ + else if (sw == 0x6985) + rc = -4; /* NullPIN is activ. */ + else if ((sw & 0xfff0) == 0x63C0) + rc = (sw & 0x000f); /* PIN has N tries left. */ + else + rc = -1; /* Other error. */ + } + xfree (result); + + return rc; +} + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int special; + } table[] = { + { "$AUTHKEYID", 1 }, + { "NKS-VERSION", 2 }, + { "CHV-STATUS", 3 }, + { NULL, 0 } + }; + gpg_error_t err = 0; + int idx; + char buffer[100]; + + err = switch_application (app, 0); + if (err) + return err; + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + switch (table[idx].special) + { + case 1: /* $AUTHKEYID */ + { + /* NetKey 3.0 cards define this key for authentication. + FIXME: We don't have the readkey command, so this + information is pretty useless. */ + char const tmp[] = "NKS-NKS3.4571"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + break; + + case 2: /* NKS-VERSION */ + snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version); + send_status_info (ctrl, table[idx].name, + buffer, strlen (buffer), NULL, 0); + break; + + case 3: /* CHV-STATUS */ + { + /* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the + two global passwords followed by the two SigG passwords. + For the values, see the function get_chv_status. */ + int tmp[4]; + + /* We use a helper array so that we can control that there is + no superfluous application switch. Note that PW2.CH.SIG + really has the identifier 0x83 and not 0x82 as one would + expect. */ + tmp[0] = get_chv_status (app, 0, 0x00); + tmp[1] = get_chv_status (app, 0, 0x01); + tmp[2] = get_chv_status (app, 1, 0x81); + tmp[3] = get_chv_status (app, 1, 0x83); + snprintf (buffer, sizeof buffer, + "%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]); + send_status_info (ctrl, table[idx].name, + buffer, strlen (buffer), NULL, 0); + } + break; + + + default: + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + break; + } + + return err; +} + + + static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) @@ -154,12 +316,19 @@ do_learn_status (app_t app, ctrl_t ctrl) char ct_buf[100], id_buf[100]; int i; - /* Output information about all useful objects. */ + err = switch_application (app, 0); + if (err) + return err; + + /* Output information about all useful objects in the NKS application. */ for (i=0; filelist[i].fid; i++) { if (filelist[i].nks_ver > app->app_local->nks_version) continue; + if (filelist[i].is_sigg) + continue; + if (filelist[i].certtype) { size_t len; @@ -171,8 +340,10 @@ do_learn_status (app_t app, ctrl_t ctrl) /* FIXME: We should store the length in the application's context so that a following readcert does only need to read that many bytes. */ - sprintf (ct_buf, "%d", filelist[i].certtype); - sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid); + snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype); + snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", + app->app_local->nks_version < 3? "DF01":"NKS3", + filelist[i].fid); send_status_info (ctrl, "CERTINFO", ct_buf, strlen (ct_buf), id_buf, strlen (id_buf), @@ -189,7 +360,9 @@ do_learn_status (app_t app, ctrl_t ctrl) filelist[i].fid, gpg_strerror (err)); else { - sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid); + snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", + app->app_local->nks_version < 3? "DF01":"NKS3", + filelist[i].fid); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, id_buf, strlen (id_buf), @@ -198,6 +371,59 @@ do_learn_status (app_t app, ctrl_t ctrl) } } + err = switch_application (app, 1); + if (err) + return 0; /* Silently ignore if we can't swicth to SigG. */ + + for (i=0; filelist[i].fid; i++) + { + if (filelist[i].nks_ver > app->app_local->nks_version) + continue; + + if (!filelist[i].is_sigg) + continue; + + if (filelist[i].certtype) + { + size_t len; + + len = app_help_read_length_of_cert (app->slot, + filelist[i].fid, NULL); + if (len) + { + /* FIXME: We should store the length in the application's + context so that a following readcert does only need to + read that many bytes. */ + snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype); + snprintf (id_buf, sizeof id_buf, "NKS-SIGG.%04X", + filelist[i].fid); + send_status_info (ctrl, "CERTINFO", + ct_buf, strlen (ct_buf), + id_buf, strlen (id_buf), + NULL, (size_t)0); + } + } + else if (filelist[i].iskeypair) + { + char gripstr[40+1]; + + err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr); + if (err) + log_error ("can't get keygrip from FID 0x%04X: %s\n", + filelist[i].fid, gpg_strerror (err)); + else + { + snprintf (id_buf, sizeof id_buf, "NKS-SIGG.%04X", + filelist[i].fid); + send_status_info (ctrl, "KEYPAIRINFO", + gripstr, 40, + id_buf, strlen (id_buf), + NULL, (size_t)0); + } + } + } + + return 0; } @@ -223,7 +449,16 @@ do_readcert (app_t app, const char *certid, *cert = NULL; *certlen = 0; - if (strncmp (certid, "NKS-DF01.", 9) ) + + err = switch_application (app, 0); + if (err) + return err; + + if (!strncmp (certid, "NKS-NKS3.", 9)) + ; + else if (!strncmp (certid, "NKS-DF01.", 9)) + ; + else return gpg_error (GPG_ERR_INV_ID); certid += 9; if (!hexdigitp (certid) || !hexdigitp (certid+1) @@ -331,21 +566,34 @@ do_readcert (app_t app, const char *certid, } +static gpg_error_t +basic_pin_checks (const char *pinvalue, int minlen, int maxlen) +{ + if (strlen (pinvalue) < minlen) + { + log_error ("PIN is too short; minimum length is %d\n", minlen); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (strlen (pinvalue) > maxlen) + { + log_error ("PIN is too large; maximum length is %d\n", maxlen); + return gpg_error (GPG_ERR_BAD_PIN); + } + return 0; +} + + /* Verify the PIN if required. */ static gpg_error_t -verify_pin (app_t app, +verify_pin (app_t app, int pwid, const char *desc, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { iso7816_pininfo_t pininfo; int rc; - /* Note that force_chv1 is never set but we do it here anyway so - that other applications may reuse this function. For example it - makes sense to set force_chv1 for German signature law cards. - NKS is very similar to the DINSIG draft standard. */ - if ( app->did_chv1 && !app->force_chv1 ) - return 0; /* No need to verify it again. */ + if (!desc) + desc = "PIN"; memset (&pininfo, 0, sizeof pininfo); pininfo.mode = 1; @@ -375,7 +623,7 @@ verify_pin (app_t app, { char *pinvalue; - rc = pincb (pincb_arg, "PIN", &pinvalue); + rc = pincb (pincb_arg, desc, &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); @@ -384,24 +632,14 @@ verify_pin (app_t app, /* The following limits are due to TCOS but also defined in the NKS specs. */ - if (strlen (pinvalue) < pininfo.minlen) + rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen); + if (rc) { - log_error ("PIN is too short; minimum length is %d\n", - pininfo.minlen); xfree (pinvalue); - return gpg_error (GPG_ERR_BAD_PIN); - } - else if (strlen (pinvalue) > pininfo.maxlen) - { - log_error ("PIN is too large; maximum length is %d\n", - pininfo.maxlen); - xfree (pinvalue); - return gpg_error (GPG_ERR_BAD_PIN); + return rc; } - /* Although it is possible to use a local PIN, we use the global - PIN for this application. */ - rc = iso7816_verify (app->slot, 0, pinvalue, strlen (pinvalue)); + rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue)); xfree (pinvalue); } @@ -413,7 +651,6 @@ verify_pin (app_t app, log_error ("verify PIN failed\n"); return rc; } - app->did_chv1 = 1; return 0; } @@ -447,9 +684,17 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, if (indatalen != 20 && indatalen != 16 && indatalen != 35) return gpg_error (GPG_ERR_INV_VALUE); + rc = switch_application (app, 0); + if (rc) + return rc; + /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ - if (strncmp (keyidstr, "NKS-DF01.", 9) ) + if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) + ; + else return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) @@ -490,7 +735,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, memcpy (data+15, indata, indatalen); } - rc = verify_pin (app, pincb, pincb_arg); + rc = verify_pin (app, 0, NULL, pincb, pincb_arg); if (!rc) rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); return rc; @@ -519,9 +764,17 @@ do_decipher (app_t app, const char *keyidstr, if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); + rc = switch_application (app, 0); + if (rc) + return rc; + /* Check that the provided ID is valid. This is not really needed but we do it to to enforce correct usage by the caller. */ - if (strncmp (keyidstr, "NKS-DF01.", 9) ) + if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) + ; + else return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) @@ -542,7 +795,7 @@ do_decipher (app_t app, const char *keyidstr, 0xC1, 0xB8, mse_parm, sizeof mse_parm); if (!rc) - rc = verify_pin (app, pincb, pincb_arg); + rc = verify_pin (app, 0, NULL, pincb, pincb_arg); if (!rc) rc = iso7816_decipher (app->slot, indata, indatalen, 0x81, outdata, outdatalen); @@ -550,67 +803,221 @@ do_decipher (app_t app, const char *keyidstr, } -/* Handle the PASSWD command. CHVNOSTR is currently ignored; we - always use VHV0. RESET_MODE is not yet implemented. */ + +/* Parse a password ID string. Returns NULL on error or a string + suitable as passpahrse prompt on success. On success stores the + reference value for the password at R_PWID and a flag indicating + that the SigG application is to be used at R_SIGG. If NEW_MODE is + true, the returned description is suitable for a new Password. + Supported values for PWIDSTR are: + + PW1.CH - Global password 1 + PW2.CH - Global password 2 + PW1.CH.SIG - SigG password 1 + PW2.CH.SIG - SigG password 2 + */ +static const char * +parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid) +{ + const char *desc; + + if (!pwidstr) + desc = NULL; + else if (!strcmp (pwidstr, "PW1.CH")) + { + *r_sigg = 0; + *r_pwid = 0x00; + /* TRANSLATORS: Do not translate the "|*|" prefixes but keep + them verbatim at the start of the string. */ + desc = (new_mode + ? _("|N|Please enter a new PIN for the standard keys.") + : _("||Please enter the PIN for the standard keys.")); + } + else if (!strcmp (pwidstr, "PW2.CH")) + { + *r_pwid = 0x01; + desc = (new_mode + ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " + "for the standard keys.") + : _("|P|Please enter the PIN Unblocking Code (PUK) " + "for the standard keys.")); + } + else if (!strcmp (pwidstr, "PW1.CH.SIG")) + { + *r_pwid = 0x81; + *r_sigg = 1; + desc = (new_mode + ? _("|N|Please enter a new PIN for the key to create " + "qualified signatures.") + : _("||Please enter the PIN for the key to create " + "qualified signatures.")); + } + else if (!strcmp (pwidstr, "PW2.CH.SIG")) + { + *r_pwid = 0x83; /* Yes, that is 83 and not 82. */ + *r_sigg = 1; + desc = (new_mode + ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " + "for the key to create qualified signatures.") + : _("|P|Please enter the PIN Unblocking Code (PUK) " + "for the key to create qualified signatures.")); + } + else + desc = NULL; + + return desc; +} + + +/* Handle the PASSWD command. See parse_pwidstr() for allowed values + for CHVNOSTR. */ static gpg_error_t -do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, +do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; - char *pinvalue; - const char *oldpin; + char *newpin = NULL; + char *oldpin = NULL; + size_t newpinlen; size_t oldpinlen; + int is_sigg; + const char *newdesc; + int pwid; + iso7816_pininfo_t pininfo; (void)ctrl; - (void)chvnostr; - if ((flags & APP_CHANGE_FLAG_RESET)) - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + /* The minimum length is enforced by TCOS, the maximum length is + just a reasonable value. */ + memset (&pininfo, 0, sizeof pininfo); + pininfo.minlen = 6; + pininfo.maxlen = 16; + + newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid); + if (!newdesc) + return gpg_error (GPG_ERR_INV_ID); + + err = switch_application (app, is_sigg); + if (err) + return err; if ((flags & APP_CHANGE_FLAG_NULLPIN)) { - /* With the nullpin flag, we do not verify the PIN - it would fail - if the Nullpin is still set. */ - oldpin = "\0\0\0\0\0"; + /* With the nullpin flag, we do not verify the PIN - it would + fail if the Nullpin is still set. */ + oldpin = xtrycalloc (1, 6); + if (!oldpin) + { + err = gpg_error_from_syserror (); + goto leave; + } oldpinlen = 6; } else { - err = verify_pin (app, pincb, pincb_arg); + const char *desc; + int dummy1, dummy2; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + /* Reset mode: Ask for the alternate PIN. */ + const char *altpwidstr; + + if (!strcmp (pwidstr, "PW1.CH")) + altpwidstr = "PW2.CH"; + else if (!strcmp (pwidstr, "PW2.CH")) + altpwidstr = "PW1.CH"; + else if (!strcmp (pwidstr, "PW1.CH.SIG")) + altpwidstr = "PW2.CH.SIG"; + else if (!strcmp (pwidstr, "PW2.CH.SIG")) + altpwidstr = "PW1.CH.SIG"; + else + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2); + } + else + { + /* Regular change mode: Ask for the old PIN. */ + desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2); + } + err = pincb (pincb_arg, desc, &oldpin); if (err) - return err; - oldpin = NULL; - oldpinlen = 0; + { + log_error ("error getting old PIN: %s\n", gpg_strerror (err)); + goto leave; + } + oldpinlen = strlen (oldpin); + err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen); + if (err) + goto leave; } - /* TRANSLATORS: Do not translate the "|*|" prefixes but - keep it at the start of the string. We need this elsewhere - to get some infos on the string. */ - err = pincb (pincb_arg, _("|N|New PIN"), &pinvalue); + err = pincb (pincb_arg, newdesc, &newpin); if (err) { log_error (_("error getting new PIN: %s\n"), gpg_strerror (err)); - return err; + goto leave; } + newpinlen = strlen (newpin); + + err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen); + if (err) + goto leave; - err = iso7816_change_reference_data (app->slot, 0x00, - oldpin, oldpinlen, - pinvalue, strlen (pinvalue)); - xfree (pinvalue); + if ((flags & APP_CHANGE_FLAG_RESET)) + { + char *data; + size_t datalen = oldpinlen + newpinlen; + + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (data, oldpin, oldpinlen); + memcpy (data+oldpinlen, newpin, newpinlen); + err = iso7816_reset_retry_counter_with_rc (app->slot, pwid, + data, datalen); + wipememory (data, datalen); + xfree (data); + } + else + err = iso7816_change_reference_data (app->slot, pwid, + oldpin, oldpinlen, + newpin, newpinlen); + leave: + xfree (oldpin); + xfree (newpin); return err; } /* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */ static gpg_error_t -do_check_pin (app_t app, const char *keyidstr, +do_check_pin (app_t app, const char *pwidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { - (void)keyidstr; - return verify_pin (app, pincb, pincb_arg); + gpg_error_t err; + int pwid; + int is_sigg; + const char *desc; + + desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid); + if (!desc) + return gpg_error (GPG_ERR_INV_ID); + + err = switch_application (app, is_sigg); + if (err) + return err; + + return verify_pin (app, pwid, desc, pincb, pincb_arg); } @@ -647,15 +1054,42 @@ get_nks_version (int slot) } +/* If ENABLE_SIGG is true switch to the SigG application if not yet + active. If false switch to the NKS application if not yet active. + Returns 0 on success. */ +static gpg_error_t +switch_application (app_t app, int enable_sigg) +{ + gpg_error_t err; + + if ((app->app_local->sigg_active && enable_sigg) + || (!app->app_local->sigg_active && !enable_sigg) ) + return 0; /* Already switched. */ + + log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS"); + if (enable_sigg) + err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0); + else + err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0); + + if (!err) + app->app_local->sigg_active = enable_sigg; + else + log_error ("app-nks: error switching to %s: %s\n", + enable_sigg? "SigG":"NKS", gpg_strerror (err)); + + return err; +} + + /* Select the NKS application. */ gpg_error_t app_select_nks (app_t app) { - static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; int slot = app->slot; int rc; - rc = iso7816_select_application (slot, aid, sizeof aid, 0); + rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0); if (!rc) { app->apptype = "NKS"; @@ -674,7 +1108,7 @@ app_select_nks (app_t app) app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; - app->fnc.getattr = NULL; + app->fnc.getattr = do_getattr; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 6b90795bb..b6e6a3565 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1,5 +1,6 @@ /* app-openpgp.c - The OpenPGP card application. - * Copyright (C) 2003, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004, 2005, 2007, 2008, + * 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -1402,7 +1403,8 @@ verify_a_chv (app_t app, int chvno, unsigned long sigcount, char **pinvalue) { int rc = 0; - char *prompt; + char *prompt_buffer = NULL; + const char *prompt; iso7816_pininfo_t pininfo; int minlen = 6; @@ -1432,30 +1434,34 @@ verify_a_chv (app_t app, memset (&pininfo, 0, sizeof pininfo); pininfo.mode = 1; pininfo.minlen = minlen; + + + if (chvno == 1) + { +#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") + size_t promptsize = strlen (PROMPTSTRING) + 50; + + prompt_buffer = xtrymalloc (promptsize); + if (!prompt_buffer) + return gpg_error_from_syserror (); + snprintf (prompt_buffer, promptsize-1, PROMPTSTRING, sigcount); + prompt = prompt_buffer; +#undef PROMPTSTRING + } + else + prompt = _("||Please enter the PIN"); + if (!opt.disable_keypad && !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) ) { - /* The reader supports the verify command through the keypad. */ - - if (chvno == 1) - { -#define PROMPTSTRING _("||Please enter your PIN at the reader's keypad%%0A" \ - "[sigs done: %lu]") - size_t promptsize = strlen (PROMPTSTRING) + 50; - - prompt = xmalloc (promptsize); - if (!prompt) - return gpg_error_from_syserror (); - snprintf (prompt, promptsize-1, PROMPTSTRING, sigcount); - rc = pincb (pincb_arg, prompt, NULL); - xfree (prompt); -#undef PROMPTSTRING - } - else - rc = pincb (pincb_arg, - _("||Please enter your PIN at the reader's keypad"), - NULL); + /* The reader supports the verify command through the keypad. + Note that the pincb appends a text to the prompt telling the + user to use the keypad. */ + rc = pincb (pincb_arg, prompt, NULL); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), @@ -1471,23 +1477,10 @@ verify_a_chv (app_t app, else { /* The reader has no keypad or we don't want to use it. */ - - if (chvno == 1) - { -#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]") - size_t promptsize = strlen (PROMPTSTRING) + 50; - - prompt = xtrymalloc (promptsize); - if (!prompt) - return gpg_error_from_syserror (); - snprintf (prompt, promptsize-1, PROMPTSTRING, sigcount); - rc = pincb (pincb_arg, prompt, pinvalue); - xfree (prompt); -#undef PROMPTSTRING - } - else - rc = pincb (pincb_arg, _("||Please enter the PIN"), pinvalue); - + rc = pincb (pincb_arg, prompt, pinvalue); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), @@ -1586,6 +1579,8 @@ verify_chv3 (app_t app, iso7816_pininfo_t pininfo; int minlen = 8; int remaining; + char *prompt_buffer = NULL; + const char *prompt; memset (&pininfo, 0, sizeof pininfo); pininfo.mode = 1; @@ -1610,31 +1605,33 @@ verify_chv3 (app_t app, log_info(_("%d Admin PIN attempts remaining before card" " is permanently locked\n"), remaining); + if (remaining < 3) + { + /* TRANSLATORS: Do not translate the "|A|" prefix but keep + it at the start of the string. Use %%0A to force a + lienfeed. */ +#define PROMPTSTRING _("|A|Please enter the Admin PIN%%0A" \ + "[remaining attempts: %d]") + size_t promptsize = strlen (PROMPTSTRING) + 50; + + prompt_buffer = xtrymalloc (promptsize); + if (!prompt_buffer) + return gpg_error_from_syserror (); + snprintf (prompt_buffer, promptsize-1, PROMPTSTRING, remaining); + prompt = prompt_buffer; +#undef PROMPTSTRING + } + else + prompt = _("|A|Please enter the Admin PIN"); + if (!opt.disable_keypad && !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) ) { /* The reader supports the verify command through the keypad. */ - - if (remaining < 3) - { -#define PROMPTSTRING _("|A|Please enter the Admin PIN" \ - " at the reader's keypad%%0A" \ - "[remaining attempts: %d]") - size_t promptsize = strlen (PROMPTSTRING) + 50; - char *prompt; - - prompt = xmalloc (promptsize); - if (!prompt) - return gpg_error_from_syserror (); - snprintf (prompt, promptsize-1, PROMPTSTRING, remaining); - rc = pincb (pincb_arg, prompt, NULL); - xfree (prompt); -#undef PROMPTSTRING - } - else - rc = pincb (pincb_arg, _("|A|Please enter the Admin PIN" - " at the reader's keypad"), NULL); - + rc = pincb (pincb_arg, prompt, NULL); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), @@ -1649,10 +1646,10 @@ verify_chv3 (app_t app, { char *pinvalue; - /* TRANSLATORS: Do not translate the "|A|" prefix but keep - it at the start of the string. We need this elsewhere to - get some infos on the string. */ - rc = pincb (pincb_arg, _("|A|Admin PIN"), &pinvalue); + rc = pincb (pincb_arg, prompt, &pinvalue); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), diff --git a/scd/command.c b/scd/command.c index 40bf2a292..91868a041 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1370,9 +1370,10 @@ cmd_random (assuan_context_t ctx, char *line) /* PASSWD [--reset] [--nullpin] - Change the PIN or reset the retry counter of the card holder - verfication vector CHVNO. The option --nullpin is used for TCOS - cards to set the initial PIN. */ + Change the PIN or, if --reset is given, reset the retry counter of + the card holder verfication vector CHVNO. The option --nullpin is + used for TCOS cards to set the initial PIN. The format of CHVNO + depends on the card application. */ static int cmd_passwd (assuan_context_t ctx, char *line) { @@ -1435,13 +1436,27 @@ cmd_passwd (assuan_context_t ctx, char *line) literal string "[CHV3]": In this case the Admin PIN is checked if and only if the retry counter is still at 3. + For Netkey: + + Any of the valid PIN Ids may be used. These are the strings: + + PW1.CH - Global password 1 + PW2.CH - Global password 2 + PW1.CH.SIG - SigG password 1 + PW2.CH.SIG - SigG password 2 + + For a definitive list, see the implementation in app-nks.c. + Note that we call a PW2.* PIN a "PUK" despite that since TCOS + 3.0 they are technically alternative PINs used to mutally + unblock each other. + */ static int cmd_checkpin (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; - char *keyidstr; + char *idstr; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); @@ -1455,14 +1470,12 @@ cmd_checkpin (assuan_context_t ctx, char *line) /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid. */ - keyidstr = xtrystrdup (line); - if (!keyidstr) + idstr = xtrystrdup (line); + if (!idstr) return out_of_core (); - rc = app_check_pin (ctrl->app_ctx, - keyidstr, - pin_cb, ctx); - xfree (keyidstr); + rc = app_check_pin (ctrl->app_ctx, idstr, pin_cb, ctx); + xfree (idstr); if (rc) log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); @@ -1566,7 +1579,7 @@ cmd_unlock (assuan_context_t ctx, char *line) deny_admin - Returns OK if admin commands are not allowed or GPG_ERR_GENERAL if admin commands are allowed. - app_list - Return a list of supported applciations. One + app_list - Return a list of supported applications. One application per line, fields delimited by colons, first field is the name. */