Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/agent/command.c b/agent/command.c
index b7a71cbe5..da91053d8 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1,4486 +1,4520 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001-2011 Free Software Foundation, Inc.
* Copyright (C) 2001-2013 Werner Koch
* Copyright (C) 2015-2021 g10 Code GmbH.
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
/* FIXME: we should not use the default assuan buffering but setup
some buffering in secure mempory to protect session keys etc. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "agent.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/ssh-utils.h"
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum allowed size of the inquired ciphertext. */
#define MAXLEN_CIPHERTEXT 4096
/* Maximum allowed size of the key parameters. */
#define MAXLEN_KEYPARAM 1024
/* Maximum allowed size of key data as used in inquiries (bytes). */
#define MAXLEN_KEYDATA 8192
/* Maximum length of a secret to store under one key. */
#define MAXLEN_PUT_SECRET 4096
/* The size of the import/export KEK key (in bytes). */
#define KEYWRAP_KEYSIZE (128/8)
/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
text string. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Check that the maximum digest length we support has at least the
length of the keygrip. */
#if MAX_DIGEST_LEN < 20
#error MAX_DIGEST_LEN shorter than keygrip
#endif
/* Data used to associate an Assuan context with local server data.
This is this modules local part of the server_control_s struct. */
struct server_local_s
{
/* Our Assuan context. */
assuan_context_t assuan_ctx;
/* If this flag is true, the passphrase cache is used for signing
operations. It defaults to true but may be set on a per
connection base. The global option opt.ignore_cache_for_signing
takes precedence over this flag. */
unsigned int use_cache_for_signing : 1;
/* Flag to suppress I/O logging during a command. */
unsigned int pause_io_logging : 1;
/* Flag indicating that the connection is from ourselves. */
unsigned int connect_from_self : 1;
/* Helper flag for io_monitor to allow suppressing of our own
* greeting in some cases. See io_monitor for details. */
unsigned int greeting_seen : 1;
/* If this flag is set to true the agent will be terminated after
the end of the current session. */
unsigned int stopme : 1;
/* Flag indicating whether pinentry notifications shall be done. */
unsigned int allow_pinentry_notify : 1;
/* An allocated description for the next key operation. This is
used if a pinnetry needs to be popped up. */
char *keydesc;
/* Malloced KEK (Key-Encryption-Key) for the import_key command. */
void *import_key;
/* Malloced KEK for the export_key command. */
void *export_key;
/* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
int allow_fully_canceled;
/* Last CACHE_NONCE sent as status (malloced). */
char *last_cache_nonce;
/* Last PASSWD_NONCE sent as status (malloced). */
char *last_passwd_nonce;
/* Per connection cache of the keyinfo from the cards. The
* eventcounters for cards at the time the info was fetched is
* stored here as a freshness indicator. */
struct {
struct card_key_info_s *ki;
unsigned int eventno;
unsigned int maybe_key_change;
} last_card_keyinfo;
};
/* An entry for the getval/putval commands. */
struct putval_item_s
{
struct putval_item_s *next;
size_t off; /* Offset to the value into DATA. */
size_t len; /* Length of the value. */
char d[1]; /* Key | Nul | value. */
};
/* A list of key value pairs fpr the getval/putval commands. */
static struct putval_item_s *putval_list;
/* To help polling clients, we keep track of the number of certain
events. This structure keeps those counters. The counters are
integers and there should be no problem if they are overflowing as
callers need to check only whether a counter changed. The actual
values are not meaningful. */
struct
{
/* Incremented if any of the other counters below changed. */
unsigned int any;
/* Incremented if a key is added or removed from the internal privat
key database. */
unsigned int key;
/* Incremented if a change of the card readers stati has been
detected. */
unsigned int card;
/* Internal counter to track possible changes to a key.
* FIXME: This should be replaced by generic notifications from scd.
*/
unsigned int maybe_key_change;
} eventcounter;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Release the memory buffer MB but first wipe out the used memory. */
static void
clear_outbuf (membuf_t *mb)
{
void *p;
size_t n;
p = get_membuf (mb, &n);
if (p)
{
wipememory (p, n);
xfree (p);
}
}
/* Write the content of memory buffer MB as assuan data to CTX and
wipe the buffer out afterwards. */
static gpg_error_t
write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
{
gpg_error_t ae;
void *p;
size_t n;
p = get_membuf (mb, &n);
if (!p)
return out_of_core ();
ae = assuan_send_data (ctx, p, n);
memset (p, 0, n);
xfree (p);
return ae;
}
/* Clear the nonces used to enable the passphrase cache for certain
multi-command command sequences. */
static void
clear_nonce_cache (ctrl_t ctrl)
{
if (ctrl->server_local->last_cache_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = NULL;
}
if (ctrl->server_local->last_passwd_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = NULL;
}
}
/* This function is called by Libassuan whenever the client sends a
reset. It has been registered similar to the other Assuan
commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
memset (ctrl->keygrip, 0, 20);
ctrl->have_keygrip = 0;
ctrl->digest.valuelen = 0;
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
clear_nonce_cache (ctrl);
return 0;
}
/* Replace all '+' by a blank in the string S. */
static void
plus_to_blank (char *s)
{
for (; *s; s++)
{
if (*s == '+')
*s = ' ';
}
}
/* Parse a hex string. Return an Assuan error code or 0 on success and the
length of the parsed string in LEN. */
static int
parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
{
const char *p;
size_t n;
/* parse the hash value */
for (p=string, n=0; hexdigitp (p); p++, n++)
;
if (*p != ' ' && *p != '\t' && *p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
*len = n;
return 0;
}
/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
provide space for 20 bytes. BUF is not changed if the function
returns an error. */
static int
parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
{
int rc;
size_t n = 0;
rc = parse_hexstring (ctx, string, &n);
if (rc)
return rc;
n /= 2;
if (n != 20)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
if (hex2bin (string, buf, 20) < 0)
return set_error (GPG_ERR_BUG, "hex2bin");
return 0;
}
/* Parse the TTL from STRING. Leading and trailing spaces are
* skipped. The value is constrained to -1 .. MAXINT. On error 0 is
* returned, else the number of bytes scanned. */
static size_t
parse_ttl (const char *string, int *r_ttl)
{
const char *string_orig = string;
long ttl;
char *pend;
ttl = strtol (string, &pend, 10);
string = pend;
if (string == string_orig || !(spacep (string) || !*string)
|| ttl < -1L || (int)ttl != (long)ttl)
{
*r_ttl = 0;
return 0;
}
while (spacep (string) || *string== '\n')
string++;
*r_ttl = (int)ttl;
return string - string_orig;
}
/* Write an Assuan status line. KEYWORD is the first item on the
* status line. The following arguments are all separated by a space
* in the output. The last argument must be a NULL. Linefeeds and
* carriage returns characters (which are not allowed in an Assuan
* status line) are silently quoted in C-style. */
gpg_error_t
agent_write_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
arg instead of an assuan context as first argument. */
gpg_error_t
agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about a launched Pinentry. Because
that might disturb some older clients, this is only done if enabled
via an option. Returns an gpg error code. */
gpg_error_t
agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
{
char line[256];
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
pid, extra?" ":"", extra? extra:"");
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/* An agent progress callback for Libgcrypt. This has been registered
* to be called via the progress dispatcher mechanism from
* gpg-agent.c */
static void
progress_cb (ctrl_t ctrl, const char *what, int printchar,
int current, int total)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
;
else if (printchar == '\n' && what && !strcmp (what, "primegen"))
agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
else
agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total);
}
/* Helper to print a message while leaving a command. Note that this
* function does not call assuan_set_error; the caller may do this
* prior to calling us. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
/* Not all users of gpg-agent know about the fully canceled
error code; map it back if needed. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->server_local->allow_fully_canceled)
err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
}
/* Most code from common/ does not know the error source, thus
we fix this here. */
if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Take the keyinfo for cards from our local cache. Actually this
* cache could be a global one but then we would need to employ
* reference counting. */
static struct card_key_info_s *
get_keyinfo_on_cards (ctrl_t ctrl)
{
struct card_key_info_s *keyinfo_on_cards = NULL;
if (opt.disable_daemon[DAEMON_SCD])
return NULL;
if (ctrl->server_local->last_card_keyinfo.ki
&& ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card
&& (ctrl->server_local->last_card_keyinfo.maybe_key_change
== eventcounter.maybe_key_change))
{
keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki;
}
else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards))
{
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards;
ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card;
ctrl->server_local->last_card_keyinfo.maybe_key_change
= eventcounter.maybe_key_change;
}
return keyinfo_on_cards;
}
static const char hlp_geteventcounter[] =
"GETEVENTCOUNTER\n"
"\n"
"Return a status line named EVENTCOUNTER with the current values\n"
"of all event counters. The values are decimal numbers in the range\n"
"0 to UINT_MAX and wrapping around to 0. The actual values should\n"
"not be relied upon, they shall only be used to detect a change.\n"
"\n"
"The currently defined counters are:\n"
"\n"
"ANY - Incremented with any change of any of the other counters.\n"
"KEY - Incremented for added or removed private keys.\n"
"CARD - Incremented for changes of the card readers stati.";
static gpg_error_t
cmd_geteventcounter (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
eventcounter.any,
eventcounter.key,
eventcounter.card);
}
/* This function should be called once for all key removals or
additions. This function is assured not to do any context
switches. */
void
bump_key_eventcounter (void)
{
eventcounter.key++;
eventcounter.any++;
}
/* This function should be called for all card reader status
changes. This function is assured not to do any context
switches. */
void
bump_card_eventcounter (void)
{
eventcounter.card++;
eventcounter.any++;
}
static const char hlp_istrusted[] =
"ISTRUSTED <hexstring_with_fingerprint>\n"
"\n"
"Return OK when we have an entry with this fingerprint in our\n"
"trustlist";
static gpg_error_t
cmd_istrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
/* Parse the fingerprint value. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
rc = agent_istrusted (ctrl, fpr, NULL);
if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
return rc;
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
return gpg_error (GPG_ERR_NOT_TRUSTED);
else
return leave_cmd (ctx, rc);
}
static const char hlp_listtrusted[] =
"LISTTRUSTED\n"
"\n"
"List all entries from the trustlist.";
static gpg_error_t
cmd_listtrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
rc = agent_listtrusted (ctx);
return leave_cmd (ctx, rc);
}
static const char hlp_martrusted[] =
"MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
"\n"
"Store a new key in into the trustlist.";
static gpg_error_t
cmd_marktrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
int flag;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the fingerprint value */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (!spacep (p) || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
while (spacep (p))
p++;
flag = *p++;
if ( (flag != 'S' && flag != 'P') || !spacep (p) )
return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
while (spacep (p))
p++;
rc = agent_marktrusted (ctrl, p, fpr, flag);
return leave_cmd (ctx, rc);
}
static const char hlp_havekey[] =
"HAVEKEY <hexstrings_with_keygrips>\n"
"HAVEKEY --list[=<limit>]\n"
+ "HAVEKEY --info <hexkeygrip>\n"
"\n"
"Return success if at least one of the secret keys with the given\n"
"keygrips is available. With --list return all available keygrips\n"
- "as binary data; with <limit> bail out at this number of keygrips";
+ "as binary data; with <limit> bail out at this number of keygrips.\n"
+ "In --info mode check just one keygrip.";
static gpg_error_t
cmd_havekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl;
gpg_error_t err;
unsigned char grip[20];
char *p;
- int list_mode; /* Less than 0 for no limit. */
+ int list_mode = 0; /* Less than 0 for no limit. */
+ int info_mode = 0;
int counter;
char *dirname;
gnupg_dir_t dir;
gnupg_dirent_t dir_entry;
char hexgrip[41];
struct card_key_info_s *keyinfo_on_cards, *l;
- if (has_option_name (line, "--list"))
+ if (has_option (line, "--info"))
+ info_mode = 1;
+ else if (has_option_name (line, "--list"))
{
if ((p = option_value (line, "--list")))
list_mode = atoi (p);
else
list_mode = -1;
}
- else
- list_mode = 0;
+
+ line = skip_options (line);
+
+
+ if (info_mode)
+ {
+ int keytype;
+ const char *infostring;
+
+ ctrl = assuan_get_pointer (ctx);
+
+ err = parse_keygrip (ctx, line, grip);
+ if (err)
+ goto leave;
+
+ err = agent_key_info_from_file (ctrl, grip, &keytype, NULL, NULL);
+ if (err)
+ goto leave;
+
+ switch (keytype)
+ {
+ case PRIVATE_KEY_CLEAR:
+ case PRIVATE_KEY_OPENPGP_NONE: infostring = "clear"; break;
+ case PRIVATE_KEY_PROTECTED: infostring = "protected"; break;
+ case PRIVATE_KEY_SHADOWED: infostring = "shadowed"; break;
+ default: infostring = "unknown"; break;
+ }
+
+ err = agent_write_status (ctrl, "KEYFILEINFO", infostring, NULL);
+ goto leave;
+ }
if (!list_mode)
{
do
{
err = parse_keygrip (ctx, line, grip);
if (err)
return err;
if (!agent_key_available (grip))
return 0; /* Found. */
while (*line && *line != ' ' && *line != '\t')
line++;
while (*line == ' ' || *line == '\t')
line++;
}
while (*line);
/* No leave_cmd() here because errors are expected and would clutter
* the log. */
return gpg_error (GPG_ERR_NO_SECKEY);
}
/* List mode. */
dir = NULL;
dirname = NULL;
ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
counter = 0;
while ((dir_entry = gnupg_readdir (dir)))
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
/* And now the keys from the current cards. If they already got a
* stub, they are listed twice but we don't care. */
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
for (l = keyinfo_on_cards; l; l = l->next)
{
if ( hex2bin (l->keygrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
err = 0;
leave:
gnupg_closedir (dir);
xfree (dirname);
return leave_cmd (ctx, err);
}
static const char hlp_sigkey[] =
"SIGKEY <hexstring_with_keygrip>\n"
"SETKEY <hexstring_with_keygrip>\n"
"\n"
"Set the key used for a sign or decrypt operation.";
static gpg_error_t
cmd_sigkey (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = parse_keygrip (ctx, line, ctrl->keygrip);
if (rc)
return rc;
ctrl->have_keygrip = 1;
return 0;
}
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
"Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
"or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
"this description implicitly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
"not be translated), \"PIN\" is used, otherwise the translation of\n"
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
"The description is only valid for the next PKSIGN, PKDECRYPT,\n"
"IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *desc, *p;
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage; we might late use it for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
plus_to_blank (desc);
xfree (ctrl->server_local->keydesc);
if (ctrl->restricted)
{
ctrl->server_local->keydesc = strconcat
((ctrl->restricted == 2
? _("Note: Request from the web browser.")
: _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
}
else
ctrl->server_local->keydesc = xtrystrdup (desc);
if (!ctrl->server_local->keydesc)
return out_of_core ();
return 0;
}
static const char hlp_sethash[] =
"SETHASH (--hash=<name>)|(<algonumber>) <hexstring>]\n"
"SETHASH [--pss] --inquire\n"
"\n"
"The client can use this command to tell the server about the data\n"
"(which usually is a hash) to be signed. The option --inquire is\n"
"used to ask back for to-be-signed data in case of PureEdDSA or\n"
"with --pss for pre-formatted rsaPSS.";
static gpg_error_t
cmd_sethash (assuan_context_t ctx, char *line)
{
gpg_error_t err;
size_t n;
char *p;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *buf;
char *endp;
int algo;
int opt_inquire, opt_pss;
/* Parse the alternative hash options which may be used instead of
the algo number. */
if (has_option_name (line, "--hash"))
{
if (has_option (line, "--hash=sha1"))
algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=rmd160"))
algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=md5"))
algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=tls-md5sha1"))
algo = MD_USER_TLS_MD5SHA1;
else if (has_option (line, "--hash=none"))
algo = 0;
else
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
goto leave;
}
}
else
algo = 0;
opt_pss = has_option (line, "--pss");
opt_inquire = has_option (line, "--inquire");
line = skip_options (line);
if (!algo && !opt_inquire)
{
/* No hash option has been given: require an algo number instead */
algo = (int)strtoul (line, &endp, 10);
for (line = endp; *line == ' ' || *line == '\t'; line++)
;
if (!algo || gcry_md_test_algo (algo))
{
err = set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
goto leave;
}
}
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
ctrl->digest.algo = algo;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = opt_pss;
if (opt_inquire)
{
/* We limit the to-be-signed data to some reasonable size which
* may eventually allow us to pass that even to smartcards. */
size_t maxlen = 2048;
if (algo)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and an algo are specified");
goto leave;
}
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!err)
err = assuan_inquire (ctx, "TBSDATA", &buf, &n, maxlen);
if (err)
goto leave;
ctrl->digest.data = buf;
ctrl->digest.valuelen = n;
}
else
{
/* Parse the hash value. */
n = 0;
err = parse_hexstring (ctx, line, &n);
if (err)
goto leave;
n /= 2;
if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
;
else if (n != 16 && n != 20 && n != 24
&& n != 28 && n != 32 && n != 48 && n != 64)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
goto leave;
}
if (n > MAX_DIGEST_LEN)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
goto leave;
}
buf = ctrl->digest.value;
ctrl->digest.valuelen = n;
for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
buf[n] = xtoi_2 (p);
for (; n < ctrl->digest.valuelen; n++)
buf[n] = 0;
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_pksign[] =
"PKSIGN [<options>] [<cache_nonce>]\n"
"\n"
"Perform the actual sign operation. Neither input nor output are\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
gpg_error_t err;
cache_mode_t cache_mode = CACHE_MODE_NORMAL;
ctrl_t ctrl = assuan_get_pointer (ctx);
membuf_t outbuf;
char *cache_nonce = NULL;
char *p;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
if (opt.ignore_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
else if (!ctrl->server_local->use_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
init_membuf (&outbuf, 512);
err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
&outbuf, cache_mode);
if (err)
clear_outbuf (&outbuf);
else
err = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT [<options>]\n"
"\n"
"Perform the actual decrypt operation. Input is not\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value;
size_t valuelen;
membuf_t outbuf;
int padding;
(void)line;
/* First inquire the data to decrypt */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
if (!rc)
rc = assuan_inquire (ctx, "CIPHERTEXT",
&value, &valuelen, MAXLEN_CIPHERTEXT);
if (rc)
return rc;
init_membuf (&outbuf, 512);
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
value, valuelen, &outbuf, &padding);
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
{
if (padding != -1)
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
else
rc = 0;
if (!rc)
rc = write_and_clear_outbuf (ctx, &outbuf);
}
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, rc);
}
static const char hlp_genkey[] =
"GENKEY [--no-protection] [--preset] [--timestamp=<isodate>]\n"
" [--inq-passwd] [--passwd-nonce=<s>] [<cache_nonce>]\n"
"\n"
"Generate a new key, store the secret part and return the public\n"
"part. Here is an example transaction:\n"
"\n"
" C: GENKEY\n"
" S: INQUIRE KEYPARAM\n"
" C: D (genkey (rsa (nbits 3072)))\n"
" C: END\n"
" S: D (public-key\n"
" S: D (rsa (n 326487324683264) (e 10001)))\n"
" S: OK key created\n"
"\n"
"If the --preset option is used the passphrase for the generated\n"
"key will be added to the cache. If --inq-passwd is used an inquire\n"
"with the keyword NEWPASSWD is used to request the passphrase for the\n"
"new key. If a --passwd-nonce is used, the corresponding cached\n"
"passphrase is used to protect the new key. If --timestamp is given\n"
"its value is recorded as the key's creation time; the value is\n"
"expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int no_protection;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *newpasswd = NULL;
membuf_t outbuf;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int opt_preset;
int opt_inq_passwd;
size_t n;
char *p, *pend;
const char *s;
time_t opt_timestamp;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
no_protection = has_option (line, "--no-protection");
opt_preset = has_option (line, "--preset");
opt_inq_passwd = has_option (line, "--inq-passwd");
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
/* First inquire the parameters */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
if (rc)
goto leave;
rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
if (rc)
goto leave;
init_membuf (&outbuf, 512);
/* If requested, ask for the password to be used for the key. If
this is not used the regular Pinentry mechanism is used. */
if (opt_inq_passwd && !no_protection)
{
/* (N is used as a dummy) */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
assuan_end_confidential (ctx);
if (rc)
goto leave;
if (!*newpasswd)
{
/* Empty password given - switch to no-protection mode. */
xfree (newpasswd);
newpasswd = NULL;
no_protection = 1;
}
}
else if (passwd_nonce)
newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
rc = agent_genkey (ctrl, cache_nonce, opt_timestamp,
(char*)value, valuelen, no_protection,
newpasswd, opt_preset, &outbuf);
leave:
if (newpasswd)
{
/* Assuan_inquire does not allow us to read into secure memory
thus we need to wipe it ourself. */
wipememory (newpasswd, strlen (newpasswd));
xfree (newpasswd);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
rc = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, rc);
}
static const char hlp_keyattr[] =
"KEYATTR [--delete] <hexstring_with_keygrip> <ATTRNAME> [<VALUE>]\n"
"\n"
"For the secret key, show the attribute of ATTRNAME. With VALUE,\n"
"put the value to the attribute. Use --delete option to delete.";
static gpg_error_t
cmd_keyattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
const char *argv[3];
int argc;
unsigned char grip[20];
int opt_delete;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_delete = has_option (line, "--delete");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
if (!strcmp (argv[1], "Key:") /* It allows only access to attribute */
/* Make sure ATTRNAME ends with colon. */
|| argv[1][strlen (argv[1]) - 1] != ':')
{
err = gpg_error (GPG_ERR_INV_PARAMETER);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (!err)
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
const char *p;
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (err)
goto leave;
if (argc == 2)
{
nve_t e = NULL;
if (keymeta)
e = nvc_lookup (keymeta, argv[1]);
if (opt_delete)
{
if (e)
{
nvc_delete (keymeta, e);
goto key_attr_write;
}
}
else if (e)
{
p = nve_value (e);
if (p)
err = assuan_send_data (ctx, p, strlen (p));
}
}
else if (argc == 3)
{
if (!keymeta)
keymeta = nvc_new_private_key ();
err = nvc_set (keymeta, argv[1], argv[2]);
key_attr_write:
if (!err)
err = nvc_set_private_key (keymeta, s_key);
if (!err)
err = agent_update_private_key (grip, keymeta);
}
nvc_release (keymeta);
gcry_sexp_release (s_key);
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_readkey[] =
"READKEY [--no-data] [--format=ssh] <hexstring_with_keygrip>\n"
" --card <keyid>\n"
"\n"
"Return the public key for the given keygrip or keyid.\n"
"With --card, private key file with card information will be created.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char grip[20];
gcry_sexp_t s_pkey = NULL;
unsigned char *pkbuf = NULL;
size_t pkbuflen;
int opt_card, opt_no_data, opt_format_ssh;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_no_data = has_option (line, "--no-data");
opt_card = has_option (line, "--card");
opt_format_ssh = has_option (line, "--format=ssh");
line = skip_options (line);
if (opt_card)
{
char *serialno = NULL;
char *keyidbuf = NULL;
const char *keyid = line;
rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL);
if (rc)
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (rc));
goto leave;
}
/* Hack to create the shadow key for the OpenPGP standard keys. */
if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID"))
&& !agent_card_getattr (ctrl, keyid, &keyidbuf, NULL))
keyid = keyidbuf;
rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL);
if (rc)
goto leave;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (rc)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
rc = gcry_pk_testkey (s_pkey);
if (rc == 0)
rc = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
if (agent_key_available (grip))
{
/* (Shadow)-key is not available in our key storage. */
char *dispserialno;
char hexgrip[40+1];
bin2hex (grip, 20, hexgrip);
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip);
rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0,
dispserialno);
xfree (dispserialno);
if (rc)
goto leave;
}
xfree (serialno);
xfree (keyidbuf);
}
else
{
rc = parse_keygrip (ctx, line, grip);
if (rc)
goto leave;
rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
if (!rc)
{
if (opt_format_ssh)
{
estream_t stream = NULL;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = ssh_public_key_in_base64 (s_pkey, stream, "(none)");
if (rc)
{
es_fclose (stream);
goto leave;
}
rc = es_fclose_snatch (stream, (void **)&pkbuf, &pkbuflen);
if (rc)
goto leave;
}
else
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (pkbuflen);
pkbuf = xtrymalloc (pkbuflen);
if (!pkbuf)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
pkbuf, pkbuflen);
}
}
}
rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen);
leave:
xfree (pkbuf);
gcry_sexp_release (s_pkey);
return leave_cmd (ctx, rc);
}
static const char hlp_keyinfo[] =
"KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh]\n"
" [--need-attr=ATTRNAME] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. If --ssh-list is given information\n"
"about all keys listed in the sshcontrol are returned. With --with-ssh\n"
"information from sshcontrol is always added to the info. If --need-attr\n"
"is used the key is only listed if the value of the given attribute name\n"
"(e.g. \"Use-for-ssh\") is true. Unless --data is given, the information\n"
"is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"TYPE is describes the type of the key:\n"
" 'D' - Regular key stored on disk,\n"
" 'T' - Key is stored on a smartcard (token),\n"
" 'X' - Unknown type,\n"
" '-' - Key is missing.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"CACHED is 1 if the passphrase for the key was found in the key cache.\n"
" If not, a '-' is used instead.\n"
"\n"
"PROTECTION describes the key protection type:\n"
" 'P' - The key is protected with a passphrase,\n"
" 'C' - The key is not protected,\n"
" '-' - Unknown protection.\n"
"\n"
"FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
" printed if the option --ssh-fpr has been used. If ALGO is not given\n"
" to that option the default ssh fingerprint algo is used. Without the\n"
" option a '-' is printed.\n"
"\n"
"TTL is the TTL in seconds for that key or '-' if n/a.\n"
"\n"
"FLAGS is a word consisting of one-letter flags:\n"
" 'D' - The key has been disabled,\n"
" 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
" 'c' - Use of the key needs to be confirmed,\n"
" 'A' - The key is available on card,\n"
" '-' - No flags given.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
int data, int with_ssh_fpr, int in_ssh,
int ttl, int disabled, int confirm, int on_card,
const char *need_attr, int list_mode)
{
gpg_error_t err;
char hexgrip[40+1];
char *fpr = NULL;
int keytype;
unsigned char *shadow_info = NULL;
unsigned char *shadow_info_type = NULL;
char *serialno = NULL;
char *idstr = NULL;
const char *keytypestr;
const char *cached;
const char *protectionstr;
char *pw;
int missing_key = 0;
char ttlbuf[20];
char flagsbuf[5];
err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info,
&shadow_info_type);
if (err)
{
if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
missing_key = 1;
else
goto leave;
}
if (need_attr || (ctrl->restricted && list_mode))
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
int istrue, has_rl;
if (missing_key)
goto leave; /* No attribute available. */
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (!keymeta)
istrue = 0;
else
{
has_rl = 0;
if (ctrl->restricted && list_mode
&& !(has_rl = nvc_get_boolean (keymeta, "Remote-list:")))
istrue = 0;
else if (need_attr)
istrue = nvc_get_boolean (keymeta, need_attr);
else
istrue = has_rl;
nvc_release (keymeta);
}
gcry_sexp_release (s_key);
if (!istrue)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* Reformat the grip so that we use uppercase as good style. */
bin2hex (grip, 20, hexgrip);
if (ttl > 0)
snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
else
strcpy (ttlbuf, "-");
*flagsbuf = 0;
if (disabled)
strcat (flagsbuf, "D");
if (in_ssh)
strcat (flagsbuf, "S");
if (confirm)
strcat (flagsbuf, "c");
if (on_card)
strcat (flagsbuf, "A");
if (!*flagsbuf)
strcpy (flagsbuf, "-");
if (missing_key)
{
protectionstr = "-"; keytypestr = "-";
}
else
{
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
protectionstr = "C"; keytypestr = "D";
break;
case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
break;
case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
break;
default: protectionstr = "-"; keytypestr = "X";
break;
}
}
/* Compute the ssh fingerprint if requested. */
if (with_ssh_fpr)
{
gcry_sexp_t key;
if (!agent_raw_key_from_file (ctrl, grip, &key, NULL))
{
ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
gcry_sexp_release (key);
}
}
/* Here we have a little race by doing the cache check separately
from the retrieval function. Given that the cache flag is only a
hint, it should not really matter. */
pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
cached = pw ? "1" : "-";
xfree (pw);
if (shadow_info)
{
if (strcmp (shadow_info_type, "t1-v1") == 0)
{
err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
if (err)
goto leave;
}
else if (strcmp (shadow_info_type, "tpm2-v1") == 0)
{
serialno = xstrdup("TPM-Protected");
idstr = NULL;
}
else
{
log_error ("unrecognised shadow key type %s\n", shadow_info_type);
err = GPG_ERR_BAD_KEY;
goto leave;
}
}
if (!data)
err = agent_write_status (ctrl, "KEYINFO",
hexgrip,
keytypestr,
serialno? serialno : "-",
idstr? idstr : "-",
cached,
protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf,
NULL);
else
{
char *string;
string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
hexgrip, keytypestr,
serialno? serialno : "-",
idstr? idstr : "-", cached, protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf);
if (!string)
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, string, strlen(string));
xfree (string);
}
leave:
xfree (fpr);
xfree (shadow_info_type);
xfree (shadow_info);
xfree (serialno);
xfree (idstr);
return err;
}
/* Entry into the command KEYINFO. This function handles the
* command option processing. For details see hlp_keyinfo above. */
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int err;
unsigned char grip[20];
gnupg_dir_t dir = NULL;
int list_mode;
int opt_data, opt_ssh_fpr, opt_with_ssh;
ssh_control_file_t cf = NULL;
char hexgrip[41];
int disabled, ttl, confirm, is_ssh;
struct card_key_info_s *keyinfo_on_cards;
struct card_key_info_s *l;
int on_card;
char *need_attr = NULL;
size_t n;
if (has_option (line, "--ssh-list"))
list_mode = 2;
else
list_mode = has_option (line, "--list");
opt_data = has_option (line, "--data");
if (has_option_name (line, "--ssh-fpr"))
{
if (has_option (line, "--ssh-fpr=md5"))
opt_ssh_fpr = GCRY_MD_MD5;
else if (has_option (line, "--ssh-fpr=sha1"))
opt_ssh_fpr = GCRY_MD_SHA1;
else if (has_option (line, "--ssh-fpr=sha256"))
opt_ssh_fpr = GCRY_MD_SHA256;
else
opt_ssh_fpr = opt.ssh_fingerprint_digest;
}
else
opt_ssh_fpr = 0;
opt_with_ssh = has_option (line, "--with-ssh");
err = get_option_value (line, "--need-attr", &need_attr);
if (err)
goto leave;
if (need_attr && (n=strlen (need_attr)) && need_attr[n-1] != ':')
{
/* We need to append a colon. */
char *tmp = strconcat (need_attr, ":", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (need_attr);
need_attr = tmp;
}
line = skip_options (line);
if (opt_with_ssh || list_mode == 2)
cf = ssh_open_control_file ();
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
if (list_mode == 2)
{
if (cf)
{
while (!ssh_read_control_file (cf, hexgrip,
&disabled, &ttl, &confirm))
{
if (hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
}
err = 0;
}
else if (list_mode)
{
char *dirname;
gnupg_dirent_t dir_entry;
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
goto leave;
}
xfree (dirname);
while ( (dir_entry = gnupg_readdir (dir)) )
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, hexgrip,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
err = 0;
}
else
{
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, line,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, line, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr, 0);
}
leave:
xfree (need_attr);
ssh_close_control_file (cf);
gnupg_closedir (dir);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
leave_cmd (ctx, err);
return err;
}
/* Helper for cmd_get_passphrase. */
static int
send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
{
size_t n;
int rc;
assuan_begin_confidential (ctx);
n = strlen (pw);
if (via_data)
rc = assuan_send_data (ctx, pw, n);
else
{
char *p = xtrymalloc_secure (n*2+1);
if (!p)
rc = gpg_error_from_syserror ();
else
{
bin2hex (pw, n, p);
rc = assuan_set_okay_line (ctx, p);
xfree (p);
}
}
assuan_end_confidential (ctx);
return rc;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
static const char hlp_get_passphrase[] =
"GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
" [--qualitybar] [--newsymkey] <cache_id>\n"
" [<error_message> <prompt> <description>]\n"
"\n"
"This function is usually used to ask for a passphrase to be used\n"
"for conventional encryption, but may also be used by programs which\n"
"need specal handling of passphrases. This command uses a syntax\n"
"which helps clients to use the agent with minimum effort. The\n"
"agent either returns with an error or with a OK followed by the hex\n"
"encoded passphrase. Note that the length of the strings is\n"
"implicitly limited by the maximum length of a command.\n"
"\n"
"If the option \"--data\" is used the passphrase is returned by usual\n"
"data lines and not on the okay line.\n"
"\n"
"If the option \"--check\" is used the passphrase constraints checks as\n"
"implemented by gpg-agent are applied. A check is not done if the\n"
"passphrase has been found in the cache.\n"
"\n"
"If the option \"--no-ask\" is used and the passphrase is not in the\n"
"cache the user will not be asked to enter a passphrase but the error\n"
"code GPG_ERR_NO_DATA is returned. \n"
"\n"
"If the option\"--newsymkey\" is used the agent asks for a new passphrase\n"
"to be used in symmetric-only encryption. This must not be empty.\n"
"\n"
"If the option \"--qualitybar\" is used a visual indication of the\n"
"entered passphrase quality is shown. (Unless no minimum passphrase\n"
"length has been configured.)";
static gpg_error_t
cmd_get_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *pw;
char *response = NULL;
char *response2 = NULL;
char *cacheid = NULL; /* May point into LINE. */
char *desc = NULL; /* Ditto */
char *prompt = NULL; /* Ditto */
char *errtext = NULL; /* Ditto */
const char *desc2 = _("Please re-enter this passphrase");
char *p;
int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey;
int opt_repeat = 0;
char *entry_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
int is_generated;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_data = has_option (line, "--data");
opt_check = has_option (line, "--check");
opt_no_ask = has_option (line, "--no-ask");
if (has_option_name (line, "--repeat"))
{
p = option_value (line, "--repeat");
if (p)
opt_repeat = atoi (p);
else
opt_repeat = 1;
}
opt_qualbar = has_option (line, "--qualitybar");
opt_newsymkey = has_option (line, "--newsymkey");
line = skip_options (line);
cacheid = line;
p = strchr (cacheid, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
errtext = p;
p = strchr (errtext, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
prompt = p;
p = strchr (prompt, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* Ignore trailing garbage. */
}
}
}
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
if (!desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (cacheid, "X"))
cacheid = NULL;
if (!strcmp (errtext, "X"))
errtext = NULL;
if (!strcmp (prompt, "X"))
prompt = NULL;
if (!strcmp (desc, "X"))
desc = NULL;
pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
if (pw)
{
rc = send_back_passphrase (ctx, opt_data, pw);
xfree (pw);
goto leave;
}
else if (opt_no_ask)
{
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* Note, that we only need to replace the + characters and should
* leave the other escaping in place because the escaped string is
* send verbatim to the pinentry which does the unescaping (but not
* the + replacing) */
if (errtext)
plus_to_blank (errtext);
if (prompt)
plus_to_blank (prompt);
if (desc)
plus_to_blank (desc);
/* If opt_repeat is 2 or higher we can't use our pin_entry_info_s
* based method but fallback to the old simple method. It is
* anyway questionable whether this extra repeat count makes any
* real sense. */
if (opt_newsymkey && opt_repeat < 2)
{
/* We do not want to break any existing usage of this command
* and thus we introduced the option --newsymkey to make this
* command more useful to query the passphrase for symmetric
* encryption. */
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = opt_qualbar;
pi->with_repeat = opt_repeat;
pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY
| CHECK_CONSTRAINTS_NEW_SYMKEY);
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 3;
pi2->check_cb = reenter_passphrase_cmp_cb;
pi2->check_cb_arg = pi->pin;
for (;;) /* (degenerated for-loop) */
{
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
desc,
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi);
if (rc)
goto leave;
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED);
/* We don't allow an empty passpharse in this mode. */
if (!is_generated
&& check_passphrase_constraints (ctrl, pi->pin,
pi->constraints_flags,
&entry_errtext))
{
pi->failed_tries = 0;
pi2->failed_tries = 0;
continue;
}
if (*pi->pin && !pi->repeat_okay
&& ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK
&& opt_repeat)
{
/* The passphrase is empty and the pinentry did not
* already run the repetition check, do it here. This
* is only called when using an old and simple pinentry.
* It is neither called in loopback mode because the
* caller does any passphrase repetition by herself nor if
* no repetition was requested. */
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
L_("Please re-enter this passphrase"),
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi2);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered passphrase one did not match and
* the user did not hit cancel. */
entry_errtext = xtrystrdup (L_("does not match - try again"));
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
goto leave;
}
continue;
}
}
break;
}
if (!rc && *pi->pin)
{
/* Return the passphrase. */
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0);
rc = send_back_passphrase (ctx, opt_data, pi->pin);
}
}
else
{
next_try:
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response, desc, prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER, NULL);
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = 0;
if (!rc)
{
int i;
if (opt_check
&& !is_generated
&& check_passphrase_constraints
(ctrl, response,
(opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0),
&entry_errtext))
{
goto next_try;
}
for (i = 0; i < opt_repeat; i++)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
break;
xfree (response2);
response2 = NULL;
rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
errtext, 0,
cacheid, CACHE_MODE_USER, NULL);
if (rc)
break;
if (strcmp (response2, response))
{
entry_errtext = try_percent_escape
(_("does not match - try again"), NULL);
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
break;
}
goto next_try;
}
}
if (!rc)
{
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
rc = send_back_passphrase (ctx, opt_data, response);
}
}
}
leave:
xfree (response);
xfree (response2);
xfree (entry_errtext);
xfree (pi2);
xfree (pi);
return leave_cmd (ctx, rc);
}
static const char hlp_clear_passphrase[] =
"CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
"\n"
"may be used to invalidate the cache entry for a passphrase. The\n"
"function returns with OK even when there is no cached passphrase.\n"
"The --mode=normal option is used to clear an entry for a cacheid\n"
"added by the agent. The --mode=ssh option is used for a cacheid\n"
"added for ssh.\n";
static gpg_error_t
cmd_clear_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *cacheid = NULL;
char *p;
cache_mode_t cache_mode = CACHE_MODE_USER;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--mode=normal"))
cache_mode = CACHE_MODE_NORMAL;
else if (has_option (line, "--mode=ssh"))
cache_mode = CACHE_MODE_SSH;
line = skip_options (line);
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
cacheid = p;
p = strchr (cacheid, ' ');
if (p)
*p = 0; /* ignore garbage */
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
agent_clear_passphrase (ctrl, cacheid, cache_mode);
return 0;
}
static const char hlp_get_confirmation[] =
"GET_CONFIRMATION <description>\n"
"\n"
"This command may be used to ask for a simple confirmation.\n"
"DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
"command uses a syntax which helps clients to use the agent with\n"
"minimum effort. The agent either returns with an error or with a\n"
"OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
"the maximum length of a command. DESCRIPTION should not contain\n"
"any spaces, those must be encoded either percent escaped or simply\n"
"as '+'.";
static gpg_error_t
cmd_get_confirmation (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *desc = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage -may be later used for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (desc, "X"))
desc = NULL;
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
if (desc)
plus_to_blank (desc);
rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
return leave_cmd (ctx, rc);
}
static const char hlp_learn[] =
"LEARN [--send] [--sendinfo] [--force]\n"
"\n"
"Learn something about the currently inserted smartcard. With\n"
"--sendinfo information about the card is returned; with --send\n"
"the available certificates are returned as D lines; with --force\n"
"private key storage will be updated by the result.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int send, sendinfo, force;
send = has_option (line, "--send");
sendinfo = send? 1 : has_option (line, "--sendinfo");
force = has_option (line, "--force");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
return leave_cmd (ctx, err);
}
static const char hlp_passwd[] =
"PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
" [--verify] <hexkeygrip>\n"
"\n"
"Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
"--preset is used then the new passphrase will be added to the cache.\n"
"If --verify is used the command asks for the passphrase and verifies\n"
"that the passphrase valid.\n";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int c;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *shadow_info = NULL;
char *passphrase = NULL;
char *pend;
int opt_preset, opt_verify;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_preset = has_option (line, "--preset");
cache_nonce = option_value (line, "--cache-nonce");
opt_verify = has_option (line, "--verify");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
ctrl->in_passwd++;
err = agent_key_from_file (ctrl,
opt_verify? NULL : cache_nonce,
ctrl->server_local->keydesc,
grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, &passphrase, NULL);
if (err)
;
else if (shadow_info)
{
log_error ("changing a smartcard PIN is not yet supported\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else if (opt_verify)
{
/* All done. */
if (passphrase)
{
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
else
{
char *newpass = NULL;
if (passwd_nonce)
newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
err = agent_protect_and_store (ctrl, s_skey, &newpass);
if (!err && passphrase)
{
/* A passphrase existed on the old key and the change was
successful. Return a nonce for that old passphrase to
let the caller try to unprotect the other subkeys with
the same key. */
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
if (newpass)
{
/* If we have a new passphrase (which might be empty) we
store it under a passwd nonce so that the caller may
send that nonce again to use it for another key. */
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
newpass, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
if (!err && opt_preset)
{
char hexgrip[40+1];
bin2hex(grip, 20, hexgrip);
err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
ctrl->cache_ttl_opt_preset);
}
xfree (newpass);
}
ctrl->in_passwd--;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
leave:
xfree (passphrase);
gcry_sexp_release (s_skey);
xfree (shadow_info);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, err);
}
static const char hlp_preset_passphrase[] =
"PRESET_PASSPHRASE [--inquire] [--restricted] \\\n"
" <string_or_keygrip> <timeout> [<hexstring>]\n"
"\n"
"Set the cached passphrase/PIN for the key identified by the keygrip\n"
"to passwd for the given time, where -1 means infinite and 0 means\n"
"the default (currently only a timeout of -1 is allowed, which means\n"
"to never expire it). If passwd is not provided, ask for it via the\n"
"pinentry module unless --inquire is passed in which case the passphrase\n"
"is retrieved from the client via a server inquire. The option\n"
"--restricted can be used to put the passphrase into the cache used\n"
"by restricted connections.";
static gpg_error_t
cmd_preset_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *grip_clear = NULL;
unsigned char *passphrase = NULL;
int ttl;
size_t len;
int opt_inquire;
int opt_restricted;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!opt.allow_preset_passphrase)
return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
opt_inquire = has_option (line, "--inquire");
opt_restricted = has_option (line, "--restricted");
line = skip_options (line);
grip_clear = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
return gpg_error (GPG_ERR_MISSING_VALUE);
*line = '\0';
line++;
while (*line && (*line == ' ' || *line == '\t'))
line++;
/* Currently, only infinite timeouts are allowed. */
ttl = -1;
if (line[0] != '-' || line[1] != '1')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
line++;
line++;
while (!(*line != ' ' && *line != '\t'))
line++;
/* Syntax check the hexstring. */
len = 0;
rc = parse_hexstring (ctx, line, &len);
if (rc)
return rc;
line[len] = '\0';
/* If there is a passphrase, use it. Currently, a passphrase is
required. */
if (*line)
{
if (opt_inquire)
{
rc = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and passphrase specified");
goto leave;
}
/* Do in-place conversion. */
passphrase = line;
if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
}
else if (opt_inquire)
{
/* Note that the passphrase will be truncated at any null byte and the
* limit is 480 characters. */
size_t maxlen = 480;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!rc)
{
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
assuan_end_confidential (ctx);
}
}
else
rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
if (!rc)
{
int save_restricted = ctrl->restricted;
if (opt_restricted)
ctrl->restricted = 1;
rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
ctrl->restricted = save_restricted;
if (opt_inquire)
{
wipememory (passphrase, len);
xfree (passphrase);
}
}
leave:
return leave_cmd (ctx, rc);
}
static const char hlp_scd[] =
"SCD <commands to pass to the scdaemon>\n"
" \n"
"This is a general quote command to redirect everything to the\n"
"SCdaemon.";
static gpg_error_t
cmd_scd (assuan_context_t ctx, char *line)
{
int rc;
#ifdef BUILD_WITH_SCDAEMON
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
{
const char *argv[5];
int argc;
char *l;
l = xtrystrdup (line);
if (!l)
return gpg_error_from_syserror ();
argc = split_fields (l, argv, DIM (argv));
/* These commands are allowed. */
if ((argc >= 1 && !strcmp (argv[0], "SERIALNO"))
|| (argc == 2
&& !strcmp (argv[0], "GETINFO")
&& !strcmp (argv[1], "version"))
|| (argc == 2
&& !strcmp (argv[0], "GETATTR")
&& !strcmp (argv[1], "KEY-FPR"))
|| (argc == 2
&& !strcmp (argv[0], "KEYINFO")
&& !strcmp (argv[1], "--list=encr")))
xfree (l);
else
{
xfree (l);
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
}
}
/* All SCD prefixed commands may change a key. */
eventcounter.maybe_key_change++;
rc = divert_generic_cmd (ctrl, line, ctx);
#else
(void)ctx; (void)line;
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif
return rc;
}
static const char hlp_keywrap_key[] =
"KEYWRAP_KEY [--clear] <mode>\n"
"\n"
"Return a key to wrap another key. For now the key is returned\n"
"verbatim and thus makes not much sense because an eavesdropper on\n"
"the gpg-agent connection will see the key as well as the wrapped key.\n"
"However, this function may either be equipped with a public key\n"
"mechanism or not used at all if the key is a pre-shared key. In any\n"
"case wrapping the import and export of keys is a requirement for\n"
"certain cryptographic validations and thus useful. The key persists\n"
"until a RESET command but may be cleared using the option --clear.\n"
"\n"
"Supported modes are:\n"
" --import - Return a key to import a key into gpg-agent\n"
" --export - Return a key to export a key from gpg-agent";
static gpg_error_t
cmd_keywrap_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clearopt = has_option (line, "--clear");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
assuan_begin_confidential (ctx);
if (has_option (line, "--import"))
{
xfree (ctrl->server_local->import_key);
if (clearopt)
ctrl->server_local->import_key = NULL;
else if (!(ctrl->server_local->import_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->import_key,
KEYWRAP_KEYSIZE);
}
else if (has_option (line, "--export"))
{
xfree (ctrl->server_local->export_key);
if (clearopt)
ctrl->server_local->export_key = NULL;
else if (!(ctrl->server_local->export_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->export_key,
KEYWRAP_KEYSIZE);
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
assuan_end_confidential (ctx);
return leave_cmd (ctx, err);
}
static const char hlp_import_key[] =
"IMPORT_KEY [--unattended] [--force] [--timestamp=<isodate>]\n"
" [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption. An existing key can be overwritten with --force.\n"
"If --timestamp is given its value is recorded as the key's creation\n"
"time; the value is expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
time_t opt_timestamp;
int force;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *key = NULL;
size_t keylen, realkeylen;
char *passphrase = NULL;
unsigned char *finalkey = NULL;
size_t finalkeylen;
unsigned char grip[20];
gcry_sexp_t openpgp_sexp = NULL;
char *cache_nonce = NULL;
char *p;
const char *s;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!ctrl->server_local->import_key)
{
err = gpg_error (GPG_ERR_MISSING_KEY);
goto leave;
}
opt_unattended = has_option (line, "--unattended");
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYDATA",
&wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (err)
goto leave;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
xfree (wrappedkey);
wrappedkey = NULL;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
err = keygrip_from_canon_sexp (key, realkeylen, grip);
if (err)
{
/* This might be due to an unsupported S-expression format.
Check whether this is openpgp-private-key and trigger that
import code. */
if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
{
const char *tag;
size_t taglen;
tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
;
else
{
gcry_sexp_release (openpgp_sexp);
openpgp_sexp = NULL;
}
}
if (!openpgp_sexp)
goto leave; /* Note that ERR is still set. */
}
if (openpgp_sexp)
{
/* In most cases the key is encrypted and thus the conversion
function from the OpenPGP format to our internal format will
ask for a passphrase. That passphrase will be returned and
used to protect the key using the same code as for regular
key import. */
xfree (key);
key = NULL;
err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
ctrl->server_local->keydesc, cache_nonce,
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
log_assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
else if (opt_unattended)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"\"--unattended\" may only be used with OpenPGP keys");
goto leave;
}
else
{
if (!force && !agent_key_available (grip))
err = gpg_error (GPG_ERR_EEXIST);
else
{
char *prompt = xtryasprintf
(_("Please enter the passphrase to protect the "
"imported object within the %s system."), GNUPG_NAME);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
xfree (prompt);
}
if (err)
goto leave;
}
if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count);
if (!err)
err = agent_write_private_key (grip, finalkey, finalkeylen, force,
NULL, NULL, NULL, opt_timestamp);
}
else
err = agent_write_private_key (grip, key, realkeylen, force,
NULL, NULL, NULL, opt_timestamp);
leave:
gcry_sexp_release (openpgp_sexp);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_export_key[] =
"EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp|--mode1003] <hexkeygrip>\n"
"\n"
"Export a secret key from the key store. The key will be encrypted\n"
"using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
"using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
"prior to using this command. The function takes the keygrip as argument.\n"
"\n"
"If --openpgp is used, the secret key material will be exported in RFC 4880\n"
"compatible passphrase-protected form. If --mode1003 is use the secret key\n"
"is exported as s-expression as storred locally. Without those options,\n"
"the secret key material will be exported in the clear (after prompting\n"
"the user to unlock it, if needed).\n";
static gpg_error_t
cmd_export_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *key = NULL;
size_t keylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
int openpgp, mode1003;
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
char *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
openpgp = has_option (line, "--openpgp");
mode1003 = has_option (line, "--mode1003");
if (mode1003)
openpgp = 0;
cache_nonce = option_value (line, "--cache-nonce");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
if (!ctrl->server_local->export_key)
{
err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
goto leave;
}
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Get the key from the file. With the openpgp flag we also ask for
* the passphrase so that we can use it to re-encrypt it. In
* mode1003 we return the key as-is. FIXME: if the key is still in
* OpenPGP-native mode we should first convert it to our internal
* protection. */
if (mode1003)
err = agent_raw_key_from_file (ctrl, grip, &s_skey, NULL);
else
err = agent_key_from_file (ctrl, cache_nonce,
ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
openpgp ? &passphrase : NULL, NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is on a smartcard. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
if (openpgp)
{
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
if (!passphrase)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
" Please enter a new passphrase to export it."),
&passphrase);
if (err)
goto leave;
}
err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
}
}
else
{
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
if (err)
goto leave;
gcry_sexp_release (s_skey);
s_skey = NULL;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
assuan_end_confidential (ctx);
leave:
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
xfree (key);
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_delete_key[] =
"DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
"\n"
"Delete a secret key from the key store. If --force is used\n"
"and a loopback pinentry is allowed, the agent will not ask\n"
"the user for confirmation. If --stub-only is used the key will\n"
"only be deleted if it is a reference to a token.";
static gpg_error_t
cmd_delete_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int force, stub_only;
unsigned char grip[20];
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
stub_only = has_option (line, "--stub-only");
line = skip_options (line);
eventcounter.maybe_key_change++;
/* If the use of a loopback pinentry has been disabled, we assume
* that a silent deletion of keys shall also not be allowed. */
if (!opt.allow_loopback_pinentry)
force = 0;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
force, stub_only);
if (err)
goto leave;
leave:
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
#else
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
#endif
static const char hlp_keytocard[] =
"KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp> [<ecdh>]]\n"
"\n"
"TIMESTAMP is required for OpenPGP and defaults to the Epoch.\n"
"ECDH are the hexified ECDH parameters for OpenPGP.\n"
"SERIALNO is used for checking; use \"-\" to disable the check.";
static gpg_error_t
cmd_keytocard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int force;
gpg_error_t err = 0;
const char *argv[5];
int argc;
unsigned char grip[20];
const char *serialno, *keyref;
gcry_sexp_t s_skey = NULL;
unsigned char *keydata;
size_t keydatalen;
unsigned char *shadow_info = NULL;
time_t timestamp;
char *ecdh_params = NULL;
unsigned int ecdh_params_len;
unsigned int extralen1, extralen2;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 3)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Note that checking of the s/n is currently not implemented but we
* want to provide a clean interface if we ever implement it. */
serialno = argv[1];
if (!strcmp (serialno, "-"))
serialno = NULL;
keyref = argv[2];
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, &timestamp);
if (err)
goto leave;
if (shadow_info)
{
/* Key is already on a smartcard - we can't extract it. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
/* Default to the creation time as stored in the private key. The
* parameter is here so that gpg can make sure that the timestamp as
* used. It is also important for OpenPGP cards to allow computing
* of the fingerprint. Same goes for the ECDH params. */
if (argc > 3)
{
timestamp = isotime2epoch (argv[3]);
if (argc > 4)
{
size_t n;
err = parse_hexstring (ctx, argv[4], &n);
if (err)
goto leave; /* Badly formatted ecdh params. */
n /= 2;
if (n < 4)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "ecdh param too short");
goto leave;
}
ecdh_params_len = n;
ecdh_params = xtrymalloc (ecdh_params_len);
if (!ecdh_params)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (hex2bin (argv[4], ecdh_params, ecdh_params_len) < 0)
{
err = set_error (GPG_ERR_BUG, "hex2bin");
goto leave;
}
}
}
else if (timestamp == (time_t)(-1))
timestamp = isotime2epoch ("19700101T000000");
if (timestamp == (time_t)(-1))
{
err = gpg_error (GPG_ERR_INV_TIME);
goto leave;
}
/* Note: We can't use make_canon_sexp because we need to allocate a
* few extra bytes for our hack below. The 20 for extralen2
* accounts for the sexp length of ecdh_params. */
keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
extralen1 = 30;
extralen2 = ecdh_params? (20+20+ecdh_params_len) : 0;
keydata = xtrymalloc_secure (keydatalen + extralen1 + extralen2);
if (keydata == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
gcry_sexp_release (s_skey);
s_skey = NULL;
keydatalen--; /* Decrement for last '\0'. */
/* Hack to insert the timestamp "created-at" into the private key. */
snprintf (keydata+keydatalen-1, extralen1, KEYTOCARD_TIMESTAMP_FORMAT,
timestamp);
keydatalen += 10 + 19 - 1;
/* Hack to insert the timestamp "ecdh-params" into the private key. */
if (ecdh_params)
{
snprintf (keydata+keydatalen-1, extralen2, "(11:ecdh-params%u:",
ecdh_params_len);
keydatalen += strlen (keydata+keydatalen-1) -1;
memcpy (keydata+keydatalen, ecdh_params, ecdh_params_len);
keydatalen += ecdh_params_len;
memcpy (keydata+keydatalen, "))", 3);
keydatalen += 2;
}
err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen);
xfree (keydata);
leave:
xfree (ecdh_params);
gcry_sexp_release (s_skey);
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_get_secret[] =
"GET_SECRET <key>\n"
"\n"
"Return the secret value stored under KEY\n";
static gpg_error_t
cmd_get_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *key;
char *value = NULL;
size_t valuelen;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
goto leave;
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
value = agent_get_cache (ctrl, key, CACHE_MODE_DATA);
if (!value)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
valuelen = percent_unescape_inplace (value, 0);
err = assuan_send_data (ctx, value, valuelen);
wipememory (value, valuelen);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_put_secret[] =
"PUT_SECRET [--clear] <key> <ttl> [<percent_escaped_value>]\n"
"\n"
"This commands stores a secret under KEY in gpg-agent's in-memory\n"
"cache. The TTL must be explicitly given by TTL and the options\n"
"from the configuration file are not used. The value is either given\n"
"percent-escaped as 3rd argument or if not given inquired by gpg-agent\n"
"using the keyword \"SECRET\".\n"
"The option --clear removes the secret from the cache."
"";
static gpg_error_t
cmd_put_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int opt_clear;
unsigned char *value = NULL;
size_t valuelen = 0;
size_t n;
char *p, *key, *ttlstr;
unsigned char *valstr;
int ttl;
char *string = NULL;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
opt_clear = has_option (line, "--clear");
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
ttlstr = NULL;
valstr = NULL;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
ttlstr = p;
p = strchr (ttlstr, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
valstr = p;
}
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl)))
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given");
goto leave;
}
if (valstr && opt_clear)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"value not expected with --clear");
goto leave;
}
if (valstr)
{
valuelen = percent_unescape_inplace (valstr, 0);
value = NULL;
}
else /* Inquire the value to store */
{
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET);
if (!err)
{
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "SECRET",
&value, &valuelen, MAXLEN_PUT_SECRET);
assuan_end_confidential (ctx);
}
if (err)
goto leave;
}
/* Our cache expects strings and thus we need to turn the buffer
* into a string. Instead of resorting to base64 encoding we use a
* special percent escaping which only quoted the Nul and the
* percent character. */
string = percent_data_escape (0, NULL, value? value : valstr, valuelen);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl);
leave:
if (string)
{
wipememory (string, strlen (string));
xfree (string);
}
if (value)
{
wipememory (value, valuelen);
xfree (value);
}
return leave_cmd (ctx, err);
}
static const char hlp_keytotpm[] =
"KEYTOTPM <hexstring_with_keygrip>\n"
"\n";
static gpg_error_t
cmd_keytotpm (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char grip[20];
gcry_sexp_t s_skey;
unsigned char *shadow_info = NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, NULL);
if (err)
{
xfree (shadow_info);
goto leave;
}
if (shadow_info)
{
/* Key is on a TPM or smartcard already. */
xfree (shadow_info);
gcry_sexp_release (s_skey);
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
err = divert_tpm2_writekey (ctrl, grip, s_skey);
gcry_sexp_release (s_skey);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getval[] =
"GETVAL <key>\n"
"\n"
"Return the value for KEY from the special environment as created by\n"
"PUTVAL.";
static gpg_error_t
cmd_getval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *p;
struct putval_item_s *vl;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list; vl; vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Got an entry. */
rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
else
return gpg_error (GPG_ERR_NO_DATA);
return leave_cmd (ctx, rc);
}
static const char hlp_putval[] =
"PUTVAL <key> [<percent_escaped_value>]\n"
"\n"
"The gpg-agent maintains a kind of environment which may be used to\n"
"store key/value pairs in it, so that they can be retrieved later.\n"
"This may be used by helper daemons to daemonize themself on\n"
"invocation and register them with gpg-agent. Callers of the\n"
"daemon's service may now first try connect to get the information\n"
"for that service from gpg-agent through the GETVAL command and then\n"
"try to connect to that daemon. Only if that fails they may start\n"
"an own instance of the service daemon. \n"
"\n"
"KEY is an arbitrary symbol with the same syntax rules as keys\n"
"for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
"corresponding value; they should be similar to the values of\n"
"envronment variables but gpg-agent does not enforce any\n"
"restrictions. If that value is not given any value under that KEY\n"
"is removed from this special environment.";
static gpg_error_t
cmd_putval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *value = NULL;
size_t valuelen = 0;
char *p;
struct putval_item_s *vl, *vlprev;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
value = p;
p = strchr (value, ' ');
if (p)
*p = 0;
valuelen = percent_plus_unescape_inplace (value, 0);
}
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Delete old entry. */
{
if (vlprev)
vlprev->next = vl->next;
else
putval_list = vl->next;
xfree (vl);
}
if (valuelen) /* Add entry. */
{
vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
if (!vl)
rc = gpg_error_from_syserror ();
else
{
vl->len = valuelen;
vl->off = strlen (key) + 1;
strcpy (vl->d, key);
memcpy (vl->d + vl->off, value, valuelen);
vl->next = putval_list;
putval_list = vl;
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_updatestartuptty[] =
"UPDATESTARTUPTTY\n"
"\n"
"Set startup TTY and X11 DISPLAY variables to the values of this\n"
"session. This command is useful to pull future pinentries to\n"
"another screen. It is only required because there is no way in the\n"
"ssh-agent protocol to convey this information.";
static gpg_error_t
cmd_updatestartuptty (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
session_env_t se;
char *lc_ctype = NULL;
char *lc_messages = NULL;
int iterator;
const char *name;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
se = session_env_new ();
if (!se)
err = gpg_error_from_syserror ();
iterator = 0;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
const char *value = session_env_getenv (ctrl->session_env, name);
if (value)
err = session_env_setenv (se, name, value);
}
if (!err && ctrl->lc_ctype)
if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && ctrl->lc_messages)
if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
err = gpg_error_from_syserror ();
if (err)
{
session_env_release (se);
xfree (lc_ctype);
xfree (lc_messages);
}
else
{
session_env_release (opt.startup_env);
opt.startup_env = se;
xfree (opt.startup_lc_ctype);
opt.startup_lc_ctype = lc_ctype;
xfree (opt.startup_lc_messages);
opt.startup_lc_messages = lc_messages;
}
return err;
}
static const char hlp_killagent[] =
"KILLAGENT\n"
"\n"
"Stop the agent.";
static gpg_error_t
cmd_killagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadagent[] =
"RELOADAGENT\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
agent_sighup_action ();
return 0;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" ssh_socket_name - Return the name of the ssh socket.\n"
" scd_running - Return OK if the SCdaemon is already running.\n"
" s2k_time - Return the time in milliseconds required for S2K.\n"
" s2k_count - Return the standard S2K count.\n"
" s2k_count_cal - Return the calibrated S2K count.\n"
" std_env_names - List the names of the standard environment.\n"
" std_session_env - List the standard session environment.\n"
" std_startup_env - List the standard startup environment.\n"
" getenv NAME - Return value of envvar NAME.\n"
" connections - Return number of active connections.\n"
" jent_active - Returns OK if Libgcrypt's JENT is active.\n"
" restricted - Returns OK if the connection is in restricted mode.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "s2k_count"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "restricted"))
{
rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (ctrl->restricted)
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All sub-commands below are not allowed in restricted mode. */
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_agent_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "ssh_socket_name"))
{
const char *s = get_agent_ssh_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "scd_running"))
{
rc = agent_daemon_check_running (DAEMON_SCD)? 0:gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "std_env_names"))
{
int iterator;
const char *name;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
rc = assuan_send_data (ctx, name, strlen (name)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
break;
}
}
else if (!strcmp (line, "std_session_env")
|| !strcmp (line, "std_startup_env"))
{
int iterator;
const char *name, *value;
char *string;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
value = session_env_getenv_or_default
(line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
if (value)
{
string = xtryasprintf ("%s=%s", name, value);
if (!string)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_send_data (ctx, string, strlen (string)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (rc)
break;
}
}
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
rc = assuan_send_data (ctx, s, strlen (s));
}
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d",
get_agent_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "jent_active"))
{
char *buf;
const char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
rc = 0;
else
rc = gpg_error (GPG_ERR_FALSE);
gcry_free (buf);
}
else if (!strcmp (line, "s2k_count_cal"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "s2k_time"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
/* This function is called by Libassuan to parse the OPTION command.
It has been registered similar to the other Assuan commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "agent-awareness"))
{
/* The value is a version string telling us of which agent
version the caller is aware of. */
ctrl->server_local->allow_fully_canceled =
gnupg_compare_version (value, "2.1.0");
}
else if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All options below are not allowed in restricted mode. */
else if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (ctrl->session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = xtrystrdup (value);
if (!ctrl->lc_ctype)
return out_of_core ();
}
else if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "use-cache-for-signing"))
ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
else if (!strcmp (key, "allow-pinentry-notify"))
ctrl->server_local->allow_pinentry_notify = 1;
else if (!strcmp (key, "pinentry-mode"))
{
int tmp = parse_pinentry_mode (value);
if (tmp == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
ctrl->pinentry_mode = tmp;
}
else if (!strcmp (key, "cache-ttl-opt-preset"))
{
ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
}
else if (!strcmp (key, "s2k-count"))
{
ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
if (ctrl->s2k_count && ctrl->s2k_count < 65536)
{
ctrl->s2k_count = 0;
}
}
else if (!strcmp (key, "pretend-request-origin"))
{
log_assert (!ctrl->restricted);
switch (parse_request_origin (value))
{
case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
/* Better pretend to be remote in case of a bad value. */
ctrl->restricted = 1;
break;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* Called by libassuan after all commands. ERR is the error from the
last assuan operation and not the one returned from the command. */
static void
post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)err;
/* Switch off any I/O monitor controlled logging pausing. */
ctrl->server_local->pause_io_logging = 0;
}
/* This function is called by libassuan for all I/O. We use it here
to disable logging for the GETEVENTCOUNTER commands. This is so
that the debug output won't get cluttered by this primitive
command. */
static unsigned int
io_monitor (assuan_context_t ctx, void *hook, int direction,
const char *line, size_t linelen)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) hook;
/* We want to suppress all Assuan log messages for connections from
* self. However, assuan_get_pid works only after
* assuan_accept. Now, assuan_accept already logs a line ending with
* the process id. We use this hack here to get the peers pid so
* that we can compare it to our pid. We should add an assuan
* function to return the pid for a file descriptor and use that to
* detect connections to self. */
if (ctx && !ctrl->server_local->greeting_seen
&& direction == ASSUAN_IO_TO_PEER)
{
ctrl->server_local->greeting_seen = 1;
if (linelen > 32
&& !strncmp (line, "OK Pleased to meet you, process ", 32)
&& strtoul (line+32, NULL, 10) == getpid ())
return ASSUAN_IO_MONITOR_NOLOG;
}
/* Do not log self-connections. This makes the log cleaner because
* we won't see the check-our-own-socket calls. */
if (ctx && ctrl->server_local->connect_from_self)
return ASSUAN_IO_MONITOR_NOLOG;
/* Note that we only check for the uppercase name. This allows the user to
see the logging for debugging if using a non-upercase command
name. */
if (ctx && direction == ASSUAN_IO_FROM_PEER
&& linelen >= 15
&& !strncmp (line, "GETEVENTCOUNTER", 15)
&& (linelen == 15 || spacep (line+15)))
{
ctrl->server_local->pause_io_logging = 1;
}
return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "GET_PASSPHRASE"))
{
if (!strcmp (cmdopt, "repeat"))
return 1;
if (!strcmp (cmdopt, "newsymkey"))
return 1;
}
else if (!strcmp (cmd, "EXPORT_KEY"))
{
if (!strcmp (cmdopt, "mode1003"))
return 1;
}
return 0;
}
/* Tell Libassuan about our commands. Also register the other Assuan
handlers. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
{ "ISTRUSTED", cmd_istrusted, hlp_istrusted },
{ "HAVEKEY", cmd_havekey, hlp_havekey },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "SIGKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
{ "SETHASH", cmd_sethash, hlp_sethash },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
{ "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
{ "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
{ "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
{ "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
{ "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
{ "LEARN", cmd_learn, hlp_learn },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "SCD", cmd_scd, hlp_scd },
{ "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
{ "IMPORT_KEY", cmd_import_key, hlp_import_key },
{ "EXPORT_KEY", cmd_export_key, hlp_export_key },
{ "DELETE_KEY", cmd_delete_key, hlp_delete_key },
{ "GET_SECRET", cmd_get_secret, hlp_get_secret },
{ "PUT_SECRET", cmd_put_secret, hlp_put_secret },
{ "GETVAL", cmd_getval, hlp_getval },
{ "PUTVAL", cmd_putval, hlp_putval },
{ "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ "KEYTOTPM", cmd_keytotpm, hlp_keytotpm },
{ "KEYATTR", cmd_keyattr, hlp_keyattr },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_register_post_cmd_notify (ctx, post_cmd_notify);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
simple piper server, otherwise it is a regular server. CTRL is the
control structure for this connection; it has only the basic
initialization. */
void
start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
if (ctrl->restricted)
{
if (agent_copy_startup_env (ctrl))
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
agent_exit (2);
}
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else if (listen_fd != GNUPG_INVALID_FD)
{
rc = assuan_init_socket_server (ctx, listen_fd, 0);
/* FIXME: Need to call assuan_sock_set_nonce for Windows. But
this branch is currently not used. */
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->use_cache_for_signing = 1;
ctrl->digest.data = NULL;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = 0;
assuan_set_io_monitor (ctx, io_monitor, NULL);
agent_set_progress_cb (progress_cb, ctrl);
for (;;)
{
assuan_peercred_t client_creds; /* Note: Points into CTX. */
pid_t pid;
rc = assuan_accept (ctx);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_get_peercred (ctx, &client_creds);
if (rc)
{
/* Note that on Windows we don't get the peer credentials
* and thus we silence the error. */
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
;
#ifdef HAVE_W32_SYSTEM
else if (gpg_err_code (rc) == GPG_ERR_ASS_GENERAL)
;
#endif
else
log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc));
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
}
else
{
#ifdef HAVE_W32_SYSTEM
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
#else
pid = client_creds->pid;
ctrl->client_uid = client_creds->uid;
#endif
}
ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid;
ctrl->server_local->connect_from_self = (pid == getpid ());
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Clear the keyinfo cache. */
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
/* Reset the nonce caches. */
clear_nonce_cache (ctrl);
/* Reset the SCD if needed. */
agent_reset_daemon (ctrl);
/* Reset the pinentry (in case of popup messages). */
agent_reset_query (ctrl);
/* Cleanup. */
assuan_release (ctx);
xfree (ctrl->server_local->keydesc);
xfree (ctrl->server_local->import_key);
xfree (ctrl->server_local->export_key);
if (ctrl->server_local->stopme)
agent_exit (0);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
/* Helper for the pinentry loopback mode. It merely passes the
parameters on to the client. */
gpg_error_t
pinentry_loopback(ctrl_t ctrl, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length)
{
gpg_error_t rc;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
if (rc)
return rc;
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
assuan_end_confidential (ctx);
return rc;
}
/* Helper for the pinentry loopback mode to ask confirmation
or just to show message. */
gpg_error_t
pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok)
{
gpg_error_t err = 0;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (desc)
err = print_assuan_status (ctx, "SETDESC", "%s", desc);
if (!err && ok)
err = print_assuan_status (ctx, "SETOK", "%s", ok);
if (!err && notok)
err = print_assuan_status (ctx, "SETNOTOK", "%s", notok);
if (!err)
err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0",
NULL, NULL, 0);
return err;
}
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
index 34b03e694..f6ce565c3 100644
--- a/tools/card-call-scd.c
+++ b/tools/card-call-scd.c
@@ -1,1799 +1,1885 @@
/* card-call-scd.c - IPC calls to scdaemon.
* Copyright (C) 2019, 2020 g10 Code GmbH
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
#define START_AGENT_NO_STARTUP_CMDS 1
#define START_AGENT_SUPPRESS_ERRORS 2
struct default_inq_parm_s
{
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct card_cardlist_parm_s
{
gpg_error_t error;
int with_apps;
strlist_t list;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
/*
* File local variables
*/
/* The established context to the agent. Note that all calls to
* scdaemon are routed via the agent and thus we only need to care
* about the IPC with the agent. */
static assuan_context_t agent_ctx;
/*
* Local prototypes
*/
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* Release the card info structure INFO. */
void
release_card_info (card_info_t info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->cardtype); info->cardtype = NULL;
xfree (info->manufacturer_name); info->manufacturer_name = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->dispserialno); info->dispserialno = NULL;
xfree (info->apptypestr); info->apptypestr = NULL;
info->apptype = APP_TYPE_NONE;
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->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
while (info->kinfo)
{
key_info_t kinfo = info->kinfo->next;
xfree (info->kinfo->label);
xfree (info->kinfo);
info->kinfo = kinfo;
}
info->chvusage[0] = info->chvusage[1] = 0;
xfree (info->chvlabels); info->chvlabels = NULL;
for (i=0; i < DIM(info->supported_keyalgo); i++)
{
free_strlist (info->supported_keyalgo[i]);
info->supported_keyalgo[i] = NULL;
}
}
/* Map an application type string to an integer. */
static app_type_t
map_apptypestr (const char *string)
{
app_type_t result;
if (!string)
result = APP_TYPE_NONE;
else if (!ascii_strcasecmp (string, "OPENPGP"))
result = APP_TYPE_OPENPGP;
else if (!ascii_strcasecmp (string, "NKS"))
result = APP_TYPE_NKS;
else if (!ascii_strcasecmp (string, "DINSIG"))
result = APP_TYPE_DINSIG;
else if (!ascii_strcasecmp (string, "P15"))
result = APP_TYPE_P15;
else if (!ascii_strcasecmp (string, "GELDKARTE"))
result = APP_TYPE_GELDKARTE;
else if (!ascii_strcasecmp (string, "SC-HSM"))
result = APP_TYPE_SC_HSM;
else if (!ascii_strcasecmp (string, "PIV"))
result = APP_TYPE_PIV;
else
result = APP_TYPE_UNKNOWN;
return result;
}
/* Return a string representation of the application type. */
const char *
app_type_string (app_type_t app_type)
{
const char *result = "?";
switch (app_type)
{
case APP_TYPE_NONE: result = "None"; break;
case APP_TYPE_OPENPGP: result = "OpenPGP"; break;
case APP_TYPE_NKS: result = "NetKey"; break;
case APP_TYPE_DINSIG: result = "DINSIG"; break;
case APP_TYPE_P15: result = "P15"; break;
case APP_TYPE_GELDKARTE: result = "Geldkarte"; break;
case APP_TYPE_SC_HSM: result = "SC-HSM"; break;
case APP_TYPE_PIV: result = "PIV"; break;
case APP_TYPE_UNKNOWN: result = "Unknown"; break;
}
return result;
}
/* If RC is not 0, write an appropriate status message. */
static gpg_error_t
status_sc_op_failure (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
case GPG_ERR_BAD_RESET_CODE:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "2");
break;
case GPG_ERR_PIN_BLOCKED:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "3");
break;
case GPG_ERR_NO_RESET_CODE:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "4");
break;
default:
gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL);
break;
}
return err;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
(void)parm;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
/* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */
/* if (err) */
/* log_error (_("failed to proxy %s inquiry to client\n"), */
/* "PINENTRY_LAUNCHED"); */
/* We do not pass errors to avoid breaking other code. */
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
return warn_server_version_mismatch (ctx, servername, mode,
gnupg_status_strings, NULL,
!opt.quiet);
}
/* Try to connect to the agent via socket or fork it off and work by
* pipes. Handle the server's initial greeting. */
static gpg_error_t
start_agent (unsigned int flags)
{
gpg_error_t err;
int started = 0;
if (agent_ctx)
err = 0;
else
{
started = 1;
err = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!err
&& !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
}
}
if (started && !err && !(flags & START_AGENT_NO_STARTUP_CMDS))
{
/* Request the serial number of the card for an early test. */
struct card_info_s info;
memset (&info, 0, sizeof info);
if (!(flags & START_AGENT_SUPPRESS_ERRORS))
err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!err)
err = assuan_transact (agent_ctx, "SCD SERIALNO --all",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (err && !(flags & START_AGENT_SUPPRESS_ERRORS))
{
switch (gpg_err_code (err))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */
break;
case GPG_ERR_OBJ_TERM_STATE:
/* Card is in termination state. */
gnupg_status_printf (STATUS_CARDCTRL, "7");
break;
default:
gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */
break;
}
}
if (!err && info.serialno)
gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno);
release_card_info (&info);
}
return err;
}
/* Return a new malloced string by unescaping the string S. Escaping
* is percent escaping and '+'/space mapping. A binary nul will
* silently be replaced by a 0xFF. Function returns NULL to indicate
* an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
* allocated string. We make sure that only hex characters are
* returned. Returns NULL on error. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a
* RESET command is send to scd. HEXAPDU may also be one of theseo
* special strings:
*
* "undefined" :: Send the command "SCD SERIALNO undefined"
* "lock" :: Send the command "SCD LOCK --wait"
* "trylock" :: Send the command "SCD LOCK"
* "unlock" :: Send the command "SCD UNLOCK"
* "reset-keep-lock" :: Send the command "SCD RESET --keep-lock"
*
* If R_DATA is not NULL the data without the status code is stored
* there. Caller must release it. If OPTIONS is not NULL, this will
* be passed verbatim to the SCDaemon's APDU command. */
gpg_error_t
scd_apdu (const char *hexapdu, const char *options, unsigned int *r_sw,
unsigned char **r_data, size_t *r_datalen)
{
gpg_error_t err;
if (r_data)
*r_data = NULL;
if (r_datalen)
*r_datalen = 0;
err = start_agent (START_AGENT_NO_STARTUP_CMDS);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "reset-keep-lock"))
{
err = assuan_transact (agent_ctx, "SCD RESET --keep-lock",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "lock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK --wait",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "trylock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "unlock"))
{
err = assuan_transact (agent_ctx, "SCD UNLOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
int no_sw;
init_membuf (&mb, 256);
no_sw = (options && (strstr (options, "--dump-atr")
|| strstr (options, "--data-atr")));
snprintf (line, DIM(line), "SCD APDU %s%s%s",
options?options:"", options?" -- ":"", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < (no_sw?1:2)) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
if (r_sw)
*r_sw = no_sw? 0 : buf16_to_uint (data+datalen-2);
if (r_data && r_datalen)
{
*r_data = data;
*r_datalen = datalen - (no_sw?0:2);
data = NULL;
}
}
xfree (data);
}
}
return err;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
/* FIXME: Should we use has_leading_keyword? */
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Make the card with SERIALNO the current one. */
gpg_error_t
scd_switchcard (const char *serialno)
{
int err;
char line[ASSUAN_LINELENGTH];
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHCARD -- %s", serialno);
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
/* Make the app APPNAME the one on the card. */
gpg_error_t
scd_switchapp (const char *appname)
{
int err;
char line[ASSUAN_LINELENGTH];
if (appname && !*appname)
appname = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s",
appname? " ":"", appname? appname:"");
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
/* For historical reasons OpenPGP cards simply use the numbers 1 to 3
* for the <keyref>. Other cards and future versions of
* scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1"
* instead of "1". This is a helper to cope with that. */
static const char *
parse_keyref_helper (const char *string)
{
if (*string == '1' && spacep (string+1))
return "OPENPGP.1";
else if (*string == '2' && spacep (string+1))
return "OPENPGP.2";
else if (*string == '3' && spacep (string+1))
return "OPENPGP.3";
else
return string;
}
/* Create a new key info object with KEYREF. All fields but the
* keyref are zeroed out. Never returns NULL. The created object is
* appended to the list at INFO. */
static key_info_t
create_kinfo (card_info_t info, const char *keyref)
{
key_info_t kinfo, ki;
kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref));
strcpy (kinfo->keyref, keyref);
if (!info->kinfo)
info->kinfo = kinfo;
else
{
for (ki=info->kinfo; ki->next; ki = ki->next)
;
ki->next = kinfo;
}
return kinfo;
}
/* The status callback to handle the LEARN and GETATTR commands. */
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL; /* In case we need a copy. */
char *pline, *endp;
key_info_t kinfo;
const char *keyref;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
switch (keywordlen)
{
case 3:
if (!memcmp (keyword, "KDF", 3))
{
parm->kdf_do_enabled = 1;
}
break;
case 5:
if (!memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
/* I am not sure why we test for 0xff but we better keep
* that in case of bogus card versions which did not
* initialize that DO correctly. */
parm->uif[no] = (data[0] == 0xff)? 0 : data[0];
xfree (data);
}
break;
case 6:
if (!memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (!memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
unsigned long number;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
else if (!strcmp (p, "pd"))
parm->extcap.private_dos = abool;
else if (!strcmp (p, "mcl3"))
parm->extcap.mcl3 = strtoul (p2, NULL, 10);
else if (!strcmp (p, "sm"))
{
/* Unfortunately this uses OpenPGP algorithm
* ids so that we need to map them to Gcrypt
* ids. The mapping is limited to what
* OpenPGP cards support. Other cards
* should use a different tag than "sm". */
parm->extcap.sm = 1;
number = strtoul (p2, NULL, 10);
switch (number)
{
case CIPHER_ALGO_3DES:
parm->extcap.smalgo = GCRY_CIPHER_3DES;
break;
case CIPHER_ALGO_AES:
parm->extcap.smalgo = GCRY_CIPHER_AES;
break;
case CIPHER_ALGO_AES192:
parm->extcap.smalgo = GCRY_CIPHER_AES192;
break;
case CIPHER_ALGO_AES256:
parm->extcap.smalgo = GCRY_CIPHER_AES256;
break;
default:
/* Unknown algorithm; dont claim SM support. */
parm->extcap.sm = 0;
break;
}
}
}
}
xfree (buf);
}
}
else if (!memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,
sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,
sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,
sizeof parm->cafpr3);
}
break;
case 7:
if (!memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptypestr);
parm->apptypestr = unescape_status_string (line);
parm->apptype = map_apptypestr (parm->apptypestr);
}
else if (!memcmp (keyword, "KEY-FPR", keywordlen))
{
/* The format of such a line is:
* KEY-FPR <keyref> <fingerprintinhex>
*/
const char *fpr;
line_buffer = pline = xstrdup (line);
keyref = parse_keyref_helper (pline);
while (*pline && !spacep (pline))
pline++;
if (*pline)
*pline++ = 0; /* Terminate keyref. */
while (spacep (pline)) /* Skip to the fingerprint. */
pline++;
fpr = pline;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear the fpr. */
memset (kinfo->fpr, 0, sizeof kinfo->fpr);
/* Set or update or the fingerprint. */
kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr);
}
break;
case 8:
if (!memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& (xtoi_2 (parm->serialno+12) == 0 /* Yubikey */
|| xtoi_2 (parm->serialno+12) >= 2));
}
else if (!memcmp (keyword, "CARDTYPE", keywordlen))
{
xfree (parm->cardtype);
parm->cardtype = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (!memcmp (keyword, "KEY-ATTR", keywordlen))
{
char keyrefbuf[20];
int keyno, algo, n;
const char *curve;
unsigned int nbits;
/* To prepare for future changes we allow for a full OpenPGP
* keyref here. */
if (!ascii_strncasecmp (line, "OPENPGP.", 8))
line += 8;
/* Note that KEY-ATTR returns OpenPGP algorithm numbers but
* we want to use the Gcrypt numbers here. A compatible
* change would be to add another parameter along with a
* magic algo number to indicate that. */
algo = PUBKEY_ALGO_RSA;
keyno = n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
algo = map_openpgp_pk_to_gcry (algo);
if (keyno < 1 || keyno > 3)
; /* Out of range - ignore. */
else
{
snprintf (keyrefbuf, sizeof keyrefbuf, "OPENPGP.%d", keyno);
keyref = keyrefbuf;
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
/* Although we could use the the value at %n directly as
* keyalgo string, we want to use the standard
* keyalgo_string function and thus we reconstruct it
* here to make sure the displayed form of the curve
* names is used. */
nbits = 0;
curve = NULL;
if (algo == GCRY_PK_ECDH || algo == GCRY_PK_ECDSA
|| algo == GCRY_PK_EDDSA || algo == GCRY_PK_ECC)
{
curve = openpgp_is_curve_supported (line + n, NULL, NULL);
}
else /* For rsa we see here for example "rsa2048". */
{
if (line[n] && line[n+1] && line[n+2])
nbits = strtoul (line+n+3, NULL, 10);
}
kinfo->keyalgo = get_keyalgo_string (algo, nbits, curve);
kinfo->keyalgo_id = algo;
}
}
break;
case 9:
if (!memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-USAGE", keywordlen))
{
unsigned int byte1, byte2;
byte1 = byte2 = 0;
sscanf (line, "%x %x", &byte1, &byte2);
parm->chvusage[0] = byte1;
parm->chvusage[1] = byte2;
}
else if (!memcmp (keyword, "CHV-LABEL", keywordlen))
{
xfree (parm->chvlabels);
parm->chvlabels = xstrdup (line);
}
else if (!memcmp (keyword, "KEY-LABEL", keywordlen))
{
/* The format of such a line is:
* KEY-LABEL <keyref> [label|"-"] */
const char *fields[2];
int nfields;
char *label;
line_buffer = pline = xstrdup (line);
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - ignore. */
keyref = fields[0];
/* We don't remove the percent escaping because that is only
* used in case of strange characters in the label; we
* should not print them. Note that this info is only for
* human consumption, anyway. */
label = xtrystrdup (fields[1]);
if (!label)
goto leave; /* We ignore malloc failures here. */
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* New entry. */
kinfo = create_kinfo (parm, keyref);
xfree (kinfo->label);
kinfo->label = label;
}
break;
case 10:
if (!memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (!memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
while (spacep (p))
p++;
if (!buf)
;
else if (parm->apptype == APP_TYPE_OPENPGP)
{
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++;
}
parm->nchvmaxlen = 3;
for (i=0; *p && i < 3; i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
parm->nchvinfo = 3;
}
else
{
for (i=0; *p && i < DIM (parm->chvinfo); i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
parm->nchvinfo = i;
}
xfree (buf);
}
else if (!memcmp (keyword, "APPVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->appversion = val;
}
break;
case 11:
if (!memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*/
const char *fields[4];
int nfields;
const char *hexgrp, *usage;
time_t keytime;
line_buffer = pline = xstrdup (line);
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - invalid status line. */
hexgrp = fields[0];
keyref = fields[1];
if (nfields > 2)
usage = fields[2];
else
usage = "";
if (nfields > 3)
keytime = parse_timestamp (fields[3], NULL);
else
keytime = 0;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* New entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear grip and usage */
{
memset (kinfo->grip, 0, sizeof kinfo->grip);
kinfo->usage = 0;
}
/* Set or update the grip. Note that due to the
* calloc/memset an erroneous too short grip will be nul
* padded on the right. */
unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip);
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
/* Store the keytime. */
kinfo->created = keytime == (time_t)(-1)? 0 : (u32)keytime;
}
else if (!memcmp (keyword, "CARDVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->cardversion = val;
}
break;
case 12:
if (!memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
else if (!memcmp (keyword, "MANUFACTURER", 12))
{
xfree (parm->manufacturer_name);
parm->manufacturer_name = NULL;
parm->manufacturer_id = strtoul (line, &endp, 0);
while (endp && spacep (endp))
endp++;
if (endp && *endp)
parm->manufacturer_name = xstrdup (endp);
}
break;
case 13:
if (!memcmp (keyword, "$DISPSERIALNO", keywordlen))
{
xfree (parm->dispserialno);
parm->dispserialno = unescape_status_string (line);
}
else if (!memcmp (keyword, "KEY-ATTR-INFO", keywordlen))
{
if (!strncmp (line, "OPENPGP.", 8))
{
int no;
line += 8;
no = atoi (line);
if (no >= 1 && no <= 3)
{
no--;
line++;
while (spacep (line))
line++;
append_to_strlist (&parm->supported_keyalgo[no],
xstrdup (line));
}
}
/* Skip when it's not "OPENPGP.[123]". */
}
break;
default:
/* Unknown. */
break;
}
leave:
xfree (line_buffer);
return 0;
}
/* Call the scdaemon to learn about a smartcard. This fills INFO
* with data from the card. */
gpg_error_t
scd_learn (card_info_t info, int reread)
{
gpg_error_t err;
struct default_inq_parm_s parm;
struct card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx,
reread? "SCD LEARN --force --reread"
/* */: "SCD LEARN --force",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get some other key attributes. */
if (!err)
{
info->initialized = 1;
err = scd_getattr ("KEY-ATTR", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
err = scd_getattr ("$DISPSERIALNO", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
err = scd_getattr ("KEY-LABEL", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
}
if (info == &dummyinfo)
release_card_info (info);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command. */
gpg_error_t
scd_getattr (const char *name, struct card_info_s *info)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return err;
}
/* Send an setattr command to the SCdaemon. */
gpg_error_t
scd_setattr (const char *name,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
char *tmp;
char *line = NULL;
struct default_inq_parm_s parm;
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
tmp = strconcat ("SCD SETATTR ", name, " ", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
line = percent_data_escape (1, tmp, value, valuelen);
xfree (tmp);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 10 > ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
err = start_agent (0);
if (err )
goto leave;
memset (&parm, 0, sizeof parm);
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
leave:
xfree (line);
return status_sc_op_failure (err);
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
* assuan_transact takes care of flushing and writing the END
* command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
gpg_error_t err;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Send a WRITECERT command to the SCdaemon. */
gpg_error_t
scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
memset (&parms, 0, sizeof parms);
snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
err = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return status_sc_op_failure (err);
}
/* Send a WRITEKEY command to the agent (so that the agent can fetch
* the key to write). KEYGRIP is the hexified keygrip of the source
* key which will be written to the slot KEYREF. FORCE must be true
* to overwrite an existing key. */
gpg_error_t
scd_writekey (const char *keyref, int force, const char *keygrip)
{
gpg_error_t err;
struct default_inq_parm_s parm;
char line[ASSUAN_LINELENGTH];
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
/* Note: We don't send the s/n but "-" because gpg-agent has
* currently no use for it. */
/* FIXME: For OpenPGP we should provide the creation time. */
snprintf (line, sizeof line, "KEYTOCARD%s %s - %s",
force? " --force":"", keygrip, keyref);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
return status_sc_op_failure (err);
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
if (createtime)
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
gnupg_status_printf (STATUS_PROGRESS, "%s", line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDAEMON. On success, creation time is stored back to CREATETIME. */
gpg_error_t
scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
if (createtime && *createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
algo? "--algo=":"",
algo? algo:"",
keyref);
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
return status_sc_op_failure (err);
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. If DEMAND is not NULL
* the reader with the a card of the serial number DEMAND is
* requested. */
gpg_error_t
scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO --all");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s --all", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
if (r_serialno)
*r_serialno = serialno;
else
xfree (serialno);
return 0;
}
/* Send a READCERT command to the SCdaemon. */
gpg_error_t
scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, sizeof line, "SCD READCERT %s", certidstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error_from_syserror ();
return 0;
}
/* Send a READKEY command to the SCdaemon. On success a new
* s-expression is stored at R_RESULT. If CREATE_SHADOW is set stub
* keys will be created if they do not exist. */
gpg_error_t
scd_readkey (const char *keyrefstr, int create_shadow, gcry_sexp_t *r_result)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
- *r_result = NULL;
+ if (r_result)
+ *r_result = NULL;
err = start_agent (0);
if (err)
return err;
init_membuf (&data, 1024);
if (create_shadow)
- snprintf (line, DIM(line), "READKEY --card -- %s", keyrefstr);
+ snprintf (line, DIM(line), "READKEY %s--card -- %s",
+ r_result? "" : "--no-data ", keyrefstr);
else
snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
NULL, NULL,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
- err = gcry_sexp_new (r_result, buf, buflen, 0);
+ err = r_result ? gcry_sexp_new (r_result, buf, buflen, 0) : 0;
xfree (buf);
return err;
}
/* Callback function for card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1))
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
if (parm->with_apps)
{
/* Format of the stored string is the S/N, a space, and a
* space separated list of appnames. */
if (*s && (*s != ' ' || spacep (s+1) || !s[1]))
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else /* We assume the rest of the line is well formatted. */
add_to_strlist (&parm->list, line);
}
else
{
if (*s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
}
return 0;
}
/* Return the serial numbers of all cards currently inserted. */
gpg_error_t
scd_cardlist (strlist_t *result)
{
gpg_error_t err;
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, "SCD GETINFO card_list",
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return err;
}
/* Return the serial numbers and appnames of the current card or, with
* ALL given has true, of all cards currently inserted. */
gpg_error_t
scd_applist (strlist_t *result, int all)
{
gpg_error_t err;
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
parm.with_apps = 1;
err = assuan_transact (agent_ctx,
all ? "SCD GETINFO all_active_apps"
/**/: "SCD GETINFO active_apps",
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return err;
}
/* Change the PIN of a card or reset the retry counter. If NULLPIN is
* set the TCOS specific NullPIN is changed. */
gpg_error_t
scd_change_pin (const char *pinref, int reset_mode, int nullpin)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD PASSWD%s %s",
nullpin? " --nullpin": reset_mode? " --reset":"", pinref);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here. */
gpg_error_t
scd_checkpin (const char *serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
+
+struct havekey_status_parm_s
+{
+ char *string;
+};
+
+static gpg_error_t
+havekey_status_cb (void *opaque, const char *line)
+{
+ struct havekey_status_parm_s *parm = opaque;
+ const char *s;
+ char *p;
+
+ if ((s = has_leading_keyword (line, "KEYFILEINFO")))
+ {
+ xfree (parm->string);
+ parm->string = xtrystrdup (s);
+ if (!parm->string)
+ return gpg_error_from_syserror ();
+ p = strchr (parm->string, ' ');
+ if (p)
+ *p = 0;
+ }
+
+ return 0;
+}
+
+
+/* Run the HAVEKEY --info command and stores the retrieved string at
+ * R_RESULT. Caller must free that string. If an error is returned
+ * R_RESULT is set to NULL. */
+gpg_error_t
+scd_havekey_info (const unsigned char *grip, char **r_result)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ struct havekey_status_parm_s parm = {NULL};
+
+ *r_result = NULL;
+
+ err = start_agent (0);
+ if (err)
+ return err;
+
+ snprintf (line, sizeof line, "HAVEKEY --info ");
+ log_assert (ASSUAN_LINELENGTH > strlen(line) + 2*KEYGRIP_LEN + 10);
+ bin2hex (grip, KEYGRIP_LEN, line+strlen(line));
+
+ err = assuan_transact (agent_ctx, line,
+ NULL, NULL, NULL, NULL,
+ havekey_status_cb, &parm);
+ if (err)
+ xfree (parm.string);
+ else
+ *r_result = parm.string;
+ return err;
+}
+
+
+/* Run the DELETE_KEY command. If FORCE is given the user will not be
+ * asked for confirmation. */
+gpg_error_t
+scd_delete_key (const unsigned char *grip, int force)
+{
+ gpg_error_t err;
+ char line[ASSUAN_LINELENGTH];
+ struct default_inq_parm_s dfltparm = {NULL};
+
+ err = start_agent (0);
+ if (err)
+ return err;
+ dfltparm.ctx = agent_ctx;
+
+ snprintf (line, sizeof line, "DELETE_KEY%s ", force?" --force":"");
+ log_assert (ASSUAN_LINELENGTH > strlen(line) + 2*KEYGRIP_LEN + 10);
+ bin2hex (grip, KEYGRIP_LEN, line+strlen(line));
+
+ err = assuan_transact (agent_ctx, line,
+ NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL);
+ return err;
+}
+
+
+
/* Return a malloced string describing the statusword SW. On error
* NULL is returned. */
char *
scd_apdu_strerror (unsigned int sw)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
char *buf;
err = start_agent (0);
if (err)
return NULL;
init_membuf (&data, 64);
snprintf (line, sizeof line, "SCD GETINFO apdu_strerror 0x%x", sw);
err = assuan_transact (agent_ctx, line, put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, NULL));
return NULL;
}
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
return buf;
}
diff --git a/tools/gpg-card.c b/tools/gpg-card.c
index 4b0457ab9..442acdc84 100644
--- a/tools/gpg-card.c
+++ b/tools/gpg-card.c
@@ -1,4254 +1,4386 @@
/* gpg-card.c - An interactive tool to work with cards.
* Copyright (C) 2019--2022 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file 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 3 of the License, or
* (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#define INCLUDED_BY_MAIN_MODULE 1
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/exechelp.h"
#include "../common/ttyio.h"
#include "../common/server-help.h"
#include "../common/openpgpdefs.h"
#include "../common/tlv.h"
#include "../common/comopt.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
#define HISTORYNAME ".gpg-card_history"
/* Constants to identify the commands and options. */
enum opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oDebug = 500,
oGpgProgram,
oGpgsmProgram,
oStatusFD,
oWithColons,
oNoAutostart,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oXauthority,
oLCctype,
oLCmessages,
oNoKeyLookup,
oNoHistory,
oChUid,
oDummy
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup",
"use --no-key-lookup for \"list\""),
ARGPARSE_s_n (oNoHistory,"no-history",
"do not use the command history file"),
ARGPARSE_s_s (oChUid, "chuid", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* An object to create lists of labels and keyrefs. */
struct keyinfolabel_s
{
const char *label;
const char *keyref;
};
typedef struct keyinfolabel_s *keyinfolabel_t;
/* Helper for --chuid. */
static const char *changeuser;
/* Limit of size of data we read from a file for certain commands. */
#define MAX_GET_DATA_FROM_FILE 16384
/* Constants for OpenPGP cards. */
#define OPENPGP_USER_PIN_DEFAULT "123456"
#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
#define OPENPGP_KDF_DATA_LENGTH_MIN 90
#define OPENPGP_KDF_DATA_LENGTH_MAX 110
/* Local prototypes. */
static void show_keysize_warning (void);
static gpg_error_t dispatch_command (card_info_t info, const char *command);
static void interactive_loop (void);
#ifdef HAVE_LIBREADLINE
static char **command_completion (const char *text, int start, int end);
#endif /*HAVE_LIBREADLINE*/
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "gpg-card"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-card"
" [options] [{[--] command [args]}] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-card"
" [options] [command [args] {-- command [args]}]\n\n"
"Tool to manage cards and tokens. Without a command an interactive\n"
"mode is used. Use command \"help\" to list all commands.");
break;
default: p = NULL; break;
}
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Command line parsing. */
static void
parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
{
while (gpgrt_argparse (NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break;
case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
case oStatusFD:
gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons: opt.with_colons = 1; break;
case oNoAutostart: opt.autostart = 0; break;
case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
case oXauthority: set_opt_session_env ("XAUTHORITY",
pargs->r.ret_str); break;
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case oNoKeyLookup: opt.no_key_lookup = 1; break;
case oNoHistory: opt.no_history = 1; break;
case oChUid: changeuser = pargs->r.ret_str; break;
default: pargs->err = 2; break;
}
}
}
/* gpg-card main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
gpgrt_argparse_t pargs;
char **command_list = NULL;
int cmdidx;
char *command;
gnupg_reopen_std ("gpg-card");
gpgrt_set_strusage (my_strusage);
gnupg_rl_initialize ();
log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup default options. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (changeuser && gnupg_chuid (changeuser, 0))
log_inc_errorcount (); /* Force later termination. */
if (log_get_errorcount (0))
exit (2);
/* Process common component options. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
if (parse_comopt (GNUPG_MODULE_NAME_CARD, opt.debug))
{
gnupg_status_printf (STATUS_FAILURE, "option-parser %u",
gpg_error (GPG_ERR_GENERAL));
exit(2);
}
if (comopt.no_autostart)
opt.autostart = 0;
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.gpgsm_program)
opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
/* Now build the list of commands. We guess the size of the array
* by assuming each item is a complete command. Obviously this will
* be rarely the case, but it is less code to allocate a possible
* too large array. */
command_list = xcalloc (argc+1, sizeof *command_list);
cmdidx = 0;
command = NULL;
while (argc)
{
for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
{
if (!command)
command = xstrdup (*argv);
else
{
char *tmp = xstrconcat (command, " ", *argv, NULL);
xfree (command);
command = tmp;
}
}
if (argc)
{ /* Skip the double dash. */
argc--;
argv++;
}
if (command)
{
command_list[cmdidx++] = command;
command = NULL;
}
}
opt.interactive = !cmdidx;
if (!opt.interactive)
opt.no_history = 1;
if (opt.interactive)
{
interactive_loop ();
err = 0;
}
else
{
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
err = 0;
for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
{
err = dispatch_command (info, command);
if (err)
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0; /* This was a "quit". */
else if (command && !opt.quiet)
log_info ("stopped at command '%s'\n", command);
}
flush_keyblock_cache ();
if (command_list)
{
for (cmdidx=0; command_list[cmdidx]; cmdidx++)
xfree (command_list[cmdidx]);
xfree (command_list);
}
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
gnupg_status_printf (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Return S or the string "[none]" if S is NULL. */
static GPGRT_INLINE const char *
nullnone (const char *s)
{
return s? s: "[none]";
}
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
* On error return an error code and stores NULL at R_BUFFER; on
* success returns 0 and stores the number of bytes read at R_BUFLEN
* and the address of a newly allocated buffer at R_BUFFER. A
* complementary nul byte is always appended to the data but not
* counted; this allows to pass NULL for R-BUFFER and consider the
* returned data as a string. */
static gpg_error_t
get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
es_fclose (fp);
return err;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp);
es_fclose (fp);
if (n < 0)
{
err = gpg_error_from_syserror ();
tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
xfree (data);
return err;
}
data[n] = 0;
*r_buffer = data;
if (r_buflen)
*r_buflen = n;
return 0;
}
/* Fixup the ENODEV error from scdaemon which we may see after
* removing a card due to scdaemon scanning for readers with cards.
* We also map the CAERD REMOVED error to the more useful CARD_NOT
* PRESENT. */
static gpg_error_t
fixup_scd_errors (gpg_error_t err)
{
if ((gpg_err_code (err) == GPG_ERR_ENODEV
|| gpg_err_code (err) == GPG_ERR_CARD_REMOVED)
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
return err;
}
/* Set the card removed flag from INFO depending on ERR. This does
* not clear the flag. */
static gpg_error_t
maybe_set_card_removed (card_info_t info, gpg_error_t err)
{
if ((gpg_err_code (err) == GPG_ERR_ENODEV
|| gpg_err_code (err) == GPG_ERR_CARD_REMOVED)
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
info->card_removed = 1;
return err;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
* success. */
static gpg_error_t
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
gpg_error_t err;
estream_t fp;
fp = es_fopen (fname, "wb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
es_fclose (fp);
return err;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
return 0;
}
/* Return a malloced string with the number opf the menu PROMPT.
* Control-D is mapped to "Q". */
static char *
get_selection (const char *prompt)
{
char *answer;
tty_printf ("\n");
tty_printf ("%s", prompt);
tty_printf ("\n");
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
strcpy (answer, "q");
return answer;
}
/* Simply prints TEXT to the output. Returns 0 as a convenience.
* This is a separate function so that it can be extended to run
* less(1) or so. The extra arguments are int values terminated by a
* 0 to indicate card application types supported with this command.
* If none are given (just the final 0), this is a general
* command. */
static gpg_error_t
print_help (const char *text, ...)
{
estream_t fp;
va_list arg_ptr;
int value;
int any = 0;
fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "%s\n", text);
va_start (arg_ptr, text);
while ((value = va_arg (arg_ptr, int)))
{
if (!any)
tty_fprintf (fp, "[Supported by: ");
tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
any = 1;
}
if (any)
tty_fprintf (fp, "]\n");
va_end (arg_ptr);
return 0;
}
/* Print an (OpenPGP) fingerprint. */
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
for (i=0; i < fprlen ; i++, fpr++)
tty_fprintf (fp, "%02X", *fpr);
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
/* Print the keygrip GRP. */
static void
-print_keygrip (estream_t fp, const unsigned char *grp)
+print_keygrip (estream_t fp, const unsigned char *grp, int with_lf)
{
int i;
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
- tty_fprintf (fp, "\n");
+ if (with_lf)
+ tty_fprintf (fp, "\n");
}
/* Print a string but avoid printing control characters. */
static void
print_string (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
/* Print an ISO formatted name or "[not set]". */
static void
print_isoname (estream_t fp, const char *name)
{
if (name && *name)
{
char *p, *given, *buf;
buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (*buf)
tty_fprintf (fp, " ");
}
if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
tty_fprintf (fp, _("[not set]"));
}
tty_fprintf (fp, "\n");
}
/* Return true if the buffer MEM of length memlen consists only of zeroes. */
static int
mem_is_zero (const char *mem, unsigned int memlen)
{
int i;
for (i=0; i < memlen && !mem[i]; i++)
;
return (i == memlen);
}
/* Helper to list a single keyref. LABEL_KEYREF is a fallback key
* reference if no info is available; it may be NULL. */
static void
list_one_kinfo (card_info_t info, key_info_t kinfo,
const char *label_keyref, estream_t fp, int no_key_lookup,
int create_shadow)
{
gpg_error_t err;
key_info_t firstkinfo = info->kinfo;
keyblock_t keyblock = NULL;
keyblock_t kb;
pubkey_t pubkey;
userid_t uid;
key_info_t ki;
const char *s;
gcry_sexp_t s_pkey;
int any;
if (firstkinfo && kinfo)
{
tty_fprintf (fp, " ");
if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
{
tty_fprintf (fp, "[none]\n");
tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref);
if (kinfo->label)
tty_fprintf (fp, " label ......: %s\n", kinfo->label);
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
goto leave;
}
- print_keygrip (fp, kinfo->grip);
+ print_keygrip (fp, kinfo->grip, 1);
tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
if (kinfo->usage)
{
any = 0;
tty_fprintf (fp, " (");
if ((kinfo->usage & GCRY_PK_USAGE_SIGN))
{ tty_fprintf (fp, "sign"); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_CERT))
{ tty_fprintf (fp, "%scert", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_AUTH))
{ tty_fprintf (fp, "%sauth", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_ENCR))
{ tty_fprintf (fp, "%sencr", any?",":""); any=1; }
tty_fprintf (fp, ")");
}
tty_fprintf (fp, "\n");
if (kinfo->label)
tty_fprintf (fp, " label ......: %s\n", kinfo->label);
if (!(err = scd_readkey (kinfo->keyref, create_shadow, &s_pkey)))
{
char *tmp = pubkey_algo_string (s_pkey, NULL);
tty_fprintf (fp, " algorithm ..: %s\n", nullnone (tmp));
xfree (tmp);
gcry_sexp_release (s_pkey);
s_pkey = NULL;
}
else
{
maybe_set_card_removed (info, err);
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
}
if (kinfo->fprlen && kinfo->created)
{
tty_fprintf (fp, " stored fpr .: ");
print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen);
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (kinfo->created));
}
if (no_key_lookup)
err = 0;
else
err = get_matching_keys (kinfo->grip,
(GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
&keyblock);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
tty_fprintf (fp, " used for ...: [%s]\n", gpg_strerror (err));
goto leave;
}
for (kb = keyblock; kb; kb = kb->next)
{
tty_fprintf (fp, " used for ...: %s\n",
kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
pubkey = kb->keys;
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
{
/* If this is not the primary key print the primary
* key's fingerprint or a reference to it. */
tty_fprintf (fp, " main key .: ");
for (ki=firstkinfo; ki; ki = ki->next)
if (pubkey->grip_valid
&& !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (ki)
{
/* Fixme: Replace mapping by a table lookup. */
if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
s = "this";
else if (!strcmp (ki->keyref, "OPENPGP.1"))
s = "Signature key";
else if (!strcmp (ki->keyref, "OPENPGP.2"))
s = "Encryption key";
else if (!strcmp (ki->keyref, "OPENPGP.3"))
s = "Authentication key";
else
s = NULL;
if (s)
tty_fprintf (fp, "<%s>\n", s);
else
tty_fprintf (fp, "<Key %s>\n", ki->keyref);
}
else /* Print the primary key as fallback. */
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
}
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP
|| kb->protocol == GNUPG_PROTOCOL_CMS)
{
/* Find the primary or subkey of that key. */
for (; pubkey; pubkey = pubkey->next)
if (pubkey->grip_valid
&& !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (pubkey)
{
tty_fprintf (fp, " fpr ......: ");
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
tty_fprintf (fp, " created ..: %s\n",
isotimestamp (pubkey->created));
}
}
for (uid = kb->uids; uid; uid = uid->next)
{
print_string (fp, " user id ..: ", uid->value);
}
}
}
else
{
tty_fprintf (fp, " [none]\n");
if (label_keyref)
tty_fprintf (fp, " keyref .....: %s\n", label_keyref);
if (kinfo)
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
}
leave:
release_keyblock (keyblock);
}
/* List all keyinfo in INFO using the list of LABELS. */
static void
list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp,
int no_key_lookup, int create_shadow)
{
key_info_t kinfo;
int idx, i, j;
/* Print the keyinfo. We first print those we known and then all
* remaining item. */
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
kinfo->xflag = 0;
if (labels)
{
for (idx=0; labels[idx].label; idx++)
{
tty_fprintf (fp, "%s", labels[idx].label);
kinfo = find_kinfo (info, labels[idx].keyref);
list_one_kinfo (info, kinfo, labels[idx].keyref,
fp, no_key_lookup, create_shadow);
if (kinfo)
kinfo->xflag = 1;
}
}
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
{
if (kinfo->xflag)
continue;
tty_fprintf (fp, "Key %s", kinfo->keyref);
for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1)
tty_fprintf (fp, j? ".":" ");
tty_fprintf (fp, ":");
list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup, create_shadow);
}
}
static void
list_retry_counter (card_info_t info, estream_t fp)
{
const char *s;
int i;
if (info->chvlabels)
tty_fprintf (fp, "PIN labels .......: %s\n", info->chvlabels);
tty_fprintf (fp, "PIN retry counter :");
for (i=0; i < DIM (info->chvinfo) && i < info->nchvinfo; i++)
{
if (info->chvinfo[i] >= 0)
tty_fprintf (fp, " %d", info->chvinfo[i]);
else
{
switch (info->chvinfo[i])
{
case -1: s = "[error]"; break;
case -2: s = "-"; break; /* No such PIN or info not available. */
case -3: s = "[blocked]"; break;
case -4: s = "[nullpin]"; break;
case -5: s = "[verified]"; break;
default: s = "[?]"; break;
}
tty_fprintf (fp, " %s", s);
}
}
tty_fprintf (fp, "\n");
}
/* List OpenPGP card specific data. */
static void
list_openpgp (card_info_t info, estream_t fp,
int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "Signature key ....:", "OPENPGP.1" },
{ "Encryption key....:", "OPENPGP.2" },
{ "Authentication key:", "OPENPGP.3" },
{ NULL, NULL }
};
if (info->apptype != APP_TYPE_OPENPGP)
{
tty_fprintf (fp, "invalid OpenPGP card\n");
return;
}
tty_fprintf (fp, "Name of cardholder: ");
print_isoname (fp, info->disp_name);
print_string (fp, "Language prefs ...: ", info->disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info->disp_sex == 1? _("Mr."):
info->disp_sex == 2? _("Ms.") : "");
print_string (fp, "URL of public key : ", info->pubkey_url);
print_string (fp, "Login data .......: ", info->login_data);
if (info->private_do[0])
print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
if (info->private_do[1])
print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
if (info->private_do[2])
print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
if (info->private_do[3])
print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
if (info->cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
}
if (info->cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
}
if (info->cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info->chv1_cached? _("not forced"): _("forced"));
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
list_retry_counter (info, fp);
tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
tty_fprintf (fp, "Capabilities .....:");
if (info->extcap.ki)
tty_fprintf (fp, " key-import");
if (info->extcap.aac)
tty_fprintf (fp, " algo-change");
if (info->extcap.bt)
tty_fprintf (fp, " button");
if (info->extcap.sm)
tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo));
if (info->extcap.private_dos)
tty_fprintf (fp, " priv-data");
tty_fprintf (fp, "\n");
if (info->extcap.kdf)
{
tty_fprintf (fp, "KDF setting ......: %s\n",
info->kdf_do_enabled ? "on" : "off");
}
if (info->extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off",
info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off",
info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off");
}
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List PIV card specific data. */
static void
list_piv (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "PIV authentication:", "PIV.9A" },
{ "Card authenticat. :", "PIV.9E" },
{ "Digital signature :", "PIV.9C" },
{ "Key management ...:", "PIV.9D" },
{ NULL, NULL }
};
if (info->chvusage[0] || info->chvusage[1])
{
tty_fprintf (fp, "PIN usage policy .:");
if ((info->chvusage[0] & 0x40))
tty_fprintf (fp, " app-pin");
if ((info->chvusage[0] & 0x20))
tty_fprintf (fp, " global-pin");
if ((info->chvusage[0] & 0x10))
tty_fprintf (fp, " occ");
if ((info->chvusage[0] & 0x08))
tty_fprintf (fp, " vci");
if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04))
tty_fprintf (fp, " pairing");
if (info->chvusage[1] == 0x10)
tty_fprintf (fp, " primary:card");
else if (info->chvusage[1] == 0x20)
tty_fprintf (fp, " primary:global");
tty_fprintf (fp, "\n");
}
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List Netkey card specific data. */
static void
list_nks (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ NULL, NULL }
};
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List PKCS#15 card specific data. */
static void
list_p15 (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ NULL, NULL }
};
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
static void
print_a_version (estream_t fp, const char *prefix, unsigned int value)
{
unsigned int a, b, c, d;
a = ((value >> 24) & 0xff);
b = ((value >> 16) & 0xff);
c = ((value >> 8) & 0xff);
d = ((value ) & 0xff);
if (a)
tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
else if (b)
tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
else if (c)
tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
else
tty_fprintf (fp, "%s %u\n", prefix, d);
}
/* Print all available information about the current card. With
* NO_KEY_LOOKUP the sometimes expensive listing of all matching
* OpenPGP and X.509 keys is not done */
static void
list_card (card_info_t info, int no_key_lookup, int create_shadow)
{
estream_t fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "Reader ...........: %s\n", nullnone (info->reader));
if (info->cardtype)
tty_fprintf (fp, "Card type ........: %s\n", info->cardtype);
if (info->cardversion)
print_a_version (fp, "Card firmware ....:", info->cardversion);
tty_fprintf (fp, "Serial number ....: %s\n", nullnone (info->serialno));
tty_fprintf (fp, "Application type .: %s%s%s%s\n",
app_type_string (info->apptype),
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr
? info->apptypestr:"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":"");
if (info->appversion)
print_a_version (fp, "Version ..........:", info->appversion);
if (info->serialno && info->dispserialno
&& strcmp (info->serialno, info->dispserialno))
tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno);
if (info->manufacturer_name && info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: %s (%x)\n",
info->manufacturer_name, info->manufacturer_id);
else if (info->manufacturer_name && !info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name);
else if (info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id);
switch (info->apptype)
{
case APP_TYPE_OPENPGP:
list_openpgp (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_PIV:
list_piv (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_NKS:
list_nks (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_P15:
list_p15 (info, fp, no_key_lookup, create_shadow);
break;
default: break;
}
}
/* Helper for cmd_list. */
static void
print_card_list (estream_t fp, card_info_t info, strlist_t cards,
int only_current)
{
int count;
strlist_t sl;
size_t snlen;
int star;
const char *s;
for (count = 0, sl = cards; sl; sl = sl->next, count++)
{
if (info && info->serialno)
{
s = strchr (sl->d, ' ');
if (s)
snlen = s - sl->d;
else
snlen = strlen (sl->d);
star = (strlen (info->serialno) == snlen
&& !memcmp (info->serialno, sl->d, snlen));
}
else
star = 0;
if (!only_current || star)
tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d);
}
}
/* The LIST command. This also updates INFO if needed. */
static gpg_error_t
cmd_list (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_cards, opt_apps, opt_info, opt_reread, opt_no_key_lookup;
int opt_shadow;
strlist_t cards = NULL;
strlist_t sl;
estream_t fp = opt.interactive? NULL : es_stdout;
const char *cardsn = NULL;
char *appstr = NULL;
int count;
int need_learn = 0;
if (!info)
return print_help
("LIST [--cards] [--apps] [--info] [--reread] [--shadow]"
" [--no-key-lookup] [N] [APP]\n\n"
"Show the content of the current card.\n"
"With N given select and list the N-th card;\n"
"with APP also given select that application.\n"
"To select an APP on the current card use '-' for N.\n"
"The S/N of the card may be used instead of N.\n"
" --cards list available cards\n"
" --apps list additional card applications\n"
" --info select a card and prints its s/n\n"
" --reread read infos from PCKS#15 cards again\n"
" --shadow create shadow keys for all card keys\n"
" --no-key-lookup do not list matching OpenPGP or X.509 keys\n"
, 0);
opt_cards = has_leading_option (argstr, "--cards");
opt_apps = has_leading_option (argstr, "--apps");
opt_info = has_leading_option (argstr, "--info");
opt_reread = has_leading_option (argstr, "--reread");
opt_shadow = has_leading_option (argstr, "--shadow");
opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup");
argstr = skip_options (argstr);
if (opt_shadow)
opt_no_key_lookup = 1;
if (opt.no_key_lookup)
opt_no_key_lookup = 1;
if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1)))
{
if (*argstr == '-' && (argstr[1] || spacep (argstr+1)))
argstr++; /* Keep current card. */
else
{
cardsn = argstr;
while (hexdigitp (argstr))
argstr++;
if (*argstr && !spacep (argstr))
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (*argstr)
*argstr++ = 0;
}
while (spacep (argstr))
argstr++;
if (*argstr)
{
appstr = argstr;
while (*argstr && !spacep (argstr))
argstr++;
while (spacep (argstr))
argstr++;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
}
}
else if (*argstr)
{
/* First argument needs to be a digit. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!info->serialno || info->need_sn_cmd)
{
/* This is probably the first call or was explicitly requested.
* We need to send a SERIALNO command to scdaemon so that our
* session knows all cards. */
err = scd_serialno (NULL, NULL);
if (err)
goto leave;
info->need_sn_cmd = 0;
need_learn = 1;
}
if (opt_cards || opt_apps)
{
/* Note that with option --apps CARDS is here the list of all
* apps. Format is "SERIALNO APPNAME {APPNAME}". We print the
* card number in the first column. */
if (opt_apps)
err = scd_applist (&cards, opt_cards);
else
err = scd_cardlist (&cards);
if (err)
goto leave;
print_card_list (fp, info, cards, 0);
}
else
{
if (cardsn)
{
int i, cardno;
err = scd_cardlist (&cards);
if (err)
goto leave;
/* Switch to the requested card. */
for (i=0; digitp (cardsn+i); i++)
;
if (i && i < 4 && !cardsn[i])
{ /* Looks like an index into the card list. */
cardno = atoi (cardsn);
for (count = 0, sl = cards; sl; sl = sl->next, count++)
if (count == cardno)
break;
if (!sl)
{
err = gpg_error (GPG_ERR_INV_INDEX);
goto leave;
}
}
else /* S/N of card specified. */
{
for (sl = cards; sl; sl = sl->next)
if (!ascii_strcasecmp (sl->d, cardsn))
break;
if (!sl)
{
err = gpg_error (GPG_ERR_INV_INDEX);
goto leave;
}
}
err = scd_switchcard (sl->d);
need_learn = 1;
}
else /* show app list. */
{
err = scd_applist (&cards, 1);
if (err)
goto leave;
}
if (appstr && *appstr)
{
/* Switch to the requested app. */
err = scd_switchapp (appstr);
if (err)
goto leave;
need_learn = 1;
}
if (need_learn)
err = scd_learn (info, opt_reread);
else
err = 0;
if (err)
;
else if (opt_info)
print_card_list (fp, info, cards, 1);
else
{
size_t snlen;
const char *s;
/* First get the list of active cards and check whether the
* current card is still in the list. If not the card has
* been removed. Note that during the listing the card
* remove state might also be detected but only if an access
* to the scdaemon is required; it is anyway better to test
* that before starting a listing. */
free_strlist (cards);
err = scd_cardlist (&cards);
if (err)
goto leave;
for (sl = cards; sl; sl = sl->next)
{
if (info && info->serialno)
{
s = strchr (sl->d, ' ');
if (s)
snlen = s - sl->d;
else
snlen = strlen (sl->d);
if (strlen (info->serialno) == snlen
&& !memcmp (info->serialno, sl->d, snlen))
break;
}
}
if (!sl)
{
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
goto leave;
}
list_card (info, opt_no_key_lookup, opt_shadow);
}
}
leave:
free_strlist (cards);
return err;
}
+
+/* The CHECKKEYS command. */
+static gpg_error_t
+cmd_checkkeys (card_info_t callerinfo, char *argstr)
+{
+ gpg_error_t err;
+ estream_t fp = opt.interactive? NULL : es_stdout;
+ strlist_t cards = NULL;
+ strlist_t sl;
+ int opt_ondisk;
+ int opt_delete_clear;
+ int opt_delete_protected;
+ int delete_count = 0;
+ struct card_info_s info_buffer = { 0 };
+ card_info_t info = &info_buffer;
+ key_info_t kinfo;
+
+
+ if (!callerinfo)
+ return print_help
+ ("CHECKKEYS [--ondisk] [--delete-clear-copy]\n\n"
+ "Print a list of keys on all inserted cards. With --ondisk only\n"
+ "keys are listed which also have a copy on disk. Missing shadow\n"
+ "keys are created. With --delete-clear, copies of keys also stored\n"
+ "on disk without any protection will be deleted.\n"
+ , 0);
+
+
+ opt_ondisk = has_leading_option (argstr, "--ondisk");
+ opt_delete_clear = has_leading_option (argstr, "--delete-clear-copy");
+ opt_delete_protected = has_leading_option (argstr, "--delete-protected-copy");
+ argstr = skip_options (argstr);
+
+ if (*argstr)
+ {
+ /* No args expected */
+ err = gpg_error (GPG_ERR_INV_ARG);
+ goto leave;
+ }
+
+ if (!callerinfo->serialno)
+ {
+ /* This is probably the first call We need to send a SERIALNO
+ * command to scdaemon so that our session knows all cards. */
+ err = scd_serialno (NULL, NULL);
+ if (err)
+ goto leave;
+ }
+
+ /* Get the list of all cards. */
+ err = scd_cardlist (&cards);
+ if (err)
+ goto leave;
+
+ /* Loop over all cards. We use our own info buffer here. */
+ for (sl = cards; sl; sl = sl->next)
+ {
+ err = scd_switchcard (sl->d);
+ if (err)
+ {
+ log_error ("Error switching to card %s: %s\n",
+ sl->d, gpg_strerror (err));
+ continue;
+ }
+ release_card_info (info);
+ err = scd_learn (info, 0);
+ if (err)
+ {
+ log_error ("Error getting infos from card %s: %s\n",
+ sl->d, gpg_strerror (err));
+ continue;
+ }
+
+ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
+ {
+ char *infostr;
+
+ err = scd_havekey_info (kinfo->grip, &infostr);
+ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+ {
+ /* Create a shadow key and try again. */
+ scd_readkey (kinfo->keyref, 1, NULL);
+ err = scd_havekey_info (kinfo->grip, &infostr);
+ }
+ if (err)
+ log_error ("Error getting infos for a key: %s\n",
+ gpg_strerror (err));
+
+ if (opt_ondisk && infostr && !strcmp (infostr, "shadowed"))
+ ; /* Don't print this one. */
+ else
+ {
+ tty_fprintf (fp, "%s %s ",
+ nullnone (info->serialno),
+ app_type_string (info->apptype));
+ print_keygrip (fp, kinfo->grip, 0);
+ tty_fprintf (fp, " %s %s\n",
+ kinfo->keyref, infostr? infostr: "error");
+ }
+ if (infostr
+ && ((opt_delete_clear && !strcmp (infostr, "clear"))
+ || (opt_delete_protected && !strcmp (infostr, "protected"))))
+ {
+ err = scd_delete_key (kinfo->grip, 0);
+ if (err)
+ log_error ("Error deleting a key copy: %s\n",
+ gpg_strerror (err));
+ else
+ delete_count++;
+ }
+ xfree (infostr);
+ }
+ }
+ if (delete_count)
+ log_info ("Number of deleted key copies: %d\n", delete_count);
+
+ err = 0;
+
+ leave:
+ release_card_info (info);
+ free_strlist (cards);
+ /* Better reset to the original card. */
+ scd_learn (callerinfo, 0);
+ return err;
+}
+
+
/* The VERIFY command. */
static gpg_error_t
cmd_verify (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
const char *pinref;
if (!info)
return print_help ("verify [chvid]", 0);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = info->serialno;
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
return gpg_error (GPG_ERR_MISSING_VALUE);
err = scd_checkpin (pinref);
if (err)
log_error ("verify failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
/* In any case update the CHV status, so that the next "list" shows
* the correct retry counter values. */
err2 = scd_getattr ("CHV-STATUS", info);
return err ? err : err2;
}
static gpg_error_t
cmd_authenticate (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_setkey;
int opt_raw;
char *string = NULL;
char *key = NULL;
size_t keylen;
if (!info)
return print_help
("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n"
"Perform a mutual authentication either by reading the key\n"
"from FILE or by taking it from the command line. Without\n"
"the option --raw the key is expected to be hex encoded.\n"
"To install a new administration key --setkey is used; this\n"
"requires a prior authentication with the old key.",
APP_TYPE_PIV, 0);
if (info->apptype != APP_TYPE_PIV)
{
log_info ("Note: This is a PIV only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
opt_setkey = has_leading_option (argstr, "--setkey");
opt_raw = has_leading_option (argstr, "--raw");
argstr = skip_options (argstr);
if (*argstr == '<') /* Read key from a file. */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &string, NULL);
if (err)
goto leave;
}
if (opt_raw)
{
key = string? string : xstrdup (argstr);
string = NULL;
keylen = strlen (key);
}
else
{
key = hex_to_buffer (string? string: argstr, &keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen);
leave:
if (key)
{
wipememory (key, keylen);
xfree (key);
}
xfree (string);
return err;
}
/* Helper for cmd_name to qyery a part of name. */
static char *
ask_one_name (const char *prompt)
{
char *name;
int i;
for (;;)
{
name = tty_get (prompt);
trim_spaces (name);
tty_kill_prompt ();
if (!*name || *name == CONTROL_D)
{
if (*name == CONTROL_D)
tty_fprintf (NULL, "\n");
xfree (name);
return NULL;
}
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
* to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
/* The NAME command. */
static gpg_error_t
cmd_name (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *surname, *givenname;
char *isoname, *p;
if (!info)
return print_help
("name [--clear]\n\n"
"Set the name field of an OpenPGP card. With --clear the stored\n"
"name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
again:
if (!strcmp (argstr, "--clear"))
isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
surname = ask_one_name (_("Cardholder's surname: "));
givenname = ask_one_name (_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return gpg_error (GPG_ERR_CANCELED);
}
isoname = xstrconcat (surname, "<<", givenname, NULL);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
log_info (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
goto again;
}
}
err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
xfree (isoname);
return err;
}
static gpg_error_t
cmd_url (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *url;
if (!info)
return print_help
("URL [--clear]\n\n"
"Set the URL data object. That data object can be used by\n"
"the FETCH command to retrieve the full public key. The\n"
"option --clear deletes the content of that data object.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!strcmp (argstr, "--clear"))
url = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
url = tty_get (_("URL to retrieve public key: "));
trim_spaces (url);
tty_kill_prompt ();
if (!*url || *url == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr ("PUBKEY-URL", url, strlen (url));
leave:
xfree (url);
return err;
}
/* Fetch the key from the URL given on the card or try to get it from
* the default keyserver. */
static gpg_error_t
cmd_fetch (card_info_t info)
{
gpg_error_t err;
key_info_t kinfo;
if (!info)
return print_help
("FETCH\n\n"
"Retrieve a key using the URL data object or if that is missing\n"
"using the fingerprint.", APP_TYPE_OPENPGP, 0);
if (info->pubkey_url && *info->pubkey_url)
{
/* strlist_t sl = NULL; */
/* add_to_strlist (&sl, info.pubkey_url); */
/* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
/* free_strlist (sl); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen)
{
/* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
/* opt.keyserver, 0); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else
err = gpg_error (GPG_ERR_NO_DATA);
return err;
}
static gpg_error_t
cmd_login (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data;
size_t datalen;
int use_default_pin;
if (!info)
return print_help
("LOGIN [--clear|--use-default-pin] [< FILE]\n\n"
"Set the login data object. If FILE is given the data is\n"
"is read from that file. This allows for binary data.\n"
"The option --clear deletes the login data. --use-default-pin\n"
"tells the card to always use the default PIN (\"123456\").",
APP_TYPE_OPENPGP, 0);
use_default_pin = has_leading_option (argstr, "--use-default-pin");
argstr = skip_options (argstr);
if (!strcmp (argstr, "--clear"))
{
data = xstrdup (" "); /* kludge. */
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else
{
data = tty_get (_("Login data (account name): "));
trim_spaces (data);
tty_kill_prompt ();
if ((!*data && !use_default_pin) || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
datalen = strlen (data);
}
if (use_default_pin)
{
char *tmpdata = xmalloc (datalen + 5);
memcpy (tmpdata, data, datalen);
memcpy (tmpdata+datalen, "\n\x14" "F=3", 5);
xfree (data);
data = tmpdata;
datalen += 5;
}
err = scd_setattr ("LOGIN-DATA", data, datalen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_lang (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data, *p;
if (!info)
return print_help
("LANG [--clear]\n\n"
"Change the language info for the card. This info can be used\n"
"by applications for a personalized greeting. Up to 4 two-digit\n"
"language identifiers can be entered as a preference. The option\n"
"--clear removes all identifiers. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
data = xstrdup (" "); /* Note that we need two spaces here. */
else
{
again:
data = tty_get (_("Language preferences: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (strlen (data) > 8 || (strlen (data) & 1))
{
log_info (_("Error: invalid length of preference string.\n"));
xfree (data);
goto again;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
log_info (_("Error: invalid characters in preference string.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-LANG", data, strlen (data));
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_salut (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *str;
if (!info)
return print_help
("SALUT [--clear]\n\n"
"Change the salutation info for the card. This info can be used\n"
"by applications for a personalized greeting. The option --clear\n"
"removes this data object. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
again:
if (!strcmp (argstr, "--clear"))
str = "9";
else
{
data = tty_get (_("Salutation (M = Mr., F = Ms., or space): "));
trim_spaces (data);
tty_kill_prompt ();
if (*data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
data = NULL;
goto again;
}
}
err = scd_setattr ("DISP-SEX", str, 1);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_cafpr (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *s;
int i, c;
unsigned char fpr[32];
int fprlen;
int fprno;
int opt_clear = 0;
if (!info)
return print_help
("CAFPR [--clear] N\n\n"
"Change the CA fingerprint number N. N must be in the\n"
"range 1 to 3. The option --clear clears the specified\n"
"CA fingerprint N or all of them if N is 0 or not given.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
fprno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
fprno = 0;
if (opt_clear && !fprno)
; /* Okay: clear all fprs. */
else if (fprno < 1 || fprno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
again:
if (opt_clear)
{
memset (fpr, 0, 20);
fprlen = 20;
}
else
{
xfree (data);
data = tty_get (_("CA fingerprint: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
for (i=0, s=data; i < sizeof fpr && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
if ((fprlen != 20 && fprlen != 32) || *s)
{
log_error (_("Error: invalid formatted fingerprint.\n"));
goto again;
}
}
if (!fprno)
{
log_assert (opt_clear);
err = scd_setattr ("CA-FPR-1", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-2", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-3", fpr, fprlen);
}
else
err = scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, fprlen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_privatedo (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *do_name = NULL;
char *data = NULL;
size_t datalen;
int do_no;
if (!info)
return print_help
("PRIVATEDO [--clear] N [< FILE]\n\n"
"Change the private data object N. N must be in the\n"
"range 1 to 4. If FILE is given the data is is read\n"
"from that file. The option --clear clears the data.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
do_no = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
do_no = 0;
if (do_no < 1 || do_no > 4)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_name = xasprintf ("PRIVATE-DO-%d", do_no);
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else if (*argstr)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
else
{
data = tty_get (_("Private DO data: "));
trim_spaces (data);
tty_kill_prompt ();
datalen = strlen (data);
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr (do_name, data, datalen);
leave:
xfree (do_name);
xfree (data);
return err;
}
static gpg_error_t
cmd_writecert (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
int opt_openpgp;
char *certref_buffer = NULL;
char *certref;
char *data = NULL;
size_t datalen;
estream_t key = NULL;
if (!info)
return print_help
("WRITECERT CERTREF '<' FILE\n"
"WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n"
"WRITECERT --clear CERTREF\n\n"
"Write a certificate to the card under the id CERTREF.\n"
"The option --clear removes the certificate from the card.\n"
"The option --openpgp expects an OpenPGP keyblock and stores\n"
"it encapsulated in a CMS container; the keyblock is taken\n"
"from FILE or directly from the OpenPGP key with FPR",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_clear = has_leading_option (argstr, "--clear");
opt_openpgp = has_leading_option (argstr, "--openpgp");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3"))
certref_buffer = xstrdup ("OPENPGP.3");
else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2"))
certref_buffer = xstrdup ("OPENPGP.2");
else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1"))
certref_buffer = xstrdup ("OPENPGP.1");
else
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be OPENPGP.N or just N"
" with N being 1..3\"");
goto leave;
}
certref = certref_buffer;
}
else /* Upcase the certref; prepend cardtype if needed. */
{
if (!strchr (certref, '.'))
certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
certref, NULL);
else
certref_buffer = xstrdup (certref);
ascii_strupr (certref_buffer);
certref = certref_buffer;
}
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----")
&& ascii_memistr (data, datalen, "-----END CERTIFICATE-----")
&& !memchr (data, 0, datalen) && !memchr (data, 1, datalen))
{
struct b64state b64;
err = b64dec_start (&b64, "");
if (!err)
err = b64dec_proc (&b64, data, datalen, &datalen);
if (!err)
err = b64dec_finish (&b64);
if (err)
goto leave;
}
}
else if (opt_openpgp && *argstr)
{
err = get_minimal_openpgp_key (&key, argstr);
if (err)
goto leave;
if (es_fclose_snatch (key, (void*)&data, &datalen))
{
err = gpg_error_from_syserror ();
goto leave;
}
key = NULL;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_openpgp && !opt_clear)
{
tlv_builder_t tb;
void *tmpder;
size_t tmpderlen;
tb = tlv_builder_new (0);
if (!tb)
{
err = gpg_error_from_syserror ();
goto leave;
}
tlv_builder_add_tag (tb, 0, TAG_SEQUENCE);
tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID,
"\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10);
tlv_builder_add_tag (tb, CLASS_CONTEXT, 0);
tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen);
tlv_builder_add_end (tb);
tlv_builder_add_end (tb);
err = tlv_builder_finalize (tb, &tmpder, &tmpderlen);
if (err)
goto leave;
xfree (data);
data = tmpder;
datalen = tmpderlen;
}
err = scd_writecert (certref, data, datalen);
leave:
es_fclose (key);
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_readcert (card_info_t info, char *argstr)
{
gpg_error_t err;
char *certref_buffer = NULL;
char *certref;
void *data = NULL;
size_t datalen, dataoff;
const char *fname;
int opt_openpgp;
if (!info)
return print_help
("READCERT [--openpgp] CERTREF > FILE\n\n"
"Read the certificate for key CERTREF and store it in FILE.\n"
"With option \"--openpgp\" an OpenPGP keyblock is expected\n"
"and stored in FILE.\n",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_openpgp = has_leading_option (argstr, "--openpgp");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3"))
certref_buffer = xstrdup ("OPENPGP.3");
else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2"))
certref_buffer = xstrdup ("OPENPGP.2");
else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1"))
certref_buffer = xstrdup ("OPENPGP.1");
else
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be OPENPGP.N or just N"
" with N being 1..3\"");
goto leave;
}
certref = certref_buffer;
}
if (*argstr == '>') /* Write it to a file */
{
for (argstr++; spacep (argstr); argstr++)
;
fname = argstr;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
dataoff = 0;
err = scd_readcert (certref, &data, &datalen);
if (err)
goto leave;
if (opt_openpgp)
{
/* Check whether DATA contains an OpenPGP keyblock and put only
* this into FILE. If the data is something different, return
* an error. */
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, cons, ndef;
p = data;
n = datalen;
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
goto not_openpgp; /* Does not start with a sequence. */
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons))
goto not_openpgp; /* No Object ID. */
if (objlen > n)
goto not_openpgp; /* Inconsistent lengths. */
if (objlen != 10
|| memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen))
goto not_openpgp; /* Wrong Object ID. */
p += objlen;
n -= objlen;
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_CONTEXT && tag == 0 && cons))
goto not_openpgp; /* Not a [0] context tag. */
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons))
goto not_openpgp; /* Not an octet string. */
if (objlen > n)
goto not_openpgp; /* Inconsistent lengths. */
dataoff = p - (const unsigned char*)data;
datalen = objlen;
}
err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen);
goto leave;
not_openpgp:
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_writekey (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_force;
const char *argv[2];
int argc;
char *keyref_buffer = NULL;
const char *keyref;
const char *keygrip;
if (!info)
return print_help
("WRITEKEY [--force] KEYREF KEYGRIP\n\n"
"Write a private key object identified by KEYGRIP to slot KEYREF.\n"
"Use --force to overwrite an existing key.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_force = has_leading_option (argstr, "--force");
argstr = skip_options (argstr);
argc = split_fields (argstr, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Upcase the keyref; prepend cardtype if needed. */
keyref = argv[0];
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
/* Get the keygrip. */
keygrip = argv[1];
if (strlen (keygrip) != 40
&& !(keygrip[0] == '&' && strlen (keygrip+1) == 40))
{
log_error (_("Not a valid keygrip (expecting 40 hex digits)\n"));
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writekey (keyref, opt_force, keygrip);
leave:
xfree (keyref_buffer);
return err;
}
static gpg_error_t
cmd_forcesig (card_info_t info)
{
gpg_error_t err;
int newstate;
if (!info)
return print_help
("FORCESIG\n\n"
"Toggle the forcesig flag of an OpenPGP card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
newstate = !info->chv1_cached;
err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (err)
goto leave;
/* Read it back to be sure we have the right toggle state the next
* time. */
err = scd_getattr ("CHV-STATUS", info);
leave:
return err;
}
/* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at
* FORCED_CHV1. */
static gpg_error_t
check_pin_for_key_operation (card_info_t info, int *forced_chv1)
{
gpg_error_t err = 0;
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
* don't get bothered with PIN queries for each self-signature. */
err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (err)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = -1; /* Not changed. */
goto leave;
}
}
/* Check the PIN now, so that we won't get asked later for each
* binding signature. */
err = scd_checkpin (info->serialno);
if (err)
log_error ("error checking the PIN: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Helper for cmd_generate_openpgp. */
static void
restore_forced_chv1 (int *forced_chv1)
{
gpg_error_t err;
/* Note the possible values stored at FORCED_CHV1:
* 0 - forcesig was not enabled.
* 1 - forcesig was enabled - enable it again.
* -1 - We have not changed anything. */
if (*forced_chv1 == 1)
{ /* Switch back to forced state. */
err = scd_setattr ("CHV-STATUS-1", "", 1);
if (err)
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = 0;
}
}
/* Ask whether existing keys shall be overwritten. With NULL used for
* KINFO it will ask for all keys, other wise for the given key. */
static gpg_error_t
ask_replace_keys (key_info_t kinfo)
{
gpg_error_t err;
char *answer;
tty_printf ("\n");
if (kinfo)
log_info (_("Note: key %s is already stored on the card!\n"),
kinfo->keyref);
else
log_info (_("Note: Keys are already stored on the card!\n"));
tty_printf ("\n");
if (kinfo)
answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref);
else
answer = tty_get (_("Replace existing keys? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
err = gpg_error (GPG_ERR_CANCELED);
else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
err = gpg_error (GPG_ERR_CANCELED);
else
err = 0;
xfree (answer);
return err;
}
/* Implementation of cmd_generate for OpenPGP cards to generate all
* standard keys at once. */
static gpg_error_t
generate_all_openpgp_card_keys (card_info_t info, char **algos)
{
gpg_error_t err;
int forced_chv1 = -1;
int want_backup;
char *answer = NULL;
key_info_t kinfo1, kinfo2, kinfo3;
if (info->extcap.ki)
{
xfree (answer);
answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else
want_backup = 0;
kinfo1 = find_kinfo (info, "OPENPGP.1");
kinfo2 = find_kinfo (info, "OPENPGP.2");
kinfo3 = find_kinfo (info, "OPENPGP.3");
if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen))
|| (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen))
|| (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen))
)
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
}
/* If no displayed name has been set, we assume that this is a fresh
* card and print a hint about the default PINs. */
if (!info->disp_name || !*info->disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
tty_printf ("\n");
}
err = check_pin_for_key_operation (info, &forced_chv1);
if (err)
goto leave;
(void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */
/* FIXME: We need to divert to a function which spawns gpg which
* will then create the key. This also requires new features in
* gpg. We might also first create the keys on the card and then
* tell gpg to use them to create the OpenPGP keyblock. */
/* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
(void)want_backup;
err = scd_genkey ("OPENPGP.1", 1, NULL, NULL);
leave:
restore_forced_chv1 (&forced_chv1);
xfree (answer);
return err;
}
/* Create a single key. This is a helper for cmd_generate. */
static gpg_error_t
generate_key (card_info_t info, const char *keyref, int force,
const char *algo)
{
gpg_error_t err;
key_info_t kinfo;
if (info->apptype == APP_TYPE_OPENPGP)
{
kinfo = find_kinfo (info, keyref);
if (!kinfo)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!force
&& kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen))
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
force = 1;
}
}
err = scd_genkey (keyref, force, algo, NULL);
leave:
return err;
}
static gpg_error_t
cmd_generate (card_info_t info, char *argstr)
{
static char * const valid_algos[] =
{ "rsa2048", "rsa3072", "rsa4096", "",
"nistp256", "nistp384", "nistp521", "",
"brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "",
"ed25519", "cv25519",
NULL
};
gpg_error_t err;
int opt_force;
char *p;
char **opt_algo = NULL; /* Malloced. */
char *keyref_buffer = NULL; /* Malloced. */
char *keyref; /* Points into argstr or keyref_buffer. */
int i, j;
if (!info)
return print_help
("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n"
"Create a new key on a card.\n"
"Use --force to overwrite an existing key.\n"
"Use \"help\" for ALGO to get a list of known algorithms.\n"
"For OpenPGP cards several algos may be given.\n"
"Note that the OpenPGP key generation is done interactively\n"
"unless a single ALGO or KEYREF are given.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
opt_force = has_leading_option (argstr, "--force");
err = get_option_value (argstr, "--algo", &p);
if (err)
goto leave;
if (p)
{
opt_algo = strtokenize (p, "+");
if (!opt_algo)
{
err = gpg_error_from_syserror ();
xfree (p);
goto leave;
}
xfree (p);
}
argstr = skip_options (argstr);
keyref = argstr;
if ((argstr = strchr (keyref, ' ')))
{
*argstr++ = 0;
trim_spaces (keyref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = keyref + strlen (keyref);
if (!*keyref)
keyref = NULL;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_algo)
{
/* opt_algo is an array of algos. */
for (i=0; opt_algo[i]; i++)
{
for (j=0; valid_algos[j]; j++)
if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i]))
break;
if (!valid_algos[j])
{
int lf = 1;
if (!ascii_strcasecmp (opt_algo[i], "help"))
log_info ("Known algorithms:\n");
else
{
log_info ("Invalid algorithm '%s' given. Use one of:\n",
opt_algo[i]);
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
for (i=0; valid_algos[i]; i++)
{
if (!*valid_algos[i])
lf = 1;
else if (lf)
{
lf = 0;
log_info (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
else
log_printf (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
log_printf ("\n");
show_keysize_warning ();
goto leave;
}
}
}
/* Upcase the keyref; if it misses the cardtype, prepend it. */
if (keyref)
{
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
}
/* Special checks. */
if ((info->cardtype && !strcmp (info->cardtype, "yubikey"))
&& info->cardversion >= 0x040200 && info->cardversion < 0x040305)
{
log_error ("On-chip key generation on this YubiKey has been blocked.\n");
log_info ("Please see <https://yubi.co/ysa201701> for details\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Divert to dedicated functions. */
if (info->apptype == APP_TYPE_OPENPGP
&& !keyref
&& (!opt_algo || (opt_algo[0] && opt_algo[1])))
{
/* With no algo requested or more than one algo requested and no
* keyref given we create all keys. */
if (opt_force || keyref)
log_info ("Note: OpenPGP key generation is interactive.\n");
err = generate_all_openpgp_card_keys (info, opt_algo);
}
else if (!keyref)
err = gpg_error (GPG_ERR_INV_ID);
else if (opt_algo && opt_algo[0] && opt_algo[1])
{
log_error ("only one algorithm expected as value for --algo.\n");
err = gpg_error (GPG_ERR_INV_ARG);
}
else
err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL);
if (!err)
{
err = scd_learn (info, 0);
if (err)
log_error ("Error re-reading card: %s\n", gpg_strerror (err));
}
leave:
xfree (opt_algo);
xfree (keyref_buffer);
return err;
}
/* Change a PIN. */
static gpg_error_t
cmd_passwd (card_info_t info, char *argstr)
{
gpg_error_t err = 0;
char *answer = NULL;
const char *pinref = NULL;
int reset_mode = 0;
int nullpin = 0;
int menu_used = 0;
if (!info)
return print_help
("PASSWD [--reset|--nullpin] [PINREF]\n\n"
"Change or unblock the PINs. Note that in interactive mode\n"
"and without a PINREF a menu is presented for certain cards;\n"
"in non-interactive and without a PINREF a default value is\n"
"used for these cards. The option --reset is used with TCOS\n"
"cards to reset the PIN using the PUK or vice versa; --nullpin\n"
"is used for these cards to set the initial PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (has_option (argstr, "--reset"))
reset_mode = 1;
else if (has_option (argstr, "--nullpin"))
nullpin = 1;
argstr = skip_options (argstr);
/* If --reset or --nullpin has been given we force non-interactive mode. */
if (*argstr || reset_mode || nullpin)
{
pinref = argstr;
if (!*pinref)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
}
else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - unblock and set new a PIN\n"
"3 - change the Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
if (strlen (answer) != 1)
continue;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "OPENPGP.1";
else if (*answer == '2')
{ pinref = "OPENPGP.1"; reset_mode = 1; }
else if (*answer == '3')
pinref = "OPENPGP.3";
else if (*answer == '4')
{ pinref = "OPENPGP.2"; reset_mode = 1; }
}
}
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = "OPENPGP.1";
else if (opt.interactive && info->apptype == APP_TYPE_PIV)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - change the PUK\n"
"3 - change the Global PIN\n"
"Q - quit\n");
if (strlen (answer) != 1)
;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "PIV.80";
else if (*answer == '2')
pinref = "PIV.81";
else if (*answer == '3')
pinref = "PIV.00";
}
}
else if (opt.interactive && info->apptype == APP_TYPE_NKS)
{
int for_qualified = 0;
menu_used = 1;
log_assert (DIM (info->chvinfo) >= 4);
/* If there is a qualified signature use a menu to select
* between standard PIN and QES PINs. */
if (info->chvinfo[2] != -2 || info->chvinfo[3] != -2)
{
for (;;)
{
xfree (answer);
answer = get_selection (" 1 - Standard PIN/PUK\n"
" 2 - PIN/PUK for qualified signature\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
break;
else if (!strcmp (answer, "2"))
{
for_qualified = 1;
break;
}
}
}
if (info->chvinfo[for_qualified? 2 : 0] == -4)
{
while (!pinref)
{
xfree (answer);
answer = get_selection
("The NullPIN is still active on this card.\n"
"You need to choose and set a PIN first.\n"
"\n"
" 1 - Set your PIN\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
nullpin = 1;
}
}
}
else
{
while (!pinref)
{
xfree (answer);
answer = get_selection (" 1 - change PIN\n"
" 2 - reset PIN\n"
" 3 - change PUK\n"
" 4 - reset PUK\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
}
else if (!strcmp (answer, "2"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
reset_mode = 1;
}
else if (!strcmp (answer, "3"))
{
pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH";
}
else if (!strcmp (answer, "4"))
{
pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH";
reset_mode = 1;
}
}
}
}
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = scd_change_pin (pinref, reset_mode, nullpin);
if (err)
{
if (!opt.interactive && !menu_used && !opt.verbose)
;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
&& gpg_err_source (err) == GPG_ERR_SOURCE_PINENTRY)
log_info ("%s\n", gpg_strerror (err));
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_error ("Error changing the PUK.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_error ("Error unblocking the PIN.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_error ("Error setting the Reset Code.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_error ("Error changing the Admin PIN.\n");
else if (reset_mode)
log_error ("Error resetting the PIN.\n");
else
log_error ("Error changing the PIN.\n");
}
else
{
if (!opt.interactive && !opt.verbose)
;
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_info ("PUK changed.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_info ("PIN unblocked and new PIN set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_info ("Reset Code set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_info ("Admin PIN changed.\n");
else if (reset_mode)
log_info ("PIN reset.\n");
else
log_info ("PIN changed.\n");
/* Update the CHV status. */
err = scd_getattr ("CHV-STATUS", info);
}
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_unblock (card_info_t info)
{
gpg_error_t err = 0;
if (!info)
return print_help
("UNBLOCK\n\n"
"Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n"
"cards prior to version 2 can't use this; instead the PASSWD\n"
"command can be used to set a new PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!info->is_v2)
{
log_error (_("This command is only available for version 2 cards\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (!info->chvinfo[1])
{
log_error (_("Reset Code not or not anymore available\n"));
err = gpg_error (GPG_ERR_NO_RESET_CODE);
}
else
{
err = scd_change_pin ("OPENPGP.2", 0, 0);
if (!err)
log_info ("PIN changed.\n");
}
}
else if (info->apptype == APP_TYPE_PIV)
{
/* Unblock the Application PIN. */
err = scd_change_pin ("PIV.80", 1, 0);
if (!err)
log_info ("PIN unblocked and changed.\n");
}
else
{
log_info ("Unblocking not supported for '%s'.\n",
app_type_string (info->apptype));
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return err;
}
/* Note: On successful execution a redisplay should be scheduled. If
* this function fails the card may be in an unknown state. */
static gpg_error_t
cmd_factoryreset (card_info_t info)
{
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int any_apdu = 0;
int is_yubikey = 0;
int locked = 0;
int i;
if (!info)
return print_help
("FACTORY-RESET\n\n"
"Do a complete reset of some OpenPGP and PIV cards. This\n"
"deletes all data and keys and resets the PINs to their default.\n"
"This is mainly used by developers with scratch cards. Don't\n"
"worry, you need to confirm before the command proceeds.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
/* We support the factory reset for most OpenPGP cards and Yubikeys
* with the PIV application. */
if (info->apptype == APP_TYPE_OPENPGP)
;
else if (info->apptype == APP_TYPE_PIV
&& info->cardtype && !strcmp (info->cardtype, "yubikey"))
is_yubikey = 1;
else
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* For an OpenPGP card the code below basically does the same what
* this gpg-connect-agent script does:
*
* scd reset
* scd serialno undefined
* scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 e6 00 00
* scd apdu 00 44 00 00
* scd reset
* /echo Card has been reset to factory defaults
*
* For a PIV application on a Yubikey it merely issues the Yubikey
* specific resset command.
*/
err = scd_learn (info, 0);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!termstate || is_yubikey)
{
if (!is_yubikey)
{
if (!(info->status_indicator == 3 || info->status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it
* is not possible to select a card application in
* termination state. */
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
}
tty_printf ("\n");
log_info
(_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
xfree (answer);
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
trim_spaces (answer);
if (*answer == CONTROL_D
|| !answer_is_yes_no_default (answer, 0/*(default to no)*/))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
xfree (answer);
answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
tty_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (is_yubikey)
{
/* If the PIV application is already selected, we only need to
* send the special reset APDU after having blocked PIN and
* PUK. Note that blocking the PUK is done using the
* unblock PIN command. */
any_apdu = 1;
for (i=0; i < 5; i++)
send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 5; i++)
send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"RESET RETRY COUNTER", 0xffff, NULL, NULL);
err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
if (err)
goto leave;
}
else /* OpenPGP card. */
{
any_apdu = 1;
/* We need to select a card application before we can send
* APDUs to the card without scdaemon doing anything on its
* own. We then lock the connection so that other tools
* (e.g. Kleopatra) don't try a new select. */
err = send_apdu ("lock", "locking connection ", 0, NULL, NULL);
if (err)
goto leave;
locked = 1;
err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
NULL, NULL);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
* counter to zero. We can't easily use the card version 2.1
* feature of presenting the admin PIN to allow the terminate
* command because there is no machinery in scdaemon to catch
* the verify command and ask for the PIN when the "APDU"
* command is used.
* Here, the length of dummy wrong PIN is 32-byte, also
* supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
if (err)
goto leave;
}
}
if (!is_yubikey)
{
any_apdu = 1;
/* Send activate datafile command. This is used without
* confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
if (err)
goto leave;
}
/* Finally we reset the card reader once more. */
if (locked)
err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL);
else
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
/* Then, connect the card again. */
err = scd_serialno (NULL, NULL);
if (!err)
info->need_sn_cmd = 0;
leave:
if (err && any_apdu && !is_yubikey)
{
log_info ("Due to an error the card might be in an inconsistent state\n"
"You should run the LIST command to check this.\n");
/* FIXME: We need a better solution in the case that the card is
* in a termination state, i.e. the card was removed before the
* activate was sent. The best solution I found with v2.1
* Zeitcontrol card was to kill scdaemon and the issue this
* sequence with gpg-connect-agent:
* scd reset
* scd serialno undefined
* scd apdu 00A4040006D27600012401 (returns error)
* scd apdu 00440000
* Then kill scdaemon again and issue:
* scd reset
* scd serialno openpgp
*/
}
if (locked)
send_apdu ("unlock", "unlocking connection ", 0, NULL, NULL);
xfree (answer);
return err;
}
/* Generate KDF data. This is a helper for cmd_kdfsetup. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
gpg_error_t err;
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
strlen (OPENPGP_USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
strlen (OPENPGP_ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
static gpg_error_t
cmd_kdfsetup (card_info_t info, char *argstr)
{
gpg_error_t err;
unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
int single = (*argstr != 0);
if (!info)
return print_help
("KDF-SETUP\n\n"
"Prepare the OpenPGP card KDF feature for this card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!info->extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave;
err = scd_setattr ("KDF", kdf_data,
single ? OPENPGP_KDF_DATA_LENGTH_MIN
/* */ : OPENPGP_KDF_DATA_LENGTH_MAX);
if (err)
goto leave;
err = scd_getattr ("KDF", info);
leave:
return err;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
static gpg_error_t
cmd_uif (card_info_t info, char *argstr)
{
gpg_error_t err;
int keyno;
char name[50];
unsigned char data[2];
char *answer = NULL;
int opt_yes;
if (!info)
return print_help
("UIF N [on|off|permanent]\n\n"
"Change the User Interaction Flag. N must in the range 1 to 3.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (!info->extcap.bt)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
opt_yes = has_leading_option (argstr, "--yes");
argstr = skip_options (argstr);
if (digitp (argstr))
{
keyno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
keyno = 0;
if (keyno < 1 || keyno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if ( !strcmp (argstr, "off") )
data[0] = 0x00;
else if ( !strcmp (argstr, "on") )
data[0] = 0x01;
else if ( !strcmp (argstr, "permanent") )
data[0] = 0x02;
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
data[1] = 0x20;
log_assert (keyno - 1 < DIM(info->uif));
if (info->uif[keyno-1] == 2)
{
log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"),
"permanent");
err = gpg_error (GPG_ERR_INV_STATE);
goto leave;
}
if (data[0] == 0x02)
{
if (opt.interactive)
{
tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n"
" can only be reverted using a factory reset!\n"
), "permanent");
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
err = gpg_error (GPG_ERR_CANCELED);
else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
err = gpg_error (GPG_ERR_CANCELED);
else
err = 0;
}
else if (!opt_yes)
{
log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n"
" can only be reverted using a factory reset!\n"
), "permanent");
log_info (_("Please use \"uif --yes %d %s\"\n"),
keyno, "permanent");
err = gpg_error (GPG_ERR_CANCELED);
}
else
err = 0;
if (err)
goto leave;
}
snprintf (name, sizeof name, "UIF-%d", keyno);
err = scd_setattr (name, data, 2);
if (!err) /* Read all UIF attributes again. */
err = scd_getattr ("UIF", info);
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_yubikey (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
estream_t fp = opt.interactive? NULL : es_stdout;
const char *words[20];
int nwords;
if (!info)
return print_help
("YUBIKEY <cmd> args\n\n"
"Various commands pertaining to Yubikey tokens with <cmd> being:\n"
"\n"
" LIST \n"
"\n"
"List supported and enabled applications.\n"
"\n"
" ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
" DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
"\n"
"Enable or disable the specified or all applications on the\n"
"given interface.",
0);
argstr = skip_options (argstr);
if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
{
log_info ("This command can only be used with Yubikeys.\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
nwords = split_fields (argstr, words, DIM (words));
if (nwords < 1)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Note that we always do a learn to get a chance to the card back
* into a usable state. */
err = yubikey_commands (info, fp, nwords, words);
err2 = scd_learn (info, 0);
if (err2)
log_error ("Error re-reading card: %s\n", gpg_strerror (err2));
leave:
return err;
}
static gpg_error_t
cmd_apdu (card_info_t info, char *argstr)
{
gpg_error_t err;
estream_t fp = opt.interactive? NULL : es_stdout;
int with_atr;
int handle_more;
const char *s;
const char *exlenstr;
int exlenstrlen;
char *options = NULL;
unsigned int sw;
unsigned char *result = NULL;
size_t i, j, resultlen;
if (!info)
return print_help
("APDU [--more] [--exlen[=N]] <hexstring>\n"
"\n"
"Send an APDU to the current card. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU.\n"
"\n"
"Using the option \"--more\" handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used.\n",
0);
if (has_option (argstr, "--dump-atr"))
with_atr = 2;
else
with_atr = has_option (argstr, "--atr");
handle_more = has_option (argstr, "--more");
exlenstr = has_option_name (argstr, "--exlen");
exlenstrlen = 0;
if (exlenstr)
{
for (s=exlenstr; *s && !spacep (s); s++)
exlenstrlen++;
}
argstr = skip_options (argstr);
if (with_atr || handle_more || exlenstr)
options = xasprintf ("%s%s%s%.*s",
with_atr == 2? " --dump-atr":
with_atr? " --data-atr":"",
handle_more?" --more":"",
exlenstr?" --exlen=":"",
exlenstrlen, exlenstr?exlenstr:"");
err = scd_apdu (argstr, options, &sw, &result, &resultlen);
if (err)
goto leave;
if (!with_atr)
{
if (opt.interactive || opt.verbose)
{
char *p = scd_apdu_strerror (sw);
log_info ("Statusword: 0x%04x (%s)\n", sw, p? p: "?");
xfree (p);
}
else
log_info ("Statusword: 0x%04x\n", sw);
}
for (i=0; i < resultlen; )
{
size_t save_i = i;
tty_fprintf (fp, "D[%04X] ", (unsigned int)i);
for (j=0; j < 16 ; j++, i++)
{
if (j == 8)
tty_fprintf (fp, " ");
if (i < resultlen)
tty_fprintf (fp, " %02X", result[i]);
else
tty_fprintf (fp, " ");
}
tty_fprintf (fp, " ");
i = save_i;
for (j=0; j < 16; j++, i++)
{
unsigned int c = result[i];
if ( i >= resultlen )
tty_fprintf (fp, " ");
else if (isascii (c) && isprint (c) && !iscntrl (c))
tty_fprintf (fp, "%c", c);
else
tty_fprintf (fp, ".");
}
tty_fprintf (fp, "\n");
}
leave:
xfree (result);
xfree (options);
return err;
}
static gpg_error_t
cmd_gpg (card_info_t info, char *argstr, int use_gpgsm)
{
gpg_error_t err;
char **argarray;
ccparray_t ccp;
const char **argv = NULL;
pid_t pid;
int i;
if (!info)
return print_help
("GPG[SM] <commands_and_options>\n"
"\n"
"Run gpg/gpgsm directly from this shell.\n",
0);
/* Fixme: We need to write and use a version of strtokenize which
* takes care of shell-style quoting. */
argarray = strtokenize (argstr, " \t\n\v");
if (!argarray)
{
err = gpg_error_from_syserror ();
goto leave;
}
ccparray_init (&ccp, 0);
for (i=0; argarray[i]; i++)
ccparray_put (&ccp, argarray[i]);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_spawn_process (use_gpgsm? opt.gpgsm_program:opt.gpg_program,
argv, NULL, (GNUPG_SPAWN_KEEP_STDOUT
|GNUPG_SPAWN_KEEP_STDERR),
NULL, NULL, NULL, &pid);
if (!err)
{
err = gnupg_wait_process (use_gpgsm? opt.gpgsm_program:opt.gpg_program,
pid, 1, NULL);
gnupg_release_process (pid);
}
leave:
xfree (argv);
xfree (argarray);
return err;
}
static gpg_error_t
cmd_history (card_info_t info, char *argstr)
{
int opt_list, opt_clear;
opt_list = has_option (argstr, "--list");
opt_clear = has_option (argstr, "--clear");
if (!info || !(opt_list || opt_clear))
return print_help
("HISTORY --list\n"
" List the command history\n"
"HISTORY --clear\n"
" Clear the command history",
0);
if (opt_list)
tty_printf ("Sorry, history listing not yet possible\n");
if (opt_clear)
tty_read_history (NULL, 0);
return 0;
}
/* Data used by the command parser. This needs to be outside of the
* function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY,
+ cmdCHECKKEYS,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
const char *desc;
} cmds[] = {
{ "quit" , cmdQUIT, N_("quit this menu")},
{ "q" , cmdQUIT, NULL },
{ "bye" , cmdQUIT, NULL },
{ "help" , cmdHELP, N_("show this help")},
{ "?" , cmdHELP, NULL },
{ "list" , cmdLIST, N_("list all available data")},
{ "l" , cmdLIST, NULL },
{ "name" , cmdNAME, N_("change card holder's name")},
{ "url" , cmdURL, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN, N_("change the login name")},
{ "lang" , cmdLANG, N_("change the language preferences")},
{ "salutation",cmdSALUT, N_("change card holder's salutation")},
{ "salut" , cmdSALUT, NULL },
{ "cafpr" , cmdCAFPR , N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, N_("generate new keys")},
{ "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")},
{ "authenticate",cmdAUTH, N_("authenticate to the card")},
{ "auth" , cmdAUTH, NULL },
{ "reset" , cmdRESET, N_("send a reset to the card daemon")},
{ "factory-reset",cmdFACTRST, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")},
{ "uif", cmdUIF, N_("change the User Interaction Flag")},
{ "privatedo", cmdPRIVATEDO, N_("change a private data object")},
{ "readcert", cmdREADCERT, N_("read a certificate from a data object")},
{ "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
+ { "checkkeys", cmdCHECKKEYS, N_("run various checks on the keys")},
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
{ "gpg", cmdGPG, NULL},
{ "gpgsm", cmdGPGSM, NULL},
{ "apdu", cmdAPDU, NULL},
{ "history", cmdHISTORY, N_("manage the command history")},
{ NULL, cmdINVCMD, NULL }
};
/* The command line command dispatcher. */
static gpg_error_t
dispatch_command (card_info_t info, const char *orig_command)
{
gpg_error_t err = 0;
enum cmdids cmd; /* The command. */
char *command; /* A malloced copy of ORIG_COMMAND. */
char *argstr; /* The argument as a string. */
int i;
int ignore_error;
if ((ignore_error = *orig_command == '-'))
orig_command++;
command = xstrdup (orig_command);
argstr = NULL;
if ((argstr = strchr (command, ' ')))
{
*argstr++ = 0;
trim_spaces (command);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (command, cmds[i].name ))
break;
cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
/* Make sure we have valid strings for the args. They are allowed
* to be modified and must thus point to a buffer. */
if (!argstr)
argstr = command + strlen (command);
/* For most commands we need to make sure that we have a card. */
if (!info)
; /* Help mode */
else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)
&& !info->initialized)
{
err = scd_learn (info, 0);
if (err)
{
err = fixup_scd_errors (err);
log_error ("Error reading card: %s\n", gpg_strerror (err));
goto leave;
}
}
if (info)
info->card_removed = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Stop processing.", 0);
else
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
dispatch_command (NULL, argstr);
else
{
es_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
es_printf ("Prefix a command with a dash to ignore its error.\n");
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL, NULL);
if (!err)
info->need_sn_cmd = 1;
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY: err = cmd_verify (info, argstr); break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */
+ case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
case cmdINVCMD:
default:
log_error (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
leave:
/* Return GPG_ERR_EOF only if its origin was "quit". */
es_fflush (es_stdout);
if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
err = gpg_error (GPG_ERR_GENERAL);
if (!err && info && info->card_removed)
{
info->card_removed = 0;
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
err = fixup_scd_errors (err);
if (ignore_error)
{
log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
err = 0;
}
else
{
log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
info->need_sn_cmd = 1;
}
}
xfree (command);
return err;
}
/* The interactive main loop. */
static void
interactive_loop (void)
{
gpg_error_t err;
char *answer = NULL; /* The input line. */
enum cmdids cmd = cmdNOP; /* The command. */
char *argstr; /* The argument as a string. */
int redisplay = 1; /* Whether to redisplay the main info. */
char *help_arg = NULL; /* Argument of the HELP command. */
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
char *p;
int i;
char *historyname = NULL;
/* In the interactive mode we do not want to print the program prefix. */
log_set_prefix (NULL, 0);
if (!opt.no_history)
{
historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL);
if (tty_read_history (historyname, 500))
log_info ("error reading '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
}
for (;;)
{
if (help_arg)
{
/* Clear info to indicate helpmode */
info = NULL;
}
else if (!info)
{
/* Get out of help. */
info = &info_buffer;
help_arg = NULL;
redisplay = 0;
}
else if (redisplay)
{
err = cmd_list (info, "");
if (err)
{
err = fixup_scd_errors (err);
log_error ("Error reading card: %s\n", gpg_strerror (err));
}
else
{
tty_printf("\n");
redisplay = 0;
}
}
if (!info)
{
/* Copy the pending help arg into our answer. Note that
* help_arg points into answer. */
p = xstrdup (help_arg);
help_arg = NULL;
xfree (answer);
answer = p;
}
else
{
do
{
xfree (answer);
tty_enable_completion (command_completion);
answer = tty_get (_("gpg/card> "));
tty_kill_prompt();
tty_disable_completion ();
trim_spaces(answer);
}
while ( *answer == '#' );
}
argstr = NULL;
if (!*answer)
cmd = cmdLIST; /* We default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((argstr = strchr (answer,' ')))
{
*argstr++ = 0;
trim_spaces (answer);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
}
/* Make sure we have valid strings for the args. They are
* allowed to be modified and must thus point to a buffer. */
if (!argstr)
argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdHISTORY || cmd == cmdINVCMD))
{
/* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */
if (!info)
; /* In help mode. */
else if (redisplay)
{
cmd = cmdLIST;
}
else if (!info->serialno)
{
/* Without a serial number most commands won't work.
* Catch it here. */
if (cmd == cmdRESET || cmd == cmdLIST)
info->need_sn_cmd = 1;
else
{
tty_printf ("\n");
tty_printf ("Serial number missing\n");
continue;
}
}
}
if (info)
info->card_removed = 0;
err = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Leave this tool.", 0);
else
{
tty_printf ("\n");
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
help_arg = argstr; /* Trigger help for a command. */
else
{
tty_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL, NULL);
if (!err)
info->need_sn_cmd = 1;
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY:
err = cmd_verify (info, argstr);
if (!err)
redisplay = 1;
break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST:
err = cmd_factoryreset (info);
if (!err)
redisplay = 1;
break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
case cmdHISTORY: err = cmd_history (info, argstr); break;
+ case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
if (!err && info && info->card_removed)
{
info->card_removed = 0;
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
}
if (gpg_err_code (err) == GPG_ERR_CANCELED)
tty_fprintf (NULL, "\n");
else if (err)
{
const char *s = "?";
for (i=0; cmds[i].name; i++ )
if (cmd == cmds[i].id)
{
s = cmds[i].name;
break;
}
err = fixup_scd_errors (err);
log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
info->need_sn_cmd = 1;
}
} /* End of main menu loop. */
leave:
if (historyname && tty_write_history (historyname))
log_info ("error writing '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
release_card_info (info);
xfree (historyname);
xfree (answer);
}
#ifdef HAVE_LIBREADLINE
/* Helper function for readline's command completion. */
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
* saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen(text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text. */
if (cmds[list_index++].desc && !strncmp (name, text, len))
return strdup(name);
}
return NULL;
}
/* Second helper function for readline's command completion. */
static char **
command_completion (const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
* If not, just do nothing for now. The support for help completion
* needs to be more smarter. */
if (!start)
return rl_completion_matches (text, command_generator);
else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5))
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
diff --git a/tools/gpg-card.h b/tools/gpg-card.h
index 86b63cda0..5b49ef31e 100644
--- a/tools/gpg-card.h
+++ b/tools/gpg-card.h
@@ -1,260 +1,262 @@
/* gpg-card.h - Common definitions for the gpg-card-tool
* Copyright (C) 2019, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file 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 3 of the License, or
* (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef GNUPG_GPG_CARD_H
#define GNUPG_GPG_CARD_H
#include "../common/session-env.h"
#include "../common/strlist.h"
/* We keep all global options in the structure OPT. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
int interactive;
int verbose;
unsigned int debug;
int quiet;
int with_colons;
const char *gpg_program;
const char *gpgsm_program;
const char *agent_program;
int autostart;
int no_key_lookup; /* Assume --no-key-lookup for "list". */
int no_history; /* Do not use the command line history. */
/* Options passed to the gpg-agent: */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
} opt;
/* Debug values and macros. */
#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */
#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* The maximum length of a binary fingerprint. */
#define MAX_FINGERPRINT_LEN 32
/*
* Data structures to store keyblocks (aka certificates).
*/
struct pubkey_s
{
struct pubkey_s *next; /* The next key. */
unsigned char grip[KEYGRIP_LEN];
unsigned char fpr[MAX_FINGERPRINT_LEN];
unsigned char fprlen; /* The used length of a FPR. */
time_t created; /* The creation date of the key. */
unsigned int grip_valid:1;/* The grip is valid. */
unsigned int requested: 1;/* This is the requested grip. */
};
typedef struct pubkey_s *pubkey_t;
struct userid_s
{
struct userid_s *next;
char *value; /* Malloced. */
};
typedef struct userid_s *userid_t;
struct keyblock_s
{
struct keyblock_s *next; /* Allow to link several keyblocks. */
int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */
pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */
userid_t uids; /* The list of user ids. */
};
typedef struct keyblock_s *keyblock_t;
/* Enumeration of the known card application types. */
typedef enum
{
APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */
APP_TYPE_OPENPGP,
APP_TYPE_NKS,
APP_TYPE_DINSIG,
APP_TYPE_P15,
APP_TYPE_GELDKARTE,
APP_TYPE_SC_HSM,
APP_TYPE_PIV,
APP_TYPE_UNKNOWN /* Unknown by this tool. */
} app_type_t;
/* An object to store information pertaining to a key pair as stored
* on a card. This is commonly used as a linked list with all keys
* known for the current card. */
struct key_info_s
{
struct key_info_s *next;
unsigned char grip[20];/* The keygrip. */
unsigned char xflag; /* Temporary flag to help processing a list. */
/* OpenPGP card and possible other cards keyalgo string (an atom)
* and the id of the algorithm. */
const char *keyalgo;
enum gcry_pk_algos keyalgo_id;
/* An optional malloced label for the key. */
char *label;
/* The three next items are mostly useful for OpenPGP cards. */
unsigned char fprlen; /* Use length of the next item. */
unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */
u32 created; /* The time the key was created. */
unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */
char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */
};
typedef struct key_info_s *key_info_t;
/*
* The object used to store information about a card.
*/
struct card_info_s
{
int initialized; /* True if a learn command was successful. */
int need_sn_cmd; /* The SERIALNO command needs to be issued. */
int card_removed; /* Helper flag set by some listing functions. */
int error; /* private. */
char *reader; /* Reader information. */
char *cardtype; /* NULL or type of the card. */
unsigned int cardversion; /* Firmware version of the card. */
char *apptypestr; /* Malloced application type string. */
app_type_t apptype;/* Translated from APPTYPESTR. */
unsigned int appversion; /* Version of the application. */
unsigned int manufacturer_id;
char *manufacturer_name; /* malloced. */
char *serialno; /* malloced hex string. */
char *dispserialno;/* malloced string. */
char *disp_name; /* malloced. */
char *disp_lang; /* malloced. */
int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
char *pubkey_url; /* malloced. */
char *login_data; /* malloced. */
char *private_do[4]; /* malloced. */
char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
char cafpr2len;
char cafpr3len;
char cafpr1[20];
char cafpr2[20];
char cafpr3[20];
key_info_t kinfo; /* Linked list with all keypair related data. */
unsigned long sig_counter;
int chv1_cached; /* For openpgp this is true if a PIN is not
required for each signing. Note that the
gpg-agent might cache it anyway. */
int is_v2; /* True if this is a v2 openpgp card. */
byte nchvmaxlen; /* Number of valid items in CHVMAXLEN. */
int chvmaxlen[4]; /* Maximum allowed length of a CHV. */
byte nchvinfo; /* Number of valid items in CHVINFO. */
int chvinfo[4]; /* Allowed retries for the CHV; 0 = blocked. */
char *chvlabels; /* Malloced String with CHV labels. */
unsigned char chvusage[2]; /* Data object 5F2F */
struct {
unsigned int ki:1; /* Key import available. */
unsigned int aac:1; /* Algorithm attributes are changeable. */
unsigned int kdf:1; /* KDF object to support PIN hashing available. */
unsigned int bt:1; /* Button for confirmation available. */
unsigned int sm:1; /* Secure messaging available. */
unsigned int smalgo:15;/* Secure messaging cipher algorithm. */
unsigned int private_dos:1;/* Support fpr private use DOs. */
unsigned int mcl3:16; /* Max. length for a OpenPGP card cert.3 */
} extcap;
unsigned int status_indicator;
int kdf_do_enabled; /* True if card has a KDF object. */
int uif[3]; /* True if User Interaction Flag is on. */
/* 1 = on, 2 = permanent on. */
strlist_t supported_keyalgo[3];
};
typedef struct card_info_s *card_info_t;
/*-- card-keys.c --*/
void release_keyblock (keyblock_t keyblock);
void flush_keyblock_cache (void);
gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol,
keyblock_t *r_keyblock);
gpg_error_t test_get_matching_keys (const char *hexgrip);
gpg_error_t get_minimal_openpgp_key (estream_t *r_key, const char *fingerprint);
/*-- card-misc.c --*/
key_info_t find_kinfo (card_info_t info, const char *keyref);
void *hex_to_buffer (const char *string, size_t *r_length);
gpg_error_t send_apdu (const char *hexapdu, const char *desc,
unsigned int ignore,
unsigned char **r_data, size_t *r_datalen);
/*-- card-call-scd.c --*/
void release_card_info (card_info_t info);
const char *app_type_string (app_type_t app_type);
gpg_error_t scd_apdu (const char *hexapdu, const char *options,
unsigned int *r_sw,
unsigned char **r_data, size_t *r_datalen);
gpg_error_t scd_switchcard (const char *serialno);
gpg_error_t scd_switchapp (const char *appname);
gpg_error_t scd_learn (card_info_t info, int reread);
gpg_error_t scd_getattr (const char *name, struct card_info_s *info);
gpg_error_t scd_setattr (const char *name,
const unsigned char *value, size_t valuelen);
gpg_error_t scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen);
gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip);
gpg_error_t scd_genkey (const char *keyref, int force, const char *algo,
u32 *createtime);
gpg_error_t scd_serialno (char **r_serialno, const char *demand);
gpg_error_t scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen);
gpg_error_t scd_readkey (const char *keyrefstr, int create_shadow,
gcry_sexp_t *r_result);
gpg_error_t scd_cardlist (strlist_t *result);
gpg_error_t scd_applist (strlist_t *result, int all);
gpg_error_t scd_change_pin (const char *pinref, int reset_mode, int nullpin);
gpg_error_t scd_checkpin (const char *serialno);
+gpg_error_t scd_havekey_info (const unsigned char *grip, char **r_result);
+gpg_error_t scd_delete_key (const unsigned char *grip, int force);
unsigned long agent_get_s2k_count (void);
char *scd_apdu_strerror (unsigned int sw);
/*-- card-yubikey.c --*/
gpg_error_t yubikey_commands (card_info_t info,
estream_t fp, int argc, const char *argv[]);
#endif /*GNUPG_GPG_CARD_H*/

File Metadata

Mime Type
text/x-diff
Expires
Sat, Dec 6, 10:41 PM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
6b/2d/48d8f99ae675776d1cf93139e104

Event Timeline