mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-04 20:38:50 +01:00
a0b4f40301
* cardglue.c (open_card): Use it.
1383 lines
35 KiB
C
1383 lines
35 KiB
C
/* cardglue.c - mainly dispatcher for card related functions.
|
||
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of GnuPG.
|
||
*
|
||
* GnuPG is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* GnuPG is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program; if not, write to the Free Software
|
||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||
* USA.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#ifndef ENABLE_CARD_SUPPORT
|
||
#error not configured for card support.
|
||
#endif
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <stdarg.h>
|
||
#include <assert.h>
|
||
|
||
#include "options.h"
|
||
#include "packet.h"
|
||
#include "errors.h"
|
||
#include "memory.h"
|
||
#include "util.h"
|
||
#include "main.h"
|
||
#include "status.h"
|
||
#include "ttyio.h"
|
||
#include "i18n.h"
|
||
|
||
#include "cardglue.h"
|
||
#include "apdu.h"
|
||
#include "app-common.h"
|
||
|
||
|
||
|
||
struct ctrl_ctx_s
|
||
{
|
||
assuan_error_t (*status_cb)(void *opaque, const char *line);
|
||
void *status_cb_arg;
|
||
};
|
||
|
||
|
||
struct pincb_parm_s
|
||
{
|
||
const char *sn;
|
||
};
|
||
|
||
|
||
struct writekey_parm_s
|
||
{
|
||
assuan_context_t ctx;
|
||
const unsigned char *keydata;
|
||
size_t keydatalen;
|
||
};
|
||
|
||
|
||
|
||
static char *default_reader_port;
|
||
static app_t current_app;
|
||
|
||
|
||
/* Local prototypes. */
|
||
static assuan_error_t learn_status_cb (void *opaque, const char *line);
|
||
|
||
|
||
/* To avoid cluttering the code with bunches of ifdefs we use a few
|
||
dummy functions instead and defines. */
|
||
#ifndef ENABLE_AGENT_SUPPORT
|
||
|
||
#define ASSUAN_LINELENGTH 100
|
||
|
||
static assuan_context_t
|
||
agent_open (int try)
|
||
{
|
||
return NULL;
|
||
}
|
||
|
||
void
|
||
agent_close (assuan_context_t ctx)
|
||
{
|
||
}
|
||
|
||
const char *
|
||
assuan_strerror (assuan_error_t err)
|
||
{
|
||
return "no Assuan support";
|
||
}
|
||
|
||
assuan_error_t
|
||
assuan_transact (assuan_context_t ctx,
|
||
const char *command,
|
||
assuan_error_t (*data_cb)(void *, const void *, size_t),
|
||
void *data_cb_arg,
|
||
assuan_error_t (*inquire_cb)(void*, const char *),
|
||
void *inquire_cb_arg,
|
||
assuan_error_t (*status_cb)(void*, const char *),
|
||
void *status_cb_arg)
|
||
{
|
||
return 100; /* ASSUAN_NOT_IMPLEMENTED */
|
||
}
|
||
assuan_error_t
|
||
assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length)
|
||
{
|
||
return 100; /* ASSUAN_NOT_IMPLEMENTED */
|
||
}
|
||
#endif /*!ENABLE_AGENT_SUPPORT*/
|
||
|
||
|
||
/* Create a serialno/fpr string from the serial number and the secret
|
||
key. caller must free the returned string. There is no error
|
||
return. [Taken from 1.9's keyid.c]*/
|
||
char *
|
||
serialno_and_fpr_from_sk (const unsigned char *sn, size_t snlen,
|
||
PKT_secret_key *sk)
|
||
{
|
||
unsigned char fpr[MAX_FINGERPRINT_LEN];
|
||
size_t fprlen;
|
||
char *buffer, *p;
|
||
int i;
|
||
|
||
fingerprint_from_sk (sk, fpr, &fprlen);
|
||
buffer = p = xmalloc (snlen*2 + 1 + fprlen*2 + 1);
|
||
for (i=0; i < snlen; i++, p+=2)
|
||
sprintf (p, "%02X", sn[i]);
|
||
*p++ = '/';
|
||
for (i=0; i < fprlen; i++, p+=2)
|
||
sprintf (p, "%02X", fpr[i]);
|
||
*p = 0;
|
||
return buffer;
|
||
}
|
||
|
||
|
||
/* Send a line with status information via assuan and escape all given
|
||
buffers. The variable elements are pairs of (char *, size_t),
|
||
terminated with a (NULL, 0). */
|
||
void
|
||
send_status_info (ctrl_t ctrl, const char *keyword, ...)
|
||
{
|
||
va_list arg_ptr;
|
||
const unsigned char *value;
|
||
size_t valuelen;
|
||
char buf[950], *p;
|
||
size_t n;
|
||
|
||
va_start (arg_ptr, keyword);
|
||
|
||
p = buf;
|
||
n = 0;
|
||
valuelen = strlen (keyword);
|
||
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, keyword++)
|
||
*p++ = *keyword;
|
||
|
||
while ( (value = va_arg (arg_ptr, const unsigned char *)) )
|
||
{
|
||
valuelen = va_arg (arg_ptr, size_t);
|
||
if (!valuelen)
|
||
continue; /* empty buffer */
|
||
if (n)
|
||
{
|
||
*p++ = ' ';
|
||
n++;
|
||
}
|
||
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
|
||
{
|
||
if (*value < ' ' || *value == '+')
|
||
{
|
||
sprintf (p, "%%%02X", *value);
|
||
p += 3;
|
||
}
|
||
else if (*value == ' ')
|
||
*p++ = '+';
|
||
else
|
||
*p++ = *value;
|
||
}
|
||
}
|
||
*p = 0;
|
||
if (ctrl && ctrl->status_cb)
|
||
ctrl->status_cb (ctrl->status_cb_arg, buf);
|
||
|
||
va_end (arg_ptr);
|
||
}
|
||
|
||
|
||
/* Replacement function of the Libgcrypt onewhich is used in gnupg
|
||
1.9. Thus function computes the digest of ALGO from the data in
|
||
BUFFER of LENGTH. ALGO must be supported. */
|
||
void
|
||
gcry_md_hash_buffer (int algo, void *digest,
|
||
const void *buffer, size_t length)
|
||
{
|
||
MD_HANDLE h = md_open (algo, 0);
|
||
if (!h)
|
||
BUG();
|
||
md_write (h, (byte *) buffer, length);
|
||
md_final (h);
|
||
memcpy (digest, md_read (h, algo), md_digest_length (algo));
|
||
md_close (h);
|
||
}
|
||
|
||
|
||
/* This is a limited version of the one in 1.9 but it should be
|
||
sufficient here. */
|
||
void
|
||
log_printf (const char *fmt, ...)
|
||
{
|
||
va_list arg_ptr;
|
||
|
||
va_start (arg_ptr, fmt);
|
||
vfprintf (log_stream (), fmt, arg_ptr);
|
||
va_end (arg_ptr);
|
||
}
|
||
|
||
|
||
|
||
/* Print a hexdump of BUFFER. With TEXT of NULL print just the raw
|
||
dump, with TEXT just an empty string, print a trailing linefeed,
|
||
otherwise print an entire debug line. */
|
||
void
|
||
log_printhex (const char *text, const void *buffer, size_t length)
|
||
{
|
||
if (text && *text)
|
||
log_debug ("%s ", text);
|
||
if (length)
|
||
{
|
||
const unsigned char *p = buffer;
|
||
log_printf ("%02X", *p);
|
||
for (length--, p++; length--; p++)
|
||
log_printf (" %02X", *p);
|
||
}
|
||
if (text)
|
||
log_printf ("\n");
|
||
}
|
||
|
||
|
||
|
||
void
|
||
app_set_default_reader_port (const char *portstr)
|
||
{
|
||
xfree (default_reader_port);
|
||
default_reader_port = portstr? xstrdup (portstr): NULL;
|
||
}
|
||
|
||
|
||
void
|
||
card_set_reader_port (const char *portstr)
|
||
{
|
||
app_set_default_reader_port (portstr);
|
||
}
|
||
|
||
|
||
/* Retrieve the serial number and the time of the last update of the
|
||
card. The serial number is returned as a malloced string (hex
|
||
encoded) in SERIAL and the time of update is returned in STAMP. If
|
||
no update time is available the returned value is 0. Caller must
|
||
free SERIAL unless the function returns an error. */
|
||
int
|
||
app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp)
|
||
{
|
||
unsigned char *buf, *p;
|
||
int i;
|
||
|
||
if (!app || !serial || !stamp)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
*serial = NULL;
|
||
*stamp = 0; /* not available */
|
||
|
||
buf = xtrymalloc (app->serialnolen * 2 + 1);
|
||
if (!buf)
|
||
return gpg_error_from_errno (errno);
|
||
for (p=buf, i=0; i < app->serialnolen; p +=2, i++)
|
||
sprintf (p, "%02X", app->serialno[i]);
|
||
*p = 0;
|
||
*serial = buf;
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Release the card info structure. */
|
||
void
|
||
agent_release_card_info (struct agent_card_info_s *info)
|
||
{
|
||
int i;
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
xfree (info->serialno); info->serialno = NULL;
|
||
xfree (info->disp_name); info->disp_name = NULL;
|
||
xfree (info->disp_lang); info->disp_lang = NULL;
|
||
xfree (info->pubkey_url); info->pubkey_url = NULL;
|
||
xfree (info->login_data); info->login_data = NULL;
|
||
info->fpr1valid = info->fpr2valid = info->fpr3valid = 0;
|
||
info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0;
|
||
for (i=0; i < 4; i++)
|
||
{
|
||
xfree (info->private_do[i]);
|
||
info->private_do[i] = NULL;
|
||
}
|
||
}
|
||
|
||
|
||
/* Print an error message for a failed assuan_transact and return a
|
||
gpg error code. No error is printed if RC is 0. */
|
||
static gpg_error_t
|
||
test_transact (int rc, const char *command)
|
||
{
|
||
if (!rc)
|
||
return 0;
|
||
log_error ("sending command `%s' to agent failed: %s\n",
|
||
command, assuan_strerror (rc));
|
||
return gpg_error (GPG_ERR_CARD);
|
||
}
|
||
|
||
|
||
/* Try to open a card using an already running agent. Prepare a
|
||
proper application context and return it. */
|
||
static app_t
|
||
open_card_via_agent (int *scd_available)
|
||
{
|
||
assuan_context_t ctx;
|
||
app_t app;
|
||
struct agent_card_info_s info;
|
||
int rc;
|
||
|
||
*scd_available = 0;
|
||
ctx = agent_open (1);
|
||
if (!ctx)
|
||
return NULL;
|
||
|
||
/* Request the serialbnumber of the card. If we get
|
||
NOT_SUPPORTED or NO_SCDAEMON back, the gpg-agent either has
|
||
disabled scdaemon or it can't be used. We close the connection
|
||
in this case and use our own code. This may happen if just the
|
||
gpg-agent has been installed for the sake of passphrase
|
||
caching. */
|
||
memset (&info, 0, sizeof info);
|
||
rc = assuan_transact (ctx, "SCD SERIALNO openpgp",
|
||
NULL, NULL, NULL, NULL,
|
||
learn_status_cb, &info);
|
||
if (rc)
|
||
{
|
||
if ((rc & 0xffff) == 60 || (rc & 0xffff) == 119)
|
||
; /* No scdaemon available to gpg-agent. */
|
||
else
|
||
{
|
||
write_status_text (STATUS_CARDCTRL, "4");
|
||
log_info ("selecting openpgp failed: %s\n", assuan_strerror (rc));
|
||
*scd_available = 1;
|
||
}
|
||
agent_release_card_info (&info);
|
||
agent_close (ctx);
|
||
return NULL;
|
||
}
|
||
|
||
app = xcalloc (1, sizeof *app);
|
||
app->assuan_ctx = ctx;
|
||
|
||
return app;
|
||
}
|
||
|
||
|
||
|
||
/* Open the current card and select the openpgp application. Return
|
||
an APP context handle to be used for further procesing or NULL on
|
||
error or if no OpenPGP application exists.*/
|
||
static app_t
|
||
open_card (void)
|
||
{
|
||
int slot = -1;
|
||
int rc;
|
||
app_t app;
|
||
int did_shutdown = 0;
|
||
int retry_count = 0;
|
||
|
||
/* First check whether we can contact a gpg-agent and divert all
|
||
operation to it. This is required because gpg as well as the
|
||
agent require exclusive access to the reader. */
|
||
if (opt.use_agent)
|
||
{
|
||
int scd_available;
|
||
|
||
app = open_card_via_agent (&scd_available);
|
||
if (app)
|
||
goto ready; /* Yes, there is a agent with a usable card, go that way. */
|
||
if (scd_available)
|
||
return NULL; /* agent avilabale but card problem. */
|
||
}
|
||
|
||
|
||
/* No agent or usable agent, thus we do it on our own. */
|
||
card_close ();
|
||
|
||
retry:
|
||
if (did_shutdown)
|
||
apdu_reset (slot);
|
||
else
|
||
{
|
||
slot = apdu_open_reader (default_reader_port);
|
||
if (slot == -1)
|
||
{
|
||
write_status_text (STATUS_CARDCTRL, "5");
|
||
log_error ("card reader not available\n");
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
app = xcalloc (1, sizeof *app);
|
||
app->slot = slot;
|
||
rc = app_select_openpgp (app);
|
||
if (opt.limit_card_insert_tries
|
||
&& ++retry_count >= opt.limit_card_insert_tries)
|
||
;
|
||
else if (rc && !opt.batch)
|
||
{
|
||
write_status_text (STATUS_CARDCTRL, "1");
|
||
|
||
did_shutdown = !!apdu_shutdown_reader (slot);
|
||
|
||
if ( cpr_get_answer_okay_cancel ("cardctrl.insert_card.okay",
|
||
_("Please insert the card and hit return or enter 'c' to cancel: "),
|
||
1) )
|
||
{
|
||
if (!did_shutdown)
|
||
apdu_close_reader (slot);
|
||
xfree (app);
|
||
goto retry;
|
||
}
|
||
}
|
||
if (rc)
|
||
{
|
||
write_status_text (STATUS_CARDCTRL, "4");
|
||
log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
|
||
apdu_close_reader (slot);
|
||
xfree (app);
|
||
return NULL;
|
||
}
|
||
|
||
ready:
|
||
app->initialized = 1;
|
||
current_app = app;
|
||
if (is_status_enabled () )
|
||
{
|
||
int i;
|
||
char *p, *buf;
|
||
|
||
buf = xmalloc (5 + app->serialnolen * 2 + 1);
|
||
p = stpcpy (buf, "3 ");
|
||
for (i=0; i < app->serialnolen; p +=2, i++)
|
||
sprintf (p, "%02X", app->serialno[i]);
|
||
write_status_text (STATUS_CARDCTRL, buf);
|
||
xfree (buf);
|
||
}
|
||
|
||
return app;
|
||
}
|
||
|
||
|
||
void
|
||
card_close (void)
|
||
{
|
||
if (current_app)
|
||
{
|
||
app_t app = current_app;
|
||
current_app = NULL;
|
||
|
||
if (app->assuan_ctx)
|
||
agent_close (app->assuan_ctx);
|
||
else
|
||
apdu_close_reader (app->slot);
|
||
xfree (app);
|
||
}
|
||
}
|
||
|
||
|
||
/* Format a cache ID from the serialnumber in SN and return it as an
|
||
allocated string. In case of an error NULL is returned. */
|
||
static char *
|
||
format_cacheid (const char *sn)
|
||
{
|
||
const char *s;
|
||
size_t snlen;
|
||
char *cacheid = NULL;
|
||
|
||
/* The serialnumber we use for a card is "CARDSN:serialno". Where
|
||
serialno is the BCD string (i.e. hex string) with the full
|
||
number. The serial number expect here constsis of hexdigits
|
||
followed by other characters, we cut off these other
|
||
characters. */
|
||
if (sn)
|
||
{
|
||
for (s=sn,snlen=0; hexdigitp (s); s++, snlen++)
|
||
;
|
||
if (snlen == 32)
|
||
{
|
||
/* Yes, this looks indeed like an OpenPGP card S/N. */
|
||
cacheid = xtrymalloc (7+snlen+1);
|
||
if (cacheid)
|
||
{
|
||
memcpy (cacheid, "CARDSN:", 7);
|
||
memcpy (cacheid+7, sn, snlen);
|
||
cacheid[7+snlen] = 0;
|
||
}
|
||
}
|
||
}
|
||
return cacheid;
|
||
}
|
||
|
||
/* Check that the serial number of the current card (as described by
|
||
APP) matches SERIALNO. If there is no match and we are not in
|
||
batch mode, present a prompt to insert the desired card. The
|
||
function return 0 is the present card is okay, -1 if the user
|
||
selected to insert a new card or an error value. Note that the
|
||
card context will be closed in all cases except for 0 as return
|
||
value and if it was possible to merely shutdown the reader. */
|
||
static int
|
||
check_card_serialno (app_t app, const char *serialno)
|
||
{
|
||
const char *s;
|
||
int ask = 0;
|
||
int n;
|
||
|
||
for (s = serialno, n=0; *s != '/' && hexdigitp (s); s++, n++)
|
||
;
|
||
if (n != 32)
|
||
{
|
||
log_error ("invalid serial number in keyring detected\n");
|
||
return gpg_error (GPG_ERR_INV_ID);
|
||
}
|
||
if (app->serialnolen != 16)
|
||
ask = 1;
|
||
for (s = serialno, n=0; !ask && n < 16; s += 2, n++)
|
||
if (app->serialno[n] != xtoi_2 (s))
|
||
ask = 1;
|
||
if (ask)
|
||
{
|
||
char buf[5+32+1];
|
||
int did_shutdown = 0;
|
||
|
||
if (current_app && !apdu_shutdown_reader (current_app->slot))
|
||
did_shutdown = 1;
|
||
else
|
||
card_close ();
|
||
tty_printf (_("Please remove the current card and "
|
||
"insert the one with serial number:\n"
|
||
" %.*s\n"), 32, serialno);
|
||
|
||
sprintf (buf, "1 %.32s", serialno);
|
||
write_status_text (STATUS_CARDCTRL, buf);
|
||
|
||
if ( cpr_get_answer_okay_cancel ("cardctrl.change_card.okay",
|
||
_("Hit return when ready "
|
||
"or enter 'c' to cancel: "),
|
||
1) )
|
||
{
|
||
card_close ();
|
||
return -1;
|
||
}
|
||
if (did_shutdown)
|
||
apdu_reset (current_app->slot);
|
||
else
|
||
card_close ();
|
||
return gpg_error (GPG_ERR_INV_ID);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Take a 20 byte hexencoded string and put it into the the provided
|
||
20 byte buffer FPR in binary format. */
|
||
static int
|
||
unhexify_fpr (const char *hexstr, unsigned char *fpr)
|
||
{
|
||
const char *s;
|
||
int n;
|
||
|
||
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
|
||
;
|
||
if (*s || (n != 40))
|
||
return 0; /* no fingerprint (invalid or wrong length). */
|
||
n /= 2;
|
||
for (s=hexstr, n=0; *s; s += 2, n++)
|
||
fpr[n] = xtoi_2 (s);
|
||
return 1; /* okay */
|
||
}
|
||
|
||
/* Take the serial number from LINE and return it verbatim in a newly
|
||
allocated string. We make sure that only hex characters are
|
||
returned. */
|
||
static char *
|
||
store_serialno (const char *line)
|
||
{
|
||
const char *s;
|
||
char *p;
|
||
|
||
for (s=line; hexdigitp (s); s++)
|
||
;
|
||
p = xmalloc (s + 1 - line);
|
||
memcpy (p, line, s-line);
|
||
p[s-line] = 0;
|
||
return p;
|
||
}
|
||
|
||
|
||
|
||
static assuan_error_t
|
||
learn_status_cb (void *opaque, const char *line)
|
||
{
|
||
struct agent_card_info_s *parm = opaque;
|
||
const char *keyword = line;
|
||
int keywordlen;
|
||
int i;
|
||
|
||
/* log_debug ("got status line `%s'\n", line); */
|
||
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
|
||
;
|
||
while (spacep (line))
|
||
line++;
|
||
|
||
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
|
||
{
|
||
xfree (parm->serialno);
|
||
parm->serialno = store_serialno (line);
|
||
}
|
||
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
|
||
{
|
||
xfree (parm->disp_name);
|
||
parm->disp_name = unescape_percent_string (line);
|
||
}
|
||
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
|
||
{
|
||
xfree (parm->disp_lang);
|
||
parm->disp_lang = unescape_percent_string (line);
|
||
}
|
||
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
|
||
{
|
||
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
|
||
}
|
||
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
|
||
{
|
||
xfree (parm->pubkey_url);
|
||
parm->pubkey_url = unescape_percent_string (line);
|
||
}
|
||
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
|
||
{
|
||
xfree (parm->login_data);
|
||
parm->login_data = unescape_percent_string (line);
|
||
}
|
||
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
|
||
{
|
||
parm->sig_counter = strtoul (line, NULL, 0);
|
||
}
|
||
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
|
||
{
|
||
char *p, *buf;
|
||
|
||
buf = p = unescape_percent_string (line);
|
||
if (buf)
|
||
{
|
||
while (spacep (p))
|
||
p++;
|
||
parm->chv1_cached = atoi (p);
|
||
while (*p && !spacep (p))
|
||
p++;
|
||
while (spacep (p))
|
||
p++;
|
||
for (i=0; *p && i < 3; i++)
|
||
{
|
||
parm->chvmaxlen[i] = atoi (p);
|
||
while (*p && !spacep (p))
|
||
p++;
|
||
while (spacep (p))
|
||
p++;
|
||
}
|
||
for (i=0; *p && i < 3; i++)
|
||
{
|
||
parm->chvretry[i] = atoi (p);
|
||
while (*p && !spacep (p))
|
||
p++;
|
||
while (spacep (p))
|
||
p++;
|
||
}
|
||
xfree (buf);
|
||
}
|
||
}
|
||
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
|
||
{
|
||
int no = atoi (line);
|
||
while (* line && !spacep (line))
|
||
line++;
|
||
while (spacep (line))
|
||
line++;
|
||
if (no == 1)
|
||
parm->fpr1valid = unhexify_fpr (line, parm->fpr1);
|
||
else if (no == 2)
|
||
parm->fpr2valid = unhexify_fpr (line, parm->fpr2);
|
||
else if (no == 3)
|
||
parm->fpr3valid = unhexify_fpr (line, parm->fpr3);
|
||
}
|
||
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
|
||
{
|
||
int no = atoi (line);
|
||
while (* line && !spacep (line))
|
||
line++;
|
||
while (spacep (line))
|
||
line++;
|
||
if (no == 1)
|
||
parm->fpr1time = strtoul (line, NULL, 10);
|
||
else if (no == 2)
|
||
parm->fpr2time = strtoul (line, NULL, 10);
|
||
else if (no == 3)
|
||
parm->fpr3time = strtoul (line, NULL, 10);
|
||
}
|
||
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
|
||
{
|
||
int no = atoi (line);
|
||
while (*line && !spacep (line))
|
||
line++;
|
||
while (spacep (line))
|
||
line++;
|
||
if (no == 1)
|
||
parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1);
|
||
else if (no == 2)
|
||
parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2);
|
||
else if (no == 3)
|
||
parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3);
|
||
}
|
||
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
|
||
&& strchr ("1234", keyword[11]))
|
||
{
|
||
int no = keyword[11] - '1';
|
||
assert (no >= 0 && no <= 3);
|
||
xfree (parm->private_do[no]);
|
||
parm->private_do[no] = unescape_percent_string (line);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Return card info. */
|
||
int
|
||
agent_learn (struct agent_card_info_s *info)
|
||
{
|
||
app_t app;
|
||
int rc;
|
||
struct ctrl_ctx_s ctrl;
|
||
time_t stamp;
|
||
char *serial;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
memset (info, 0, sizeof *info);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
rc = assuan_transact (app->assuan_ctx, "SCD LEARN --force",
|
||
NULL, NULL, NULL, NULL,
|
||
learn_status_cb, info);
|
||
rc = test_transact (rc, "SCD LEARN");
|
||
}
|
||
else
|
||
{
|
||
memset (&ctrl, 0, sizeof ctrl);
|
||
ctrl.status_cb = learn_status_cb;
|
||
ctrl.status_cb_arg = info;
|
||
|
||
rc = app_get_serial_and_stamp (app, &serial, &stamp);
|
||
if (!rc)
|
||
{
|
||
send_status_info (&ctrl, "SERIALNO",
|
||
serial, strlen(serial), NULL, 0);
|
||
xfree (serial);
|
||
rc = app->fnc.learn_status (app, &ctrl);
|
||
}
|
||
}
|
||
|
||
return rc;
|
||
}
|
||
|
||
|
||
/* Get an attribute from the card. Make sure info is initialized. */
|
||
int
|
||
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
|
||
{
|
||
int rc;
|
||
app_t app;
|
||
struct ctrl_ctx_s ctrl;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char line[ASSUAN_LINELENGTH];
|
||
|
||
/* We assume that NAME does not need escaping. */
|
||
if (12 + strlen (name) > DIM(line)-1)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
stpcpy (stpcpy (line, "SCD GETATTR "), name);
|
||
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL,
|
||
learn_status_cb, info),
|
||
"SCD GETATTR");
|
||
}
|
||
else
|
||
{
|
||
ctrl.status_cb = learn_status_cb;
|
||
ctrl.status_cb_arg = info;
|
||
rc = app->fnc.getattr (app, &ctrl, name);
|
||
}
|
||
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
static int
|
||
pin_cb (void *opaque, const char *info, char **retstr)
|
||
{
|
||
struct pincb_parm_s *parm = opaque;
|
||
char *value;
|
||
int canceled;
|
||
int isadmin = 0;
|
||
int newpin = 0;
|
||
const char *again_text = NULL;
|
||
const char *ends, *s;
|
||
char *cacheid = NULL;
|
||
|
||
*retstr = NULL;
|
||
/* log_debug ("asking for PIN '%s'\n", info); */
|
||
|
||
/* We use a special prefix to check whether the Admin PIN has been
|
||
requested. */
|
||
if (info && *info =='|' && (ends=strchr (info+1, '|')))
|
||
{
|
||
for (s=info+1; s < ends; s++)
|
||
{
|
||
if (*s == 'A')
|
||
isadmin = 1;
|
||
else if (*s == 'N')
|
||
newpin = 1;
|
||
}
|
||
info = ends+1;
|
||
}
|
||
else if (info && *info == '|')
|
||
log_debug ("pin_cb called without proper PIN info hack\n");
|
||
|
||
/* If we are not requesting a new PIN and we are not requesting an
|
||
AdminPIN, compute a string to be used as the cacheID for
|
||
gpg-agent. */
|
||
if (!newpin && !isadmin && parm)
|
||
{
|
||
cacheid = format_cacheid (parm->sn);
|
||
}
|
||
else if (newpin && parm)
|
||
{
|
||
/* Make really sure that it is not cached anymore. */
|
||
agent_clear_pin_cache (parm->sn);
|
||
}
|
||
|
||
|
||
again:
|
||
if (is_status_enabled())
|
||
write_status_text (STATUS_NEED_PASSPHRASE_PIN,
|
||
isadmin? "OPENPGP 3" : "OPENPGP 1");
|
||
|
||
value = ask_passphrase (info, again_text,
|
||
newpin && isadmin? "passphrase.adminpin.new.ask" :
|
||
newpin? "passphrase.pin.new.ask" :
|
||
isadmin? "passphrase.adminpin.ask" :
|
||
"passphrase.pin.ask",
|
||
newpin && isadmin? _("Enter New Admin PIN: ") :
|
||
newpin? _("Enter New PIN: ") :
|
||
isadmin? _("Enter Admin PIN: ")
|
||
: _("Enter PIN: "),
|
||
cacheid,
|
||
&canceled);
|
||
xfree (cacheid);
|
||
cacheid = NULL;
|
||
again_text = NULL;
|
||
if (!value && canceled)
|
||
return -1;
|
||
else if (!value)
|
||
return G10ERR_GENERAL;
|
||
|
||
if (newpin)
|
||
{
|
||
char *value2;
|
||
|
||
value2 = ask_passphrase (info, NULL, NULL,
|
||
"passphrase.pin.repeat",
|
||
_("Repeat this PIN: "),
|
||
&canceled);
|
||
if (!value && canceled)
|
||
{
|
||
xfree (value);
|
||
return -1;
|
||
}
|
||
else if (!value)
|
||
{
|
||
xfree (value);
|
||
return G10ERR_GENERAL;
|
||
}
|
||
if (strcmp (value, value2))
|
||
{
|
||
again_text = N_("PIN not correctly repeated; try again");
|
||
xfree (value2);
|
||
xfree (value);
|
||
value = NULL;
|
||
goto again;
|
||
}
|
||
xfree (value2);
|
||
}
|
||
|
||
*retstr = value;
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Send a SETATTR command to the SCdaemon. */
|
||
int
|
||
agent_scd_setattr (const char *name,
|
||
const unsigned char *value, size_t valuelen)
|
||
{
|
||
app_t app;
|
||
int rc;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char line[ASSUAN_LINELENGTH];
|
||
char *p;
|
||
|
||
/* We assume that NAME does not need escaping. */
|
||
if (12 + strlen (name) > DIM(line)-1)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
|
||
*p++ = ' ';
|
||
for (; valuelen; value++, valuelen--)
|
||
{
|
||
if (p >= line + DIM(line)-5 )
|
||
return gpg_error (GPG_ERR_CARD);
|
||
if (*value < ' ' || *value == '+' || *value == '%')
|
||
{
|
||
sprintf (p, "%%%02X", *value);
|
||
p += 3;
|
||
}
|
||
else if (*value == ' ')
|
||
*p++ = '+';
|
||
else
|
||
*p++ = *value;
|
||
}
|
||
*p = 0;
|
||
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL, NULL, NULL),
|
||
"SCD SETATTR");
|
||
}
|
||
else
|
||
{
|
||
rc = app->fnc.setattr (app, name, pin_cb, NULL, value, valuelen);
|
||
}
|
||
|
||
if (rc)
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
return rc;
|
||
}
|
||
|
||
|
||
/* Handle a KEYDATA inquiry. Note, we only send the data,
|
||
assuan_transact takes care of flushing and writing the end */
|
||
static assuan_error_t
|
||
inq_writekey_parms (void *opaque, const char *keyword)
|
||
{
|
||
struct writekey_parm_s *parm = opaque;
|
||
|
||
return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen);
|
||
}
|
||
|
||
|
||
/* Send a WRITEKEY command to the SCdaemon. */
|
||
int
|
||
agent_scd_writekey (int keyno, const unsigned char *keydata, size_t keydatalen)
|
||
{
|
||
app_t app;
|
||
int rc;
|
||
char line[ASSUAN_LINELENGTH];
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
struct writekey_parm_s parms;
|
||
|
||
snprintf (line, DIM(line)-1, "SCD WRITEKEY --force OPENPGP.%d", keyno);
|
||
line[DIM(line)-1] = 0;
|
||
parms.ctx = app->assuan_ctx;
|
||
parms.keydata = keydata;
|
||
parms.keydatalen = keydatalen;
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL,
|
||
inq_writekey_parms, &parms,
|
||
NULL, NULL),
|
||
"SCD WRITEKEY");
|
||
}
|
||
else
|
||
{
|
||
snprintf (line, DIM(line)-1, "OPENPGP.%d", keyno);
|
||
line[DIM(line)-1] = 0;
|
||
rc = app->fnc.writekey (app, NULL, line, 0x0001,
|
||
pin_cb, NULL,
|
||
keydata, keydatalen);
|
||
}
|
||
|
||
if (rc)
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
static assuan_error_t
|
||
genkey_status_cb (void *opaque, const char *line)
|
||
{
|
||
struct agent_card_genkey_s *parm = opaque;
|
||
const char *keyword = line;
|
||
int keywordlen;
|
||
|
||
/* log_debug ("got status line `%s'\n", line); */
|
||
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
|
||
;
|
||
while (spacep (line))
|
||
line++;
|
||
|
||
if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
|
||
{
|
||
parm->fprvalid = unhexify_fpr (line, parm->fpr);
|
||
}
|
||
if (keywordlen == 8 && !memcmp (keyword, "KEY-DATA", keywordlen))
|
||
{
|
||
MPI a;
|
||
const char *name = line;
|
||
char *buf;
|
||
|
||
while (*line && !spacep (line))
|
||
line++;
|
||
while (spacep (line))
|
||
line++;
|
||
|
||
buf = xmalloc ( 2 + strlen (line) + 1);
|
||
strcpy (stpcpy (buf, "0x"), line);
|
||
a = mpi_alloc (300);
|
||
if( mpi_fromstr (a, buf) )
|
||
log_error ("error parsing received key data\n");
|
||
else if (*name == 'n' && spacep (name+1))
|
||
parm->n = a;
|
||
else if (*name == 'e' && spacep (name+1))
|
||
parm->e = a;
|
||
else
|
||
{
|
||
log_info ("unknown parameter name in received key data\n");
|
||
mpi_free (a);
|
||
}
|
||
xfree (buf);
|
||
}
|
||
else if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
|
||
{
|
||
parm->created_at = (u32)strtoul (line, NULL, 10);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Send a GENKEY command to the SCdaemon. */
|
||
int
|
||
agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force)
|
||
{
|
||
app_t app;
|
||
char line[ASSUAN_LINELENGTH];
|
||
struct ctrl_ctx_s ctrl;
|
||
int rc;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
memset (info, 0, sizeof *info);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
snprintf (line, DIM(line)-1, "SCD GENKEY %s%d",
|
||
force? "--force ":"", keyno);
|
||
line[DIM(line)-1] = 0;
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL,
|
||
genkey_status_cb, info),
|
||
"SCD GENKEY");
|
||
}
|
||
else
|
||
{
|
||
snprintf (line, DIM(line)-1, "%d", keyno);
|
||
ctrl.status_cb = genkey_status_cb;
|
||
ctrl.status_cb_arg = info;
|
||
rc = app->fnc.genkey (app, &ctrl, line,
|
||
force? 1:0,
|
||
pin_cb, NULL);
|
||
}
|
||
|
||
if (rc)
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
return rc;
|
||
}
|
||
|
||
|
||
static assuan_error_t
|
||
membuf_data_cb (void *opaque, const void *buffer, size_t length)
|
||
{
|
||
membuf_t *data = opaque;
|
||
|
||
if (buffer)
|
||
put_membuf (data, buffer, length);
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Send a PKSIGN command to the SCdaemon. */
|
||
int
|
||
agent_scd_pksign (const char *serialno, int hashalgo,
|
||
const unsigned char *indata, size_t indatalen,
|
||
unsigned char **r_buf, size_t *r_buflen)
|
||
{
|
||
struct pincb_parm_s parm;
|
||
app_t app;
|
||
int rc;
|
||
|
||
*r_buf = NULL;
|
||
*r_buflen = 0;
|
||
memset (&parm, 0, sizeof parm);
|
||
parm.sn = serialno;
|
||
retry:
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char *p, line[ASSUAN_LINELENGTH];
|
||
membuf_t data;
|
||
size_t len;
|
||
int i;
|
||
|
||
if (indatalen*2 + 50 > DIM(line))
|
||
return gpg_error (GPG_ERR_GENERAL);
|
||
|
||
p = stpcpy (line, "SCD SETDATA ");
|
||
for (i=0; i < indatalen ; i++, p += 2 )
|
||
sprintf (p, "%02X", indata[i]);
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL, NULL, NULL),
|
||
"SCD SETDATA");
|
||
if (!rc)
|
||
{
|
||
init_membuf (&data, 1024);
|
||
snprintf (line, DIM(line)-1, "SCD PKSIGN %s", serialno);
|
||
line[DIM(line)-1] = 0;
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
membuf_data_cb, &data,
|
||
NULL, NULL, NULL, NULL),
|
||
"SCD PKSIGN");
|
||
if (rc)
|
||
xfree (get_membuf (&data, &len));
|
||
else
|
||
*r_buf = get_membuf (&data, r_buflen);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Check that the card's serialnumber is as required.*/
|
||
rc = check_card_serialno (app, serialno);
|
||
if (rc == -1)
|
||
goto retry;
|
||
|
||
if (!rc)
|
||
rc = app->fnc.sign (app, serialno, hashalgo,
|
||
pin_cb, &parm,
|
||
indata, indatalen,
|
||
r_buf, r_buflen);
|
||
}
|
||
|
||
if (rc)
|
||
{
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
if (!app->assuan_ctx)
|
||
agent_clear_pin_cache (serialno);
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
|
||
/* Send a PKDECRYPT command to the SCdaemon. */
|
||
int
|
||
agent_scd_pkdecrypt (const char *serialno,
|
||
const unsigned char *indata, size_t indatalen,
|
||
unsigned char **r_buf, size_t *r_buflen)
|
||
{
|
||
struct pincb_parm_s parm;
|
||
app_t app;
|
||
int rc;
|
||
|
||
*r_buf = NULL;
|
||
*r_buflen = 0;
|
||
memset (&parm, 0, sizeof parm);
|
||
parm.sn = serialno;
|
||
retry:
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char *p, line[ASSUAN_LINELENGTH];
|
||
membuf_t data;
|
||
size_t len;
|
||
int i;
|
||
|
||
if (indatalen*2 + 50 > DIM(line))
|
||
return gpg_error (GPG_ERR_GENERAL);
|
||
|
||
p = stpcpy (line, "SCD SETDATA ");
|
||
for (i=0; i < indatalen ; i++, p += 2 )
|
||
sprintf (p, "%02X", indata[i]);
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL, NULL, NULL),
|
||
"SCD SETDATA");
|
||
if (!rc)
|
||
{
|
||
init_membuf (&data, 1024);
|
||
snprintf (line, DIM(line)-1, "SCD PKDECRYPT %s", serialno);
|
||
line[DIM(line)-1] = 0;
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
membuf_data_cb, &data,
|
||
NULL, NULL, NULL, NULL),
|
||
"SCD PKDECRYPT");
|
||
if (rc)
|
||
xfree (get_membuf (&data, &len));
|
||
else
|
||
*r_buf = get_membuf (&data, r_buflen);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Check that the card's serialnumber is as required.*/
|
||
rc = check_card_serialno (app, serialno);
|
||
if (rc == -1)
|
||
goto retry;
|
||
|
||
if (!rc)
|
||
rc = app->fnc.decipher (app, serialno,
|
||
pin_cb, &parm,
|
||
indata, indatalen,
|
||
r_buf, r_buflen);
|
||
}
|
||
|
||
if (rc)
|
||
{
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
if (!app->assuan_ctx)
|
||
agent_clear_pin_cache (serialno);
|
||
}
|
||
return rc;
|
||
}
|
||
|
||
/* Change the PIN of an OpenPGP card or reset the retry counter. */
|
||
int
|
||
agent_scd_change_pin (int chvno)
|
||
{
|
||
app_t app;
|
||
int reset = 0;
|
||
int rc;
|
||
|
||
reset = (chvno >= 100);
|
||
chvno %= 100;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char line[ASSUAN_LINELENGTH];
|
||
|
||
snprintf (line, DIM(line)-1, "SCD PASSWD%s %d",
|
||
reset? " --reset":"", chvno);
|
||
line[DIM(line)-1] = 0;
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL, NULL, NULL),
|
||
"SCD PASSWD");
|
||
}
|
||
else
|
||
{
|
||
char chvnostr[50];
|
||
|
||
sprintf (chvnostr, "%d", chvno);
|
||
rc = app->fnc.change_pin (app, NULL, chvnostr, reset,
|
||
pin_cb, NULL);
|
||
}
|
||
|
||
if (rc)
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
return rc;
|
||
}
|
||
|
||
/* Perform a CHECKPIN operation. SERIALNO should be the serial
|
||
number of the card - optionally followed by the fingerprint;
|
||
however the fingerprint is ignored here. */
|
||
int
|
||
agent_scd_checkpin (const char *serialnobuf)
|
||
{
|
||
app_t app;
|
||
int rc;
|
||
|
||
app = current_app? current_app : open_card ();
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
|
||
if (app->assuan_ctx)
|
||
{
|
||
char line[ASSUAN_LINELENGTH];
|
||
|
||
if (15 + strlen (serialnobuf) > DIM(line)-1)
|
||
return gpg_error (GPG_ERR_CARD);
|
||
stpcpy (stpcpy (line, "SCD CHECKPIN "), serialnobuf);
|
||
rc = test_transact (assuan_transact (app->assuan_ctx, line,
|
||
NULL, NULL, NULL, NULL, NULL, NULL),
|
||
"SCD CHECKPIN");
|
||
}
|
||
else
|
||
{
|
||
rc = app->fnc.check_pin (app, serialnobuf, pin_cb, NULL);
|
||
}
|
||
|
||
if (rc)
|
||
write_status (STATUS_SC_OP_FAILURE);
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
void
|
||
agent_clear_pin_cache (const char *sn)
|
||
{
|
||
char *cacheid = format_cacheid (sn);
|
||
if (cacheid)
|
||
{
|
||
passphrase_clear_cache (NULL, cacheid, 0);
|
||
xfree (cacheid);
|
||
}
|
||
}
|