mirror of git://git.gnupg.org/gnupg.git
New PIN Callback attributes in gpg-agent.
Common prompts for keypad and simple card reader. More support for Netkey cards; PIN management works now.
This commit is contained in:
parent
2749c6bcd9
commit
59d7a54e72
4
NEWS
4
NEWS
|
@ -1,3 +1,7 @@
|
||||||
|
Noteworthy changes in version 2.0.12
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
Noteworthy changes in version 2.0.11 (2009-03-03)
|
Noteworthy changes in version 2.0.11 (2009-03-03)
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
2009-03-05 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* 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 <wk@g10code.com>
|
2009-03-02 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* command.c (cmd_getinfo): Add subcommand "scd_running".
|
* command.c (cmd_getinfo): Add subcommand "scd_running".
|
||||||
|
|
|
@ -1061,7 +1061,7 @@ cmd_learn (assuan_context_t ctx, char *line)
|
||||||
|
|
||||||
/* PASSWD <hexstring_with_keygrip>
|
/* PASSWD <hexstring_with_keygrip>
|
||||||
|
|
||||||
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
|
static int
|
||||||
cmd_passwd (assuan_context_t ctx, char *line)
|
cmd_passwd (assuan_context_t ctx, char *line)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* divert-scd.c - divert operations to the scdaemon
|
/* 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.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
|
@ -181,10 +181,11 @@ encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo,
|
||||||
|
|
||||||
Flags:
|
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
|
PIN. If the PIN is not correctly repeated it starts from
|
||||||
all over.
|
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.
|
'R' = The PIN is a Reset Code.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
@ -204,6 +205,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
|
||||||
int any_flags = 0;
|
int any_flags = 0;
|
||||||
int newpin = 0;
|
int newpin = 0;
|
||||||
int resetcode = 0;
|
int resetcode = 0;
|
||||||
|
int is_puk = 0;
|
||||||
const char *again_text = NULL;
|
const char *again_text = NULL;
|
||||||
const char *prompt = "PIN";
|
const char *prompt = "PIN";
|
||||||
|
|
||||||
|
@ -217,6 +219,13 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
|
||||||
{
|
{
|
||||||
if (*s == 'A')
|
if (*s == 'A')
|
||||||
prompt = _("Admin PIN");
|
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')
|
else if (*s == 'N')
|
||||||
newpin = 1;
|
newpin = 1;
|
||||||
else if (*s == 'R')
|
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. */
|
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
|
else
|
||||||
rc = gpg_error (GPG_ERR_INV_VALUE);
|
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 ();
|
return gpg_error_from_syserror ();
|
||||||
pi->max_length = maxbuf-1;
|
pi->max_length = maxbuf-1;
|
||||||
pi->min_digits = 0; /* we want a real passphrase */
|
pi->min_digits = 0; /* we want a real passphrase */
|
||||||
pi->max_digits = 8;
|
pi->max_digits = 16;
|
||||||
pi->max_tries = 3;
|
pi->max_tries = 3;
|
||||||
|
|
||||||
if (any_flags)
|
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->max_length = maxbuf-1;
|
||||||
pi2->min_digits = 0;
|
pi2->min_digits = 0;
|
||||||
pi2->max_digits = 8;
|
pi2->max_digits = 16;
|
||||||
pi2->max_tries = 1;
|
pi2->max_tries = 1;
|
||||||
rc = agent_askpin (ctrl,
|
rc = agent_askpin (ctrl,
|
||||||
(resetcode?
|
(resetcode?
|
||||||
_("Repeat this Reset Code"):
|
_("Repeat this Reset Code"):
|
||||||
|
is_puk?
|
||||||
|
_("Repeat this PUK"):
|
||||||
_("Repeat this PIN")),
|
_("Repeat this PIN")),
|
||||||
prompt, NULL, pi2);
|
prompt, NULL, pi2);
|
||||||
if (!rc && strcmp (pi->pin, pi2->pin))
|
if (!rc && strcmp (pi->pin, pi2->pin))
|
||||||
{
|
{
|
||||||
again_text = (resetcode?
|
again_text = (resetcode?
|
||||||
N_("Reset Code not correctly repeated; try again"):
|
N_("Reset Code not correctly repeated; try again"):
|
||||||
|
is_puk?
|
||||||
|
N_("PUK not correctly repeated; try again"):
|
||||||
N_("PIN not correctly repeated; try again"));
|
N_("PIN not correctly repeated; try again"));
|
||||||
xfree (pi2);
|
xfree (pi2);
|
||||||
xfree (pi);
|
xfree (pi);
|
||||||
|
|
|
@ -367,7 +367,7 @@ unprotect (ctrl_t ctrl, const char *desc_text,
|
||||||
return gpg_error_from_syserror ();
|
return gpg_error_from_syserror ();
|
||||||
pi->max_length = 100;
|
pi->max_length = 100;
|
||||||
pi->min_digits = 0; /* we want a real passphrase */
|
pi->min_digits = 0; /* we want a real passphrase */
|
||||||
pi->max_digits = 8;
|
pi->max_digits = 16;
|
||||||
pi->max_tries = 3;
|
pi->max_tries = 3;
|
||||||
pi->check_cb = try_unprotect_cb;
|
pi->check_cb = try_unprotect_cb;
|
||||||
arg.ctrl = ctrl;
|
arg.ctrl = ctrl;
|
||||||
|
|
|
@ -24,8 +24,8 @@ min_automake_version="1.10"
|
||||||
# Remember to change the version number immediately *after* a release.
|
# Remember to change the version number immediately *after* a release.
|
||||||
# Set my_issvn to "yes" for non-released code. Remember to run an
|
# Set my_issvn to "yes" for non-released code. Remember to run an
|
||||||
# "svn up" and "autogen.sh" right before creating a distribution.
|
# "svn up" and "autogen.sh" right before creating a distribution.
|
||||||
m4_define([my_version], [2.0.11])
|
m4_define([my_version], [2.0.12])
|
||||||
m4_define([my_issvn], [no])
|
m4_define([my_issvn], [yes])
|
||||||
|
|
||||||
m4_define([svn_revision], m4_esyscmd([printf "%d" $(svn info 2>/dev/null \
|
m4_define([svn_revision], m4_esyscmd([printf "%d" $(svn info 2>/dev/null \
|
||||||
| sed -n '/^Revision:/ s/[^0-9]//gp'|head -1)]))
|
| sed -n '/^Revision:/ s/[^0-9]//gp'|head -1)]))
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
2009-03-04 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* help.txt (gpg.keygen.size): Add a link to web page.
|
||||||
|
|
||||||
|
2009-03-03 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* gpg.texi (Operational GPG Commands): "merge-only" is an
|
||||||
|
import-option. Reported by Joseph Oreste Bruni.
|
||||||
|
|
||||||
2009-03-02 Werner Koch <wk@g10code.com>
|
2009-03-02 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* gpg-agent.texi (Invoking GPG-AGENT): Modernized instructions.
|
* gpg-agent.texi (Invoking GPG-AGENT): Modernized instructions.
|
||||||
|
|
|
@ -425,7 +425,7 @@ Import/merge keys. This adds the given keys to the
|
||||||
keyring. The fast version is currently just a synonym.
|
keyring. The fast version is currently just a synonym.
|
||||||
|
|
||||||
There are a few other options which control how this command works.
|
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
|
which does not insert new keys but does only the merging of new
|
||||||
signatures, user-IDs and subkeys.
|
signatures, user-IDs and subkeys.
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,13 @@ Please consult your security expert first.
|
||||||
|
|
||||||
|
|
||||||
.gpg.keygen.size
|
.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
|
.gpg.keygen.size.huge.okay
|
||||||
|
|
|
@ -1,3 +1,30 @@
|
||||||
|
2009-03-05 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* 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 <wk@g10code.com>
|
||||||
|
|
||||||
|
* 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 <wk@g10code.com>
|
2009-03-03 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* command.c (scd_command_handler): Remove dereference of STOPME
|
* command.c (scd_command_handler): Remove dereference of STOPME
|
||||||
|
|
586
scd/app-nks.c
586
scd/app-nks.c
|
@ -17,6 +17,27 @@
|
||||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* 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 <config.h>
|
#include <config.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -30,9 +51,15 @@
|
||||||
#include "iso7816.h"
|
#include "iso7816.h"
|
||||||
#include "app-common.h"
|
#include "app-common.h"
|
||||||
#include "tlv.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
|
static struct
|
||||||
{
|
{
|
||||||
|
int is_sigg; /* Valid for SigG application. */
|
||||||
int fid; /* File ID. */
|
int fid; /* File ID. */
|
||||||
int nks_ver; /* 0 for NKS version 2, 3 for version 3. */
|
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. */
|
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 issignkey; /* True if file is a key usable for signing. */
|
||||||
int isenckey; /* True if file is a key usable for decryption. */
|
int isenckey; /* True if file is a key usable for decryption. */
|
||||||
} filelist[] = {
|
} filelist[] = {
|
||||||
{ 0x4531, 0, 0, 0xC000, 1, 0 }, /* EF_PK.NKS.SIG */
|
{ 0, 0x4531, 0, 0, 0xC000, 1, 0 }, /* EF_PK.NKS.SIG */
|
||||||
{ 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
|
{ 1, 0x4531, 3, 0, 0x0000, 1, 1 }, /* EF_PK.CH.SIG */
|
||||||
{ 0x4331, 0, 100 },
|
{ 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
|
||||||
{ 0x4332, 0, 100 },
|
{ 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */
|
||||||
{ 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
|
{ 0, 0x4331, 0, 100 },
|
||||||
{ 0x45B1, 0, 0, 0xC200, 0, 1 }, /* EF_PK.NKS.ENC */
|
{ 0, 0x4332, 0, 100 },
|
||||||
{ 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
|
{ 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
|
||||||
{ 0x43B1, 0, 100 },
|
{ 0, 0x45B1, 0, 0, 0xC200, 0, 1 }, /* EF_PK.NKS.ENC */
|
||||||
{ 0x43B2, 0, 100 },
|
{ 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
|
||||||
{ 0x4571, 3, 0, 0xc500, 0, 0 }, /* EF_PK.NKS.AUT */
|
{ 0, 0x43B1, 0, 100 },
|
||||||
{ 0xC500, 3, 101 }, /* EF_C.NKS.AUT */
|
{ 0, 0x43B2, 0, 100 },
|
||||||
{ 0x45B2, 3, 0, 0xC201, 0, 1 }, /* EF_PK.NKS.ENC1024 */
|
{ 0, 0x4571, 3, 0, 0xc500, 0, 0 }, /* EF_PK.NKS.AUT */
|
||||||
{ 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */
|
{ 0, 0xC500, 3, 101 }, /* EF_C.NKS.AUT */
|
||||||
{ 0 }
|
{ 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 {
|
struct app_local_s {
|
||||||
int nks_version; /* NKS version. */
|
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. */
|
/* 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
|
static gpg_error_t
|
||||||
do_learn_status (app_t app, ctrl_t ctrl)
|
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];
|
char ct_buf[100], id_buf[100];
|
||||||
int i;
|
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++)
|
for (i=0; filelist[i].fid; i++)
|
||||||
{
|
{
|
||||||
if (filelist[i].nks_ver > app->app_local->nks_version)
|
if (filelist[i].nks_ver > app->app_local->nks_version)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (filelist[i].is_sigg)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (filelist[i].certtype)
|
if (filelist[i].certtype)
|
||||||
{
|
{
|
||||||
size_t len;
|
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
|
/* FIXME: We should store the length in the application's
|
||||||
context so that a following readcert does only need to
|
context so that a following readcert does only need to
|
||||||
read that many bytes. */
|
read that many bytes. */
|
||||||
sprintf (ct_buf, "%d", filelist[i].certtype);
|
snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype);
|
||||||
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, "CERTINFO",
|
send_status_info (ctrl, "CERTINFO",
|
||||||
ct_buf, strlen (ct_buf),
|
ct_buf, strlen (ct_buf),
|
||||||
id_buf, strlen (id_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));
|
filelist[i].fid, gpg_strerror (err));
|
||||||
else
|
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",
|
send_status_info (ctrl, "KEYPAIRINFO",
|
||||||
gripstr, 40,
|
gripstr, 40,
|
||||||
id_buf, strlen (id_buf),
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +449,16 @@ do_readcert (app_t app, const char *certid,
|
||||||
|
|
||||||
*cert = NULL;
|
*cert = NULL;
|
||||||
*certlen = 0;
|
*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);
|
return gpg_error (GPG_ERR_INV_ID);
|
||||||
certid += 9;
|
certid += 9;
|
||||||
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|
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. */
|
/* Verify the PIN if required. */
|
||||||
static gpg_error_t
|
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 **),
|
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||||
void *pincb_arg)
|
void *pincb_arg)
|
||||||
{
|
{
|
||||||
iso7816_pininfo_t pininfo;
|
iso7816_pininfo_t pininfo;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Note that force_chv1 is never set but we do it here anyway so
|
if (!desc)
|
||||||
that other applications may reuse this function. For example it
|
desc = "PIN";
|
||||||
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. */
|
|
||||||
|
|
||||||
memset (&pininfo, 0, sizeof pininfo);
|
memset (&pininfo, 0, sizeof pininfo);
|
||||||
pininfo.mode = 1;
|
pininfo.mode = 1;
|
||||||
|
@ -375,7 +623,7 @@ verify_pin (app_t app,
|
||||||
{
|
{
|
||||||
char *pinvalue;
|
char *pinvalue;
|
||||||
|
|
||||||
rc = pincb (pincb_arg, "PIN", &pinvalue);
|
rc = pincb (pincb_arg, desc, &pinvalue);
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
log_info ("PIN callback returned error: %s\n", gpg_strerror (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
|
/* The following limits are due to TCOS but also defined in the
|
||||||
NKS specs. */
|
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);
|
xfree (pinvalue);
|
||||||
return gpg_error (GPG_ERR_BAD_PIN);
|
return rc;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Although it is possible to use a local PIN, we use the global
|
rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue));
|
||||||
PIN for this application. */
|
|
||||||
rc = iso7816_verify (app->slot, 0, pinvalue, strlen (pinvalue));
|
|
||||||
xfree (pinvalue);
|
xfree (pinvalue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +651,6 @@ verify_pin (app_t app,
|
||||||
log_error ("verify PIN failed\n");
|
log_error ("verify PIN failed\n");
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
app->did_chv1 = 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -447,9 +684,17 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
|
||||||
if (indatalen != 20 && indatalen != 16 && indatalen != 35)
|
if (indatalen != 20 && indatalen != 16 && indatalen != 35)
|
||||||
return gpg_error (GPG_ERR_INV_VALUE);
|
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
|
/* Check that the provided ID is valid. This is not really needed
|
||||||
but we do it to enforce correct usage by the caller. */
|
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);
|
return gpg_error (GPG_ERR_INV_ID);
|
||||||
keyidstr += 9;
|
keyidstr += 9;
|
||||||
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|
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);
|
memcpy (data+15, indata, indatalen);
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = verify_pin (app, pincb, pincb_arg);
|
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
|
||||||
if (!rc)
|
if (!rc)
|
||||||
rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen);
|
rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen);
|
||||||
return rc;
|
return rc;
|
||||||
|
@ -519,9 +764,17 @@ do_decipher (app_t app, const char *keyidstr,
|
||||||
if (!keyidstr || !*keyidstr || !indatalen)
|
if (!keyidstr || !*keyidstr || !indatalen)
|
||||||
return gpg_error (GPG_ERR_INV_VALUE);
|
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
|
/* Check that the provided ID is valid. This is not really needed
|
||||||
but we do it to to enforce correct usage by the caller. */
|
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);
|
return gpg_error (GPG_ERR_INV_ID);
|
||||||
keyidstr += 9;
|
keyidstr += 9;
|
||||||
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|
||||||
|
@ -542,7 +795,7 @@ do_decipher (app_t app, const char *keyidstr,
|
||||||
0xC1, 0xB8,
|
0xC1, 0xB8,
|
||||||
mse_parm, sizeof mse_parm);
|
mse_parm, sizeof mse_parm);
|
||||||
if (!rc)
|
if (!rc)
|
||||||
rc = verify_pin (app, pincb, pincb_arg);
|
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
|
||||||
if (!rc)
|
if (!rc)
|
||||||
rc = iso7816_decipher (app->slot, indata, indatalen, 0x81,
|
rc = iso7816_decipher (app->slot, indata, indatalen, 0x81,
|
||||||
outdata, outdatalen);
|
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
|
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,
|
unsigned int flags,
|
||||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||||
void *pincb_arg)
|
void *pincb_arg)
|
||||||
{
|
{
|
||||||
gpg_error_t err;
|
gpg_error_t err;
|
||||||
char *pinvalue;
|
char *newpin = NULL;
|
||||||
const char *oldpin;
|
char *oldpin = NULL;
|
||||||
|
size_t newpinlen;
|
||||||
size_t oldpinlen;
|
size_t oldpinlen;
|
||||||
|
int is_sigg;
|
||||||
|
const char *newdesc;
|
||||||
|
int pwid;
|
||||||
|
iso7816_pininfo_t pininfo;
|
||||||
|
|
||||||
(void)ctrl;
|
(void)ctrl;
|
||||||
(void)chvnostr;
|
|
||||||
|
|
||||||
if ((flags & APP_CHANGE_FLAG_RESET))
|
/* The minimum length is enforced by TCOS, the maximum length is
|
||||||
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
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))
|
if ((flags & APP_CHANGE_FLAG_NULLPIN))
|
||||||
{
|
{
|
||||||
/* With the nullpin flag, we do not verify the PIN - it would fail
|
/* With the nullpin flag, we do not verify the PIN - it would
|
||||||
if the Nullpin is still set. */
|
fail if the Nullpin is still set. */
|
||||||
oldpin = "\0\0\0\0\0";
|
oldpin = xtrycalloc (1, 6);
|
||||||
|
if (!oldpin)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
oldpinlen = 6;
|
oldpinlen = 6;
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (err)
|
||||||
return err;
|
{
|
||||||
oldpin = NULL;
|
log_error ("error getting old PIN: %s\n", gpg_strerror (err));
|
||||||
oldpinlen = 0;
|
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
|
err = pincb (pincb_arg, newdesc, &newpin);
|
||||||
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);
|
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
log_error (_("error getting new PIN: %s\n"), gpg_strerror (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,
|
if ((flags & APP_CHANGE_FLAG_RESET))
|
||||||
oldpin, oldpinlen,
|
{
|
||||||
pinvalue, strlen (pinvalue));
|
char *data;
|
||||||
xfree (pinvalue);
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
|
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
|
||||||
static gpg_error_t
|
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 **),
|
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||||
void *pincb_arg)
|
void *pincb_arg)
|
||||||
{
|
{
|
||||||
(void)keyidstr;
|
gpg_error_t err;
|
||||||
return verify_pin (app, pincb, pincb_arg);
|
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. */
|
/* Select the NKS application. */
|
||||||
gpg_error_t
|
gpg_error_t
|
||||||
app_select_nks (app_t app)
|
app_select_nks (app_t app)
|
||||||
{
|
{
|
||||||
static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
|
|
||||||
int slot = app->slot;
|
int slot = app->slot;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = iso7816_select_application (slot, aid, sizeof aid, 0);
|
rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0);
|
||||||
if (!rc)
|
if (!rc)
|
||||||
{
|
{
|
||||||
app->apptype = "NKS";
|
app->apptype = "NKS";
|
||||||
|
@ -674,7 +1108,7 @@ app_select_nks (app_t app)
|
||||||
app->fnc.deinit = do_deinit;
|
app->fnc.deinit = do_deinit;
|
||||||
app->fnc.learn_status = do_learn_status;
|
app->fnc.learn_status = do_learn_status;
|
||||||
app->fnc.readcert = do_readcert;
|
app->fnc.readcert = do_readcert;
|
||||||
app->fnc.getattr = NULL;
|
app->fnc.getattr = do_getattr;
|
||||||
app->fnc.setattr = NULL;
|
app->fnc.setattr = NULL;
|
||||||
app->fnc.genkey = NULL;
|
app->fnc.genkey = NULL;
|
||||||
app->fnc.sign = do_sign;
|
app->fnc.sign = do_sign;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* app-openpgp.c - The OpenPGP card application.
|
/* 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.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
|
@ -1402,7 +1403,8 @@ verify_a_chv (app_t app,
|
||||||
int chvno, unsigned long sigcount, char **pinvalue)
|
int chvno, unsigned long sigcount, char **pinvalue)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
char *prompt;
|
char *prompt_buffer = NULL;
|
||||||
|
const char *prompt;
|
||||||
iso7816_pininfo_t pininfo;
|
iso7816_pininfo_t pininfo;
|
||||||
int minlen = 6;
|
int minlen = 6;
|
||||||
|
|
||||||
|
@ -1432,30 +1434,34 @@ verify_a_chv (app_t app,
|
||||||
memset (&pininfo, 0, sizeof pininfo);
|
memset (&pininfo, 0, sizeof pininfo);
|
||||||
pininfo.mode = 1;
|
pininfo.mode = 1;
|
||||||
pininfo.minlen = minlen;
|
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
|
if (!opt.disable_keypad
|
||||||
&& !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) )
|
&& !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) )
|
||||||
{
|
{
|
||||||
/* The reader supports the verify command through the keypad. */
|
/* The reader supports the verify command through the keypad.
|
||||||
|
Note that the pincb appends a text to the prompt telling the
|
||||||
if (chvno == 1)
|
user to use the keypad. */
|
||||||
{
|
rc = pincb (pincb_arg, prompt, NULL);
|
||||||
#define PROMPTSTRING _("||Please enter your PIN at the reader's keypad%%0A" \
|
prompt = NULL;
|
||||||
"[sigs done: %lu]")
|
xfree (prompt_buffer);
|
||||||
size_t promptsize = strlen (PROMPTSTRING) + 50;
|
prompt_buffer = NULL;
|
||||||
|
|
||||||
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);
|
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
log_info (_("PIN callback returned error: %s\n"),
|
log_info (_("PIN callback returned error: %s\n"),
|
||||||
|
@ -1471,23 +1477,10 @@ verify_a_chv (app_t app,
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* The reader has no keypad or we don't want to use it. */
|
/* The reader has no keypad or we don't want to use it. */
|
||||||
|
rc = pincb (pincb_arg, prompt, pinvalue);
|
||||||
if (chvno == 1)
|
prompt = NULL;
|
||||||
{
|
xfree (prompt_buffer);
|
||||||
#define PROMPTSTRING _("||Please enter the PIN%%0A[sigs done: %lu]")
|
prompt_buffer = NULL;
|
||||||
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);
|
|
||||||
|
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
log_info (_("PIN callback returned error: %s\n"),
|
log_info (_("PIN callback returned error: %s\n"),
|
||||||
|
@ -1586,6 +1579,8 @@ verify_chv3 (app_t app,
|
||||||
iso7816_pininfo_t pininfo;
|
iso7816_pininfo_t pininfo;
|
||||||
int minlen = 8;
|
int minlen = 8;
|
||||||
int remaining;
|
int remaining;
|
||||||
|
char *prompt_buffer = NULL;
|
||||||
|
const char *prompt;
|
||||||
|
|
||||||
memset (&pininfo, 0, sizeof pininfo);
|
memset (&pininfo, 0, sizeof pininfo);
|
||||||
pininfo.mode = 1;
|
pininfo.mode = 1;
|
||||||
|
@ -1610,31 +1605,33 @@ verify_chv3 (app_t app,
|
||||||
log_info(_("%d Admin PIN attempts remaining before card"
|
log_info(_("%d Admin PIN attempts remaining before card"
|
||||||
" is permanently locked\n"), remaining);
|
" 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
|
if (!opt.disable_keypad
|
||||||
&& !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) )
|
&& !iso7816_check_keypad (app->slot, ISO7816_VERIFY, &pininfo) )
|
||||||
{
|
{
|
||||||
/* The reader supports the verify command through the keypad. */
|
/* The reader supports the verify command through the keypad. */
|
||||||
|
rc = pincb (pincb_arg, prompt, NULL);
|
||||||
if (remaining < 3)
|
prompt = NULL;
|
||||||
{
|
xfree (prompt_buffer);
|
||||||
#define PROMPTSTRING _("|A|Please enter the Admin PIN" \
|
prompt_buffer = NULL;
|
||||||
" 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);
|
|
||||||
|
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
log_info (_("PIN callback returned error: %s\n"),
|
log_info (_("PIN callback returned error: %s\n"),
|
||||||
|
@ -1649,10 +1646,10 @@ verify_chv3 (app_t app,
|
||||||
{
|
{
|
||||||
char *pinvalue;
|
char *pinvalue;
|
||||||
|
|
||||||
/* TRANSLATORS: Do not translate the "|A|" prefix but keep
|
rc = pincb (pincb_arg, prompt, &pinvalue);
|
||||||
it at the start of the string. We need this elsewhere to
|
prompt = NULL;
|
||||||
get some infos on the string. */
|
xfree (prompt_buffer);
|
||||||
rc = pincb (pincb_arg, _("|A|Admin PIN"), &pinvalue);
|
prompt_buffer = NULL;
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
log_info (_("PIN callback returned error: %s\n"),
|
log_info (_("PIN callback returned error: %s\n"),
|
||||||
|
|
|
@ -1370,9 +1370,10 @@ cmd_random (assuan_context_t ctx, char *line)
|
||||||
|
|
||||||
/* PASSWD [--reset] [--nullpin] <chvno>
|
/* PASSWD [--reset] [--nullpin] <chvno>
|
||||||
|
|
||||||
Change the PIN or reset the retry counter of the card holder
|
Change the PIN or, if --reset is given, reset the retry counter of
|
||||||
verfication vector CHVNO. The option --nullpin is used for TCOS
|
the card holder verfication vector CHVNO. The option --nullpin is
|
||||||
cards to set the initial PIN. */
|
used for TCOS cards to set the initial PIN. The format of CHVNO
|
||||||
|
depends on the card application. */
|
||||||
static int
|
static int
|
||||||
cmd_passwd (assuan_context_t ctx, char *line)
|
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
|
literal string "[CHV3]": In this case the Admin PIN is checked
|
||||||
if and only if the retry counter is still at 3.
|
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
|
static int
|
||||||
cmd_checkpin (assuan_context_t ctx, char *line)
|
cmd_checkpin (assuan_context_t ctx, char *line)
|
||||||
{
|
{
|
||||||
ctrl_t ctrl = assuan_get_pointer (ctx);
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
||||||
int rc;
|
int rc;
|
||||||
char *keyidstr;
|
char *idstr;
|
||||||
|
|
||||||
if ( IS_LOCKED (ctrl) )
|
if ( IS_LOCKED (ctrl) )
|
||||||
return gpg_error (GPG_ERR_LOCKED);
|
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
|
/* 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
|
the pin_cb which in turn uses the assuan line buffer and thus
|
||||||
overwriting the original line with the keyid. */
|
overwriting the original line with the keyid. */
|
||||||
keyidstr = xtrystrdup (line);
|
idstr = xtrystrdup (line);
|
||||||
if (!keyidstr)
|
if (!idstr)
|
||||||
return out_of_core ();
|
return out_of_core ();
|
||||||
|
|
||||||
rc = app_check_pin (ctrl->app_ctx,
|
rc = app_check_pin (ctrl->app_ctx, idstr, pin_cb, ctx);
|
||||||
keyidstr,
|
xfree (idstr);
|
||||||
pin_cb, ctx);
|
|
||||||
xfree (keyidstr);
|
|
||||||
if (rc)
|
if (rc)
|
||||||
log_error ("app_check_pin failed: %s\n", gpg_strerror (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
|
deny_admin - Returns OK if admin commands are not allowed or
|
||||||
GPG_ERR_GENERAL if admin commands are allowed.
|
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,
|
application per line, fields delimited by colons,
|
||||||
first field is the name.
|
first field is the name.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue