diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index 0b2b98212..21beb29c7 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,3231 +1,3243 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2000-2016 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# ifdef HAVE_WINSOCK2_H
# include
# endif
# include
# include
#else /*!HAVE_W32_SYSTEM*/
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#define GNUPG_COMMON_NEED_AFLOCAL
#include "agent.h"
#include /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/asshelp.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
aUseStandardSocketP,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugQuickRandom,
oDebugPinentry,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oGrab,
oNoGrab,
oLogFile,
oServer,
oDaemon,
oSupervised,
oBatch,
oPinentryProgram,
oPinentryTouchFile,
oPinentryInvisibleChar,
oPinentryTimeout,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oScdaemonProgram,
oDefCacheTTL,
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oMaxPassphraseDays,
oEnablePassphraseHistory,
oEnableExtendedKeyFormat,
oUseStandardSocket,
oNoUseStandardSocket,
oExtraSocket,
oBrowserSocket,
oFakedSystemTime,
oIgnoreCacheForSigning,
oAllowMarkTrusted,
oNoAllowMarkTrusted,
oAllowPresetPassphrase,
oAllowLoopbackPinentry,
oNoAllowLoopbackPinentry,
oNoAllowExternalCache,
oAllowEmacsPinentry,
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
oSSHFingerprintDigest,
oPuttySupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oS2KCount,
+ oAutoExpandSecmem,
oWriteEnvFile
};
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")),
#endif
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oGrab, "grab", "@"),
/* FIXME: Add the below string for 2.3 */
/* N_("let PIN-Entry grab keyboard and mouse")), */
ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
/* */ N_("|PGM|use PGM as the PIN-Entry program")),
ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"),
ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
/* */ N_("|PGM|use PGM as the SCdaemon program") ),
ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
/* */ N_("do not use the SCdaemon") ),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oExtraSocket, "extra-socket",
/* */ N_("|NAME|accept some commands via NAME")),
ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_n (oKeepTTY, "keep-tty",
/* */ N_("ignore requests to change the TTY")),
ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
/* */ N_("ignore requests to change the X display")),
ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
N_("|N|expire cached PINs after N seconds")),
ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ),
ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ),
ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ),
ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
/* */ "@"),
ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"),
ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"),
ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"),
ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"),
ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"),
ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
/* */ N_("do not use the PIN cache when signing")),
ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
/* */ N_("disallow the use of an external password cache")),
ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
/* */ N_("disallow clients to mark keys as \"trusted\"")),
ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
/* */ N_("allow presetting passphrase")),
ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
N_("disallow caller to override the pinentry")),
ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
/* */ N_("allow passphrase to be prompted through Emacs")),
ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
N_("|ALGO|use ALGO to show ssh fingerprints")),
ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable putty support")
#else
/* */ "@"
#endif
),
ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"),
ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
+ ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
+
/* Dummy options for backward compatibility. */
ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
#define MAX_PASSPHRASE_DAYS (0)
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms except for
* Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
#else
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
#endif
/* Flag indicating that the ssh-agent subsystem has been enabled. */
static int ssh_support;
#ifdef HAVE_W32_SYSTEM
/* Flag indicating that support for Putty has been enabled. */
static int putty_support;
/* A magic value used with WM_COPYDATA. */
#define PUTTY_IPC_MAGIC 0x804e50ba
/* To avoid surprises we limit the size of the mapped IPC file to this
value. Putty currently (0.62) uses 8k, thus 16k should be enough
for the foreseeable future. */
#define PUTTY_IPC_MAXLEN 16384
#endif /*HAVE_W32_SYSTEM*/
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flags to indicate that check_own_socket shall not be called. */
static int disable_check_own_socket;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Name of the communication socket used for native gpg-agent
requests. The second variable is either NULL or a malloced string
with the real socket name in case it has been redirected. */
static char *socket_name;
static char *redir_socket_name;
/* Name of the optional extra socket used for native gpg-agent requests. */
static char *socket_name_extra;
static char *redir_socket_name_extra;
/* Name of the optional browser socket used for native gpg-agent requests. */
static char *socket_name_browser;
static char *redir_socket_name_browser;
/* Name of the communication socket used for ssh-agent protocol. */
static char *socket_name_ssh;
static char *redir_socket_name_ssh;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
static assuan_sock_nonce_t socket_nonce_extra;
static assuan_sock_nonce_t socket_nonce_browser;
static assuan_sock_nonce_t socket_nonce_ssh;
/* Default values for options passed to the pinentry. */
static char *default_display;
static char *default_ttyname;
static char *default_ttytype;
static char *default_lc_ctype;
static char *default_lc_messages;
static char *default_xauthority;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Helper to implement --debug-level */
static const char *debug_level;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* The handle_tick() function may test whether a parent is still
* running. We record the PID of the parent here or -1 if it should
* be watched. */
static pid_t parent_pid = (pid_t)(-1);
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how gpg-agent was started, the homedir inotify watch
* may not be reliable. This flag is set if we assume that inotify
* works reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void agent_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void agent_init_default_ctrl (ctrl_t ctrl);
static void agent_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh);
static void check_own_socket (void);
static int check_for_running_agent (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
This function is expected to be called only once. GETFNC is
expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
/* Return strings describing this program. The case values are
described in common/argparse.c:strusage. The values here override
the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@GPG_AGENT@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
only the active debug flags are propagated to the subsystems. With
DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
all flags already set. Note that we don't fail here, because it is
important to keep gpg-agent running even after re-reading the
options due to a SIGHUP. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
the corresponding real name if the socket has been redirected. */
static void
remove_socket (char *name, char *redir_name)
{
if (name && *name)
{
if (redir_name)
name = redir_name;
gnupg_remove (name);
*name = 0;
}
}
/* Discover which inherited file descriptors correspond to which
* services/sockets offered by gpg-agent, using the LISTEN_FDS and
* LISTEN_FDNAMES convention. The understood labels are "ssh",
* "extra", and "browser". "std" or other labels will be interpreted
* as the standard socket.
*
* This function is designed to log errors when the expected file
* descriptors don't make sense, but to do its best to continue to
* work even in the face of minor misconfigurations.
*
* For more information on the LISTEN_FDS convention, see
* sd_listen_fds(3) on certain Linux distributions.
*/
#ifndef HAVE_W32_SYSTEM
static void
map_supervised_sockets (gnupg_fd_t *r_fd,
gnupg_fd_t *r_fd_extra,
gnupg_fd_t *r_fd_browser,
gnupg_fd_t *r_fd_ssh)
{
struct {
const char *label;
int **fdaddr;
char **nameaddr;
} tbl[] = {
{ "ssh", &r_fd_ssh, &socket_name_ssh },
{ "browser", &r_fd_browser, &socket_name_browser },
{ "extra", &r_fd_extra, &socket_name_extra },
{ "std", &r_fd, &socket_name } /* (Must be the last item.) */
};
const char *envvar;
char **fdnames;
int nfdnames;
int fd_count;
*r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
/* Print a warning if LISTEN_PID does not match outr pid. */
envvar = getenv ("LISTEN_PID");
if (!envvar)
log_error ("no LISTEN_PID environment variable found in "
"--supervised mode (ignoring)\n");
else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
log_error ("environment variable LISTEN_PID (%lu) does not match"
" our pid (%lu) in --supervised mode (ignoring)\n",
(unsigned long)strtoul (envvar, NULL, 10),
(unsigned long)getpid ());
/* Parse LISTEN_FDNAMES into the array FDNAMES. */
envvar = getenv ("LISTEN_FDNAMES");
if (envvar)
{
fdnames = strtokenize (envvar, ":");
if (!fdnames)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
agent_exit (1);
}
for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
;
}
else
{
fdnames = NULL;
nfdnames = 0;
}
/* Parse LISTEN_FDS into fd_count or provide a replacement. */
envvar = getenv ("LISTEN_FDS");
if (envvar)
fd_count = atoi (envvar);
else if (fdnames)
{
log_error ("no LISTEN_FDS environment variable found in --supervised"
" mode (relying on LISTEN_FDNAMES instead)\n");
fd_count = nfdnames;
}
else
{
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
"found in --supervised mode"
" (assuming 1 active descriptor)\n");
fd_count = 1;
}
if (fd_count < 1)
{
log_error ("--supervised mode expects at least one file descriptor"
" (was told %d, carrying on as though it were 1)\n",
fd_count);
fd_count = 1;
}
/* Assign the descriptors to the return values. */
if (!fdnames)
{
struct stat statbuf;
if (fd_count != 1)
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
" in --supervised mode."
" (ignoring all sockets but the first one)\n",
fd_count);
if (fstat (3, &statbuf) == -1 && errno ==EBADF)
log_fatal ("file descriptor 3 must be valid in --supervised mode"
" if LISTEN_FDNAMES is not set\n");
*r_fd = 3;
socket_name = gnupg_get_socket_name (3);
}
else if (fd_count != nfdnames)
{
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
"LISTEN_FDS (%d) in --supervised mode\n",
nfdnames, fd_count);
}
else
{
int i, j, fd;
char *name;
for (i = 0; i < nfdnames; i++)
{
for (j = 0; j < DIM (tbl); j++)
{
if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
{
fd = 3 + i;
if (**tbl[j].fdaddr == -1)
{
name = gnupg_get_socket_name (fd);
if (name)
{
**tbl[j].fdaddr = fd;
*tbl[j].nameaddr = name;
log_info ("using fd %d for %s socket (%s)\n",
fd, tbl[j].label, name);
}
else
{
log_error ("cannot listen on fd %d for %s socket\n",
fd, tbl[j].label);
close (fd);
}
}
else
{
log_error ("cannot listen on more than one %s socket\n",
tbl[j].label);
close (fd);
}
break;
}
}
}
}
xfree (fdnames);
}
#endif /*!HAVE_W32_SYSTEM*/
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
deinitialize_module_cache ();
if (!is_supervised && !inhibit_socket_removal)
{
remove_socket (socket_name, redir_socket_name);
if (opt.extra_socket > 1)
remove_socket (socket_name_extra, redir_socket_name_extra);
if (opt.browser_socket > 1)
remove_socket (socket_name_browser, redir_socket_name_browser);
remove_socket (socket_name_ssh, redir_socket_name_ssh);
}
}
/* Handle options which are allowed to be reset after program start.
Return true when the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
int i;
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.no_grab = 1;
opt.debug_pinentry = 0;
opt.pinentry_program = NULL;
opt.pinentry_touch_file = NULL;
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = NULL;
opt.pinentry_timeout = 0;
opt.scdaemon_program = NULL;
opt.def_cache_ttl = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
opt.enable_passphrase_history = 0;
opt.enable_extended_key_format = 0;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 1;
opt.allow_external_cache = 1;
opt.allow_loopback_pinentry = 1;
opt.allow_emacs_pinentry = 0;
opt.disable_scdaemon = 0;
disable_check_own_socket = 0;
/* Note: When changing the next line, change also gpgconf_list. */
opt.ssh_fingerprint_digest = GCRY_MD_MD5;
opt.s2k_count = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oDebugPinentry: opt.debug_pinentry = 1; break;
case oLogFile:
if (!reread)
return 0; /* not handeld */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oNoGrab: opt.no_grab |= 1; break;
case oGrab: opt.no_grab |= 2; break;
case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
case oPinentryInvisibleChar:
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
break;
case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
case oDisableScdaemon: opt.disable_scdaemon = 1; break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oMaxPassphraseDays:
opt.max_passphrase_days = pargs->r.ret_ulong;
break;
case oEnablePassphraseHistory:
opt.enable_passphrase_history = 1;
break;
case oEnableExtendedKeyFormat:
opt.enable_extended_key_format = 1;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
case oNoAllowExternalCache: opt.allow_external_cache = 0;
break;
case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
break;
case oSSHFingerprintDigest:
i = gcry_md_map_name (pargs->r.ret_str);
if (!i)
log_error (_("selected digest algorithm is invalid\n"));
else
opt.ssh_fingerprint_digest = i;
break;
case oS2KCount:
opt.s2k_count = pargs->r.ret_ulong;
break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
/* Hack to allow --grab to override --no-grab. */
if ((opt.no_grab & 2))
opt.no_grab = 0;
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
#if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
#endif
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
initialize_module_cache ();
initialize_module_call_pinentry ();
initialize_module_call_scd ();
initialize_module_trustlist ();
}
/* The main entry point. */
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
gpg_error_t err;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
file descriptors and the signal mask. This info is required to
do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
+ assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
disable_core_dumps ();
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Record some of the original environment strings. */
{
const char *s;
int idx;
static const char *names[] =
{ "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
err = 0;
opt.startup_env = session_env_new ();
if (!opt.startup_env)
err = gpg_error_from_syserror ();
for (idx=0; !err && names[idx]; idx++)
{
s = getenv (names[idx]);
if (s)
err = session_env_setenv (opt.startup_env, names[idx], s);
}
if (!err)
{
s = gnupg_ttyname (0);
if (s)
err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
}
if (err)
log_fatal ("error recording startup environment: %s\n",
gpg_strerror (err));
/* Fixme: Better use the locale function here. */
opt.startup_lc_ctype = getenv ("LC_CTYPE");
if (opt.startup_lc_ctype)
opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
opt.startup_lc_messages = getenv ("LC_MESSAGES");
if (opt.startup_lc_messages)
opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
}
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
else if (pargs.r_opt == oDebugQuickRandom)
{
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
}
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
/* Save the default conf file name so that
reread_configuration is able to test whether the
config file has been created in the meantime. */
xfree (config_filename);
config_filename = configname;
configname = NULL;
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case aUseStandardSocketP: gpgconf_list = 3; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oSupervised: is_supervised = 1; break;
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
break;
case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
break;
case oUseStandardSocket:
case oNoUseStandardSocket:
obsolete_option (configname, configlineno, "use-standard-socket");
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oKeepTTY: opt.keep_tty = 1; break;
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport:
ssh_support = 1;
break;
case oPuttySupport:
# ifdef HAVE_W32_SYSTEM
putty_support = 1;
# endif
break;
case oExtraSocket:
opt.extra_socket = 1; /* (1 = points into argv) */
socket_name_extra = pargs.r.ret_str;
break;
case oBrowserSocket:
opt.browser_socket = 1; /* (1 = points into argv) */
socket_name_browser = pargs.r.ret_str;
break;
+ case oAutoExpandSecmem:
+ /* Try to enable this option. It will officially only be
+ * supported by Libgcrypt 1.9 but 1.8.2 already supports it
+ * on the quiet and thus we use the numeric value value. */
+ gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
+ (unsigned int)pargs.r.ret_ulong, 0);
+ break;
+
case oDebugQuickRandom:
/* Only used by the first stage command line parser. */
break;
case oWriteEnvFile:
obsolete_option (configname, configlineno, "write-env-file");
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
if (config_filename != configname)
{
xfree (config_filename);
config_filename = configname;
}
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* gpg-agent usually does not output any messages because it runs in
the background. For log files it is acceptable to have messages
always encoded in utf-8. We switch here to utf-8, so that
commands like --help still give native messages. It is far
easier to switch only once instead of for every message and it
actually helps when more then one thread is active (avoids an
extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
{
/* We have been called without any command and thus we merely
check whether an agent is already running. We do this right
here so that we don't clobber a logfile with this check but
print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_agent (0);
agent_exit (0);
}
if (is_supervised)
;
else if (!opt.extra_socket)
opt.extra_socket = 1;
else if (socket_name_extra
&& (!strcmp (socket_name_extra, "none")
|| !strcmp (socket_name_extra, "/dev/null")))
{
/* User requested not to create this socket. */
opt.extra_socket = 0;
socket_name_extra = NULL;
}
if (is_supervised)
;
else if (!opt.browser_socket)
opt.browser_socket = 1;
else if (socket_name_browser
&& (!strcmp (socket_name_browser, "none")
|| !strcmp (socket_name_browser, "/dev/null")))
{
/* User requested not to create this socket. */
opt.browser_socket = 0;
socket_name_browser = NULL;
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 3)
{
/* We now use the standard socket always - return true for
backward compatibility. */
agent_exit (0);
}
else if (gpgconf_list == 2)
agent_exit (0);
else if (gpgconf_list)
{
char *filename;
char *filename_esc;
/* List options and default values in the GPG Conf format. */
filename = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPG_AGENT_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename);
xfree (filename_esc);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
es_printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL );
es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH );
es_printf ("max-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
es_printf ("enforce-passphrase-constraints:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MIN_PASSPHRASE_NONALPHA);
es_printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("max-passphrase-days:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MAX_PASSPHRASE_DAYS);
es_printf ("enable-passphrase-history:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("ignore-cache-for-signing:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-external-cache:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-mark-trusted:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("disable-scdaemon:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, "md5");
#ifdef HAVE_W32_SYSTEM
es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE);
#endif
es_printf ("no-allow-loopback-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("allow-emacs-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("pinentry-timeout:%lu:0:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("enable-extended-key-format:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
agent_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
/* Make sure that we have a default ttyname. */
if (!default_ttyname && gnupg_ttyname (1))
default_ttyname = xstrdup (gnupg_ttyname (1));
if (!default_ttytype && getenv ("TERM"))
default_ttytype = xstrdup (getenv ("TERM"));
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
agent_exit (1);
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
xfree (ctrl);
agent_exit (1);
}
agent_init_default_ctrl (ctrl);
start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (is_supervised)
{
#ifndef HAVE_W32_SYSTEM
gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
initialize_modules ();
/* when supervised and sending logs to stderr, the process
supervisor should handle log entry metadata (pid, name,
timestamp) */
if (!logfile)
log_set_prefix (NULL, 0);
log_info ("%s %s starting in supervised mode.\n",
strusage(11), strusage(13) );
/* See below in "regular server mode" on why we remove certain
* envvars. */
if (!opt.keep_display)
gnupg_unsetenv ("DISPLAY");
gnupg_unsetenv ("INSIDE_EMACS");
/* Virtually create the sockets. Note that we use -1 here
* because the whole thing works only on Unix. */
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
if (fd == -1)
log_fatal ("no standard socket provided\n");
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
fd, fd_extra, fd_browser, fd_ssh);
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
#endif /*!HAVE_W32_SYSTEM*/
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular server mode */
gnupg_fd_t fd;
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Remove the DISPLAY variable so that a pinentry does not
default to a specific display. There is still a default
display when gpg-agent was started using --display or a
client requested this using an OPTION command. Note, that we
don't do this when running in reverse daemon mode (i.e. when
exec the program given as arguments). */
#ifndef HAVE_W32_SYSTEM
if (!opt.keep_display && !argc)
gnupg_unsetenv ("DISPLAY");
#endif
/* Remove the INSIDE_EMACS variable so that a pinentry does not
always try to interact with Emacs. The variable is set when
a client requested this using an OPTION command. */
gnupg_unsetenv ("INSIDE_EMACS");
/* Create the sockets. */
socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 1, 0,
&redir_socket_name, &socket_nonce);
if (opt.extra_socket)
{
if (socket_name_extra)
socket_name_extra = create_socket_name (socket_name_extra, 0);
else
socket_name_extra = create_socket_name
/**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
opt.extra_socket = 2; /* Indicate that it has been malloced. */
fd_extra = create_server_socket (socket_name_extra, 0, 0,
&redir_socket_name_extra,
&socket_nonce_extra);
}
if (opt.browser_socket)
{
if (socket_name_browser)
socket_name_browser = create_socket_name (socket_name_browser, 0);
else
socket_name_browser= create_socket_name
/**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
opt.browser_socket = 2; /* Indicate that it has been malloced. */
fd_browser = create_server_socket (socket_name_browser, 0, 0,
&redir_socket_name_browser,
&socket_nonce_browser);
}
socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
&redir_socket_name_ssh,
&socket_nonce_ssh);
/* If we are going to exec a program in the parent, we record
the PID, so that the child may check whether the program is
still alive. */
if (argc)
parent_pid = getpid ();
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
char *infostr_ssh_sock, *infostr_ssh_valid;
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
we restore it. That is not strictly necessary but some
programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
/* Create the SSH info string if enabled. */
if (ssh_support)
{
if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
socket_name_ssh) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
(unsigned long)getpid()) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
}
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
if (opt.extra_socket)
*socket_name_extra = 0;
if (opt.browser_socket)
*socket_name_browser = 0;
*socket_name_ssh = 0;
if (argc)
{ /* Run the program given on the commandline. */
if (ssh_support && (putenv (infostr_ssh_sock)
|| putenv (infostr_ssh_valid)))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
/* Close all the file descriptors except the standard
ones and those open at startup. We explicitly don't
close 0,1,2 in case something went wrong collecting
them at startup. */
close_all_fds (3, startup_fd_list);
/* Run the command. */
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
if (ssh_support)
{
*strchr (infostr_ssh_sock, '=') = ' ';
es_printf ("setenv %s;\n", infostr_ssh_sock);
}
}
else
{
if (ssh_support)
{
es_printf ("%s; export SSH_AUTH_SOCK;\n",
infostr_ssh_sock);
}
}
if (ssh_support)
{
xfree (infostr_ssh_sock);
xfree (infostr_ssh_valid);
}
exit (0);
}
/*NOTREACHED*/
} /* End parent */
/*
This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Unless we are running with a program given on the command
* line we can assume that the inotify things works and thus
* we can avoid tye regular stat calls. */
if (!argc)
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
log_info ("%s %s started\n", strusage(11), strusage(13) );
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
agent_exit (int rc)
{
/*FIXME: update_random_seed_file();*/
/* We run our cleanup handler because that may close cipher contexts
stored in secure memory and thus this needs to be done before we
explicitly terminate secure memory. */
cleanup ();
#if 1
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
set once at startup and dispatches progress messages to the
corresponding threads of the agent. */
static void
agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
/* Libgcrypt < 1.8 does not know about nPth and thus when it reads
* from /dev/random this will block the process. To mitigate this
* problem we yield the thread when Libgcrypt tells us that it needs
* more entropy. This way other threads have chance to run. */
#if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */
if (what && !strcmp (what, "need_entropy"))
{
#if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */
/* In older gpg-error versions gpgrt_yield is buggy for use with
* nPth and thus we need to resort to a sleep call. */
npth_usleep (1000); /* 1ms */
#else
gpgrt_yield ();
#endif
}
#endif
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
structure usually identified by an argument named CTRL. This
function is called immediately after allocating the control
structure. Its purpose is to setup the default values for that
structure. Note that some values may have already been set. */
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
assert (ctrl->session_env);
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to agent_init_default_ctrl. */
static void
agent_deinit_default_ctrl (ctrl_t ctrl)
{
unregister_progress_cb ();
session_env_release (ctrl->session_env);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
}
/* Because the ssh protocol does not send us information about the
current TTY setting, we use this function to use those from startup
or those explicitly set. This is also used for the restricted mode
where we ignore requests to change the environment. */
gpg_error_t
agent_copy_startup_env (ctrl_t ctrl)
{
static const char *names[] =
{"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
gpg_error_t err = 0;
int idx;
const char *value;
for (idx=0; !err && names[idx]; idx++)
if ((value = session_env_getenv (opt.startup_env, names[idx])))
err = session_env_setenv (ctrl->session_env, names[idx], value);
if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
err = gpg_error_from_syserror ();
if (err)
log_error ("error setting default session environment: %s\n",
gpg_strerror (err));
return err;
}
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the PTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process' original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!config_filename)
return; /* No config file. */
fp = fopen (config_filename, "r");
if (!fp)
{
log_info (_("option file '%s': %s\n"),
config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
finalize_rereadable_options ();
set_debug ();
}
/* Return the file name of the socket we are using for native
requests. */
const char *
get_agent_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the file name of the socket we are using for SSH
requests. */
const char *
get_agent_ssh_socket_name (void)
{
const char *s = socket_name_ssh;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_agent_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
void *
get_agent_scd_notify_event (void)
{
static HANDLE the_event = INVALID_HANDLE_VALUE;
if (the_event == INVALID_HANDLE_VALUE)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting syncronize for scd notify event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
the_event = h2;
}
}
return the_event;
}
#endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/
/* Create a name for the socket in the home directory as using
STANDARD_NAME. We also check for valid characters as well as
against a maximum allowed length for a unix domain socket is done.
The function terminates the process in case of an error. Returns:
Pointer to an allocated string with the absolute name of the socket
used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
agent_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. Note that this
function needs to be used for the regular socket first (indicated
by PRIMARY) and only then for the extra and the ssh sockets. If
the socket has been redirected the name of the real socket is
stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
agent_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* Our error code mapping on W32CE returns EEXIST thus we also test
for this. */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a gpg-agent is already running. We do this
test only if this is the primary socket. For secondary
sockets we assume that a test for gpg-agent has already been
done and reuse the requested socket. Testing the ssh-socket
is not possible because at this point, though we know the new
Assuan socket, the Assuan server and thus the ssh-agent
server is not yet operational; this would lead to a hang. */
if (primary && !check_for_running_agent (1))
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a gpg-agent is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), 5 ) == -1)
{
log_error (_("listen() failed: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the private keys exists and
create it if not. This function won't fail as it is only a
convenience function and not strictly necessary. */
static void
create_private_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we compare only the suffix if we see that the default
homedir does start with a tilde. We don't stop here in case of
problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if ( stat (home, &statbuf) )
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_private_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_private_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* Check whether the scdaemon has died and cleanup in this case. */
agent_scd_check_aliveness ();
/* If we are running as a child of another process, check whether
the parent is still alive and shutdown if not. */
#ifndef HAVE_W32_SYSTEM
if (parent_pid != (pid_t)(-1))
{
if (kill (parent_pid, 0))
{
shutdown_pending = 2;
log_info ("parent process died - shutting down\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
other places too. This is only used when build for W32. */
void
agent_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
agent_flush_cache ();
reread_configuration ();
agent_reload_trustlist ();
/* We flush the module name cache so that after installing a
"pinentry" binary that one can be used in case the
"pinentry-basic" fallback was in use. */
gnupg_module_name_flush_some ();
}
/* A helper function to handle SIGUSR2. */
static void
agent_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - updating card event counter\n");
/* Nothing to check right now. We only increment a counter. */
bump_card_eventcounter ();
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
agent_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
agent_query_dump_state ();
agent_scd_dump_state ();
break;
case SIGUSR2:
agent_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
agent_exit (0);
break;
#endif
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
#ifdef HAVE_W32_SYSTEM
/* The window message processing function for Putty. Warning: This
code runs as a native Windows thread. Use of our own functions
needs to be bracket with pth_leave/pth_enter. */
static LRESULT CALLBACK
putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int ret = 0;
int w32rc;
COPYDATASTRUCT *cds;
const char *mapfile;
HANDLE maphd;
PSID mysid = NULL;
PSID mapsid = NULL;
void *data = NULL;
PSECURITY_DESCRIPTOR psd = NULL;
ctrl_t ctrl = NULL;
if (msg != WM_COPYDATA)
{
return DefWindowProc (hwnd, msg, wparam, lparam);
}
cds = (COPYDATASTRUCT*)lparam;
if (cds->dwData != PUTTY_IPC_MAGIC)
return 0; /* Ignore data with the wrong magic. */
mapfile = cds->lpData;
if (!cds->cbData || mapfile[cds->cbData - 1])
return 0; /* Ignore empty and non-properly terminated strings. */
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map file '%s'", mapfile);
npth_unprotect ();
}
maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map handle %p\n", maphd);
npth_unprotect ();
}
if (!maphd || maphd == INVALID_HANDLE_VALUE)
return 0;
npth_protect ();
mysid = w32_get_user_sid ();
if (!mysid)
{
log_error ("error getting my sid\n");
goto leave;
}
w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION,
&mapsid, NULL, NULL, NULL,
&psd);
if (w32rc)
{
log_error ("error getting sid of ssh map file: rc=%d", w32rc);
goto leave;
}
if (DBG_IPC)
{
char *sidstr;
if (!ConvertSidToStringSid (mysid, &sidstr))
sidstr = NULL;
log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
if (!ConvertSidToStringSid (mapsid, &sidstr))
sidstr = NULL;
log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
}
if (!EqualSid (mysid, mapsid))
{
log_error ("ssh map file has a non-matching sid\n");
goto leave;
}
data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (DBG_IPC)
log_debug ("ssh IPC buffer at %p\n", data);
if (!data)
goto leave;
/* log_printhex ("request:", data, 20); */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
goto leave;
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
goto leave;
}
agent_init_default_ctrl (ctrl);
if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
ret = 1; /* Valid ssh message has been constructed. */
agent_deinit_default_ctrl (ctrl);
/* log_printhex (" reply:", data, 20); */
leave:
xfree (ctrl);
if (data)
UnmapViewOfFile (data);
xfree (mapsid);
if (psd)
LocalFree (psd);
xfree (mysid);
CloseHandle (maphd);
npth_unprotect ();
return ret;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* The thread handling Putty's IPC requests. */
static void *
putty_message_thread (void *arg)
{
WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
NULL, NULL, NULL, NULL, NULL, "Pageant"};
HWND hwnd;
MSG msg;
(void)arg;
if (opt.verbose)
log_info ("putty message loop thread started\n");
/* The message loop runs as thread independent from our nPth system.
This also means that we need to make sure that we switch back to
our system before calling any no-windows function. */
npth_unprotect ();
/* First create a window to make sure that a message queue exists
for this thread. */
if (!RegisterClass (&wndwclass))
{
npth_protect ();
log_error ("error registering Pageant window class");
return NULL;
}
hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
0, 0, 0, 0,
HWND_MESSAGE, /* hWndParent */
NULL, /* hWndMenu */
NULL, /* hInstance */
NULL); /* lpParm */
if (!hwnd)
{
npth_protect ();
log_error ("error creating Pageant window");
return NULL;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Back to nPth. */
npth_protect ();
if (opt.verbose)
log_info ("putty message loop thread stopped\n");
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
static void *
do_start_connection_thread (ctrl_t ctrl)
{
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread_std (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* This is the extra socket connection thread's main function. */
static void *
start_connection_thread_extra (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_extra))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 1;
return do_start_connection_thread (ctrl);
}
/* This is the browser socket connection thread's main function. */
static void *
start_connection_thread_browser (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_browser))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 2;
return do_start_connection_thread (ctrl);
}
/* This is the ssh connection thread's main function. */
static void *
start_connection_thread_ssh (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_ssh))
return NULL;
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread_std },
{ "extra", start_connection_thread_extra },
{ "browser", start_connection_thread_browser },
{ "ssh", start_connection_thread_ssh }
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n",
strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
# ifdef HAVE_W32CE_SYSTEM
/* Use a dummy event. */
sigs = 0;
ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
# else
events[0] = get_agent_scd_notify_event ();
events[1] = INVALID_HANDLE_VALUE;
# endif
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
/* On Windows we need to fire up a separate thread to listen for
requests from Putty (an SSH client), so we can replace Putty's
Pageant (its ssh-agent implementation). */
#ifdef HAVE_W32_SYSTEM
if (putty_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
if (ret)
{
log_error ("error spawning putty message loop: %s\n", strerror (ret));
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (listen_fd_extra != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_extra), &fdset);
if (FD2INT (listen_fd_extra) > nfd)
nfd = FD2INT (listen_fd_extra);
}
if (listen_fd_browser != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_browser), &fdset);
if (FD2INT (listen_fd_browser) > nfd)
nfd = FD2INT (listen_fd_browser);
}
if (listen_fd_ssh != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_ssh), &fdset);
if (FD2INT (listen_fd_ssh) > nfd)
nfd = FD2INT (listen_fd_ssh);
}
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
listentbl[1].l_fd = listen_fd_extra;
listentbl[2].l_fd = listen_fd_browser;
listentbl[3].l_fd = listen_fd_ssh;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if (events_set & 1)
agent_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else if ( !(ctrl->session_env = session_env_new ()))
{
log_error ("error allocating session env block for %s: %s\n",
listentbl[idx].name, strerror (errno) );
xfree (ctrl);
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
separate thread so that check_own_thread can be called from the
timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
return;
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether an agent is available and running. Prints an
error if not. If SILENT is true, no messages are printed.
Returns 0 if the agent is running. */
static int
check_for_running_agent (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no gpg-agent running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("gpg-agent running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index 68fbdbc65..2e48b346e 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -1,425 +1,425 @@
keyformat.txt emacs, please switch to -*- org -*- mode
-------------
Some notes on the format of the secret keys used with gpg-agent.
* Location of keys
The secret keys[1] are stored on a per file basis in a directory below
the ~/.gnupg home directory. This directory is named
private-keys-v1.d
and should have permissions 700.
The secret keys are stored in files with a name matching the
hexadecimal representation of the keygrip[2] and suffixed with ".key".
* Extended Private Key Format
GnuPG 2.3+ will use a new format to store private keys that is both
more flexible and easier to read and edit by human beings. The new
format stores name,value-pairs using the common mail and http header
convention. Example (here indented with two spaces):
Description: Key to sign all GnuPG released tarballs.
The key is actually stored on a smart card.
Use-for-ssh: yes
OpenSSH-cert: long base64 encoded string wrapped so that this
key file can be easily edited with a standard editor.
Key: (shadowed-private-key
(rsa
(n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900
2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4
83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7
19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997
601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E
72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D
F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0
8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A
E186A02BA2497FDC5D1221#)
(e #00010001#)
(shadowed t1-v1
(#D2760001240102000005000011730000# OPENPGP.1)
)))
GnuPG 2.2 is able to read and update keys using the new format, but
will not create new files using the new format. Furthermore, it only
makes use of the value stored under the name 'Key:'.
Keys in the extended format can be recognized by looking at the first
byte of the file. If it starts with a '(' it is a naked S-expression,
otherwise it is a key in extended format.
** Names
A name must start with a letter and end with a colon. Valid
characters are all ASCII letters, numbers and the hyphen. Comparison
of names is done case insensitively. Names may be used several times
to represent an array of values.
The name "Key:" is special in that it may occur only once and the
associated value holds the actual S-expression with the cryptographic
key. The S-expression is formatted using the 'Advanced Format'
(GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that
the file can be easily inspected and edited. See section 'Private Key
Format' below for details.
** Values
Values are UTF-8 encoded strings. Values can be wrapped at any point,
and continued in the next line indicated by leading whitespace. A
continuation line with one leading space does not introduce a blank so
that the lines can be effectively concatenated. A blank line as part
of a continuation line encodes a newline.
** Comments
Lines containing only whitespace, and lines starting with whitespace
followed by '#' are considered to be comments and are ignored.
* Private Key Format
** Unprotected Private Key Format
The content of the file is an S-Expression like the ones used with
Libgcrypt. Here is an example of an unprotected file:
(private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
(created-at timestamp)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
"comment", "created-at" and "uri" are optional. "comment" is
currently used to keep track of ssh key comments. "created-at" is used
to keep track of the creation time stamp used with OpenPGP keys; it is
optional but required for some operations to calculate the fingerprint
of the key. This timestamp should be a string with the number of
seconds since Epoch or an ISO time string (yyyymmddThhmmss).
** Protected Private Key Format
A protected key is like this:
(protected-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected mode (parms) encrypted_octet_string)
(protected-at )
)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
In this scheme the encrypted_octet_string is encrypted according to
the algorithm described after the keyword protected; most protection
algorithms need some parameters, which are given in a list before the
encrypted_octet_string. The result of the decryption process is a
list of the secret key parameters. The protected-at expression is
optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The currently defined protection modes are:
*** openpgp-s2k3-sha1-aes-cbc
This describes an algorithm using AES in CBC mode for
encryption, SHA-1 for integrity protection and the String to Key
algorithm 3 from OpenPGP (rfc4880).
Example:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 16byte_salt no_of_iterations) 16byte_iv)
encrypted_octet_string
)
The encrypted_octet string should yield this S-Exp (in canonical
representation) after decryption:
(
(
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
(hash sha1 #...[hashvalue]...#)
)
For padding reasons, random bytes are appended to this list - they can
easily be stripped by looking for the end of the list.
The hash is calculated on the concatenation of the public key and
secret key parameter lists: i.e. it is required to hash the
concatenation of these 6 canonical encoded lists for RSA, including
the parenthesis, the algorithm keyword and (if used) the protected-at
list.
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
(protected-at "18950523T000000")
)
After decryption the hash must be recalculated and compared against
the stored one - If they don't match the integrity of the key is not
given.
*** openpgp-s2k3-ocb-aes
This describes an algorithm using AES-128 in OCB mode, a nonce
of 96 bit, a taglen of 128 bit, and the String to Key algorithm 3
from OpenPGP (rfc4880).
Example:
(protected openpgp-s2k3-ocb-aes
((sha1 16byte_salt no_of_iterations) 12byte_nonce)
encrypted_octet_string
)
The encrypted_octet string should yield this S-Exp (in canonical
representation) after decryption:
(
(
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
)
)
For padding reasons, random bytes may be appended to this list -
they can easily be stripped by looking for the end of the list.
The associated data required for this protection mode is the list
formiing the public key parameters. For the above example this is
is this canonical encoded S-expression:
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected-at "18950523T000000")
)
*** openpgp-native
This is a wrapper around the OpenPGP Private Key Transport format
which resembles the standard OpenPGP format and allows the use of an
existing key without re-encrypting to the default protection format.
Example:
(protected openpgp-native
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(skey _ P1 _ P2 _ P3 ... e PN)
(csum n)
(protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)))
Note that the public key paramaters in SKEY are duplicated and
should be identical to their copies in the standard parameter
elements. Here is an example of an entire protected private key
using this format:
(protected-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(protected openpgp-native
(openpgp-private-key
(version 4)
(algo rsa)
(skey _ #00e0ce9..[some bytes not shown]..51#
_ #010001#
e #.........................#)
(protection sha1 aes #aabbccddeeff00112233445566778899#
3 sha1 #2596f93e85f41e53# 3:190))))
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever))
** Shadowed Private Key Format
To keep track of keys stored on IC cards we use a third format for
private kyes which are called shadow keys as they are only a reference
to keys stored on a token:
(shadowed-private-key
(rsa
(n #00e0ce9..[some bytes not shown]..51#)
(e #010001#)
(shadowed protocol (info))
)
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever)
)
-The currently used protocol is "ti-v1" (token info version 1). The
+The currently used protocol is "t1-v1" (token info version 1). The
second list with the information has this layout:
(card_serial_number id_string_of_key fixed_pin_length)
FIXED_PIN_LENGTH is optional. It can be used to store the length of
the PIN; a value of 0 indicates that this information is not
available. The rationale for this field is that some pinpad equipped
readers don't allow passing a variable length PIN.
More items may be added to the list.
** OpenPGP Private Key Transfer Format
This format is used to transfer keys between gpg and gpg-agent.
(openpgp-private-key
(version V)
(algo PUBKEYALGO)
(curve CURVENAME)
(skey _ P1 _ P2 _ P3 ... e PN)
(csum n)
(protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))
* V is the packet version number (3 or 4).
* PUBKEYALGO is a Libgcrypt algo name
* CURVENAME is the name of the curve - only used with ECC.
* P1 .. PN are the parameters; the public parameters are never encrypted
the secrect key parameters are encrypted if the "protection" list is
given. To make this more explicit each parameter is preceded by a
flag "_" for cleartext or "e" for encrypted text.
* CSUM is the deprecated 16 bit checksum as defined by OpenPGP. This
is an optional element.
* If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum"
the old 16 bit checksum (above) is used and if it is "none" no
protection at all is used.
* PROTALGO is a Libgcrypt style cipher algorithm name
* IV is the initialization verctor.
* S2KMODE is the value from RFC-4880.
* S2KHASH is a libgcrypt style hash algorithm identifier.
* S2KSALT is the 8 byte salt
* S2KCOUNT is the count value from RFC-4880.
** Persistent Passphrase Format
Note: That this has not yet been implemented.
To allow persistent storage of cached passphrases we use a scheme
similar to the private-key storage format. This is a master
passphrase format where each file may protect several secrets under
one master passphrase. It is possible to have several of those files
each protected by a dedicated master passphrase. Clear text keywords
allow listing the available protected passphrases.
The name of the files with these protected secrets have this form:
pw-.dat. STRING may be an arbitrary string, as a default name
for the passphrase storage the name "pw-default.dat" is suggested.
(protected-shared-secret
((desc descriptive_text)
(key [key_1] (keyword_1 keyword_2 keyword_n))
(key [key_2] (keyword_21 keyword_22 keyword_2n))
(key [key_n] (keyword_n1 keyword_n2 keyword_nn))
(protected mode (parms) encrypted_octet_string)
(protected-at )
)
)
After decryption the encrypted_octet_string yields this S-expression:
(
(
(value key_1 value_1)
(value key_2 value_2)
(value key_n value_n)
)
(hash sha1 #...[hashvalue]...#)
)
The "descriptive_text" is displayed with the prompt to enter the
unprotection passphrase.
KEY_1 to KEY_N are unique identifiers for the shared secret, for
example an URI. In case this information should be kept confidential
as well, they may not appear in the unprotected part; however they are
mandatory in the encrypted_octet_string. The list of keywords is
optional. The oder of the "key" lists and the order of the "value"
lists mut match, that is the first "key"-list is associated with the
first "value" list in the encrypted_octet_string.
The protection mode etc. is identical to the protection mode as
described for the private key format.
list of the secret key parameters. The protected-at expression is
optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The "hash" in the encrypted_octet_string is calculated on the
concatenation of the key list and value lists: i.e it is required to
hash the concatenation of all these lists, including the
parenthesis and (if used) the protected-at list.
Example:
(protected-shared-secret
((desc "List of system passphrases")
(key "uid-1002" ("Knuth" "Donald Ervin Knuth"))
- (key "uid-1001" ("Dijkstra" "Edsgar Wybe Dijkstra"))
+ (key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra"))
(key)
(protected mode (parms) encrypted_octet_string)
(protected-at "20100915T111722")
)
)
with "encrypted_octet_string" decoding to:
(
(
(value 4:1002 "signal flags at the lock")
(value 4:1001 "taocp")
(value 1:0 "premature optimization is the root of all evil")
)
(hash sha1 #0102030405060708091011121314151617181920#)
)
To compute the hash this S-expression (in canoncical format) was
hashed:
((desc "List of system passphrases")
(key "uid-1002" ("Knuth" "Donald Ervin Knuth"))
- (key "uid-1001" ("Dijkstra" "Edsgar Wybe Dijkstra"))
+ (key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra"))
(key)
(value 4:1002 "signal flags at the lock")
(value 4:1001 "taocp")
(value 1:0 "premature optimization is the root of all evil")
(protected-at "20100915T111722")
)
* Notes
[1] I usually use the terms private and secret key exchangeable but prefer the
term secret key because it can be visually be better distinguished
from the term public key.
[2] The keygrip is a unique identifier for a key pair, it is
independent of any protocol, so that the same key can be used with
different protocols. PKCS-15 calls this a subjectKeyHash; it can be
calculated using Libgcrypt's gcry_pk_get_keygrip ().
[3] Even when canonical representation are required we will show the
S-expression here in a more readable representation.
diff --git a/agent/protect.c b/agent/protect.c
index 90690d950..16ae715e1 100644
--- a/agent/protect.c
+++ b/agent/protect.c
@@ -1,1741 +1,1741 @@
/* protect.c - Un/Protect a secret key
* Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 1998-2003, 2007, 2009, 2011, 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include
# endif
# include
#else
# include
#endif
#include "agent.h"
#include "cvt-openpgp.h"
#include "../common/sexp-parse.h"
/* The protection mode for encryption. The supported modes for
decryption are listed in agent_unprotect(). */
#define PROT_CIPHER GCRY_CIPHER_AES128
#define PROT_CIPHER_STRING "aes"
#define PROT_CIPHER_KEYLEN (128/8)
/* Decode an rfc4880 encoded S2K count. */
#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6))
/* A table containing the information needed to create a protected
private key. */
static const struct {
const char *algo;
const char *parmlist;
int prot_from, prot_to;
int ecc_hack;
} protect_info[] = {
{ "rsa", "nedpqu", 2, 5 },
{ "dsa", "pqgyx", 4, 4 },
{ "elg", "pgyx", 3, 3 },
{ "ecdsa","pabgnqd", 6, 6, 1 },
{ "ecdh", "pabgnqd", 6, 6, 1 },
{ "ecc", "pabgnqd", 6, 6, 1 },
{ NULL }
};
/* A helper object for time measurement. */
struct calibrate_time_s
{
#ifdef HAVE_W32_SYSTEM
FILETIME creation_time, exit_time, kernel_time, user_time;
#else
clock_t ticks;
#endif
};
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt, unsigned long s2kcount,
unsigned char *key, size_t keylen);
/* Get the process time and store it in DATA. */
static void
calibrate_get_time (struct calibrate_time_s *data)
{
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_W32CE_SYSTEM
GetThreadTimes (GetCurrentThread (),
&data->creation_time, &data->exit_time,
&data->kernel_time, &data->user_time);
# else
GetProcessTimes (GetCurrentProcess (),
&data->creation_time, &data->exit_time,
&data->kernel_time, &data->user_time);
# endif
#elif defined (CLOCK_THREAD_CPUTIME_ID)
struct timespec tmp;
clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp);
data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 +
tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000);
#else
data->ticks = clock ();
#endif
}
static unsigned long
calibrate_elapsed_time (struct calibrate_time_s *starttime)
{
struct calibrate_time_s stoptime;
calibrate_get_time (&stoptime);
#ifdef HAVE_W32_SYSTEM
{
unsigned long long t1, t2;
t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32)
+ starttime->kernel_time.dwLowDateTime);
t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32)
+ starttime->user_time.dwLowDateTime);
t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32)
+ stoptime.kernel_time.dwLowDateTime);
t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32)
+ stoptime.user_time.dwLowDateTime);
return (unsigned long)((t2 - t1)/10000);
}
#else
return (unsigned long)((((double) (stoptime.ticks - starttime->ticks))
/CLOCKS_PER_SEC)*1000);
#endif
}
/* Run a test hashing for COUNT and return the time required in
milliseconds. */
static unsigned long
calibrate_s2k_count_one (unsigned long count)
{
int rc;
char keybuf[PROT_CIPHER_KEYLEN];
struct calibrate_time_s starttime;
calibrate_get_time (&starttime);
rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1,
3, "saltsalt", count, keybuf, sizeof keybuf);
if (rc)
BUG ();
return calibrate_elapsed_time (&starttime);
}
/* Measure the time we need to do the hash operations and deduce an
S2K count which requires roughly some targeted amount of time. */
static unsigned long
calibrate_s2k_count (void)
{
unsigned long count;
unsigned long ms;
for (count = 65536; count; count *= 2)
{
ms = calibrate_s2k_count_one (count);
if (opt.verbose > 1)
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
if (ms > AGENT_S2K_CALIBRATION)
break;
}
count = (unsigned long)(((double)count / ms) * AGENT_S2K_CALIBRATION);
count /= 1024;
count *= 1024;
if (count < 65536)
count = 65536;
if (opt.verbose)
{
ms = calibrate_s2k_count_one (count);
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
}
return count;
}
/* Return the calibrated S2K count. This is only public for the use
* of the Assuan getinfo s2k_count_cal command. */
unsigned long
get_calibrated_s2k_count (void)
{
static unsigned long count;
if (!count)
count = calibrate_s2k_count ();
/* Enforce a lower limit. */
return count < 65536 ? 65536 : count;
}
/* Return the standard S2K count. */
unsigned long
get_standard_s2k_count (void)
{
if (opt.s2k_count)
return opt.s2k_count < 65536 ? 65536 : opt.s2k_count;
return get_calibrated_s2k_count ();
}
/* Return the milliseconds required for the standard S2K
* operation. */
unsigned long
get_standard_s2k_time (void)
{
return calibrate_s2k_count_one (get_standard_s2k_count ());
}
/* Same as get_standard_s2k_count but return the count in the encoding
as described by rfc4880. */
unsigned char
get_standard_s2k_count_rfc4880 (void)
{
unsigned long iterations;
unsigned int count;
unsigned char result;
unsigned char c=0;
iterations = get_standard_s2k_count ();
if (iterations >= 65011712)
return 255;
/* Need count to be in the range 16-31 */
for (count=iterations>>6; count>=32; count>>=1)
c++;
result = (c<<4)|(count-16);
if (S2K_DECODE_COUNT(result) < iterations)
result++;
return result;
}
/* Calculate the MIC for a private key or shared secret S-expression.
SHA1HASH should point to a 20 byte buffer. This function is
suitable for all algorithms. */
static gpg_error_t
calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
{
const unsigned char *hash_begin, *hash_end;
const unsigned char *s;
size_t n;
int is_shared_secret;
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "private-key"))
is_shared_secret = 0;
else if (smatch (&s, n, "shared-secret"))
is_shared_secret = 1;
else
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
hash_begin = s;
if (!is_shared_secret)
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* Skip the algorithm name. */
}
while (*s == '(')
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
if ( *s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
}
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
hash_end = s;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
hash_begin, hash_end - hash_begin);
return 0;
}
/* Encrypt the parameter block starting at PROTBEGIN with length
PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
encrypted block in RESULT or return with an error code. SHA1HASH
is the 20 byte SHA-1 hash required for the integrity code.
The parameter block is expected to be an incomplete canonical
encoded S-Expression of the form (example in advanced format):
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
the returned block is the S-Expression:
(protected mode (parms) encrypted_octet_string)
*/
static int
do_encryption (const unsigned char *hashbegin, size_t hashlen,
const unsigned char *protbegin, size_t protlen,
const char *passphrase,
const char *timestamp_exp, size_t timestamp_exp_len,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb)
{
gcry_cipher_hd_t hd;
const char *modestr;
unsigned char hashvalue[20];
int blklen, enclen, outlen;
unsigned char *iv = NULL;
unsigned int ivsize; /* Size of the buffer allocated for IV. */
const unsigned char *s2ksalt; /* Points into IV. */
int rc;
char *outbuf = NULL;
char *p;
int saltpos, ivpos, encpos;
s2ksalt = iv; /* Silence compiler warning. */
*resultlen = 0;
*result = NULL;
modestr = (use_ocb? "openpgp-s2k3-ocb-aes"
/* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc");
rc = gcry_cipher_open (&hd, PROT_CIPHER,
use_ocb? GCRY_CIPHER_MODE_OCB :
GCRY_CIPHER_MODE_CBC,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
/* We need to work on a copy of the data because this makes it
* easier to add the trailer and the padding and more important we
* have to prefix the text with 2 parenthesis. In CBC mode we
* have to allocate enough space for:
*
* (()(4:hash4:sha120:)) + padding
*
* we always append a full block of random bytes as padding but
* encrypt only what is needed for a full blocksize. In OCB mode we
* have to allocate enough space for just:
*
* (())
*/
blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
if (use_ocb)
{
/* (( )) */
outlen = 2 + protlen + 2 ;
enclen = outlen + 16 /* taglen */;
outbuf = gcry_malloc_secure (enclen);
}
else
{
/* (( )( 4:hash 4:sha1 20: )) */
outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
enclen = outlen/blklen * blklen;
outbuf = gcry_malloc_secure (outlen);
}
if (!outbuf)
{
rc = out_of_core ();
goto leave;
}
/* Allocate a buffer for the nonce and the salt. */
if (!rc)
{
/* Allocate random bytes to be used as IV, padding and s2k salt
* or in OCB mode for a nonce and the s2k salt. The IV/nonce is
* set later because for OCB we need to set the key first. */
ivsize = (use_ocb? 12 : (blklen*2)) + 8;
iv = xtrymalloc (ivsize);
if (!iv)
rc = gpg_error_from_syserror ();
else
{
gcry_create_nonce (iv, ivsize);
s2ksalt = iv + ivsize - 8;
}
}
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
size_t keylen = PROT_CIPHER_KEYLEN;
key = gcry_malloc_secure (keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt,
s2k_count? s2k_count:get_standard_s2k_count(),
key, keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, keylen);
xfree (key);
}
}
if (rc)
goto leave;
/* Set the IV/nonce. */
rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen);
if (rc)
goto leave;
if (use_ocb)
{
/* In OCB Mode we use only the public key parameters as AAD. */
rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin);
if (!rc)
rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len);
if (!rc)
rc = gcry_cipher_authenticate
(hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin));
}
else
{
/* Hash the entire expression for CBC mode. Because
* TIMESTAMP_EXP won't get protected, we can't simply hash a
* continuous buffer but need to call md_write several times. */
gcry_md_hd_t md;
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 );
if (!rc)
{
gcry_md_write (md, hashbegin, protbegin - hashbegin);
gcry_md_write (md, protbegin, protlen);
gcry_md_write (md, timestamp_exp, timestamp_exp_len);
gcry_md_write (md, protbegin+protlen,
hashlen - (protbegin+protlen - hashbegin));
memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20);
gcry_md_close (md);
}
}
/* Encrypt. */
if (!rc)
{
p = outbuf;
*p++ = '(';
*p++ = '(';
memcpy (p, protbegin, protlen);
p += protlen;
if (use_ocb)
{
*p++ = ')';
*p++ = ')';
}
else
{
memcpy (p, ")(4:hash4:sha120:", 17);
p += 17;
memcpy (p, hashvalue, 20);
p += 20;
*p++ = ')';
*p++ = ')';
memcpy (p, iv+blklen, blklen); /* Add padding. */
p += blklen;
}
assert ( p - outbuf == outlen);
if (use_ocb)
{
gcry_cipher_final (hd);
rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0);
if (!rc)
{
log_assert (outlen + 16 == enclen);
rc = gcry_cipher_gettag (hd, outbuf + outlen, 16);
}
}
else
{
rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
}
}
if (rc)
goto leave;
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
/* Now allocate the buffer we want to return. This is
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 salt no_of_iterations) 16byte_iv)
encrypted_octet_string)
in canoncical format of course. We use asprintf and %n modifier
and dummy values as placeholders. */
{
char countbuf[35];
snprintf (countbuf, sizeof countbuf, "%lu",
s2k_count ? s2k_count : get_standard_s2k_count ());
p = xtryasprintf
("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)",
(int)strlen (modestr), modestr,
&saltpos,
(unsigned int)strlen (countbuf), countbuf,
use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "",
enclen, &encpos, enclen, "");
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (iv);
xfree (outbuf);
return tmperr;
}
}
*resultlen = strlen (p);
*result = (unsigned char*)p;
memcpy (p+saltpos, s2ksalt, 8);
memcpy (p+ivpos, iv, use_ocb? 12 : blklen);
memcpy (p+encpos, outbuf, enclen);
xfree (iv);
xfree (outbuf);
return 0;
leave:
gcry_cipher_close (hd);
xfree (iv);
xfree (outbuf);
return rc;
}
/* Protect the key encoded in canonical format in PLAINKEY. We assume
a valid S-Exp here. With USE_UCB set to -1 the default scheme is
used (ie. either CBC or OCB), set to 0 the old CBC mode is used,
and set to 1 OCB is used. */
int
agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb)
{
int rc;
const char *parmlist;
int prot_from_idx, prot_to_idx;
const unsigned char *s;
const unsigned char *hash_begin, *hash_end;
const unsigned char *prot_begin, *prot_end, *real_end;
size_t n;
int c, infidx, i;
char timestamp_exp[35];
unsigned char *protected;
size_t protectedlen;
int depth = 0;
unsigned char *p;
int have_curve = 0;
if (use_ocb == -1)
use_ocb = opt.enable_extended_key_format;
/* Create an S-expression with the protected-at timestamp. */
memcpy (timestamp_exp, "(12:protected-at15:", 19);
gnupg_get_isotime (timestamp_exp+19);
timestamp_exp[19+15] = ')';
/* Parse original key. */
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
hash_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* The parser below is a complete mess: To make it robust for ECC
use we should reorder the s-expression to include only what we
really need and thus guarantee the right order for saving stuff.
This should be done before calling this function and maybe with
the help of the new gcry_sexp_extract_param. */
parmlist = protect_info[infidx].parmlist;
prot_from_idx = protect_info[infidx].prot_from;
prot_to_idx = protect_info[infidx].prot_to;
prot_begin = prot_end = NULL;
for (i=0; (c=parmlist[i]); i++)
{
if (i == prot_from_idx)
prot_begin = s;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 1 || c != *s)
{
if (n == 5 && !memcmp (s, "curve", 5)
&& !i && protect_info[infidx].ecc_hack)
{
/* This is a private ECC key but the first parameter is
the name of the curve. We change the parameter list
here to the one we expect in this case. */
have_curve = 1;
parmlist = "?qd";
prot_from_idx = 2;
prot_to_idx = 2;
}
else if (n == 5 && !memcmp (s, "flags", 5)
&& i == 1 && have_curve)
{
/* "curve" followed by "flags": Change again. */
parmlist = "??qd";
prot_from_idx = 3;
prot_to_idx = 3;
}
else
return gpg_error (GPG_ERR_INV_SEXP);
}
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
if (i == prot_to_idx)
prot_end = s;
s++;
}
if (*s != ')' || !prot_begin || !prot_end )
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
hash_end = s;
s++;
/* Skip to the end of the S-expression. */
assert (depth == 1);
rc = sskip (&s, &depth);
if (rc)
return rc;
assert (!depth);
real_end = s-1;
rc = do_encryption (hash_begin, hash_end - hash_begin + 1,
prot_begin, prot_end - prot_begin + 1,
passphrase, timestamp_exp, sizeof (timestamp_exp),
&protected, &protectedlen, s2k_count, use_ocb);
if (rc)
return rc;
/* Now create the protected version of the key. Note that the 10
extra bytes are for the inserted "protected-" string (the
beginning of the plaintext reads: "((11:private-key(" ). The 35
term is the space for (12:protected-at15:). */
*resultlen = (10
+ (prot_begin-plainkey)
+ protectedlen
+ 35
+ (real_end-prot_end));
*result = p = xtrymalloc (*resultlen);
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (protected);
return tmperr;
}
memcpy (p, "(21:protected-", 14);
p += 14;
memcpy (p, plainkey+4, prot_begin - plainkey - 4);
p += prot_begin - plainkey - 4;
memcpy (p, protected, protectedlen);
p += protectedlen;
memcpy (p, timestamp_exp, 35);
p += 35;
memcpy (p, prot_end+1, real_end - prot_end);
p += real_end - prot_end;
assert ( p - *result == *resultlen);
xfree (protected);
return 0;
}
/* Do the actual decryption and check the return list for consistency. */
static gpg_error_t
do_decryption (const unsigned char *aad_begin, size_t aad_len,
const unsigned char *aadhole_begin, size_t aadhole_len,
const unsigned char *protected, size_t protectedlen,
const char *passphrase,
const unsigned char *s2ksalt, unsigned long s2kcount,
const unsigned char *iv, size_t ivlen,
int prot_cipher, int prot_cipher_keylen, int is_ocb,
unsigned char **result)
{
int rc;
int blklen;
gcry_cipher_hd_t hd;
unsigned char *outbuf;
size_t reallen;
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
if (is_ocb)
{
/* OCB does not require a multiple of the block length but we
* check that it is long enough for the 128 bit tag and that we
* have the 96 bit nonce. */
if (protectedlen < (4 + 16) || ivlen != 12)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (protectedlen < 4 || (protectedlen%blklen))
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
rc = gcry_cipher_open (&hd, prot_cipher,
is_ocb? GCRY_CIPHER_MODE_OCB :
GCRY_CIPHER_MODE_CBC,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
outbuf = gcry_malloc_secure (protectedlen);
if (!outbuf)
rc = out_of_core ();
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
key = gcry_malloc_secure (prot_cipher_keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt, s2kcount, key, prot_cipher_keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen);
xfree (key);
}
}
/* Set the IV/nonce. */
if (!rc)
{
rc = gcry_cipher_setiv (hd, iv, ivlen);
}
/* Decrypt. */
if (!rc)
{
if (is_ocb)
{
rc = gcry_cipher_authenticate (hd, aad_begin,
aadhole_begin - aad_begin);
if (!rc)
rc = gcry_cipher_authenticate
(hd, aadhole_begin + aadhole_len,
aad_len - (aadhole_begin+aadhole_len - aad_begin));
if (!rc)
{
gcry_cipher_final (hd);
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16,
protected, protectedlen - 16);
}
if (!rc)
{
rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16);
if (gpg_err_code (rc) == GPG_ERR_CHECKSUM)
{
/* Return Bad Passphrase instead of checksum error */
rc = gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
}
}
else
{
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
protected, protectedlen);
}
}
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
if (rc)
{
xfree (outbuf);
return rc;
}
/* Do a quick check on the data structure. */
if (*outbuf != '(' && outbuf[1] != '(')
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Check that we have a consistent S-Exp. */
reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
if (!reallen || (reallen + blklen < protectedlen) )
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
*result = outbuf;
return 0;
}
/* Merge the parameter list contained in CLEARTEXT with the original
* protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
* Return the new list in RESULT and the MIC value in the 20 byte
* buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed.
* CUTOFF and CUTLEN will receive the offset and the length of the
* resulting list which should go into the MIC calculation but then be
* removed. */
static gpg_error_t
merge_lists (const unsigned char *protectedkey,
size_t replacepos,
const unsigned char *cleartext,
unsigned char *sha1hash,
unsigned char **result, size_t *resultlen,
size_t *cutoff, size_t *cutlen)
{
size_t n, newlistlen;
unsigned char *newlist, *p;
const unsigned char *s;
const unsigned char *startpos, *endpos;
int i, rc;
*result = NULL;
*resultlen = 0;
*cutoff = 0;
*cutlen = 0;
if (replacepos < 26)
return gpg_error (GPG_ERR_BUG);
/* Estimate the required size of the resulting list. We have a large
safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the
removed "protected-" */
newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
if (!newlistlen)
return gpg_error (GPG_ERR_BUG);
n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
if (!n)
return gpg_error (GPG_ERR_BUG);
newlistlen += n;
newlist = gcry_malloc_secure (newlistlen);
if (!newlist)
return out_of_core ();
/* Copy the initial segment */
strcpy ((char*)newlist, "(11:private-key");
p = newlist + 15;
memcpy (p, protectedkey+15+10, replacepos-15-10);
p += replacepos-15-10;
/* Copy the cleartext. */
s = cleartext;
if (*s != '(' && s[1] != '(')
return gpg_error (GPG_ERR_BUG); /*we already checked this */
s += 2;
startpos = s;
while ( *s == '(' )
{
s++;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
if ( *s != ')' )
goto invalid_sexp;
s++;
}
if ( *s != ')' )
goto invalid_sexp;
endpos = s;
s++;
/* Intermezzo: Get the MIC if requested. */
if (sha1hash)
{
if (*s != '(')
goto invalid_sexp;
s++;
n = snext (&s);
if (!smatch (&s, n, "hash"))
goto invalid_sexp;
n = snext (&s);
if (!smatch (&s, n, "sha1"))
goto invalid_sexp;
n = snext (&s);
if (n != 20)
goto invalid_sexp;
memcpy (sha1hash, s, 20);
s += n;
if (*s != ')')
goto invalid_sexp;
}
/* Append the parameter list. */
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* Skip over the protected list element in the original list. */
s = protectedkey + replacepos;
assert (*s == '(');
s++;
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
/* Record the position of the optional protected-at expression. */
if (*s == '(')
{
const unsigned char *save_s = s;
s++;
n = snext (&s);
if (smatch (&s, n, "protected-at"))
{
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
*cutlen = s - save_s;
}
s = save_s;
}
startpos = s;
i = 2; /* we are inside this level */
rc = sskip (&s, &i);
if (rc)
goto failure;
assert (s[-1] == ')');
endpos = s; /* one behind the end of the list */
/* Append the rest. */
if (*cutlen)
*cutoff = p - newlist;
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* ready */
*result = newlist;
*resultlen = newlistlen;
return 0;
failure:
wipememory (newlist, newlistlen);
xfree (newlist);
return rc;
invalid_sexp:
wipememory (newlist, newlistlen);
xfree (newlist);
return gpg_error (GPG_ERR_INV_SEXP);
}
/* Unprotect the key encoded in canonical format. We assume a valid
S-Exp here. If a protected-at item is available, its value will
be stored at protected_at unless this is NULL. */
gpg_error_t
agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen)
{
static const struct {
const char *name; /* Name of the protection method. */
int algo; /* (A zero indicates the "openpgp-native" hack.) */
int keylen; /* Used key length in bytes. */
unsigned int is_ocb:1;
} algotable[] = {
{ "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)},
{ "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)},
{ "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1},
{ "openpgp-native", 0, 0 }
};
int rc;
const unsigned char *s;
const unsigned char *protect_list;
size_t n;
int infidx, i;
unsigned char sha1hash[20], sha1hash2[20];
const unsigned char *s2ksalt;
unsigned long s2kcount;
const unsigned char *iv;
int prot_cipher, prot_cipher_keylen;
int is_ocb;
const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end;
const unsigned char *prot_begin;
unsigned char *cleartext;
unsigned char *final;
size_t finallen;
size_t cutoff, cutlen;
if (protected_at)
*protected_at = 0;
s = protectedkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "protected-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
{
aad_begin = aad_end = s;
aad_end++;
i = 1;
rc = sskip (&aad_end, &i);
if (rc)
return rc;
}
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* See wether we have a protected-at timestamp. */
protect_list = s; /* Save for later. */
if (protected_at)
{
while (*s == '(')
{
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected-at"))
{
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 15)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
memcpy (protected_at, s, 15);
protected_at[15] = 0;
break;
}
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
}
/* Now find the list with the protected information. Here is an
example for such a list:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 ) )
)
*/
s = protect_list;
for (;;)
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
/* found */
{
aadhole_begin = aadhole_end = prot_begin;
aadhole_end++;
i = 1;
rc = sskip (&aadhole_end, &i);
if (rc)
return rc;
}
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
/* Lookup the protection algo. */
prot_cipher = 0; /* (avoid gcc warning) */
prot_cipher_keylen = 0; /* (avoid gcc warning) */
is_ocb = 0;
for (i=0; i < DIM (algotable); i++)
if (smatch (&s, n, algotable[i].name))
{
prot_cipher = algotable[i].algo;
prot_cipher_keylen = algotable[i].keylen;
is_ocb = algotable[i].is_ocb;
break;
}
if (i == DIM (algotable))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
if (!prot_cipher) /* This is "openpgp-native". */
{
gcry_sexp_t s_prot_begin;
rc = gcry_sexp_sscan (&s_prot_begin, NULL,
prot_begin,
gcry_sexp_canon_len (prot_begin, 0,NULL,NULL));
if (rc)
return rc;
rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final);
gcry_sexp_release (s_prot_begin);
if (!rc)
{
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
}
return rc;
}
if (*s != '(' || s[1] != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s += 2;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "sha1"))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
n = snext (&s);
if (n != 8)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s2ksalt = s;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
/* We expect a list close as next, so we can simply use strtoul()
here. We might want to check that we only have digits - but this
is nothing we should worry about */
if (s[n] != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
/* Old versions of gpg-agent used the funny floating point number in
a byte encoding as specified by OpenPGP. However this is not
needed and thus we now store it as a plain unsigned integer. We
can easily distinguish the old format by looking at its value:
Less than 256 is an old-style encoded number; other values are
plain integers. In any case we check that they are at least
65536 because we never used a lower value in the past and we
should have a lower limit. */
s2kcount = strtoul ((const char*)s, NULL, 10);
if (!s2kcount)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (s2kcount < 256)
s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
if (s2kcount < 65536)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s += n;
s++; /* skip list end */
n = snext (&s);
if (is_ocb)
{
if (n != 12) /* Wrong size of the nonce. */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
iv = s;
s += n;
if (*s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
cleartext = NULL; /* Avoid cc warning. */
rc = do_decryption (aad_begin, aad_end - aad_begin,
aadhole_begin, aadhole_end - aadhole_begin,
s, n,
passphrase, s2ksalt, s2kcount,
iv, is_ocb? 12:16,
prot_cipher, prot_cipher_keylen, is_ocb,
&cleartext);
if (rc)
return rc;
rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
is_ocb? NULL : sha1hash,
&final, &finallen, &cutoff, &cutlen);
/* Albeit cleartext has been allocated in secure memory and thus
xfree will wipe it out, we do an extra wipe just in case
somethings goes badly wrong. */
wipememory (cleartext, n);
xfree (cleartext);
if (rc)
return rc;
if (!is_ocb)
{
rc = calculate_mic (final, sha1hash2);
if (!rc && memcmp (sha1hash, sha1hash2, 20))
rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (rc)
{
wipememory (final, finallen);
xfree (final);
return rc;
}
}
/* Now remove the part which is included in the MIC but should not
go into the final thing. */
if (cutlen)
{
memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen);
finallen -= cutlen;
}
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
return 0;
}
/* Check the type of the private key, this is one of the constants:
PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
PRIVATE_KEY_PROTECTED for an protected private key or
PRIVATE_KEY_SHADOWED for a sub key where the secret parts are
stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned
is the key is still in the openpgp-native format but without
protection. */
int
agent_private_key_type (const unsigned char *privatekey)
{
const unsigned char *s;
size_t n;
int i;
s = privatekey;
if (*s != '(')
return PRIVATE_KEY_UNKNOWN;
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN;
if (smatch (&s, n, "protected-private-key"))
{
/* We need to check whether this is openpgp-native protected
with the protection method "none". In that case we return a
different key type so that the caller knows that there is no
need to ask for a passphrase. */
if (*s != '(')
return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over the algo */
/* Find the (protected ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is this openpgp-native? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "openpgp-native")) /* Yes. */
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over "openpgp-private-key". */
/* Find the (protection ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protection"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is the mode "none"? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "none"))
return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */
}
return PRIVATE_KEY_PROTECTED;
}
if (smatch (&s, n, "shadowed-private-key"))
return PRIVATE_KEY_SHADOWED;
if (smatch (&s, n, "private-key"))
return PRIVATE_KEY_CLEAR;
return PRIVATE_KEY_UNKNOWN;
}
/* Transform a passphrase into a suitable key of length KEYLEN and
store this key in the caller provided buffer KEY. The caller must
provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
that mode an S2KSALT of 8 random bytes and an S2KCOUNT.
Returns an error code on failure. */
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned long s2kcount,
unsigned char *key, size_t keylen)
{
/* The key derive function does not support a zero length string for
the passphrase in the S2K modes. Return a better suited error
code than GPG_ERR_INV_DATA. */
if (!passphrase || !*passphrase)
return gpg_error (GPG_ERR_NO_PASSPHRASE);
return gcry_kdf_derive (passphrase, strlen (passphrase),
s2kmode == 3? GCRY_KDF_ITERSALTED_S2K :
s2kmode == 1? GCRY_KDF_SALTED_S2K :
s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE,
hashalgo, s2ksalt, 8, s2kcount,
keylen, key);
}
gpg_error_t
s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen)
{
return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt,
S2K_DECODE_COUNT (s2kcount),
key, keylen);
}
/* Create an canonical encoded S-expression with the shadow info from
a card's SERIALNO and the IDSTRING. */
unsigned char *
make_shadow_info (const char *serialno, const char *idstring)
{
const char *s;
char *info, *p;
char numbuf[20];
size_t n;
for (s=serialno, n=0; *s && s[1]; s += 2)
n++;
info = p = xtrymalloc (1 + sizeof numbuf + n
+ sizeof numbuf + strlen (idstring) + 1 + 1);
if (!info)
return NULL;
*p++ = '(';
p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL));
for (s=serialno; *s && s[1]; s += 2)
*(unsigned char *)p++ = xtoi_2 (s);
p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL));
p = stpcpy (p, idstring);
*p++ = ')';
*p = 0;
return (unsigned char *)info;
}
/* Create a shadow key from a public key. We use the shadow protocol
- "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting
+ "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
S-expression is returned in an allocated buffer RESULT will point
to. The input parameters are expected to be valid canonicalized
S-expressions */
int
agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result)
{
const unsigned char *s;
const unsigned char *point;
size_t n;
int depth = 0;
char *p;
size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL);
size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL);
if (!pubkey_len || !shadow_info_len)
return gpg_error (GPG_ERR_INV_VALUE);
s = pubkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "public-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
while (*s != ')')
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
point = s; /* insert right before the point */
depth--;
s++;
assert (depth == 1);
/* Calculate required length by taking in account: the "shadowed-"
prefix, the "shadowed", "t1-v1" as well as some parenthesis */
n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
*result = xtrymalloc (n);
p = (char*)*result;
if (!p)
return out_of_core ();
p = stpcpy (p, "(20:shadowed-private-key");
/* (10:public-key ...)*/
memcpy (p, pubkey+14, point - (pubkey+14));
p += point - (pubkey+14);
p = stpcpy (p, "(8:shadowed5:t1-v1");
memcpy (p, shadow_info, shadow_info_len);
p += shadow_info_len;
*p++ = ')';
memcpy (p, point, pubkey_len - (point - pubkey));
p += pubkey_len - (point - pubkey);
return 0;
}
/* Parse a canonical encoded shadowed key and return a pointer to the
inner list with the shadow_info */
gpg_error_t
agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info)
{
const unsigned char *s;
size_t n;
int depth = 0;
s = shadowkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "shadowed-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
for (;;)
{
if (*s == ')')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "shadowed"))
break;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
/* Found the shadowed list, S points to the protocol */
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "t1-v1"))
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
*shadow_info = s;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
return 0;
}
/* Parse the canonical encoded SHADOW_INFO S-expression. On success
the hex encoded serial number is returned as a malloced strings at
R_HEXSN and the Id string as a malloced string at R_IDSTR. On
error an error code is returned and NULL is stored at the result
parameters addresses. If the serial number or the ID string is not
required, NULL may be passed for them. */
gpg_error_t
parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen)
{
const unsigned char *s;
size_t n;
if (r_hexsn)
*r_hexsn = NULL;
if (r_idstr)
*r_idstr = NULL;
if (r_pinlen)
*r_pinlen = 0;
s = shadow_info;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (r_hexsn)
{
*r_hexsn = bin2hex (s, n, NULL);
if (!*r_hexsn)
return gpg_error_from_syserror ();
}
s += n;
n = snext (&s);
if (!n)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error (GPG_ERR_INV_SEXP);
}
if (r_idstr)
{
*r_idstr = xtrymalloc (n+1);
if (!*r_idstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (*r_idstr, s, n);
(*r_idstr)[n] = 0;
}
/* Parse the optional PINLEN. */
n = snext (&s);
if (!n)
return 0;
if (r_pinlen)
{
char *tmpstr = xtrymalloc (n+1);
if (!tmpstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
if (r_idstr)
{
xfree (*r_idstr);
*r_idstr = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (tmpstr, s, n);
tmpstr[n] = 0;
*r_pinlen = (int)strtol (tmpstr, NULL, 10);
xfree (tmpstr);
}
return 0;
}
diff --git a/agent/t-protect.c b/agent/t-protect.c
index 92d312c9b..d17c19325 100644
--- a/agent/t-protect.c
+++ b/agent/t-protect.c
@@ -1,350 +1,350 @@
/* t-protect.c - Module tests for protect.c
* Copyright (C) 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 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 .
*/
#include
#include
#include
#include
#include
#include "agent.h"
#define pass() do { ; } while(0)
#define fail() do { fprintf (stderr, "%s:%d: test failed\n",\
__FILE__,__LINE__); \
exit (1); \
} while(0)
static void
test_agent_protect (void)
{
/* Protect the key encoded in canonical format in PLAINKEY. We assume
a valid S-Exp here. */
unsigned int i;
int ret;
struct key_spec
{
const char *string;
};
/* Valid RSA key. */
struct key_spec key_rsa_valid =
{
"\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
"\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92"
"\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04"
"\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D"
"\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49"
"\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F"
"\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77"
"\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28"
"\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3"
"\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A"
"\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9"
"\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77"
"\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7"
"\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD"
"\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34"
"\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B"
"\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A"
"\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85"
"\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28"
"\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F"
"\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2"
"\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21"
"\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33"
"\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72"
"\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B"
"\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17"
"\xD0\x68\x0B\x29\x29\x29\x00"
};
/* This RSA key is missing the last closing brace. */
struct key_spec key_rsa_bogus_0 =
{
"\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
"\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92"
"\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04"
"\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D"
"\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49"
"\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F"
"\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77"
"\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28"
"\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3"
"\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A"
"\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9"
"\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77"
"\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7"
"\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD"
"\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34"
"\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B"
"\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A"
"\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85"
"\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28"
"\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F"
"\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2"
"\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21"
"\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33"
"\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72"
"\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B"
"\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17"
"\xD0\x68\x0B\x29\x29\x00"
};
/* This RSA key is the 'e' value. */
struct key_spec key_rsa_bogus_1 =
{
"\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73"
"\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xA8\x80\xB6\x71\xF4\x95\x9F\x49\x84\xED"
"\xC1\x1D\x5F\xFF\xED\x14\x7B\x9C\x6A\x62\x0B\x7B\xE2\x3E\x41\x48\x49\x85\xF5\x64"
"\x50\x04\x9D\x30\xFC\x84\x1F\x01\xC3\xC3\x15\x03\x48\x6D\xFE\x59\x0B\xB0\xD0\x3E"
"\x68\x8A\x05\x7A\x62\xB0\xB9\x6E\xC5\xD2\xA8\xEE\x0C\x6B\xDE\x5E\x3D\x8E\xE8\x8F"
"\xB3\xAE\x86\x99\x7E\xDE\x2B\xC2\x4D\x60\x51\xDB\xB1\x2C\xD0\x38\xEC\x88\x62\x3E"
"\xA9\xDD\x11\x53\x04\x17\xE4\xF2\x07\x50\xDC\x44\xED\x14\xF5\x0B\xAB\x9C\xBC\x24"
"\xC6\xCB\xAD\x0F\x05\x25\x94\xE2\x73\xEB\x14\xD5\xEE\x5E\x18\xF0\x40\x31\x29\x28"
"\x31\x3A\x64\x31\x32\x38\x3A\x40\xD0\x55\x9D\x2A\xA7\xBC\xBF\xE2\x3E\x33\x98\x71"
"\x7B\x37\x3D\xB8\x38\x57\xA1\x43\xEA\x90\x81\x42\xCA\x23\xE1\xBF\x9C\xA8\xBC\xC5"
"\x9B\xF8\x9D\x77\x71\xCD\xD3\x85\x8B\x20\x3A\x92\xE9\xBC\x79\xF3\xF7\xF5\x6D\x15"
"\xA3\x58\x3F\xC2\xEB\xED\x72\xD4\xE0\xCF\xEC\xB3\xEC\xEB\x09\xEA\x1E\x72\x6A\xBA"
"\x95\x82\x2C\x7E\x30\x95\x66\x3F\xA8\x2D\x40\x0F\x7A\x12\x4E\xF0\x71\x0F\x97\xDB"
"\x81\xE4\x39\x6D\x24\x58\xFA\xAB\x3A\x36\x73\x63\x01\x77\x42\xC7\x9A\xEA\x87\xDA"
"\x93\x8F\x6C\x64\xAD\x9E\xF0\xCA\xA2\x89\xA4\x0E\xB3\x25\x73\x29\x28\x31\x3A\x70"
"\x36\x35\x3A\x00\xC3\xF7\x37\x3F\x9D\x93\xEC\xC7\x5E\x4C\xB5\x73\x29\x62\x35\x80"
"\xC6\x7C\x1B\x1E\x68\x5F\x92\x56\x77\x0A\xE2\x8E\x95\x74\x87\xA5\x2F\x83\x2D\xF7"
"\xA1\xC2\x78\x54\x18\x6E\xDE\x35\xF0\x9F\x7A\xCA\x80\x5C\x83\x5C\x44\xAD\x8B\xE7"
"\x5B\xE2\x63\x7D\x6A\xC7\x98\x97\x29\x28\x31\x3A\x71\x36\x35\x3A\x00\xDC\x1F\xB1"
"\xB3\xD8\x13\xE0\x09\x19\xFD\x1C\x58\xA1\x2B\x02\xB4\xC8\xF2\x1C\xE7\xF9\xC6\x3B"
"\x68\xB9\x72\x43\x86\xEF\xA9\x94\x68\x02\xEF\x7D\x77\xE0\x0A\xD1\xD7\x48\xFD\xCD"
"\x98\xDA\x13\x8A\x76\x48\xD4\x0F\x63\x28\xFA\x01\x1B\xF3\xC7\x15\xB8\x53\x22\x7E"
"\x77\x29\x28\x31\x3A\x75\x36\x35\x3A\x00\xB3\xBB\x4D\xEE\x5A\xAF\xD0\xF2\x56\x8A"
"\x10\x2D\x6F\x4B\x2D\x76\x49\x9B\xE9\xA8\x60\x5D\x9E\x7E\x50\x86\xF1\xA1\x0F\x28"
"\x9B\x7B\xE8\xDD\x1F\x87\x4E\x79\x7B\x50\x12\xA7\xB4\x8B\x52\x38\xEC\x7C\xBB\xB9"
"\x55\x87\x11\x1C\x74\xE7\x7F\xA0\xBA\xE3\x34\x5D\x61\xBF\x29\x29\x29\x00"
};
struct key_spec key_ecdsa_valid =
{
"\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28"
"\x35\x3A\x65\x63\x64\x73\x61\x28\x35\x3A\x63\x75\x72\x76\x65\x31"
"\x30\x3A\x4E\x49\x53\x54\x20\x50\x2D\x32\x35\x36\x29\x28\x31\x3A"
"\x71\x36\x35\x3A\x04\x64\x5A\x12\x6F\x86\x7C\x43\x87\x2B\x7C\xAF"
"\x77\xFE\xD8\x22\x31\xEA\xE6\x89\x9F\xAA\xEA\x63\x26\xBC\x49\xED"
"\x85\xC6\xD2\xC9\x8B\x38\xD2\x78\x75\xE6\x1C\x27\x57\x01\xC5\xA1"
"\xE3\xF9\x1F\xBE\xCF\xC1\x72\x73\xFE\xA4\x58\xB6\x6A\x92\x7D\x33"
"\x1D\x02\xC9\xCB\x12\x29\x28\x31\x3A\x64\x33\x33\x3A\x00\x81\x2D"
"\x69\x9A\x5F\x5B\x6F\x2C\x99\x61\x36\x15\x6B\x44\xD8\x06\xC1\x54"
"\xC1\x4C\xFB\x70\x6A\xB6\x64\x81\x78\xF3\x94\x2F\x30\x5D\x29\x29"
"\x28\x37\x3A\x63\x6F\x6D\x6D\x65\x6E\x74\x32\x32\x3A\x2F\x68\x6F"
"\x6D\x65\x2F\x77\x6B\x2F\x2E\x73\x73\x68\x2F\x69\x64\x5F\x65\x63"
"\x64\x73\x61\x29\x29"
};
struct
{
const char *key;
const char *passphrase;
int no_result_expected;
int compare_results;
unsigned char *result_expected;
size_t resultlen_expected;
int ret_expected;
unsigned char *result;
size_t resultlen;
} specs[] =
{
/* Invalid S-Expressions */
/* - non-NULL */
{ "",
"passphrase", 1, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
/* - NULL; disabled, this segfaults */
//{ NULL,
// "passphrase", 1, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
/* Valid and invalid keys. */
{ key_rsa_valid.string,
"passphrase", 0, 0, NULL, 0, 0, NULL, 0 },
{ key_rsa_bogus_0.string,
"passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
{ key_rsa_bogus_1.string,
"passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 },
{ key_ecdsa_valid.string,
"passphrase", 0, 0, NULL, 0, 0, NULL, 0 },
/* FIXME: add more test data. */
};
for (i = 0; i < DIM (specs); i++)
{
ret = agent_protect ((const unsigned char*)specs[i].key,
specs[i].passphrase,
&specs[i].result, &specs[i].resultlen, 0, -1);
if (gpg_err_code (ret) != specs[i].ret_expected)
{
printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n",
i, ret, gpg_strerror (ret),
specs[i].ret_expected, gpg_strerror (specs[i].ret_expected));
abort ();
}
if (specs[i].no_result_expected)
{
assert (! specs[i].result);
assert (! specs[i].resultlen);
}
else
{
if (specs[i].compare_results)
{
assert (specs[i].resultlen == specs[i].resultlen_expected);
if (specs[i].result_expected)
assert (! memcmp (specs[i].result, specs[i].result_expected,
specs[i].resultlen));
else
assert (! specs[i].result);
}
xfree (specs[i].result);
}
}
}
static void
test_agent_unprotect (void)
{
/* Unprotect the key encoded in canonical format. We assume a valid
S-Exp here. */
/* int */
/* agent_unprotect (const unsigned char *protectedkey, const char *passphrase, */
/* unsigned char **result, size_t *resultlen) */
}
static void
test_agent_private_key_type (void)
{
/* Check the type of the private key, this is one of the constants:
PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
PRIVATE_KEY_PROTECTED for an protected private key or
PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored
elsewhere. */
/* int */
/* agent_private_key_type (const unsigned char *privatekey) */
}
static void
test_make_shadow_info (void)
{
#if 0
static struct
{
const char *snstr;
const char *idstr;
const char *expected;
} data[] = {
{ "", "", NULL },
};
int i;
unsigned char *result;
for (i=0; i < DIM(data); i++)
{
result = make_shadow_info (data[i].snstr, data[i].idstr);
if (!result && !data[i].expected)
pass ();
else if (!result && data[i].expected)
fail ();
else if (!data[i].expected)
fail ();
/* fixme: Need to compare the result but also need to check
proper S-expression syntax. */
}
#endif
}
static void
test_agent_shadow_key (void)
{
/* Create a shadow key from a public key. We use the shadow protocol
- "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting
+ "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
S-expression is returned in an allocated buffer RESULT will point
to. The input parameters are expected to be valid canonicalized
S-expressions */
/* int */
/* agent_shadow_key (const unsigned char *pubkey, */
/* const unsigned char *shadow_info, */
/* unsigned char **result) */
}
static void
test_agent_get_shadow_info (void)
{
/* Parse a canonical encoded shadowed key and return a pointer to the
inner list with the shadow_info */
/* int */
/* agent_get_shadow_info (const unsigned char *shadowkey, */
/* unsigned char const **shadow_info) */
}
static void
test_agent_protect_shared_secret (void)
{
}
int
main (int argc, char **argv)
{
(void)argv;
opt.verbose = argc - 1; /* We can do "./t-protect -v -v" */
gcry_control (GCRYCTL_DISABLE_SECMEM);
test_agent_protect ();
test_agent_unprotect ();
test_agent_private_key_type ();
test_make_shadow_info ();
test_agent_shadow_key ();
test_agent_get_shadow_info ();
test_agent_protect_shared_secret ();
return 0;
}
/* Stub function. */
gpg_error_t
convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase,
unsigned char **r_key)
{
(void)s_pgp;
(void)passphrase;
(void)r_key;
return gpg_error (GPG_ERR_BUG);
}
diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk
index 7276787e1..2b3b72b86 100644
--- a/build-aux/speedo.mk
+++ b/build-aux/speedo.mk
@@ -1,1269 +1,1272 @@
# speedo.mk - Speedo rebuilds speedily.
# Copyright (C) 2008, 2014 g10 Code GmbH
#
# speedo 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.
#
# speedo 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 .
# speedo builds gnupg-related packages from GIT and installs them in a
# user directory, thereby providing a non-obstrusive test environment.
# speedo does only work with GNU make. The build system is similar to
# that of gpg4win. The following commands are supported:
#
# make -f speedo.mk all pkg2rep=/dir/with/tarballs
# or
# make -f speedo.mk
#
# Builds all packages and installs them under PLAY/inst. At the end,
# speedo prints commands that can be executed in the local shell to
# make use of the installed packages.
#
# make -f speedo.mk clean
# or
# make -f speedo.mk clean-PACKAGE
#
# Removes all packages or the package PACKAGE from the installation
# and build tree. A subsequent make will rebuild these (and only
# these) packages.
#
# make -f speedo.mk report
# or
# make -f speedo.mk report-PACKAGE
#
# Lists packages and versions.
#
# We need to know our own name.
SPEEDO_MK := $(realpath $(lastword $(MAKEFILE_LIST)))
.PHONY : help native native-gui w32-installer w32-source
.PHONY : git-native git-native-gui git-w32-installer git-w32-source
.PHONY : this-native this-native-gui this-w32-installer this-w32-source
help:
@echo 'usage: make -f speedo.mk TARGET'
@echo ' with TARGET being one of:'
@echo ' help This help'
@echo ' native Native build of the GnuPG core'
@echo ' native-gui Ditto but with pinentry and GPA'
@echo ' w32-installer Build a Windows installer'
@echo ' w32-source Pack a source archive'
@echo ' w32-release Build a Windows release'
@echo ' w32-sign-installer Sign the installer'
@echo
@echo 'You may append INSTALL_PREFIX= for native builds.'
@echo 'Prepend TARGET with "git-" to build from GIT repos.'
@echo 'Prepend TARGET with "this-" to build from the source tarball.'
@echo 'Use SELFCHECK=0 for a non-released version.'
@echo 'Use CUSTOM_SWDB=1 for an already downloaded swdb.lst.'
SPEEDOMAKE := $(MAKE) -f $(SPEEDO_MK) UPD_SWDB=1
native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=release WITH_GUI=0 all
git-native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=git WITH_GUI=0 all
this-native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=this WITH_GUI=0 all
native-gui: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=release WITH_GUI=1 all
git-native-gui: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=git WITH_GUI=1 all
this-native-gui: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=this WITH_GUI=1 all
w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 installer
git-w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=git WITH_GUI=0 installer
this-w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=this WITH_GUI=0 \
CUSTOM_SWDB=1 installer
w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 dist-source
git-w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=git WITH_GUI=0 dist-source
this-w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=this WITH_GUI=0 \
CUSTOM_SWDB=1 dist-source
w32-release: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \
installer-from-source
w32-sign-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \
sign-installer
w32-release-offline: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \
CUSTOM_SWDB=1 pkgrep=${HOME}/b pkg10rep=${HOME}/b \
installer-from-source
# Set this to "git" to build from git,
# to "release" from tarballs,
# to "this" from the unpacked sources.
WHAT=git
# Set target to "native" or "w32"
TARGETOS=
# Set to 1 to build the GUI tools
WITH_GUI=0
# Set to 1 to use a pre-installed swdb.lst instead of the online version.
CUSTOM_SWDB=0
# Set to 1 to really download the swdb.
UPD_SWDB=0
# Set to 0 to skip the GnuPG version self-check
SELFCHECK=1
# Set to the location of the directory with tarballs of
# external packages.
TARBALLS=$(shell pwd)/../tarballs
# Number of parallel make jobs
MAKE_J=3
# Name to use for the w32 installer and sources
INST_NAME=gnupg-w32
# Use this to override the installaion directory for native builds.
INSTALL_PREFIX=none
# The Authenticode key used to sign the Windows installer
AUTHENTICODE_KEY=${HOME}/.gnupg/g10code-authenticode-key.p12
# Directory names.
# They must be absolute, as we switch directories pretty often.
root := $(shell pwd)/PLAY
sdir := $(root)/src
bdir := $(root)/build
bdir6:= $(root)/build-w64
ifeq ($(INSTALL_PREFIX),none)
idir := $(root)/inst
else
idir := $(abspath $(INSTALL_PREFIX))
endif
idir6:= $(root)/inst-w64
stampdir := $(root)/stamps
topsrc := $(shell cd $(dir $(SPEEDO_MK)).. && pwd)
auxsrc := $(topsrc)/build-aux/speedo
patdir := $(topsrc)/build-aux/speedo/patches
w32src := $(topsrc)/build-aux/speedo/w32
# =====BEGIN LIST OF PACKAGES=====
# The packages that should be built. The order is also the build order.
# Fixme: Do we need to build pkg-config for cross-building?
speedo_spkgs = \
libgpg-error npth libgcrypt
ifeq ($(TARGETOS),w32)
speedo_spkgs += \
zlib bzip2 sqlite
ifeq ($(WITH_GUI),1)
speedo_spkgs += gettext libiconv
endif
endif
speedo_spkgs += \
libassuan libksba
ifeq ($(TARGETOS),w32)
speedo_spkgs += \
ntbtls
endif
speedo_spkgs += \
gnupg
ifeq ($(TARGETOS),w32)
ifeq ($(WITH_GUI),1)
speedo_spkgs += \
libffi glib pkg-config
endif
endif
speedo_spkgs += \
gpgme
ifeq ($(TARGETOS),w32)
ifeq ($(WITH_GUI),1)
speedo_spkgs += \
libpng \
gdk-pixbuf atk pixman cairo pango gtk+
endif
endif
ifeq ($(TARGETOS),w32)
speedo_spkgs += pinentry
ifeq ($(WITH_GUI),1)
speedo_spkgs += gpa gpgex
endif
else
ifeq ($(WITH_GUI),1)
speedo_spkgs += pinentry gpa
endif
endif
# =====END LIST OF PACKAGES=====
# Packages which are additionally build for 64 bit Windows. They are
# only used for gpgex and thus we need to build them only if we want
# a full installer.
speedo_w64_spkgs =
ifeq ($(WITH_GUI),1)
speedo_w64_spkgs += libgpg-error libiconv gettext libassuan gpgex
endif
# Packages which use the gnupg autogen.sh build style
speedo_gnupg_style = \
libgpg-error npth libgcrypt \
libassuan libksba ntbtls gnupg gpgme \
pinentry gpa gpgex
# Packages which use only make and no build directory
speedo_make_only_style = \
zlib bzip2
# Get the content of the software DB.
ifeq ($(CUSTOM_SWDB),1)
getswdb_options = --skip-download --skip-verify
else
getswdb_options =
endif
ifeq ($(SELFCHECK),0)
getswdb_options += --skip-selfcheck
endif
ifeq ($(UPD_SWDB),1)
SWDB := $(shell $(topsrc)/build-aux/getswdb.sh $(getswdb_options) && echo okay)
ifeq ($(strip $(SWDB)),)
ifneq ($(WHAT),git)
$(error Error getting GnuPG software version database)
endif
endif
# Version numbers of the released packages
gnupg_ver_this = $(shell cat $(topsrc)/VERSION)
gnupg_ver := $(shell awk '$$1=="gnupg22_ver" {print $$2}' swdb.lst)
libgpg_error_ver := $(shell awk '$$1=="libgpg_error_ver" {print $$2}' swdb.lst)
libgpg_error_sha1:= $(shell awk '$$1=="libgpg_error_sha1" {print $$2}' swdb.lst)
libgpg_error_sha2:= $(shell awk '$$1=="libgpg_error_sha2" {print $$2}' swdb.lst)
npth_ver := $(shell awk '$$1=="npth_ver" {print $$2}' swdb.lst)
npth_sha1 := $(shell awk '$$1=="npth_sha1" {print $$2}' swdb.lst)
npth_sha2 := $(shell awk '$$1=="npth_sha2" {print $$2}' swdb.lst)
libgcrypt_ver := $(shell awk '$$1=="libgcrypt_ver" {print $$2}' swdb.lst)
libgcrypt_sha1 := $(shell awk '$$1=="libgcrypt_sha1" {print $$2}' swdb.lst)
libgcrypt_sha2 := $(shell awk '$$1=="libgcrypt_sha2" {print $$2}' swdb.lst)
libassuan_ver := $(shell awk '$$1=="libassuan_ver" {print $$2}' swdb.lst)
libassuan_sha1 := $(shell awk '$$1=="libassuan_sha1" {print $$2}' swdb.lst)
libassuan_sha2 := $(shell awk '$$1=="libassuan_sha2" {print $$2}' swdb.lst)
libksba_ver := $(shell awk '$$1=="libksba_ver" {print $$2}' swdb.lst)
libksba_sha1 := $(shell awk '$$1=="libksba_sha1" {print $$2}' swdb.lst)
libksba_sha2 := $(shell awk '$$1=="libksba_sha2" {print $$2}' swdb.lst)
ntbtls_ver := $(shell awk '$$1=="ntbtls_ver" {print $$2}' swdb.lst)
ntbtls_sha1 := $(shell awk '$$1=="ntbtls_sha1" {print $$2}' swdb.lst)
ntbtls_sha2 := $(shell awk '$$1=="ntbtls_sha2" {print $$2}' swdb.lst)
gpgme_ver := $(shell awk '$$1=="gpgme_ver" {print $$2}' swdb.lst)
gpgme_sha1 := $(shell awk '$$1=="gpgme_sha1" {print $$2}' swdb.lst)
gpgme_sha2 := $(shell awk '$$1=="gpgme_sha2" {print $$2}' swdb.lst)
pinentry_ver := $(shell awk '$$1=="pinentry_ver" {print $$2}' swdb.lst)
pinentry_sha1 := $(shell awk '$$1=="pinentry_sha1" {print $$2}' swdb.lst)
pinentry_sha2 := $(shell awk '$$1=="pinentry_sha2" {print $$2}' swdb.lst)
gpa_ver := $(shell awk '$$1=="gpa_ver" {print $$2}' swdb.lst)
gpa_sha1 := $(shell awk '$$1=="gpa_sha1" {print $$2}' swdb.lst)
gpa_sha2 := $(shell awk '$$1=="gpa_sha2" {print $$2}' swdb.lst)
gpgex_ver := $(shell awk '$$1=="gpgex_ver" {print $$2}' swdb.lst)
gpgex_sha1 := $(shell awk '$$1=="gpgex_sha1" {print $$2}' swdb.lst)
gpgex_sha2 := $(shell awk '$$1=="gpgex_sha2" {print $$2}' swdb.lst)
zlib_ver := $(shell awk '$$1=="zlib_ver" {print $$2}' swdb.lst)
zlib_sha1 := $(shell awk '$$1=="zlib_sha1_gz" {print $$2}' swdb.lst)
zlib_sha2 := $(shell awk '$$1=="zlib_sha2_gz" {print $$2}' swdb.lst)
bzip2_ver := $(shell awk '$$1=="bzip2_ver" {print $$2}' swdb.lst)
bzip2_sha1 := $(shell awk '$$1=="bzip2_sha1_gz" {print $$2}' swdb.lst)
bzip2_sha2 := $(shell awk '$$1=="bzip2_sha2_gz" {print $$2}' swdb.lst)
sqlite_ver := $(shell awk '$$1=="sqlite_ver" {print $$2}' swdb.lst)
sqlite_sha1 := $(shell awk '$$1=="sqlite_sha1_gz" {print $$2}' swdb.lst)
sqlite_sha2 := $(shell awk '$$1=="sqlite_sha2_gz" {print $$2}' swdb.lst)
$(info Information from the version database)
$(info GnuPG ..........: $(gnupg_ver) (building $(gnupg_ver_this)))
$(info Libgpg-error ...: $(libgpg_error_ver))
$(info Npth ...........: $(npth_ver))
$(info Libgcrypt ......: $(libgcrypt_ver))
$(info Libassuan ......: $(libassuan_ver))
$(info Libksba ........: $(libksba_ver))
$(info Zlib ...........: $(zlib_ver))
$(info Bzip2 ..........: $(bzip2_ver))
$(info SQLite .........: $(sqlite_ver))
$(info NtbTLS .. ......: $(ntbtls_ver))
$(info GPGME ..........: $(gpgme_ver))
$(info Pinentry .......: $(pinentry_ver))
$(info GPA ............: $(gpa_ver))
$(info GpgEX.... ......: $(gpgex_ver))
endif
# Version number for external packages
pkg_config_ver = 0.23
libiconv_ver = 1.14
gettext_ver = 0.18.2.1
libffi_ver = 3.0.13
glib_ver = 2.34.3
libpng_ver = 1.4.12
gdk_pixbuf_ver = 2.26.5
atk_ver = 1.32.0
pango_ver = 1.29.4
pixman_ver = 0.32.4
cairo_ver = 1.12.16
gtk__ver = 2.24.17
# The GIT repository. Using a local repo is much faster.
#gitrep = git://git.gnupg.org
gitrep = ${HOME}/s
# The tarball directories
pkgrep = ftp://ftp.gnupg.org/gcrypt
pkg10rep = ftp://ftp.g10code.com/g10code
pkg2rep = $(TARBALLS)
# For each package, the following variables can be defined:
#
# speedo_pkg_PACKAGE_git: The GIT repository that should be built.
# speedo_pkg_PACKAGE_gitref: The GIT revision to checkout
#
# speedo_pkg_PACKAGE_tar: URL to the tar file that should be built.
#
# Exactly one of the above variables is required. Note that this
# version of speedo does not cache repositories or tar files, and does
# not test the integrity of the downloaded software. If you care
# about this, you can also specify filenames to locally verified files.
# Filenames are differentiated from URLs by starting with a slash '/'.
#
# speedo_pkg_PACKAGE_configure: Extra arguments to configure.
#
# speedo_pkg_PACKAGE_make_args: Extra arguments to make.
#
# speedo_pkg_PACKAGE_make_args_inst: Extra arguments to make install.
#
# Note that you can override the defaults in this file in a local file
# "config.mk"
ifeq ($(WHAT),this)
else ifeq ($(WHAT),git)
speedo_pkg_libgpg_error_git = $(gitrep)/libgpg-error
speedo_pkg_libgpg_error_gitref = master
speedo_pkg_npth_git = $(gitrep)/npth
speedo_pkg_npth_gitref = master
speedo_pkg_libassuan_git = $(gitrep)/libassuan
speedo_pkg_libassuan_gitref = master
speedo_pkg_libgcrypt_git = $(gitrep)/libgcrypt
speedo_pkg_libgcrypt_gitref = master
speedo_pkg_libksba_git = $(gitrep)/libksba
speedo_pkg_libksba_gitref = master
speedo_pkg_ntbtls_git = $(gitrep)/ntbtls
speedo_pkg_ntbtls_gitref = master
speedo_pkg_gpgme_git = $(gitrep)/gpgme
speedo_pkg_gpgme_gitref = master
speedo_pkg_pinentry_git = $(gitrep)/pinentry
speedo_pkg_pinentry_gitref = master
speedo_pkg_gpa_git = $(gitrep)/gpa
speedo_pkg_gpa_gitref = master
speedo_pkg_gpgex_git = $(gitrep)/gpgex
speedo_pkg_gpgex_gitref = master
else ifeq ($(WHAT),release)
speedo_pkg_libgpg_error_tar = \
$(pkgrep)/libgpg-error/libgpg-error-$(libgpg_error_ver).tar.bz2
speedo_pkg_npth_tar = \
$(pkgrep)/npth/npth-$(npth_ver).tar.bz2
speedo_pkg_libassuan_tar = \
$(pkgrep)/libassuan/libassuan-$(libassuan_ver).tar.bz2
speedo_pkg_libgcrypt_tar = \
$(pkgrep)/libgcrypt/libgcrypt-$(libgcrypt_ver).tar.bz2
speedo_pkg_libksba_tar = \
$(pkgrep)/libksba/libksba-$(libksba_ver).tar.bz2
speedo_pkg_ntbtls_tar = \
$(pkgrep)/ntbtls/ntbtls-$(ntbtls_ver).tar.bz2
speedo_pkg_gpgme_tar = \
$(pkgrep)/gpgme/gpgme-$(gpgme_ver).tar.bz2
speedo_pkg_pinentry_tar = \
$(pkgrep)/pinentry/pinentry-$(pinentry_ver).tar.bz2
speedo_pkg_gpa_tar = \
$(pkgrep)/gpa/gpa-$(gpa_ver).tar.bz2
speedo_pkg_gpgex_tar = \
$(pkg10rep)/gpgex/gpgex-$(gpgex_ver).tar.bz2
else
$(error invalid value for WHAT (use on of: git release this))
endif
speedo_pkg_pkg_config_tar = $(pkg2rep)/pkg-config-$(pkg_config_ver).tar.gz
speedo_pkg_zlib_tar = $(pkgrep)/zlib/zlib-$(zlib_ver).tar.gz
speedo_pkg_bzip2_tar = $(pkgrep)/bzip2/bzip2-$(bzip2_ver).tar.gz
speedo_pkg_sqlite_tar = $(pkgrep)/sqlite/sqlite-autoconf-$(sqlite_ver).tar.gz
speedo_pkg_libiconv_tar = $(pkg2rep)/libiconv-$(libiconv_ver).tar.gz
speedo_pkg_gettext_tar = $(pkg2rep)/gettext-$(gettext_ver).tar.gz
speedo_pkg_libffi_tar = $(pkg2rep)/libffi-$(libffi_ver).tar.gz
speedo_pkg_glib_tar = $(pkg2rep)/glib-$(glib_ver).tar.xz
speedo_pkg_libpng_tar = $(pkg2rep)/libpng-$(libpng_ver).tar.bz2
speedo_pkg_gdk_pixbuf_tar = $(pkg2rep)/gdk-pixbuf-$(gdk_pixbuf_ver).tar.xz
speedo_pkg_atk_tar = $(pkg2rep)/atk-$(atk_ver).tar.bz2
speedo_pkg_pango_tar = $(pkg2rep)/pango-$(pango_ver).tar.bz2
speedo_pkg_pixman_tar = $(pkg2rep)/pixman-$(pixman_ver).tar.gz
speedo_pkg_cairo_tar = $(pkg2rep)/cairo-$(cairo_ver).tar.xz
speedo_pkg_gtk__tar = $(pkg2rep)/gtk+-$(gtk__ver).tar.xz
#
# Package build options
#
speedo_pkg_libgpg_error_configure = --enable-static
speedo_pkg_w64_libgpg_error_configure = --enable-static
speedo_pkg_libassuan_configure = --enable-static
speedo_pkg_w64_libassuan_configure = --enable-static
speedo_pkg_libgcrypt_configure = --disable-static
speedo_pkg_libksba_configure = --disable-static
# For now we build ntbtls only static
speedo_pkg_ntbtls_configure = --enable-static --disable-shared
ifeq ($(TARGETOS),w32)
speedo_pkg_gnupg_configure = \
--disable-g13 --enable-ntbtls \
--enable-build-timestamp
else
speedo_pkg_gnupg_configure = --disable-g13
endif
speedo_pkg_gnupg_extracflags = -g
# Create the version info files only for W32 so that they won't get
# installed if for example INSTALL_PREFIX=/usr/local is used.
ifeq ($(TARGETOS),w32)
define speedo_pkg_gnupg_post_install
(set -e; \
sed -n 's/.*PACKAGE_VERSION "\(.*\)"/\1/p' config.h >$(idir)/INST_VERSION; \
sed -n 's/.*W32INFO_VI_PRODUCTVERSION \(.*\)/\1/p' common/w32info-rc.h \
|sed 's/,/./g' >$(idir)/INST_PROD_VERSION )
endef
endif
# The LDFLAGS is needed for -lintl for glib.
ifeq ($(WITH_GUI),1)
speedo_pkg_gpgme_configure = \
--enable-static --enable-w32-glib --disable-w32-qt \
--with-gpg-error-prefix=$(idir) \
LDFLAGS=-L$(idir)/lib
else
speedo_pkg_gpgme_configure = \
--disable-static --disable-w32-glib --disable-w32-qt \
--with-gpg-error-prefix=$(idir) \
LDFLAGS=-L$(idir)/lib
endif
ifeq ($(TARGETOS),w32)
speedo_pkg_pinentry_configure = --disable-pinentry-gtk2
else
speedo_pkg_pinentry_configure = --enable-pinentry-gtk2
endif
speedo_pkg_pinentry_configure += \
- --disable-pinentry-qt4 \
+ --disable-pinentry-qt5 \
+ --disable-pinentry-qt \
+ --disable-pinentry-fltk \
+ --disable-pinentry-tty \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib \
CXXFLAGS=-static-libstdc++
speedo_pkg_gpa_configure = \
--with-libiconv-prefix=$(idir) --with-libintl-prefix=$(idir) \
--with-gpgme-prefix=$(idir) --with-zlib=$(idir) \
--with-libassuan-prefix=$(idir) --with-gpg-error-prefix=$(idir)
speedo_pkg_gpgex_configure = \
--with-gpg-error-prefix=$(idir) \
--with-libassuan-prefix=$(idir) \
--enable-gpa-only
speedo_pkg_w64_gpgex_configure = \
--with-gpg-error-prefix=$(idir6) \
--with-libassuan-prefix=$(idir6) \
--enable-gpa-only
#
# External packages
#
ifeq ($(TARGETOS),w32)
speedo_pkg_zlib_make_args = \
-fwin32/Makefile.gcc PREFIX=$(host)- IMPLIB=libz.dll.a
speedo_pkg_zlib_make_args_inst = \
-fwin32/Makefile.gcc \
BINARY_PATH=$(idir)/bin INCLUDE_PATH=$(idir)/include \
LIBRARY_PATH=$(idir)/lib SHARED_MODE=1 IMPLIB=libz.dll.a
# Zlib needs some special magic to generate a libtool file.
# We also install the pc file here.
define speedo_pkg_zlib_post_install
(set -e; mkdir $(idir)/lib/pkgconfig || true; \
cp $(auxsrc)/zlib.pc $(idir)/lib/pkgconfig/; \
cd $(idir); \
echo "# Generated by libtool" > lib/libz.la \
echo "dlname='../bin/zlib1.dll'" >> lib/libz.la; \
echo "library_names='libz.dll.a'" >> lib/libz.la; \
echo "old_library='libz.a'" >> lib/libz.la; \
echo "dependency_libs=''" >> lib/libz.la; \
echo "current=1" >> lib/libz.la; \
echo "age=2" >> lib/libz.la; \
echo "revision=5" >> lib/libz.la; \
echo "installed=yes" >> lib/libz.la; \
echo "shouldnotlink=no" >> lib/libz.la; \
echo "dlopen=''" >> lib/libz.la; \
echo "dlpreopen=''" >> lib/libz.la; \
echo "libdir=\"$(idir)/lib\"" >> lib/libz.la)
endef
endif
ifeq ($(TARGETOS),w32)
speedo_pkg_bzip2_make_args = \
CC="$(host)-gcc" AR="$(host)-ar" RANLIB="$(host)-ranlib"
speedo_pkg_bzip2_make_args_inst = \
PREFIX=$(idir) CC="$(host)-gcc" AR="$(host)-ar" RANLIB="$(host)-ranlib"
endif
speedo_pkg_w64_libiconv_configure = \
--enable-shared=no --enable-static=yes
speedo_pkg_gettext_configure = \
--with-lib-prefix=$(idir) --with-libiconv-prefix=$(idir) \
CPPFLAGS=-I$(idir)/include LDFLAGS=-L$(idir)/lib
speedo_pkg_w64_gettext_configure = \
--with-lib-prefix=$(idir) --with-libiconv-prefix=$(idir) \
CPPFLAGS=-I$(idir6)/include LDFLAGS=-L$(idir6)/lib
speedo_pkg_gettext_extracflags = -O2
# We only need gettext-runtime and there is sadly no top level
# configure option for this
speedo_pkg_gettext_make_dir = gettext-runtime
speedo_pkg_glib_configure = \
--disable-modular-tests \
--with-libiconv=gnu \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib \
CCC=$(host)-g++ \
LIBFFI_CFLAGS=-I$(idir)/lib/libffi-$(libffi_ver)/include \
LIBFFI_LIBS=\"-L$(idir)/lib -lffi\"
ifeq ($(TARGETOS),w32)
speedo_pkg_glib_extracflags = -march=i486
endif
ifeq ($(TARGETOS),w32)
speedo_pkg_libpng_configure = \
CPPFLAGS=\"-I$(idir)/include -DPNG_BUILD_DLL\" \
LDFLAGS=\"-L$(idir)/lib\" LIBPNG_DEFINES=\"-DPNG_BUILD_DLL\"
else
speedo_pkg_libpng_configure = \
CPPFLAGS=\"-I$(idir)/include\" \
LDFLAGS=\"-L$(idir)/lib\"
endif
ifneq ($(TARGETOS),w32)
speedo_pkg_gdk_pixbuf_configure = --without-libtiff --without-libjpeg
endif
speedo_pkg_pixman_configure = \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib
ifeq ($(TARGETOS),w32)
speedo_pkg_cairo_configure = \
--disable-qt --disable-ft --disable-fc \
--enable-win32 --enable-win32-font \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib
else
speedo_pkg_cairo_configure = \
--disable-qt \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib
endif
speedo_pkg_pango_configure = \
--disable-gtk-doc \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib
speedo_pkg_gtk__configure = \
--disable-cups \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib
# ---------
all: all-speedo
report: report-speedo
clean: clean-speedo
ifeq ($(TARGETOS),w32)
STRIP = i686-w64-mingw32-strip
else
STRIP = strip
endif
W32CC = i686-w64-mingw32-gcc
-include config.mk
#
# The generic speedo code
#
MKDIR=mkdir
MAKENSIS=makensis
SHA1SUM := $(shell $(topsrc)/build-aux/getswdb.sh --find-sha1sum)
ifeq ($(SHA1SUM),false)
$(error The sha1sum tool is missing)
endif
SHA2SUM := $(shell $(topsrc)/build-aux/getswdb.sh --find-sha256sum)
ifeq ($(SHA2SUM),false)
$(error The sha256sum tool is missing)
endif
BUILD_ISODATE=$(shell date -u +%Y-%m-%d)
BUILD_DATESTR=$(subst -,,$(BUILD_ISODATE))
# The next two macros will work only after gnupg has been build.
ifeq ($(TARGETOS),w32)
INST_VERSION=$(shell head -1 $(idir)/INST_VERSION)
INST_PROD_VERSION=$(shell head -1 $(idir)/INST_PROD_VERSION)
endif
# List with packages
speedo_build_list = $(speedo_spkgs)
speedo_w64_build_list = $(speedo_w64_spkgs)
# To avoid running external commands during the read phase (":=" style
# assignments), we check that the targetos has been given
ifneq ($(TARGETOS),)
# Determine build and host system
build := $(shell $(topsrc)/autogen.sh --silent --print-build)
ifeq ($(TARGETOS),w32)
speedo_autogen_buildopt := --build-w32
speedo_autogen_buildopt6 := --build-w64
host := $(shell $(topsrc)/autogen.sh --silent --print-host --build-w32)
host6:= $(shell $(topsrc)/autogen.sh --silent --print-host --build-w64)
speedo_host_build_option := --host=$(host) --build=$(build)
speedo_host_build_option6 := --host=$(host6) --build=$(build)
speedo_w32_cflags := -mms-bitfields
else
speedo_autogen_buildopt :=
host :=
speedo_host_build_option :=
speedo_w32_cflags :=
endif
ifeq ($(MAKE_J),)
speedo_makeopt=
else
speedo_makeopt=-j$(MAKE_J)
endif
# End non-empty TARGETOS
endif
# The playground area is our scratch area, where we unpack, build and
# install the packages.
$(stampdir)/stamp-directories:
$(MKDIR) $(root) || true
$(MKDIR) $(stampdir) || true
$(MKDIR) $(sdir) || true
$(MKDIR) $(bdir) || true
$(MKDIR) $(idir) || true
ifeq ($(TARGETOS),w32)
$(MKDIR) $(bdir6) || true
$(MKDIR) $(idir6) || true
endif
touch $(stampdir)/stamp-directories
# Frob the name $1 by converting all '-' and '+' characters to '_'.
define FROB_macro
$(subst +,_,$(subst -,_,$(1)))
endef
# Get the variable $(1) (which may contain '-' and '+' characters).
define GETVAR
$($(call FROB_macro,$(1)))
endef
# Set a couple of common variables.
define SETVARS
pkg="$(1)"; \
git="$(call GETVAR,speedo_pkg_$(1)_git)"; \
gitref="$(call GETVAR,speedo_pkg_$(1)_gitref)"; \
tar="$(call GETVAR,speedo_pkg_$(1)_tar)"; \
ver="$(call GETVAR,$(1)_ver)"; \
sha2="$(call GETVAR,$(1)_sha2)"; \
sha1="$(call GETVAR,$(1)_sha1)"; \
pkgsdir="$(sdir)/$(1)"; \
if [ "$(1)" = "gnupg" ]; then \
git=''; \
gitref=''; \
tar=''; \
pkgsdir="$(topsrc)"; \
fi; \
pkgbdir="$(bdir)/$(1)"; \
pkgcfg="$(call GETVAR,speedo_pkg_$(1)_configure)"; \
tmp="$(speedo_w32_cflags) \
$(call GETVAR,speedo_pkg_$(1)_extracflags)"; \
if [ x$$$$(echo "$$$$tmp" | tr -d '[:space:]')x != xx ]; then \
pkgextracflags="CFLAGS=\"$$$$tmp\""; \
else \
pkgextracflags=; \
fi; \
pkgmkdir="$(call GETVAR,speedo_pkg_$(1)_make_dir)"; \
pkgmkargs="$(call GETVAR,speedo_pkg_$(1)_make_args)"; \
pkgmkargs_inst="$(call GETVAR,speedo_pkg_$(1)_make_args_inst)"; \
pkgmkargs_uninst="$(call GETVAR,speedo_pkg_$(1)_make_args_uninst)"; \
export PKG_CONFIG="/usr/bin/pkg-config"; \
export PKG_CONFIG_PATH="$(idir)/lib/pkgconfig"; \
[ "$(TARGETOS)" != native ] && export PKG_CONFIG_LIBDIR=""; \
export SYSROOT="$(idir)"; \
export PATH="$(idir)/bin:$${PATH}"; \
export LD_LIBRARY_PATH="$(idir)/lib:$${LD_LIBRARY_PATH}"
endef
define SETVARS_W64
pkg="$(1)"; \
git="$(call GETVAR,speedo_pkg_$(1)_git)"; \
gitref="$(call GETVAR,speedo_pkg_$(1)_gitref)"; \
tar="$(call GETVAR,speedo_pkg_$(1)_tar)"; \
ver="$(call GETVAR,$(1)_ver)"; \
sha2="$(call GETVAR,$(1)_sha2)"; \
sha1="$(call GETVAR,$(1)_sha1)"; \
pkgsdir="$(sdir)/$(1)"; \
if [ "$(1)" = "gnupg" ]; then \
git=''; \
gitref=''; \
tar=''; \
pkgsdir="$(topsrc)"; \
fi; \
pkgbdir="$(bdir6)/$(1)"; \
pkgcfg="$(call GETVAR,speedo_pkg_w64_$(1)_configure)"; \
tmp="$(speedo_w32_cflags) \
$(call GETVAR,speedo_pkg_$(1)_extracflags)"; \
if [ x$$$$(echo "$$$$tmp" | tr -d '[:space:]')x != xx ]; then \
pkgextracflags="CFLAGS=\"$$$$tmp\""; \
else \
pkgextracflags=; \
fi; \
pkgmkdir="$(call GETVAR,speedo_pkg_$(1)_make_dir)"; \
pkgmkargs="$(call GETVAR,speedo_pkg_$(1)_make_args)"; \
pkgmkargs_inst="$(call GETVAR,speedo_pkg_$(1)_make_args_inst)"; \
pkgmkargs_uninst="$(call GETVAR,speedo_pkg_$(1)_make_args_uninst)"; \
export PKG_CONFIG="/usr/bin/pkg-config"; \
export PKG_CONFIG_PATH="$(idir6)/lib/pkgconfig"; \
[ "$(TARGETOS)" != native ] && export PKG_CONFIG_LIBDIR=""; \
export SYSROOT="$(idir6)"; \
export PATH="$(idir6)/bin:$${PATH}"; \
export LD_LIBRARY_PATH="$(idir6)/lib:$${LD_LIBRARY_PATH}"
endef
# Template for source packages.
# Note that the gnupg package is special: The package source dir is
# the same as the topsrc dir and thus we need to detect the gnupg
# package and cd to that directory. We also test that no in-source build
# has been done. autogen.sh is not run for gnupg.
#
define SPKG_template
$(stampdir)/stamp-$(1)-00-unpack: $(stampdir)/stamp-directories
@echo "speedo: /*"
@echo "speedo: * $(1)"
@echo "speedo: */"
@(set -e; cd $(sdir); \
$(call SETVARS,$(1)); \
if [ "$(WHAT)" = "this" ]; then \
echo "speedo: using included source"; \
elif [ "$(1)" = "gnupg" ]; then \
cd $$$${pkgsdir}; \
if [ -f config.log ]; then \
echo "GnuPG has already been build in-source" >&2 ;\
echo "Please run \"make distclean\" and retry" >&2 ;\
exit 1 ; \
fi; \
echo "speedo: unpacking gnupg not needed"; \
elif [ -n "$$$${git}" ]; then \
echo "speedo: unpacking $(1) from $$$${git}:$$$${gitref}"; \
git clone -b "$$$${gitref}" "$$$${git}" "$$$${pkg}"; \
cd "$$$${pkg}"; \
AUTOGEN_SH_SILENT=1 ./autogen.sh; \
elif [ -n "$$$${tar}" ]; then \
echo "speedo: unpacking $(1) from $$$${tar}"; \
case "$$$${tar}" in \
*.gz) pretar=zcat ;; \
*.bz2) pretar=bzcat ;; \
*.xz) pretar=xzcat ;; \
*) pretar=cat ;; \
esac; \
[ -f tmp.tgz ] && rm tmp.tgz; \
case "$$$${tar}" in \
/*) $$$${pretar} < $$$${tar} | tar xf - ;; \
*) wget -q -O - $$$${tar} | tee tmp.tgz \
| $$$${pretar} | tar x$$$${opt}f - ;; \
esac; \
if [ -f tmp.tgz ]; then \
if [ -n "$$$${sha2}" ]; then \
tmp=$$$$($(SHA2SUM) > $(bdir)/pkg-versions.txt)
@echo "speedo: $(1) done"
@touch $(stampdir)/stamp-final-$(1)
$(stampdir)/stamp-w64-final-$(1): $(stampdir)/stamp-w64-$(1)-03-install
@echo "speedo: $(1) (64 bit) done"
@touch $(stampdir)/stamp-w64-final-$(1)
.PHONY : clean-$(1)
clean-$(1):
@echo "speedo: uninstalling $(1)"
@($(call SETVARS,$(1)); \
(cd "$$$${pkgbdir}" 2>/dev/null && \
$(MAKE) --no-print-directory \
$$$${pkgmkargs_uninst} uninstall V=0 ) || true;\
if [ "$(1)" = "gnupg" ]; then \
rm -fR "$$$${pkgbdir}" || true ;\
else \
rm -fR "$$$${pkgsdir}" "$$$${pkgbdir}" || true;\
fi)
-rm -f $(stampdir)/stamp-final-$(1) $(stampdir)/stamp-$(1)-*
.PHONY : build-$(1)
build-$(1): $(stampdir)/stamp-final-$(1)
.PHONY : report-$(1)
report-$(1):
@($(call SETVARS,$(1)); \
echo -n $(1):\ ; \
if [ -n "$$$${git}" ]; then \
if [ -e "$$$${pkgsdir}/.git" ]; then \
cd "$$$${pkgsdir}" && \
git describe ; \
else \
echo missing; \
fi \
elif [ -n "$$$${tar}" ]; then \
base=`echo "$$$${tar}" | sed -e 's,^.*/,,' \
| sed -e 's,\.tar.*$$$$,,'`; \
echo $$$${base} ; \
fi)
endef
# Insert the template for each source package.
$(foreach spkg, $(speedo_spkgs), $(eval $(call SPKG_template,$(spkg))))
$(stampdir)/stamp-final: $(stampdir)/stamp-directories clean-pkg-versions
ifeq ($(TARGETOS),w32)
$(stampdir)/stamp-final: $(addprefix $(stampdir)/stamp-w64-final-,$(speedo_w64_build_list))
endif
$(stampdir)/stamp-final: $(addprefix $(stampdir)/stamp-final-,$(speedo_build_list))
touch $(stampdir)/stamp-final
clean-pkg-versions:
@: >$(bdir)/pkg-versions.txt
all-speedo: $(stampdir)/stamp-final
report-speedo: $(addprefix report-,$(speedo_build_list))
# Just to check if we catched all stamps.
clean-stamps:
$(RM) -fR $(stampdir)
clean-speedo:
$(RM) -fR PLAY
#
# Windows installer
#
# {{{
ifeq ($(TARGETOS),w32)
dist-source: installer
for i in 00 01 02 03; do sleep 1;touch PLAY/stamps/stamp-*-${i}-*;done
(set -e;\
tarname="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).tar" ;\
[ -f "$$tarname" ] && rm "$$tarname" ;\
tar -C $(topsrc) -cf "$$tarname" --exclude-backups --exclude-vcs \
--transform='s,^\./,$(INST_NAME)-$(INST_VERSION)/,' \
--anchored --exclude './PLAY' . ;\
tar --totals -rf "$$tarname" --exclude-backups --exclude-vcs \
--transform='s,^,$(INST_NAME)-$(INST_VERSION)/,' \
PLAY/stamps/stamp-*-00-unpack PLAY/src swdb.lst swdb.lst.sig ;\
[ -f "$$tarname".xz ] && rm "$$tarname".xz;\
xz "$$tarname" ;\
)
# Extract the two latest news entries. */
$(bdir)/NEWS.tmp: $(topsrc)/NEWS
awk '/^Notewo/ {if(okay>1){exit}; okay++};okay {print $0}' \
<$(topsrc)/NEWS >$(bdir)/NEWS.tmp
# Sort the file with the package versions.
$(bdir)/pkg-versions.sorted: $(bdir)/pkg-versions.txt
grep -v '^gnupg ' <$(bdir)/pkg-versions.txt \
| sort | uniq >$(bdir)/pkg-versions.sorted
$(bdir)/README.txt: $(bdir)/NEWS.tmp $(topsrc)/README $(w32src)/README.txt \
$(w32src)/pkg-copyright.txt $(bdir)/pkg-versions.sorted
sed -e '/^;.*/d;' \
-e '/!NEWSFILE!/{r $(bdir)/NEWS.tmp' -e 'd;}' \
-e '/!GNUPGREADME!/{r $(topsrc)/README' -e 'd;}' \
-e '/!PKG-COPYRIGHT!/{r $(w32src)/pkg-copyright.txt' -e 'd;}' \
-e '/!PKG-VERSIONS!/{r $(bdir)/pkg-versions.sorted' -e 'd;}' \
-e 's,!VERSION!,$(INST_VERSION),g' \
< $(w32src)/README.txt \
| sed -e '/^#/d' \
| awk '{printf "%s\r\n", $$0}' >$(bdir)/README.txt
$(bdir)/g4wihelp.dll: $(w32src)/g4wihelp.c $(w32src)/exdll.h
(set -e; cd $(bdir); \
$(W32CC) -I. -shared -O2 -o g4wihelp.dll $(w32src)/g4wihelp.c \
-lwinmm -lgdi32; \
$(STRIP) g4wihelp.dll)
w32_insthelpers: $(bdir)/g4wihelp.dll
$(bdir)/inst-options.ini: $(w32src)/inst-options.ini
cat $(w32src)/inst-options.ini >$(bdir)/inst-options.ini
extra_installer_options =
ifeq ($(WITH_GUI),1)
extra_installer_options += -DWITH_GUI=1
endif
installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt
$(MAKENSIS) -V2 \
-DINST_DIR=$(idir) \
-DINST6_DIR=$(idir6) \
-DBUILD_DIR=$(bdir) \
-DTOP_SRCDIR=$(topsrc) \
-DW32_SRCDIR=$(w32src) \
-DBUILD_ISODATE=$(BUILD_ISODATE) \
-DBUILD_DATESTR=$(BUILD_DATESTR) \
-DNAME=$(INST_NAME) \
-DVERSION=$(INST_VERSION) \
-DPROD_VERSION=$(INST_PROD_VERSION) \
$(extra_installer_options) $(w32src)/inst.nsi
@echo "Ready: $(idir)/$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe"
define MKSWDB_commands
( pref="#+macro: gnupg22_w32_" ;\
echo "$${pref}ver $(INST_VERSION)_$(BUILD_DATESTR)" ;\
echo "$${pref}date $(2)" ;\
echo "$${pref}size $$(wc -c <$(1)|awk '{print int($$1/1024)}')k";\
echo "$${pref}sha1 $$(sha1sum <$(1)|cut -d' ' -f1)" ;\
echo "$${pref}sha2 $$(sha256sum <$(1)|cut -d' ' -f1)" ;\
) | tee $(1).swdb
endef
# Build the installer from the source tarball.
installer-from-source: dist-source
(set -e;\
[ -d PLAY-release ] && rm -rf PLAY-release; \
mkdir PLAY-release;\
cd PLAY-release; \
tar xJf "../$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).tar.xz";\
cd $(INST_NAME)-$(INST_VERSION); \
$(MAKE) -f build-aux/speedo.mk this-w32-installer SELFCHECK=0;\
reldate="$$(date -u +%Y-%m-%d)" ;\
exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
cp "PLAY/inst/$$exefile" ../.. ;\
exefile="../../$$exefile" ;\
$(call MKSWDB_commands,$${exefile},$${reldate}); \
)
# This target repeats some of the installer-from-source steps but it
# is intended to be called interactively, so that the passphrase can be
# entered.
sign-installer:
@(set -e; \
cd PLAY-release; \
cd $(INST_NAME)-$(INST_VERSION); \
reldate="$$(date -u +%Y-%m-%d)" ;\
exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
echo "speedo: /*" ;\
echo "speedo: * Signing installer" ;\
echo "speedo: * Key: $(AUTHENTICODE_KEY)";\
echo "speedo: */" ;\
osslsigncode sign -pkcs12 $(AUTHENTICODE_KEY) -askpass \
-h sha256 -in "PLAY/inst/$$exefile" -out "../../$$exefile" ;\
exefile="../../$$exefile" ;\
$(call MKSWDB_commands,$${exefile},$${reldate}); \
echo "speedo: /*" ;\
echo "speedo: * Verification result" ;\
echo "speedo: */" ;\
osslsigncode verify $${exefile} \
)
endif
# }}} W32
#
# Check availibility of standard tools
#
check-tools:
#
# Mark phony targets
#
.PHONY: all all-speedo report-speedo clean-stamps clean-speedo installer \
w32_insthelpers check-tools clean-pkg-versions
diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi
index afe280462..65df9708b 100644
--- a/doc/gpg-agent.texi
+++ b/doc/gpg-agent.texi
@@ -1,1578 +1,1589 @@
@c Copyright (C) 2002 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking GPG-AGENT
@chapter Invoking GPG-AGENT
@cindex GPG-AGENT command options
@cindex command options
@cindex options, GPG-AGENT command
@manpage gpg-agent.1
@ifset manverb
.B gpg-agent
\- Secret key management for GnuPG
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-agent
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.br
.B gpg-agent
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.B \-\-server
.br
.B gpg-agent
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.B \-\-daemon
.RI [ command_line ]
@end ifset
@mansect description
@command{gpg-agent} is a daemon to manage secret (private) keys
independently from any protocol. It is used as a backend for
@command{gpg} and @command{gpgsm} as well as for a couple of other
utilities.
The agent is automatically started on demand by @command{gpg},
@command{gpgsm}, @command{gpgconf}, or @command{gpg-connect-agent}.
Thus there is no reason to start it manually. In case you want to use
the included Secure Shell Agent you may start the agent using:
@c From dkg on gnupg-devel on 2016-04-21:
@c
@c Here's an attempt at writing a short description of the goals of an
@c isolated cryptographic agent:
@c
@c A cryptographic agent should control access to secret key material.
@c The agent permits use of the secret key material by a supplicant
@c without providing a copy of the secret key material to the supplicant.
@c
@c An isolated cryptographic agent separates the request for use of
@c secret key material from permission for use of secret key material.
@c That is, the system or process requesting use of the key (the
@c "supplicant") can be denied use of the key by the owner/operator of
@c the agent (the "owner"), which the supplicant has no control over.
@c
@c One way of enforcing this split is a per-key or per-session
@c passphrase, known only by the owner, which must be supplied to the
@c agent to permit the use of the secret key material. Another way is
@c with an out-of-band permission mechanism (e.g. a button or GUI
@c interface that the owner has access to, but the supplicant does not).
@c
@c The rationale for this separation is that it allows access to the
@c secret key to be tightly controlled and audited, and it doesn't permit
@c the supplicant to either copy the key or to override the owner's
@c intentions.
@example
gpg-connect-agent /bye
@end example
@noindent
If you want to manually terminate the currently-running agent, you can
safely do so with:
@example
gpgconf --kill gpg-agent
@end example
@noindent
@efindex GPG_TTY
You should always add the following lines to your @code{.bashrc} or
whatever initialization file is used for all shell invocations:
@smallexample
GPG_TTY=$(tty)
export GPG_TTY
@end smallexample
@noindent
It is important that this environment variable always reflects the
output of the @code{tty} command. For W32 systems this option is not
required.
Please make sure that a proper pinentry program has been installed
under the default filename (which is system dependent) or use the
option @option{pinentry-program} to specify the full name of that program.
It is often useful to install a symbolic link from the actual used
pinentry (e.g. @file{@value{BINDIR}/pinentry-gtk}) to the expected
one (e.g. @file{@value{BINDIR}/pinentry}).
@manpause
@noindent
@xref{Option Index}, for an index to @command{GPG-AGENT}'s commands and options.
@mancont
@menu
* Agent Commands:: List of all commands.
* Agent Options:: List of all options.
* Agent Configuration:: Configuration files.
* Agent Signals:: Use of some signals.
* Agent Examples:: Some usage examples.
* Agent Protocol:: The protocol the agent uses.
@end menu
@mansect commands
@node Agent Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help
@itemx -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
@item --daemon [@var{command line}]
@opindex daemon
Start the gpg-agent as a daemon; that is, detach it from the console
and run it in the background.
As an alternative you may create a new process as a child of
gpg-agent: @code{gpg-agent --daemon /bin/sh}. This way you get a new
shell with the environment setup properly; after you exit from this
shell, gpg-agent terminates within a few seconds.
@item --supervised
@opindex supervised
Run in the foreground, sending logs by default to stderr, and
listening on provided file descriptors, which must already be bound to
listening sockets. This command is useful when running under systemd
or other similar process supervision schemes. This option is not
supported on Windows.
In --supervised mode, different file descriptors can be provided for
use as different socket types (e.g. ssh, extra) as long as they are
identified in the environment variable @code{LISTEN_FDNAMES} (see
sd_listen_fds(3) on some Linux distributions for more information on
this convention).
@end table
@mansect options
@node Agent Options
@section Option Summary
Options may either be used on the command line or, after stripping off
the two leading dashes, in the configuration file.
@table @gnupgtabopt
@anchor{option --options}
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{gpg-agent.conf} and expected in the @file{.gnupg} directory
directly below the home directory of the user. This option is ignored
if used in an options file.
@anchor{option --homedir}
@include opt-homedir.texi
@item -v
@item --verbose
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @command{gpg-agent}, such as @samp{-vv}.
@item -q
@item --quiet
@opindex quiet
Try to be as quiet as possible.
@item --batch
@opindex batch
Don't invoke a pinentry or do any other thing requiring human interaction.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back or
forth to @var{epoch} which is the number of seconds elapsed since the year
1970.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
This option is only useful for debugging and the behavior may change at
any time without notice. FLAGS are bit encoded and may be given in
usual C-Syntax. The currently defined bits are:
@table @code
@item 0 (1)
X.509 or OpenPGP protocol related data
@item 1 (2)
values of big number integers
@item 2 (4)
low level crypto operations
@item 5 (32)
memory allocation
@item 6 (64)
caching
@item 7 (128)
show memory statistics
@item 9 (512)
write hashed data to files named @code{dbgmd-000*}
@item 10 (1024)
trace Assuan protocol
@item 12 (4096)
bypass all certificate validation
@end table
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item --debug-quick-random
@opindex debug-quick-random
This option inhibits the use of the very secure random quality level
(Libgcrypt’s @code{GCRY_VERY_STRONG_RANDOM}) and degrades all request
down to standard random quality. It is only used for testing and
should not be used for any production quality keys. This option is
only effective when given on the command line.
On GNU/Linux, another way to quickly generate insecure keys is to use
@command{rngd} to fill the kernel's entropy pool with lower quality
random data. @command{rngd} is typically provided by the
@command{rng-tools} package. It can be run as follows: @samp{sudo
rngd -f -r /dev/urandom}.
@item --debug-pinentry
@opindex debug-pinentry
This option enables extra debug information pertaining to the
Pinentry. As of now it is only useful when used along with
@code{--debug 1024}.
@item --no-detach
@opindex no-detach
Don't detach the process from the console. This is mainly useful for
debugging.
@item -s
@itemx --sh
@itemx -c
@itemx --csh
@opindex sh
@opindex csh
@efindex SHELL
Format the info output in daemon mode for use with the standard Bourne
shell or the C-shell respectively. The default is to guess it based on
the environment variable @code{SHELL} which is correct in almost all
cases.
@item --grab
@itemx --no-grab
@opindex grab
@opindex no-grab
Tell the pinentry to grab the keyboard and mouse. This option should
be used on X-Servers to avoid X-sniffing attacks. Any use of the
option @option{--grab} overrides an used option @option{--no-grab}.
The default is @option{--no-grab}.
@anchor{option --log-file}
@item --log-file @var{file}
@opindex log-file
@efindex HKCU\Software\GNU\GnuPG:DefaultLogFile
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does. Use @file{socket://} to log to
socket. If neither a log file nor a log file descriptor has been set
on a Windows platform, the Registry entry
@code{HKCU\Software\GNU\GnuPG:DefaultLogFile}, if set, is used to
specify the logging output.
@anchor{option --no-allow-mark-trusted}
@item --no-allow-mark-trusted
@opindex no-allow-mark-trusted
Do not allow clients to mark keys as trusted, i.e. put them into the
@file{trustlist.txt} file. This makes it harder for users to inadvertently
accept Root-CA keys.
@anchor{option --allow-preset-passphrase}
@item --allow-preset-passphrase
@opindex allow-preset-passphrase
This option allows the use of @command{gpg-preset-passphrase} to seed the
internal cache of @command{gpg-agent} with passphrases.
@anchor{option --no-allow-loopback-pinentry}
@item --no-allow-loopback-pinentry
@item --allow-loopback-pinentry
@opindex no-allow-loopback-pinentry
@opindex allow-loopback-pinentry
Disallow or allow clients to use the loopback pinentry features; see
the option @option{pinentry-mode} for details. Allow is the default.
The @option{--force} option of the Assuan command @command{DELETE_KEY}
is also controlled by this option: The option is ignored if a loopback
pinentry is disallowed.
@item --no-allow-external-cache
@opindex no-allow-external-cache
Tell Pinentry not to enable features which use an external cache for
passphrases.
Some desktop environments prefer to unlock all
credentials with one master password and may have installed a Pinentry
which employs an additional external cache to implement such a policy.
By using this option the Pinentry is advised not to make use of such a
cache and instead always ask the user for the requested passphrase.
@item --allow-emacs-pinentry
@opindex allow-emacs-pinentry
Tell Pinentry to allow features to divert the passphrase entry to a
running Emacs instance. How this is exactly handled depends on the
version of the used Pinentry.
@item --ignore-cache-for-signing
@opindex ignore-cache-for-signing
This option will let @command{gpg-agent} bypass the passphrase cache for all
signing operation. Note that there is also a per-session option to
control this behavior but this command line option takes precedence.
@item --default-cache-ttl @var{n}
@opindex default-cache-ttl
Set the time a cache entry is valid to @var{n} seconds. The default
is 600 seconds. Each time a cache entry is accessed, the entry's
timer is reset. To set an entry's maximum lifetime, use
@command{max-cache-ttl}.
@item --default-cache-ttl-ssh @var{n}
@opindex default-cache-ttl
Set the time a cache entry used for SSH keys is valid to @var{n}
seconds. The default is 1800 seconds. Each time a cache entry is
accessed, the entry's timer is reset. To set an entry's maximum
lifetime, use @command{max-cache-ttl-ssh}.
@item --max-cache-ttl @var{n}
@opindex max-cache-ttl
Set the maximum time a cache entry is valid to @var{n} seconds. After
this time a cache entry will be expired even if it has been accessed
recently or has been set using @command{gpg-preset-passphrase}. The
default is 2 hours (7200 seconds).
@item --max-cache-ttl-ssh @var{n}
@opindex max-cache-ttl-ssh
Set the maximum time a cache entry used for SSH keys is valid to
@var{n} seconds. After this time a cache entry will be expired even
if it has been accessed recently or has been set using
@command{gpg-preset-passphrase}. The default is 2 hours (7200
seconds).
@item --enforce-passphrase-constraints
@opindex enforce-passphrase-constraints
Enforce the passphrase constraints by not allowing the user to bypass
them using the ``Take it anyway'' button.
@item --min-passphrase-len @var{n}
@opindex min-passphrase-len
Set the minimal length of a passphrase. When entering a new passphrase
shorter than this value a warning will be displayed. Defaults to 8.
@item --min-passphrase-nonalpha @var{n}
@opindex min-passphrase-nonalpha
Set the minimal number of digits or special characters required in a
passphrase. When entering a new passphrase with less than this number
of digits or special characters a warning will be displayed. Defaults
to 1.
@item --check-passphrase-pattern @var{file}
@opindex check-passphrase-pattern
Check the passphrase against the pattern given in @var{file}. When
entering a new passphrase matching one of these pattern a warning will
be displayed. @var{file} should be an absolute filename. The default is
not to use any pattern file.
Security note: It is known that checking a passphrase against a list of
pattern or even against a complete dictionary is not very effective to
enforce good passphrases. Users will soon figure up ways to bypass such
a policy. A better policy is to educate users on good security
behavior and optionally to run a passphrase cracker regularly on all
users passphrases to catch the very simple ones.
@item --max-passphrase-days @var{n}
@opindex max-passphrase-days
Ask the user to change the passphrase if @var{n} days have passed since
the last change. With @option{--enforce-passphrase-constraints} set the
user may not bypass this check.
@item --enable-passphrase-history
@opindex enable-passphrase-history
This option does nothing yet.
@item --pinentry-invisible-char @var{char}
@opindex pinentry-invisible-char
This option asks the Pinentry to use @var{char} for displaying hidden
characters. @var{char} must be one character UTF-8 string. A
Pinentry may or may not honor this request.
@item --pinentry-timeout @var{n}
@opindex pinentry-timeout
This option asks the Pinentry to timeout after @var{n} seconds with no
user input. The default value of 0 does not ask the pinentry to
timeout, however a Pinentry may use its own default timeout value in
this case. A Pinentry may or may not honor this request.
@item --pinentry-program @var{filename}
@opindex pinentry-program
Use program @var{filename} as the PIN entry. The default is
installation dependent. With the default configuration the name of
the default pinentry is @file{pinentry}; if that file does not exist
but a @file{pinentry-basic} exist the latter is used.
On a Windows platform the default is to use the first existing program
from this list:
@file{bin\pinentry.exe},
@file{..\Gpg4win\bin\pinentry.exe},
@file{..\Gpg4win\pinentry.exe},
@file{..\GNU\GnuPG\pinentry.exe},
@file{..\GNU\bin\pinentry.exe},
@file{bin\pinentry-basic.exe}
where the file names are relative to the GnuPG installation directory.
@item --pinentry-touch-file @var{filename}
@opindex pinentry-touch-file
By default the filename of the socket gpg-agent is listening for
requests is passed to Pinentry, so that it can touch that file before
exiting (it does this only in curses mode). This option changes the
file passed to Pinentry to @var{filename}. The special name
@code{/dev/null} may be used to completely disable this feature. Note
that Pinentry will not create that file, it will only change the
modification and access time.
@item --scdaemon-program @var{filename}
@opindex scdaemon-program
Use program @var{filename} as the Smartcard daemon. The default is
installation dependent and can be shown with the @command{gpgconf}
command.
@item --disable-scdaemon
@opindex disable-scdaemon
Do not make use of the scdaemon tool. This option has the effect of
disabling the ability to do smartcard operations. Note, that enabling
this option at runtime does not kill an already forked scdaemon.
@item --disable-check-own-socket
@opindex disable-check-own-socket
@command{gpg-agent} employs a periodic self-test to detect a stolen
socket. This usually means a second instance of @command{gpg-agent}
has taken over the socket and @command{gpg-agent} will then terminate
itself. This option may be used to disable this self-test for
debugging purposes.
@item --use-standard-socket
@itemx --no-use-standard-socket
@itemx --use-standard-socket-p
@opindex use-standard-socket
@opindex no-use-standard-socket
@opindex use-standard-socket-p
Since GnuPG 2.1 the standard socket is always used. These options
have no more effect. The command @code{gpg-agent
--use-standard-socket-p} will thus always return success.
@item --display @var{string}
@itemx --ttyname @var{string}
@itemx --ttytype @var{string}
@itemx --lc-ctype @var{string}
@itemx --lc-messages @var{string}
@itemx --xauthority @var{string}
@opindex display
@opindex ttyname
@opindex ttytype
@opindex lc-ctype
@opindex lc-messages
@opindex xauthority
These options are used with the server mode to pass localization
information.
@item --keep-tty
@itemx --keep-display
@opindex keep-tty
@opindex keep-display
Ignore requests to change the current @code{tty} or X window system's
@code{DISPLAY} variable respectively. This is useful to lock the
pinentry to pop up at the @code{tty} or display you started the agent.
@anchor{option --extra-socket}
@item --extra-socket @var{name}
@opindex extra-socket
The extra socket is created by default, you may use this option to
change the name of the socket. To disable the creation of the socket
use ``none'' or ``/dev/null'' for @var{name}.
Also listen on native gpg-agent connections on the given socket. The
intended use for this extra socket is to setup a Unix domain socket
forwarding from a remote machine to this socket on the local machine.
A @command{gpg} running on the remote machine may then connect to the
local gpg-agent and use its private keys. This enables decrypting or
signing data on a remote machine without exposing the private keys to the
remote machine.
@anchor{option --enable-extended-key-format}
@item --enable-extended-key-format
@opindex enable-extended-key-format
This option creates keys in the extended private key format. Changing
the passphrase of a key will also convert the key to that new format.
Using this option makes the private keys unreadable for gpg-agent
versions before 2.1.12. The advantage of the extended private key
format is that it is text based and can carry additional meta data.
Note that this option also changes the key protection format to use
OCB mode.
@anchor{option --enable-ssh-support}
@item --enable-ssh-support
@itemx --enable-putty-support
@opindex enable-ssh-support
@opindex enable-putty-support
The OpenSSH Agent protocol is always enabled, but @command{gpg-agent}
will only set the @code{SSH_AUTH_SOCK} variable if this flag is given.
In this mode of operation, the agent does not only implement the
gpg-agent protocol, but also the agent protocol used by OpenSSH
(through a separate socket). Consequently, it should be possible to use
the gpg-agent as a drop-in replacement for the well known ssh-agent.
SSH Keys, which are to be used through the agent, need to be added to
the gpg-agent initially through the ssh-add utility. When a key is
added, ssh-add will ask for the password of the provided key file and
send the unprotected key material to the agent; this causes the
gpg-agent to ask for a passphrase, which is to be used for encrypting
the newly received key and storing it in a gpg-agent specific
directory.
Once a key has been added to the gpg-agent this way, the gpg-agent
will be ready to use the key.
Note: in case the gpg-agent receives a signature request, the user might
need to be prompted for a passphrase, which is necessary for decrypting
the stored key. Since the ssh-agent protocol does not contain a
mechanism for telling the agent on which display/terminal it is running,
gpg-agent's ssh-support will use the TTY or X display where gpg-agent
has been started. To switch this display to the current one, the
following command may be used:
@smallexample
gpg-connect-agent updatestartuptty /bye
@end smallexample
Although all GnuPG components try to start the gpg-agent as needed, this
is not possible for the ssh support because ssh does not know about it.
Thus if no GnuPG tool which accesses the agent has been run, there is no
guarantee that ssh is able to use gpg-agent for authentication. To fix
this you may start gpg-agent if needed using this simple command:
@smallexample
gpg-connect-agent /bye
@end smallexample
Adding the @option{--verbose} shows the progress of starting the agent.
The @option{--enable-putty-support} is only available under Windows
and allows the use of gpg-agent with the ssh implementation
@command{putty}. This is similar to the regular ssh-agent support but
makes use of Windows message queue as required by @command{putty}.
@anchor{option --ssh-fingerprint-digest}
@item --ssh-fingerprint-digest
@opindex ssh-fingerprint-digest
Select the digest algorithm used to compute ssh fingerprints that are
communicated to the user, e.g. in pinentry dialogs. OpenSSH has
transitioned from using MD5 to the more secure SHA256.
+
+@item --auto-expand-secmem @var{n}
+@opindex auto-expand-secmem
+Allow Libgcrypt to expand its secure memory area as required. The
+optional value @var{n} is a non-negative integer with a suggested size
+in bytes of each additionally allocated secure memory area. The value
+is rounded up to the next 32 KiB; usual C style prefixes are allowed.
+For an heavy loaded gpg-agent with many concurrent connection this
+option avoids sign or decrypt errors due to out of secure memory error
+returns.
+
@item --s2k-count @var{n}
@opindex s2k-count
Specify the iteration count used to protect the passphrase. This
option can be used to override the auto-calibration done by default.
The auto-calibration computes a count which requires 100ms to mangle
a given passphrase.
To view the actually used iteration count and the milliseconds
required for an S2K operation use:
@example
gpg-connect-agent 'GETINFO s2k_count' /bye
gpg-connect-agent 'GETINFO s2k_time' /bye
@end example
To view the auto-calibrated count use:
@example
gpg-connect-agent 'GETINFO s2k_count_cal' /bye
@end example
@end table
@mansect files
@node Agent Configuration
@section Configuration
There are a few configuration files needed for the operation of the
agent. By default they may all be found in the current home directory
(@pxref{option --homedir}).
@table @file
@item gpg-agent.conf
@efindex gpg-agent.conf
This is the standard configuration file read by @command{gpg-agent} on
startup. It may contain any valid long option; the leading
two dashes may not be entered and the option may not be abbreviated.
This file is also read after a @code{SIGHUP} however only a few
options will actually have an effect. This default name may be
changed on the command line (@pxref{option --options}).
You should backup this file.
@item trustlist.txt
@efindex trustlist.txt
This is the list of trusted keys. You should backup this file.
Comment lines, indicated by a leading hash mark, as well as empty
lines are ignored. To mark a key as trusted you need to enter its
fingerprint followed by a space and a capital letter @code{S}. Colons
may optionally be used to separate the bytes of a fingerprint; this
enables cutting and pasting the fingerprint from a key listing output. If
the line is prefixed with a @code{!} the key is explicitly marked as
not trusted.
Here is an example where two keys are marked as ultimately trusted
and one as not trusted:
@cartouche
@smallexample
# CN=Wurzel ZS 3,O=Intevation GmbH,C=DE
A6935DD34EF3087973C706FC311AA2CCF733765B S
# CN=PCA-1-Verwaltung-02/O=PKI-1-Verwaltung/C=DE
DC:BD:69:25:48:BD:BB:7E:31:6E:BB:80:D3:00:80:35:D4:F8:A6:CD S
# CN=Root-CA/O=Schlapphuete/L=Pullach/C=DE
!14:56:98:D3:FE:9C:CA:5A:31:6E:BC:81:D3:11:4E:00:90:A3:44:C2 S
@end smallexample
@end cartouche
Before entering a key into this file, you need to ensure its
authenticity. How to do this depends on your organisation; your
administrator might have already entered those keys which are deemed
trustworthy enough into this file. Places where to look for the
fingerprint of a root certificate are letters received from the CA or
the website of the CA (after making 100% sure that this is indeed the
website of that CA). You may want to consider disallowing interactive
updates of this file by using the @ref{option --no-allow-mark-trusted}.
It might even be advisable to change the permissions to read-only so
that this file can't be changed inadvertently.
As a special feature a line @code{include-default} will include a global
list of trusted certificates (e.g. @file{@value{SYSCONFDIR}/trustlist.txt}).
This global list is also used if the local list is not available.
It is possible to add further flags after the @code{S} for use by the
caller:
@table @code
@item relax
@cindex relax
Relax checking of some root certificate requirements. As of now this
flag allows the use of root certificates with a missing basicConstraints
attribute (despite that it is a MUST for CA certificates) and disables
CRL checking for the root certificate.
@item cm
If validation of a certificate finally issued by a CA with this flag set
fails, try again using the chain validation model.
@end table
@item sshcontrol
@efindex sshcontrol
This file is used when support for the secure shell agent protocol has
been enabled (@pxref{option --enable-ssh-support}). Only keys present in
this file are used in the SSH protocol. You should backup this file.
The @command{ssh-add} tool may be used to add new entries to this file;
you may also add them manually. Comment lines, indicated by a leading
hash mark, as well as empty lines are ignored. An entry starts with
optional whitespace, followed by the keygrip of the key given as 40 hex
digits, optionally followed by the caching TTL in seconds and another
optional field for arbitrary flags. A non-zero TTL overrides the global
default as set by @option{--default-cache-ttl-ssh}.
The only flag support is @code{confirm}. If this flag is found for a
key, each use of the key will pop up a pinentry to confirm the use of
that key. The flag is automatically set if a new key was loaded into
@code{gpg-agent} using the option @option{-c} of the @code{ssh-add}
command.
The keygrip may be prefixed with a @code{!} to disable an entry.
The following example lists exactly one key. Note that keys available
through a OpenPGP smartcard in the active smartcard reader are
implicitly added to this list; i.e. there is no need to list them.
@cartouche
@smallexample
# Key added on: 2011-07-20 20:38:46
# Fingerprint: 5e:8d:c4:ad:e7:af:6e:27:8a:d6:13:e4:79:ad:0b:81
34B62F25E277CF13D3C6BCEBFD3F85D08F0A864B 0 confirm
@end smallexample
@end cartouche
@item private-keys-v1.d/
@efindex private-keys-v1.d
This is the directory where gpg-agent stores the private keys. Each
key is stored in a file with the name made up of the keygrip and the
suffix @file{key}. You should backup all files in this directory
and take great care to keep this backup closed away.
@end table
Note that on larger installations, it is useful to put predefined
files into the directory @file{@value{SYSCONFSKELDIR}} so that newly created
users start up with a working configuration. For existing users the
a small helper script is provided to create these files (@pxref{addgnupghome}).
@c
@c Agent Signals
@c
@mansect signals
@node Agent Signals
@section Use of some signals
A running @command{gpg-agent} may be controlled by signals, i.e. using
the @command{kill} command to send a signal to the process.
Here is a list of supported signals:
@table @gnupgtabopt
@item SIGHUP
@cpindex SIGHUP
This signal flushes all cached passphrases and if the program has been
started with a configuration file, the configuration file is read
again. Only certain options are honored: @code{quiet},
@code{verbose}, @code{debug}, @code{debug-all}, @code{debug-level},
@code{debug-pinentry},
@code{no-grab},
@code{pinentry-program},
@code{pinentry-invisible-char},
@code{default-cache-ttl},
@code{max-cache-ttl}, @code{ignore-cache-for-signing},
@code{s2k-count},
@code{no-allow-external-cache}, @code{allow-emacs-pinentry},
@code{no-allow-mark-trusted}, @code{disable-scdaemon}, and
@code{disable-check-own-socket}. @code{scdaemon-program} is also
supported but due to the current implementation, which calls the
scdaemon only once, it is not of much use unless you manually kill the
scdaemon.
@item SIGTERM
@cpindex SIGTERM
Shuts down the process but waits until all current requests are
fulfilled. If the process has received 3 of these signals and requests
are still pending, a shutdown is forced.
@item SIGINT
@cpindex SIGINT
Shuts down the process immediately.
@item SIGUSR1
@cpindex SIGUSR1
Dump internal information to the log file.
@item SIGUSR2
@cpindex SIGUSR2
This signal is used for internal purposes.
@end table
@c
@c Examples
@c
@mansect examples
@node Agent Examples
@section Examples
It is important to set the environment variable @code{GPG_TTY} in
your login shell, for example in the @file{~/.bashrc} init script:
@cartouche
@example
export GPG_TTY=$(tty)
@end example
@end cartouche
If you enabled the Ssh Agent Support, you also need to tell ssh about
it by adding this to your init script:
@cartouche
@example
unset SSH_AGENT_PID
if [ "$@{gnupg_SSH_AUTH_SOCK_by:-0@}" -ne $$ ]; then
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi
@end example
@end cartouche
@c
@c Assuan Protocol
@c
@manpause
@node Agent Protocol
@section Agent's Assuan Protocol
Note: this section does only document the protocol, which is used by
GnuPG components; it does not deal with the ssh-agent protocol. To
see the full specification of each command, use
@example
gpg-connect-agent 'help COMMAND' /bye
@end example
@noindent
or just 'help' to list all available commands.
@noindent
The @command{gpg-agent} daemon is started on demand by the GnuPG
components.
To identify a key we use a thing called keygrip which is the SHA-1 hash
of an canonical encoded S-Expression of the public key as used in
Libgcrypt. For the purpose of this interface the keygrip is given as a
hex string. The advantage of using this and not the hash of a
certificate is that it will be possible to use the same keypair for
different protocols, thereby saving space on the token used to keep the
secret keys.
The @command{gpg-agent} may send status messages during a command or when
returning from a command to inform a client about the progress or result of an
operation. For example, the @var{INQUIRE_MAXLEN} status message may be sent
during a server inquire to inform the client of the maximum usable length of
the inquired data (which should not be exceeded).
@menu
* Agent PKDECRYPT:: Decrypting a session key
* Agent PKSIGN:: Signing a Hash
* Agent GENKEY:: Generating a Key
* Agent IMPORT:: Importing a Secret Key
* Agent EXPORT:: Exporting a Secret Key
* Agent ISTRUSTED:: Importing a Root Certificate
* Agent GET_PASSPHRASE:: Ask for a passphrase
* Agent CLEAR_PASSPHRASE:: Expire a cached passphrase
* Agent PRESET_PASSPHRASE:: Set a passphrase for a keygrip
* Agent GET_CONFIRMATION:: Ask for confirmation
* Agent HAVEKEY:: Check whether a key is available
* Agent LEARN:: Register a smartcard
* Agent PASSWD:: Change a Passphrase
* Agent UPDATESTARTUPTTY:: Change the Standard Display
* Agent GETEVENTCOUNTER:: Get the Event Counters
* Agent GETINFO:: Return information about the process
* Agent OPTION:: Set options for the session
@end menu
@node Agent PKDECRYPT
@subsection Decrypting a session key
The client asks the server to decrypt a session key. The encrypted
session key should have all information needed to select the
appropriate secret key or to delegate it to a smartcard.
@example
SETKEY
@end example
Tell the server about the key to be used for decryption. If this is
not used, @command{gpg-agent} may try to figure out the key by trying to
decrypt the message with each key available.
@example
PKDECRYPT
@end example
The agent checks whether this command is allowed and then does an
INQUIRY to get the ciphertext the client should then send the cipher
text.
@example
S: INQUIRE CIPHERTEXT
C: D (xxxxxx
C: D xxxx)
C: END
@end example
Please note that the server may send status info lines while reading the
data lines from the client. The data send is a SPKI like S-Exp with
this structure:
@example
(enc-val
(
( )
...
( )))
@end example
Where algo is a string with the name of the algorithm; see the libgcrypt
documentation for a list of valid algorithms. The number and names of
the parameters depend on the algorithm. The agent does return an error
if there is an inconsistency.
If the decryption was successful the decrypted data is returned by
means of "D" lines.
Here is an example session:
@cartouche
@smallexample
C: PKDECRYPT
S: INQUIRE CIPHERTEXT
C: D (enc-val elg (a 349324324)
C: D (b 3F444677CA)))
C: END
S: # session key follows
S: S PADDING 0
S: D (value 1234567890ABCDEF0)
S: OK decryption successful
@end smallexample
@end cartouche
The “PADDING” status line is only send if gpg-agent can tell what kind
of padding is used. As of now only the value 0 is used to indicate
that the padding has been removed.
@node Agent PKSIGN
@subsection Signing a Hash
The client asks the agent to sign a given hash value. A default key
will be chosen if no key has been set. To set a key a client first
uses:
@example
SIGKEY
@end example
This can be used multiple times to create multiple signature, the list
of keys is reset with the next PKSIGN command or a RESET. The server
tests whether the key is a valid key to sign something and responds with
okay.
@example
SETHASH --hash=|
@end example
The client can use this command to tell the server about the data
(which usually is a hash) to be signed. is the decimal encoded hash
algorithm number as used by Libgcrypt. Either or --hash=
must be given. Valid names for are:
@table @code
@item sha1
The SHA-1 hash algorithm
@item sha256
The SHA-256 hash algorithm
@item rmd160
The RIPE-MD160 hash algorithm
@item md5
The old and broken MD5 hash algorithm
@item tls-md5sha1
A combined hash algorithm as used by the TLS protocol.
@end table
@noindent
The actual signing is done using
@example
PKSIGN
@end example
Options are not yet defined, but may later be used to choose among
different algorithms. The agent does then some checks, asks for the
passphrase and as a result the server returns the signature as an SPKI
like S-expression in "D" lines:
@example
(sig-val
(
( )
...
( )))
@end example
The operation is affected by the option
@example
OPTION use-cache-for-signing=0|1
@end example
The default of @code{1} uses the cache. Setting this option to @code{0}
will lead @command{gpg-agent} to ignore the passphrase cache. Note, that there is
also a global command line option for @command{gpg-agent} to globally disable the
caching.
Here is an example session:
@cartouche
@smallexample
C: SIGKEY
S: OK key available
C: SIGKEY
S: OK key available
C: PKSIGN
S: # I did ask the user whether he really wants to sign
S: # I did ask the user for the passphrase
S: INQUIRE HASHVAL
C: D ABCDEF012345678901234
C: END
S: # signature follows
S: D (sig-val rsa (s 45435453654612121212))
S: OK
@end smallexample
@end cartouche
@node Agent GENKEY
@subsection Generating a Key
This is used to create a new keypair and store the secret key inside the
active PSE --- which is in most cases a Soft-PSE. A not-yet-defined
option allows choosing the storage location. To get the secret key out
of the PSE, a special export tool has to be used.
@example
GENKEY [--no-protection] [--preset] []
@end example
Invokes the key generation process and the server will then inquire
on the generation parameters, like:
@example
S: INQUIRE KEYPARM
C: D (genkey (rsa (nbits 1024)))
C: END
@end example
The format of the key parameters which depends on the algorithm is of
the form:
@example
(genkey
(algo
(parameter_name_1 ....)
....
(parameter_name_n ....)))
@end example
If everything succeeds, the server returns the *public key* in a SPKI
like S-Expression like this:
@example
(public-key
(rsa
(n )
(e )))
@end example
Here is an example session:
@cartouche
@smallexample
C: GENKEY
S: INQUIRE KEYPARM
C: D (genkey (rsa (nbits 1024)))
C: END
S: D (public-key
S: D (rsa (n 326487324683264) (e 10001)))
S OK key created
@end smallexample
@end cartouche
The @option{--no-protection} option may be used to prevent prompting for a
passphrase to protect the secret key while leaving the secret key unprotected.
The @option{--preset} option may be used to add the passphrase to the cache
using the default cache parameters.
The @option{--inq-passwd} option may be used to create the key with a
supplied passphrase. When used the agent does an inquiry with the
keyword @code{NEWPASSWD} to retrieve that passphrase. This option
takes precedence over @option{--no-protection}; however if the client
sends a empty (zero-length) passphrase, this is identical to
@option{--no-protection}.
@node Agent IMPORT
@subsection Importing a Secret Key
This operation is not yet supported by GpgAgent. Specialized tools
are to be used for this.
There is no actual need because we can expect that secret keys
created by a 3rd party are stored on a smartcard. If we have
generated the key ourselves, we do not need to import it.
@node Agent EXPORT
@subsection Export a Secret Key
Not implemented.
Should be done by an extra tool.
@node Agent ISTRUSTED
@subsection Importing a Root Certificate
Actually we do not import a Root Cert but provide a way to validate
any piece of data by storing its Hash along with a description and
an identifier in the PSE. Here is the interface description:
@example
ISTRUSTED
@end example
Check whether the OpenPGP primary key or the X.509 certificate with the
given fingerprint is an ultimately trusted key or a trusted Root CA
certificate. The fingerprint should be given as a hexstring (without
any blanks or colons or whatever in between) and may be left padded with
00 in case of an MD5 fingerprint. GPGAgent will answer with:
@example
OK
@end example
The key is in the table of trusted keys.
@example
ERR 304 (Not Trusted)
@end example
The key is not in this table.
Gpg needs the entire list of trusted keys to maintain the web of
trust; the following command is therefore quite helpful:
@example
LISTTRUSTED
@end example
GpgAgent returns a list of trusted keys line by line:
@example
S: D 000000001234454556565656677878AF2F1ECCFF P
S: D 340387563485634856435645634856438576457A P
S: D FEDC6532453745367FD83474357495743757435D S
S: OK
@end example
The first item on a line is the hexified fingerprint where MD5
fingerprints are @code{00} padded to the left and the second item is a
flag to indicate the type of key (so that gpg is able to only take care
of PGP keys). P = OpenPGP, S = S/MIME. A client should ignore the rest
of the line, so that we can extend the format in the future.
Finally a client should be able to mark a key as trusted:
@example
MARKTRUSTED @var{fingerprint} "P"|"S"
@end example
The server will then pop up a window to ask the user whether she
really trusts this key. For this it will probably ask for a text to
be displayed like this:
@example
S: INQUIRE TRUSTDESC
C: D Do you trust the key with the fingerprint @@FPR@@
C: D bla fasel blurb.
C: END
S: OK
@end example
Known sequences with the pattern @@foo@@ are replaced according to this
table:
@table @code
@item @@FPR16@@
Format the fingerprint according to gpg rules for a v3 keys.
@item @@FPR20@@
Format the fingerprint according to gpg rules for a v4 keys.
@item @@FPR@@
Choose an appropriate format to format the fingerprint.
@item @@@@
Replaced by a single @code{@@}.
@end table
@node Agent GET_PASSPHRASE
@subsection Ask for a passphrase
This function is usually used to ask for a passphrase to be used for
symmetric encryption, but may also be used by programs which need
special handling of passphrases. This command uses a syntax which helps
clients to use the agent with minimum effort.
@example
GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]] \
[--qualitybar] @var{cache_id} \
[@var{error_message} @var{prompt} @var{description}]
@end example
@var{cache_id} is expected to be a string used to identify a cached
passphrase. Use a @code{X} to bypass the cache. With no other
arguments the agent returns a cached passphrase or an error. By
convention either the hexified fingerprint of the key shall be used for
@var{cache_id} or an arbitrary string prefixed with the name of the
calling application and a colon: Like @code{gpg:somestring}.
@var{error_message} is either a single @code{X} for no error message or
a string to be shown as an error message like (e.g. "invalid
passphrase"). Blanks must be percent escaped or replaced by @code{+}'.
@var{prompt} is either a single @code{X} for a default prompt or the
text to be shown as the prompt. Blanks must be percent escaped or
replaced by @code{+}.
@var{description} is a text shown above the entry field. Blanks must be
percent escaped or replaced by @code{+}.
The agent either returns with an error or with a OK followed by the hex
encoded passphrase. Note that the length of the strings is implicitly
limited by the maximum length of a command. If the option
@option{--data} is used, the passphrase is not returned on the OK line
but by regular data lines; this is the preferred method.
If the option @option{--check} is used, the standard passphrase
constraints checks are applied. A check is not done if the passphrase
has been found in the cache.
If the option @option{--no-ask} is used and the passphrase is not in the
cache the user will not be asked to enter a passphrase but the error
code @code{GPG_ERR_NO_DATA} is returned.
If the option @option{--qualitybar} is used and a minimum passphrase
length has been configured, a visual indication of the entered
passphrase quality is shown.
@example
CLEAR_PASSPHRASE @var{cache_id}
@end example
may be used to invalidate the cache entry for a passphrase. The
function returns with OK even when there is no cached passphrase.
@node Agent CLEAR_PASSPHRASE
@subsection Remove a cached passphrase
Use this command to remove a cached passphrase.
@example
CLEAR_PASSPHRASE [--mode=normal]
@end example
The @option{--mode=normal} option can be used to clear a @var{cache_id} that
was set by gpg-agent.
@node Agent PRESET_PASSPHRASE
@subsection Set a passphrase for a keygrip
This command adds a passphrase to the cache for the specified @var{keygrip}.
@example
PRESET_PASSPHRASE [--inquire] []
@end example
The passphrase is a hexadecimal string when specified. When not specified, the
passphrase will be retrieved from the pinentry module unless the
@option{--inquire} option was specified in which case the passphrase will be
retrieved from the client.
The @var{timeout} parameter keeps the passphrase cached for the specified
number of seconds. A value of @code{-1} means infinite while @code{0} means
the default (currently only a timeout of -1 is allowed, which means to never
expire it).
@node Agent GET_CONFIRMATION
@subsection Ask for confirmation
This command may be used to ask for a simple confirmation by
presenting a text and 2 buttons: Okay and Cancel.
@example
GET_CONFIRMATION @var{description}
@end example
@var{description}is displayed along with a Okay and Cancel
button. Blanks must be percent escaped or replaced by @code{+}. A
@code{X} may be used to display confirmation dialog with a default
text.
The agent either returns with an error or with a OK. Note, that the
length of @var{description} is implicitly limited by the maximum
length of a command.
@node Agent HAVEKEY
@subsection Check whether a key is available
This can be used to see whether a secret key is available. It does
not return any information on whether the key is somehow protected.
@example
HAVEKEY @var{keygrips}
@end example
The agent answers either with OK or @code{No_Secret_Key} (208). The
caller may want to check for other error codes as well. More than one
keygrip may be given. In this case the command returns success if at
least one of the keygrips corresponds to an available secret key.
@node Agent LEARN
@subsection Register a smartcard
@example
LEARN [--send]
@end example
This command is used to register a smartcard. With the @option{--send}
option given the certificates are sent back.
@node Agent PASSWD
@subsection Change a Passphrase
@example
PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset] @var{keygrip}
@end example
This command is used to interactively change the passphrase of the key
identified by the hex string @var{keygrip}. The @option{--preset}
option may be used to add the new passphrase to the cache using the
default cache parameters.
@node Agent UPDATESTARTUPTTY
@subsection Change the standard display
@example
UPDATESTARTUPTTY
@end example
Set the startup TTY and X-DISPLAY variables to the values of this
session. This command is useful to direct future pinentry invocations
to another screen. It is only required because there is no way in the
ssh-agent protocol to convey this information.
@node Agent GETEVENTCOUNTER
@subsection Get the Event Counters
@example
GETEVENTCOUNTER
@end example
This function return one status line with the current values of the
event counters. The event counters are useful to avoid polling by
delaying a poll until something has changed. The values are decimal
numbers in the range @code{0} to @code{UINT_MAX} and wrapping around to
0. The actual values should not be relied upon; they shall only be used
to detect a change.
The currently defined counters are:
@table @code
@item ANY
Incremented with any change of any of the other counters.
@item KEY
Incremented for added or removed private keys.
@item CARD
Incremented for changes of the card readers stati.
@end table
@node Agent GETINFO
@subsection Return information about the process
This is a multipurpose function to return a variety of information.
@example
GETINFO @var{what}
@end example
The value of @var{what} specifies the kind of information returned:
@table @code
@item version
Return the version of the program.
@item pid
Return the process id of the process.
@item socket_name
Return the name of the socket used to connect the agent.
@item ssh_socket_name
Return the name of the socket used for SSH connections. If SSH support
has not been enabled the error @code{GPG_ERR_NO_DATA} will be returned.
@end table
@node Agent OPTION
@subsection Set options for the session
Here is a list of session options which are not yet described with
other commands. The general syntax for an Assuan option is:
@smallexample
OPTION @var{key}=@var{value}
@end smallexample
@noindent
Supported @var{key}s are:
@table @code
@item agent-awareness
This may be used to tell gpg-agent of which gpg-agent version the
client is aware of. gpg-agent uses this information to enable
features which might break older clients.
@item putenv
Change the session's environment to be used for the
Pinentry. Valid values are:
@table @code
@item @var{name}
Delete envvar @var{name}
@item @var{name}=
Set envvar @var{name} to the empty string
@item @var{name}=@var{value}
Set envvar @var{name} to the string @var{value}.
@end table
@item use-cache-for-signing
See Assuan command @code{PKSIGN}.
@item allow-pinentry-notify
This does not need any value. It is used to enable the
PINENTRY_LAUNCHED inquiry.
@item pinentry-mode
This option is used to change the operation mode of the pinentry. The
following values are defined:
@table @code
@item ask
This is the default mode which pops up a pinentry as needed.
@item cancel
Instead of popping up a pinentry, return the error code
@code{GPG_ERR_CANCELED}.
@item error
Instead of popping up a pinentry, return the error code
@code{GPG_ERR_NO_PIN_ENTRY}.
@item loopback
Use a loopback pinentry. This fakes a pinentry by using inquiries
back to the caller to ask for a passphrase. This option may only be
set if the agent has been configured for that.
To disable this feature use @ref{option --no-allow-loopback-pinentry}.
@end table
@item cache-ttl-opt-preset
This option sets the cache TTL for new entries created by GENKEY and
PASSWD commands when using the @option{--preset} option. It is not
used a default value is used.
@item s2k-count
Instead of using the standard S2K count (which is computed on the
fly), the given S2K count is used for new keys or when changing the
passphrase of a key. Values below 65536 are considered to be 0. This
option is valid for the entire session or until reset to 0. This
option is useful if the key is later used on boxes which are either
much slower or faster than the actual box.
@end table
@mansect see also
@ifset isman
@command{@gpgname}(1),
@command{gpgsm}(1),
@command{gpgconf}(1),
@command{gpg-connect-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
diff --git a/g10/parse-packet.c b/g10/parse-packet.c
index 0b6ee8b4e..eee14f64e 100644
--- a/g10/parse-packet.c
+++ b/g10/parse-packet.c
@@ -1,3346 +1,3352 @@
/* parse-packet.c - read packets
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2009, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2015 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 .
*/
#include
#include
#include
#include
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "filter.h"
#include "photoid.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/host2net.h"
/* Maximum length of packets to avoid excessive memory allocation. */
#define MAX_KEY_PACKET_LENGTH (256 * 1024)
#define MAX_UID_PACKET_LENGTH ( 2 * 1024)
#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
static int mpi_print_mode;
static int list_mode;
static estream_t listfp;
static int parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts,
off_t * retpos, int *skip, IOBUF out, int do_skip
#if DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
);
static int copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial);
static void skip_packet (IOBUF inp, int pkttype,
unsigned long pktlen, int partial);
static void *read_rest (IOBUF inp, size_t pktlen);
static int parse_marker (IOBUF inp, int pkttype, unsigned long pktlen);
static int parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops);
static int parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * packet);
static int parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static int parse_comment (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet);
static gpg_error_t parse_ring_trust (parse_packet_ctx_t ctx,
unsigned long pktlen);
static int parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static int parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb, int partial);
static int parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int new_ctb);
static int parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial);
/* Read a 16-bit value in MSB order (big endian) from an iobuf. */
static unsigned short
read_16 (IOBUF inp)
{
unsigned short a;
a = (unsigned short)iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read a 32-bit value in MSB order (big endian) from an iobuf. */
static unsigned long
read_32 (IOBUF inp)
{
unsigned long a;
a = (unsigned long)iobuf_get_noeof (inp) << 24;
a |= iobuf_get_noeof (inp) << 16;
a |= iobuf_get_noeof (inp) << 8;
a |= iobuf_get_noeof (inp);
return a;
}
/* Read an external representation of an MPI and return the MPI. The
external format is a 16-bit unsigned value stored in network byte
order giving the number of bits for the following integer. The
integer is stored MSB first and is left padded with zero bits to
align on a byte boundary.
The caller must set *RET_NREAD to the maximum number of bytes to
read from the pipeline INP. This function sets *RET_NREAD to be
the number of bytes actually read from the pipeline.
If SECURE is true, the integer is stored in secure memory
(allocated using gcry_xmalloc_secure). */
static gcry_mpi_t
mpi_read (iobuf_t inp, unsigned int *ret_nread, int secure)
{
int c, c1, c2, i;
unsigned int nmax = *ret_nread;
unsigned int nbits, nbytes;
size_t nread = 0;
gcry_mpi_t a = NULL;
byte *buf = NULL;
byte *p;
if (!nmax)
goto overflow;
if ((c = c1 = iobuf_get (inp)) == -1)
goto leave;
if (++nread == nmax)
goto overflow;
nbits = c << 8;
if ((c = c2 = iobuf_get (inp)) == -1)
goto leave;
++nread;
nbits |= c;
if (nbits > MAX_EXTERN_MPI_BITS)
{
log_error ("mpi too large (%u bits)\n", nbits);
goto leave;
}
nbytes = (nbits + 7) / 8;
buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2);
p = buf;
p[0] = c1;
p[1] = c2;
for (i = 0; i < nbytes; i++)
{
if (nread == nmax)
goto overflow;
c = iobuf_get (inp);
if (c == -1)
goto leave;
p[i + 2] = c;
nread ++;
}
if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread))
a = NULL;
*ret_nread = nread;
gcry_free(buf);
return a;
overflow:
log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax);
leave:
*ret_nread = nread;
gcry_free(buf);
return a;
}
int
set_packet_list_mode (int mode)
{
int old = list_mode;
list_mode = mode;
/* We use stdout only if invoked by the --list-packets command
but switch to stderr in all other cases. This breaks the
previous behaviour but that seems to be more of a bug than
intentional. I don't believe that any application makes use of
this long standing annoying way of printing to stdout except when
doing a --list-packets. If this assumption fails, it will be easy
to add an option for the listing stream. Note that we initialize
it only once; mainly because there is code which switches
opt.list_mode back to 1 and we want to have all output to the
same stream. The MPI_PRINT_MODE will be enabled if the
corresponding debug flag is set or if we are in --list-packets
and --verbose is given.
Using stderr is not actually very clean because it bypasses the
logging code but it is a special thing anyway. I am not sure
whether using log_stream() would be better. Perhaps we should
enable the list mode only with a special option. */
if (!listfp)
{
if (opt.list_packets)
{
listfp = es_stdout;
if (opt.verbose)
mpi_print_mode = 1;
}
else
listfp = es_stderr;
if (DBG_MPI)
mpi_print_mode = 1;
}
return old;
}
/* If OPT.VERBOSE is set, print a warning that the algorithm ALGO is
not suitable for signing and encryption. */
static void
unknown_pubkey_warning (int algo)
{
static byte unknown_pubkey_algos[256];
/* First check whether the algorithm is usable but not suitable for
encryption/signing. */
if (pubkey_get_npkey (algo))
{
if (opt.verbose)
{
if (!pubkey_get_nsig (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "signing");
if (!pubkey_get_nenc (algo))
log_info ("public key algorithm %s not suitable for %s\n",
openpgp_pk_algo_name (algo), "encryption");
}
}
else
{
algo &= 0xff;
if (!unknown_pubkey_algos[algo])
{
if (opt.verbose)
log_info (_("can't handle public key algorithm %d\n"), algo);
unknown_pubkey_algos[algo] = 1;
}
}
}
#if DEBUG_PARSE_PACKET
int
dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *pkt,
const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
parse_packet (parse_packet_ctx_t ctx, PACKET *pkt)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Like parse packet, but only return secret or public (sub)key
* packets.
*/
#if DEBUG_PARSE_PACKET
int
dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t * retpos, int with_uid,
const char *dbg_f, int dbg_l)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0, "search",
dbg_f, dbg_l);
}
while (skip && ! rc);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t * retpos, int with_uid)
{
int skip, rc;
do
{
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0);
}
while (skip && ! rc);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy all packets from INP to OUT, thereby removing unused spaces.
*/
#if DEBUG_PARSE_PACKET
int
dbg_copy_all_packets (iobuf_t inp, iobuf_t out, const char *dbg_f, int dbg_l)
{
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
init_parse_packet (&parsectx, inp);
do
{
init_packet (&pkt);
}
while (!
(rc =
parse (&parsectx, &pkt, 0, NULL, &skip, out, 0, "copy",
dbg_f, dbg_l)));
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_all_packets (iobuf_t inp, iobuf_t out)
{
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip, rc = 0;
if (! out)
log_bug ("copy_all_packets: OUT may not be NULL.\n");
init_parse_packet (&parsectx, inp);
do
{
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Copy some packets from INP to OUT, thereby removing unused spaces.
* Stop at offset STOPoff (i.e. don't copy packets at this or later
* offsets)
*/
#if DEBUG_PARSE_PACKET
int
dbg_copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff,
const char *dbg_f, int dbg_l)
{
int rc = 0;
PACKET pkt;
int skip;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
do
{
if (iobuf_tell (inp) >= stopoff)
{
deinit_parse_packet (&parsectx);
return 0;
}
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0,
"some", dbg_f, dbg_l)));
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff)
{
int rc = 0;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int skip;
init_parse_packet (&parsectx, inp);
do
{
if (iobuf_tell (inp) >= stopoff)
{
deinit_parse_packet (&parsectx);
return 0;
}
init_packet (&pkt);
}
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/*
* Skip over N packets
*/
#if DEBUG_PARSE_PACKET
int
dbg_skip_some_packets (iobuf_t inp, unsigned n, const char *dbg_f, int dbg_l)
{
int rc = 0;
int skip;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1, "skip",
dbg_f, dbg_l);
}
deinit_parse_packet (&parsectx);
return rc;
}
#else /*!DEBUG_PARSE_PACKET*/
int
skip_some_packets (iobuf_t inp, unsigned int n)
{
int rc = 0;
int skip;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
init_parse_packet (&parsectx, inp);
for (; n && !rc; n--)
{
init_packet (&pkt);
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1);
}
deinit_parse_packet (&parsectx);
return rc;
}
#endif /*!DEBUG_PARSE_PACKET*/
/* Parse a packet and save it in *PKT.
If OUT is not NULL and the packet is valid (its type is not 0),
then the header, the initial length field and the packet's contents
are written to OUT. In this case, the packet is not saved in *PKT.
ONLYKEYPKTS is a simple packet filter. If ONLYKEYPKTS is set to 1,
then only public subkey packets, public key packets, private subkey
packets and private key packets are parsed. The rest are skipped
(i.e., the header and the contents are read from the pipeline and
discarded). If ONLYKEYPKTS is set to 2, then in addition to the
above 4 types of packets, user id packets are also accepted.
DO_SKIP is a more coarse grained filter. Unless ONLYKEYPKTS is set
to 2 and the packet is a user id packet, all packets are skipped.
Finally, if a packet is invalid (it's type is 0), it is skipped.
If a packet is skipped and SKIP is not NULL, then *SKIP is set to
1.
Note: ONLYKEYPKTS and DO_SKIP are only respected if OUT is NULL,
i.e., the packets are not simply being copied.
If RETPOS is not NULL, then the position of CTX->INP (as returned by
iobuf_tell) is saved there before any data is read from CTX->INP.
*/
static int
parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos,
int *skip, IOBUF out, int do_skip
#if DEBUG_PARSE_PACKET
, const char *dbg_w, const char *dbg_f, int dbg_l
#endif
)
{
int rc = 0;
iobuf_t inp;
int c, ctb, pkttype, lenbytes;
unsigned long pktlen;
byte hdr[8];
int hdrlen;
int new_ctb = 0, partial = 0;
int with_uid = (onlykeypkts == 2);
off_t pos;
*skip = 0;
inp = ctx->inp;
again:
log_assert (!pkt->pkt.generic);
if (retpos || list_mode)
{
pos = iobuf_tell (inp);
if (retpos)
*retpos = pos;
}
else
pos = 0; /* (silence compiler warning) */
/* The first byte of a packet is the so-called tag. The highest bit
must be set. */
if ((ctb = iobuf_get (inp)) == -1)
{
rc = -1;
goto leave;
}
hdrlen = 0;
hdr[hdrlen++] = ctb;
if (!(ctb & 0x80))
{
log_error ("%s: invalid packet (ctb=%02x)\n", iobuf_where (inp), ctb);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Immediately following the header is the length. There are two
formats: the old format and the new format. If bit 6 (where the
least significant bit is bit 0) is set in the tag, then we are
dealing with a new format packet. Otherwise, it is an old format
packet. */
pktlen = 0;
new_ctb = !!(ctb & 0x40);
if (new_ctb)
{
/* Get the packet's type. This is encoded in the 6 least
significant bits of the tag. */
pkttype = ctb & 0x3f;
/* Extract the packet's length. New format packets have 4 ways
to encode the packet length. The value of the first byte
determines the encoding and partially determines the length.
See section 4.2.2 of RFC 4880 for details. */
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 1st length byte missing\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
if (c < 192)
pktlen = c;
else if (c < 224)
{
pktlen = (c - 192) * 256;
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 2nd length byte missing\n",
iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
hdr[hdrlen++] = c;
pktlen += c + 192;
}
else if (c == 255)
{
int i;
char value[4];
for (i = 0; i < 4; i ++)
{
if ((c = iobuf_get (inp)) == -1)
{
log_error ("%s: 4 byte length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
value[i] = hdr[hdrlen++] = c;
}
pktlen = buf32_to_ulong (value);
}
else /* Partial body length. */
{
switch (pkttype)
{
case PKT_PLAINTEXT:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_COMPRESSED:
iobuf_set_partial_body_length_mode (inp, c & 0xff);
pktlen = 0; /* To indicate partial length. */
partial = 1;
break;
default:
log_error ("%s: partial length invalid for"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else
/* This is an old format packet. */
{
/* Extract the packet's type. This is encoded in bits 2-5. */
pkttype = (ctb >> 2) & 0xf;
/* The type of length encoding is encoded in bits 0-1 of the
tag. */
lenbytes = ((ctb & 3) == 3) ? 0 : (1 << (ctb & 3));
if (!lenbytes)
{
pktlen = 0; /* Don't know the value. */
/* This isn't really partial, but we can treat it the same
in a "read until the end" sort of way. */
partial = 1;
if (pkttype != PKT_ENCRYPTED && pkttype != PKT_PLAINTEXT
&& pkttype != PKT_COMPRESSED)
{
log_error ("%s: indeterminate length for invalid"
" packet type %d\n", iobuf_where (inp), pkttype);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
else
{
for (; lenbytes; lenbytes--)
{
pktlen <<= 8;
c = iobuf_get (inp);
if (c == -1)
{
log_error ("%s: length invalid\n", iobuf_where (inp));
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pktlen |= hdr[hdrlen++] = c;
}
}
}
/* Sometimes the decompressing layer enters an error state in which
it simply outputs 0xff for every byte read. If we have a stream
of 0xff bytes, then it will be detected as a new format packet
with type 63 and a 4-byte encoded length that is 4G-1. Since
packets with type 63 are private and we use them as a control
packet, which won't be 4 GB, we reject such packets as
invalid. */
if (pkttype == 63 && pktlen == 0xFFFFFFFF)
{
/* With some probability this is caused by a problem in the
* the uncompressing layer - in some error cases it just loops
* and spits out 0xff bytes. */
log_error ("%s: garbled packet detected\n", iobuf_where (inp));
g10_exit (2);
}
if (out && pkttype)
{
/* This type of copying won't work if the packet uses a partial
body length. (In other words, this only works if HDR is
actually the length.) Currently, no callers require this
functionality so we just log this as an error. */
if (partial)
{
log_error ("parse: Can't copy partial packet. Aborting.\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
rc = iobuf_write (out, hdr, hdrlen);
if (!rc)
rc = copy_packet (inp, out, pkttype, pktlen, partial);
goto leave;
}
if (with_uid && pkttype == PKT_USER_ID)
/* If ONLYKEYPKTS is set to 2, then we never skip user id packets,
even if DO_SKIP is set. */
;
else if (do_skip
/* type==0 is not allowed. This is an invalid packet. */
|| !pkttype
/* When ONLYKEYPKTS is set, we don't skip keys. */
|| (onlykeypkts && pkttype != PKT_PUBLIC_SUBKEY
&& pkttype != PKT_PUBLIC_KEY
&& pkttype != PKT_SECRET_SUBKEY && pkttype != PKT_SECRET_KEY))
{
iobuf_skip_rest (inp, pktlen, partial);
*skip = 1;
rc = 0;
goto leave;
}
if (DBG_PACKET)
{
#if DEBUG_PARSE_PACKET
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s (%s.%s.%d)\n",
iobuf_id (inp), pkttype, pktlen, new_ctb ? " (new_ctb)" : "",
dbg_w, dbg_f, dbg_l);
#else
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s\n",
iobuf_id (inp), pkttype, pktlen,
new_ctb ? " (new_ctb)" : "");
#endif
}
if (list_mode)
es_fprintf (listfp, "# off=%lu ctb=%02x tag=%d hlen=%d plen=%lu%s%s\n",
(unsigned long)pos, ctb, pkttype, hdrlen, pktlen,
partial? (new_ctb ? " partial" : " indeterminate") :"",
new_ctb? " new-ctb":"");
/* Count it. */
ctx->n_parsed_packets++;
pkt->pkttype = pkttype;
rc = GPG_ERR_UNKNOWN_PACKET; /* default error */
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
pkt->pkt.public_key = xmalloc_clear (sizeof *pkt->pkt.public_key);
rc = parse_key (inp, pkttype, pktlen, hdr, hdrlen, pkt);
break;
case PKT_SYMKEY_ENC:
rc = parse_symkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_PUBKEY_ENC:
rc = parse_pubkeyenc (inp, pkttype, pktlen, pkt);
break;
case PKT_SIGNATURE:
pkt->pkt.signature = xmalloc_clear (sizeof *pkt->pkt.signature);
rc = parse_signature (inp, pkttype, pktlen, pkt->pkt.signature);
break;
case PKT_ONEPASS_SIG:
pkt->pkt.onepass_sig = xmalloc_clear (sizeof *pkt->pkt.onepass_sig);
rc = parse_onepass_sig (inp, pkttype, pktlen, pkt->pkt.onepass_sig);
break;
case PKT_USER_ID:
rc = parse_user_id (inp, pkttype, pktlen, pkt);
break;
case PKT_ATTRIBUTE:
pkt->pkttype = pkttype = PKT_USER_ID; /* we store it in the userID */
rc = parse_attribute (inp, pkttype, pktlen, pkt);
break;
case PKT_OLD_COMMENT:
case PKT_COMMENT:
rc = parse_comment (inp, pkttype, pktlen, pkt);
break;
case PKT_RING_TRUST:
{
rc = parse_ring_trust (ctx, pktlen);
if (!rc)
goto again; /* Directly read the next packet. */
}
break;
case PKT_PLAINTEXT:
rc = parse_plaintext (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_COMPRESSED:
rc = parse_compressed (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
rc = parse_encrypted (inp, pkttype, pktlen, pkt, new_ctb, partial);
break;
case PKT_MDC:
rc = parse_mdc (inp, pkttype, pktlen, pkt, new_ctb);
break;
case PKT_GPG_CONTROL:
rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial);
break;
case PKT_MARKER:
rc = parse_marker (inp, pkttype, pktlen);
break;
default:
/* Unknown packet. Skip it. */
skip_packet (inp, pkttype, pktlen, partial);
break;
}
/* Store a shallow copy of certain packets in the context. */
free_packet (NULL, ctx);
if (!rc && (pkttype == PKT_PUBLIC_KEY
|| pkttype == PKT_SECRET_KEY
|| pkttype == PKT_USER_ID
|| pkttype == PKT_ATTRIBUTE
|| pkttype == PKT_SIGNATURE))
{
ctx->last_pkt = *pkt;
}
leave:
/* FIXME: We leak in case of an error (see the xmalloc's above). */
if (!rc && iobuf_error (inp))
rc = GPG_ERR_INV_KEYRING;
/* FIXME: We use only the error code for now to avoid problems with
callers which have not been checked to always use gpg_err_code()
when comparing error codes. */
return rc == -1? -1 : gpg_err_code (rc);
}
static void
dump_hex_line (int c, int *i)
{
if (*i && !(*i % 8))
{
if (*i && !(*i % 24))
es_fprintf (listfp, "\n%4d:", *i);
else
es_putc (' ', listfp);
}
if (c == -1)
es_fprintf (listfp, " EOF");
else
es_fprintf (listfp, " %02x", c);
++*i;
}
/* Copy the contents of a packet from the pipeline IN to the pipeline
OUT.
The header and length have already been read from INP and the
decoded values are given as PKGTYPE and PKTLEN.
If the packet is a partial body length packet (RFC 4880, Section
4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode
should already have been called on INP and PARTIAL should be set.
If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED,
copy until the first EOF is encountered on INP.
Returns 0 on success and an error code if an error occurs. */
static int
copy_packet (IOBUF inp, IOBUF out, int pkttype,
unsigned long pktlen, int partial)
{
int rc;
int n;
char buf[100];
if (partial)
{
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else if (!pktlen && pkttype == PKT_COMPRESSED)
{
log_debug ("copy_packet: compressed!\n");
/* compressed packet, copy till EOF */
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
else
{
for (; pktlen; pktlen -= n)
{
n = pktlen > sizeof (buf) ? sizeof (buf) : pktlen;
n = iobuf_read (inp, buf, n);
if (n == -1)
return gpg_error (GPG_ERR_EOF);
if ((rc = iobuf_write (out, buf, n)))
return rc; /* write error */
}
}
return 0;
}
/* Skip an unknown packet. PKTTYPE is the packet's type, PKTLEN is
the length of the packet's content and PARTIAL is whether partial
body length encoding in used (in this case PKTLEN is ignored). */
static void
skip_packet (IOBUF inp, int pkttype, unsigned long pktlen, int partial)
{
if (list_mode)
{
es_fprintf (listfp, ":unknown packet: type %2d, length %lu\n",
pkttype, pktlen);
if (pkttype)
{
int c, i = 0;
es_fputs ("dump:", listfp);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
return;
}
}
iobuf_skip_rest (inp, pktlen, partial);
}
/* Read PKTLEN bytes form INP and return them in a newly allocated
buffer. In case of an error (including reading fewer than PKTLEN
bytes from INP before EOF is returned), NULL is returned and an
error message is logged. */
static void *
read_rest (IOBUF inp, size_t pktlen)
{
int c;
byte *buf, *p;
buf = xtrymalloc (pktlen);
if (!buf)
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("error reading rest of packet: %s\n", gpg_strerror (err));
return NULL;
}
for (p = buf; pktlen; pktlen--)
{
c = iobuf_get (inp);
if (c == -1)
{
log_error ("premature eof while reading rest of packet\n");
xfree (buf);
return NULL;
}
*p++ = c;
}
return buf;
}
/* Read a special size+body from INP. On success store an opaque MPI
with it at R_DATA. On error return an error code and store NULL at
R_DATA. Even in the error case store the number of read bytes at
R_NREAD. The caller shall pass the remaining size of the packet in
PKTLEN. */
static gpg_error_t
read_size_body (iobuf_t inp, int pktlen, size_t *r_nread,
gcry_mpi_t *r_data)
{
char buffer[256];
char *tmpbuf;
int i, c, nbytes;
*r_nread = 0;
*r_data = NULL;
if (!pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
c = iobuf_readbyte (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
pktlen--;
++*r_nread;
nbytes = c;
if (nbytes < 2 || nbytes > 254)
return gpg_error (GPG_ERR_INV_PACKET);
if (nbytes > pktlen)
return gpg_error (GPG_ERR_INV_PACKET);
buffer[0] = nbytes;
for (i = 0; i < nbytes; i++)
{
c = iobuf_get (inp);
if (c < 0)
return gpg_error (GPG_ERR_INV_PACKET);
++*r_nread;
buffer[1+i] = c;
}
tmpbuf = xtrymalloc (1 + nbytes);
if (!tmpbuf)
return gpg_error_from_syserror ();
memcpy (tmpbuf, buffer, 1 + nbytes);
*r_data = gcry_mpi_set_opaque (NULL, tmpbuf, 8 * (1 + nbytes));
if (!*r_data)
{
xfree (tmpbuf);
return gpg_error_from_syserror ();
}
return 0;
}
/* Parse a marker packet. */
static int
parse_marker (IOBUF inp, int pkttype, unsigned long pktlen)
{
(void) pkttype;
if (pktlen != 3)
goto fail;
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'G')
{
pktlen--;
goto fail;
}
if (iobuf_get (inp) != 'P')
{
pktlen--;
goto fail;
}
if (list_mode)
es_fputs (":marker packet: PGP\n", listfp);
return 0;
fail:
log_error ("invalid marker packet\n");
if (list_mode)
es_fputs (":marker packet: [invalid]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
PKT_symkey_enc *k;
int rc = 0;
int i, version, s2kmode, cipher_algo, hash_algo, seskeylen, minlen;
if (pktlen < 4)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
version = iobuf_get_noeof (inp);
pktlen--;
if (version != 4)
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown version]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen > 200)
{ /* (we encode the seskeylen in a byte) */
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too large]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
cipher_algo = iobuf_get_noeof (inp);
pktlen--;
s2kmode = iobuf_get_noeof (inp);
pktlen--;
hash_algo = iobuf_get_noeof (inp);
pktlen--;
switch (s2kmode)
{
case 0: /* Simple S2K. */
minlen = 0;
break;
case 1: /* Salted S2K. */
minlen = 8;
break;
case 3: /* Iterated+salted S2K. */
minlen = 9;
break;
default:
log_error ("unknown S2K mode %d\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [unknown S2K mode]\n");
goto leave;
}
if (minlen > pktlen)
{
log_error ("packet with S2K %d too short\n", s2kmode);
if (list_mode)
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
seskeylen = pktlen - minlen;
k = packet->pkt.symkey_enc = xmalloc_clear (sizeof *packet->pkt.symkey_enc
+ seskeylen - 1);
k->version = version;
k->cipher_algo = cipher_algo;
k->s2k.mode = s2kmode;
k->s2k.hash_algo = hash_algo;
if (s2kmode == 1 || s2kmode == 3)
{
for (i = 0; i < 8 && pktlen; i++, pktlen--)
k->s2k.salt[i] = iobuf_get_noeof (inp);
}
if (s2kmode == 3)
{
k->s2k.count = iobuf_get (inp);
pktlen--;
}
k->seskeylen = seskeylen;
if (k->seskeylen)
{
for (i = 0; i < seskeylen && pktlen; i++, pktlen--)
k->seskey[i] = iobuf_get_noeof (inp);
/* What we're watching out for here is a session key decryptor
with no salt. The RFC says that using salt for this is a
MUST. */
if (s2kmode != 1 && s2kmode != 3)
log_info (_("WARNING: potentially insecure symmetrically"
" encrypted session key\n"));
}
log_assert (!pktlen);
if (list_mode)
{
es_fprintf (listfp,
":symkey enc packet: version %d, cipher %d, s2k %d, hash %d",
version, cipher_algo, s2kmode, hash_algo);
if (seskeylen)
es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8);
es_fprintf (listfp, "\n");
if (s2kmode == 1 || s2kmode == 3)
{
es_fprintf (listfp, "\tsalt ");
es_write_hexstring (listfp, k->s2k.salt, 8, 0, NULL);
if (s2kmode == 3)
es_fprintf (listfp, ", count %lu (%lu)",
S2K_DECODE_COUNT ((ulong) k->s2k.count),
(ulong) k->s2k.count);
es_fprintf (listfp, "\n");
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
static int
parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
int rc = 0;
int i, ndata;
PKT_pubkey_enc *k;
k = packet->pkt.pubkey_enc = xmalloc_clear (sizeof *packet->pkt.pubkey_enc);
if (pktlen < 12)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":pubkey enc packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->version = iobuf_get_noeof (inp);
pktlen--;
if (k->version != 2 && k->version != 3)
{
log_error ("packet(%d) with unknown version %d\n", pkttype, k->version);
if (list_mode)
es_fputs (":pubkey enc packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
k->keyid[0] = read_32 (inp);
pktlen -= 4;
k->keyid[1] = read_32 (inp);
pktlen -= 4;
k->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
k->throw_keyid = 0; /* Only used as flag for build_packet. */
if (list_mode)
es_fprintf (listfp,
":pubkey enc packet: version %d, algo %d, keyid %08lX%08lX\n",
k->version, k->pubkey_algo, (ulong) k->keyid[0],
(ulong) k->keyid[1]);
ndata = pubkey_get_nenc (k->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunsupported algorithm %d\n", k->pubkey_algo);
unknown_pubkey_warning (k->pubkey_algo);
k->data[0] = NULL; /* No need to store the encrypted data. */
}
else
{
for (i = 0; i < ndata; i++)
{
if (k->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1)
{
size_t n;
rc = read_size_body (inp, pktlen, &n, k->data+i);
pktlen -= n;
}
else
{
int n = pktlen;
k->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!k->data[i])
rc = gpg_error (GPG_ERR_INV_PACKET);
}
if (rc)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, k->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
/* Dump a subpacket to LISTFP. BUFFER contains the subpacket in
question and points to the type field in the subpacket header (not
the start of the header). TYPE is the subpacket's type with the
critical bit cleared. CRITICAL is the value of the CRITICAL bit.
BUFLEN is the length of the buffer and LENGTH is the length of the
subpacket according to the subpacket's header. */
static void
dump_sig_subpkt (int hashed, int type, int critical,
const byte * buffer, size_t buflen, size_t length)
{
const char *p = NULL;
int i;
/* The CERT has warning out with explains how to use GNUPG to detect
* the ARRs - we print our old message here when it is a faked ARR
* and add an additional notice. */
if (type == SIGSUBPKT_ARR && !hashed)
{
es_fprintf (listfp,
"\tsubpkt %d len %u (additional recipient request)\n"
"WARNING: PGP versions > 5.0 and < 6.5.8 will automagically "
"encrypt to this key and thereby reveal the plaintext to "
"the owner of this ARR key. Detailed info follows:\n",
type, (unsigned) length);
}
buffer++;
length--;
es_fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*) */
critical ? "critical " : "",
hashed ? "hashed " : "", type, (unsigned) length);
if (length > buflen)
{
es_fprintf (listfp, "too short: buffer is only %u)\n", (unsigned) buflen);
return;
}
switch (type)
{
case SIGSUBPKT_SIG_CREATED:
if (length >= 4)
es_fprintf (listfp, "sig created %s",
strtimestamp (buf32_to_u32 (buffer)));
break;
case SIGSUBPKT_SIG_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "sig expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "sig does not expire");
}
break;
case SIGSUBPKT_EXPORTABLE:
if (length)
es_fprintf (listfp, "%sexportable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_TRUST:
if (length != 2)
p = "[invalid trust subpacket]";
else
es_fprintf (listfp, "trust signature of depth %d, value %d", buffer[0],
buffer[1]);
break;
case SIGSUBPKT_REGEXP:
if (!length)
p = "[invalid regexp subpacket]";
else
{
es_fprintf (listfp, "regular expression: \"");
es_write_sanitized (listfp, buffer, length, "\"", NULL);
p = "\"";
}
break;
case SIGSUBPKT_REVOCABLE:
if (length)
es_fprintf (listfp, "%srevocable", *buffer ? "" : "not ");
break;
case SIGSUBPKT_KEY_EXPIRE:
if (length >= 4)
{
if (buf32_to_u32 (buffer))
es_fprintf (listfp, "key expires after %s",
strtimevalue (buf32_to_u32 (buffer)));
else
es_fprintf (listfp, "key does not expire");
}
break;
case SIGSUBPKT_PREF_SYM:
es_fputs ("pref-sym-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_REV_KEY:
es_fputs ("revocation key: ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
for (i = 2; i < length; i++)
es_fprintf (listfp, "%02X", buffer[i]);
}
break;
case SIGSUBPKT_ISSUER:
if (length >= 8)
es_fprintf (listfp, "issuer key ID %08lX%08lX",
(ulong) buf32_to_u32 (buffer),
(ulong) buf32_to_u32 (buffer + 4));
break;
case SIGSUBPKT_ISSUER_FPR:
if (length >= 21)
{
char *tmp;
es_fprintf (listfp, "issuer fpr v%d ", buffer[0]);
tmp = bin2hex (buffer+1, length-1, NULL);
if (tmp)
{
es_fputs (tmp, listfp);
xfree (tmp);
}
}
break;
case SIGSUBPKT_NOTATION:
{
es_fputs ("notation: ", listfp);
if (length < 8)
p = "[too short]";
else
{
const byte *s = buffer;
size_t n1, n2;
n1 = (s[4] << 8) | s[5];
n2 = (s[6] << 8) | s[7];
s += 8;
if (8 + n1 + n2 != length)
p = "[error]";
else
{
es_write_sanitized (listfp, s, n1, ")", NULL);
es_putc ('=', listfp);
if (*buffer & 0x80)
es_write_sanitized (listfp, s + n1, n2, ")", NULL);
else
p = "[not human readable]";
}
}
}
break;
case SIGSUBPKT_PREF_HASH:
es_fputs ("pref-hash-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_PREF_COMPR:
es_fputs ("pref-zip-algos:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %d", buffer[i]);
break;
case SIGSUBPKT_KS_FLAGS:
es_fputs ("keyserver preferences:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_PREF_KS:
es_fputs ("preferred keyserver: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_PRIMARY_UID:
p = "primary user ID";
break;
case SIGSUBPKT_POLICY:
es_fputs ("policy: ", listfp);
es_write_sanitized (listfp, buffer, length, ")", NULL);
break;
case SIGSUBPKT_KEY_FLAGS:
es_fputs ("key flags:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02X", buffer[i]);
break;
case SIGSUBPKT_SIGNERS_UID:
p = "signer's user ID";
break;
case SIGSUBPKT_REVOC_REASON:
if (length)
{
es_fprintf (listfp, "revocation reason 0x%02x (", *buffer);
es_write_sanitized (listfp, buffer + 1, length - 1, ")", NULL);
p = ")";
}
break;
case SIGSUBPKT_ARR:
es_fputs ("Big Brother's key (ignored): ", listfp);
if (length < 22)
p = "[too short]";
else
{
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
if (length > 2)
es_write_hexstring (listfp, buffer+2, length-2, 0, NULL);
}
break;
case SIGSUBPKT_FEATURES:
es_fputs ("features:", listfp);
for (i = 0; i < length; i++)
es_fprintf (listfp, " %02x", buffer[i]);
break;
case SIGSUBPKT_SIGNATURE:
es_fputs ("signature: ", listfp);
if (length < 17)
p = "[too short]";
else
es_fprintf (listfp, "v%d, class 0x%02X, algo %d, digest algo %d",
buffer[0],
buffer[0] == 3 ? buffer[2] : buffer[1],
buffer[0] == 3 ? buffer[15] : buffer[2],
buffer[0] == 3 ? buffer[16] : buffer[3]);
break;
default:
if (type >= 100 && type <= 110)
p = "experimental / private subpacket";
else
p = "?";
break;
}
es_fprintf (listfp, "%s)\n", p ? p : "");
}
/*
* Returns: >= 0 use this offset into buffer
* -1 explicitly reject returning this type
* -2 subpacket too short
*/
int
parse_one_sig_subpkt (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_REV_KEY:
if (n < 22)
break;
return 0;
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
if (n < 4)
break;
return 0;
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_KS_FLAGS:
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_REGEXP:
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REVOC_REASON:
if (!n)
break;
return 0;
case SIGSUBPKT_ISSUER: /* issuer key ID */
if (n < 8)
break;
return 0;
case SIGSUBPKT_ISSUER_FPR: /* issuer key ID */
if (n < 21)
break;
return 0;
case SIGSUBPKT_NOTATION:
/* minimum length needed, and the subpacket must be well-formed
where the name length and value length all fit inside the
packet. */
if (n < 8
|| 8 + ((buffer[4] << 8) | buffer[5]) +
((buffer[6] << 8) | buffer[7]) != n)
break;
return 0;
case SIGSUBPKT_PRIMARY_UID:
if (n != 1)
break;
return 0;
case SIGSUBPKT_TRUST:
if (n != 2)
break;
return 0;
default:
return 0;
}
return -2;
}
/* Return true if we understand the critical notation. */
static int
can_handle_critical_notation (const byte * name, size_t len)
{
if (len == 32 && memcmp (name, "preferred-email-encoding@pgp.com", 32) == 0)
return 1;
if (len == 21 && memcmp (name, "pka-address@gnupg.org", 21) == 0)
return 1;
return 0;
}
static int
can_handle_critical (const byte * buffer, size_t n, int type)
{
switch (type)
{
case SIGSUBPKT_NOTATION:
if (n >= 8)
{
size_t notation_len = ((buffer[4] << 8) | buffer[5]);
if (n - 8 >= notation_len)
return can_handle_critical_notation (buffer + 8, notation_len);
}
return 0;
case SIGSUBPKT_SIGNATURE:
case SIGSUBPKT_SIG_CREATED:
case SIGSUBPKT_SIG_EXPIRE:
case SIGSUBPKT_KEY_EXPIRE:
case SIGSUBPKT_EXPORTABLE:
case SIGSUBPKT_REVOCABLE:
case SIGSUBPKT_REV_KEY:
case SIGSUBPKT_ISSUER: /* issuer key ID */
case SIGSUBPKT_ISSUER_FPR: /* issuer fingerprint */
case SIGSUBPKT_PREF_SYM:
case SIGSUBPKT_PREF_HASH:
case SIGSUBPKT_PREF_COMPR:
case SIGSUBPKT_KEY_FLAGS:
case SIGSUBPKT_PRIMARY_UID:
case SIGSUBPKT_FEATURES:
case SIGSUBPKT_TRUST:
case SIGSUBPKT_REGEXP:
/* Is it enough to show the policy or keyserver? */
case SIGSUBPKT_POLICY:
case SIGSUBPKT_PREF_KS:
case SIGSUBPKT_REVOC_REASON: /* At least we know about it. */
return 1;
default:
return 0;
}
}
const byte *
enum_sig_subpkt (const subpktarea_t * pktbuf, sigsubpkttype_t reqtype,
size_t * ret_n, int *start, int *critical)
{
const byte *buffer;
int buflen;
int type;
int critical_dummy;
int offset;
size_t n;
int seq = 0;
int reqseq = start ? *start : 0;
if (!critical)
critical = &critical_dummy;
if (!pktbuf || reqseq == -1)
{
static char dummy[] = "x";
/* Return a value different from NULL to indicate that
* there is no critical bit we do not understand. */
return reqtype == SIGSUBPKT_TEST_CRITICAL ? dummy : NULL;
}
buffer = pktbuf->data;
buflen = pktbuf->len;
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 4 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
type = *buffer;
if (type & 0x80)
{
type &= 0x7f;
*critical = 1;
}
else
*critical = 0;
if (!(++seq > reqseq))
;
else if (reqtype == SIGSUBPKT_TEST_CRITICAL)
{
if (*critical)
{
if (n - 1 > buflen + 1)
goto too_short;
if (!can_handle_critical (buffer + 1, n - 1, type))
{
if (opt.verbose)
log_info (_("subpacket of type %d has "
"critical bit set\n"), type);
if (start)
*start = seq;
return NULL; /* This is an error. */
}
}
}
else if (reqtype < 0) /* List packets. */
dump_sig_subpkt (reqtype == SIGSUBPKT_LIST_HASHED,
type, *critical, buffer, buflen, n);
else if (type == reqtype) /* Found. */
{
buffer++;
n--;
if (n > buflen)
goto too_short;
if (ret_n)
*ret_n = n;
offset = parse_one_sig_subpkt (buffer, n, type);
switch (offset)
{
case -2:
log_error ("subpacket of type %d too short\n", type);
return NULL;
case -1:
return NULL;
default:
break;
}
if (start)
*start = seq;
return buffer + offset;
}
buffer += n;
buflen -= n;
}
if (reqtype == SIGSUBPKT_TEST_CRITICAL)
/* Returning NULL means we found a subpacket with the critical bit
set that we don't grok. We've iterated over all the subpackets
and haven't found such a packet so we need to return a non-NULL
value. */
return buffer;
/* Critical bit we don't understand. */
if (start)
*start = -1;
return NULL; /* End of packets; not found. */
too_short:
if (opt.verbose)
log_info ("buffer shorter than subpacket\n");
if (start)
*start = -1;
return NULL;
}
const byte *
parse_sig_subpkt (const subpktarea_t * buffer, sigsubpkttype_t reqtype,
size_t * ret_n)
{
return enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL);
}
const byte *
parse_sig_subpkt2 (PKT_signature * sig, sigsubpkttype_t reqtype)
{
const byte *p;
p = parse_sig_subpkt (sig->hashed, reqtype, NULL);
if (!p)
p = parse_sig_subpkt (sig->unhashed, reqtype, NULL);
return p;
}
/* Find all revocation keys. Look in hashed area only. */
void
parse_revkeys (PKT_signature * sig)
{
const byte *revkey;
int seq = 0;
size_t len;
if (sig->sig_class != 0x1F)
return;
while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY,
&len, &seq, NULL)))
{
if (/* The only valid length is 22 bytes. See RFC 4880
5.2.3.15. */
len == 22
/* 0x80 bit must be set on the class. */
&& (revkey[0] & 0x80))
{
sig->revkey = xrealloc (sig->revkey,
sizeof (struct revocation_key) *
(sig->numrevkeys + 1));
/* Copy the individual fields. */
sig->revkey[sig->numrevkeys].class = revkey[0];
sig->revkey[sig->numrevkeys].algid = revkey[1];
memcpy (sig->revkey[sig->numrevkeys].fpr, &revkey[2], 20);
sig->numrevkeys++;
}
}
}
int
parse_signature (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_signature * sig)
{
int md5_len = 0;
unsigned n;
int is_v4 = 0;
int rc = 0;
int i, ndata;
if (pktlen < 16)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
goto leave;
}
sig->version = iobuf_get_noeof (inp);
pktlen--;
if (sig->version == 4)
is_v4 = 1;
else if (sig->version != 2 && sig->version != 3)
{
log_error ("packet(%d) with unknown version %d\n",
pkttype, sig->version);
if (list_mode)
es_fputs (":signature packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (!is_v4)
{
if (pktlen == 0)
goto underflow;
md5_len = iobuf_get_noeof (inp);
pktlen--;
}
if (pktlen == 0)
goto underflow;
sig->sig_class = iobuf_get_noeof (inp);
pktlen--;
if (!is_v4)
{
if (pktlen < 12)
goto underflow;
sig->timestamp = read_32 (inp);
pktlen -= 4;
sig->keyid[0] = read_32 (inp);
pktlen -= 4;
sig->keyid[1] = read_32 (inp);
pktlen -= 4;
}
if (pktlen < 2)
goto underflow;
sig->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
sig->digest_algo = iobuf_get_noeof (inp);
pktlen--;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
if (is_v4) /* Read subpackets. */
{
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of hashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: hashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [hashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->hashed = xmalloc (sizeof (*sig->hashed) + n - 1);
sig->hashed->size = n;
sig->hashed->len = n;
if (iobuf_read (inp, sig->hashed->data, n) != n)
{
log_error ("premature eof while reading "
"hashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
if (pktlen < 2)
goto underflow;
n = read_16 (inp);
pktlen -= 2; /* Length of unhashed data. */
if (pktlen < n)
goto underflow;
if (n > 10000)
{
log_error ("signature packet: unhashed data too long\n");
if (list_mode)
es_fputs (":signature packet: [unhashed data too long]\n", listfp);
rc = GPG_ERR_INV_PACKET;
goto leave;
}
if (n)
{
sig->unhashed = xmalloc (sizeof (*sig->unhashed) + n - 1);
sig->unhashed->size = n;
sig->unhashed->len = n;
if (iobuf_read (inp, sig->unhashed->data, n) != n)
{
log_error ("premature eof while reading "
"unhashed signature data\n");
if (list_mode)
es_fputs (":signature packet: [premature eof]\n", listfp);
rc = -1;
goto leave;
}
pktlen -= n;
}
}
if (pktlen < 2)
goto underflow;
sig->digest_start[0] = iobuf_get_noeof (inp);
pktlen--;
sig->digest_start[1] = iobuf_get_noeof (inp);
pktlen--;
if (is_v4 && sig->pubkey_algo) /* Extract required information. */
{
const byte *p;
size_t len;
/* Set sig->flags.unknown_critical if there is a critical bit
* set for packets which we do not understand. */
if (!parse_sig_subpkt (sig->hashed, SIGSUBPKT_TEST_CRITICAL, NULL)
|| !parse_sig_subpkt (sig->unhashed, SIGSUBPKT_TEST_CRITICAL, NULL))
sig->flags.unknown_critical = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_CREATED, NULL);
if (p)
sig->timestamp = buf32_to_u32 (p);
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without timestamp\n");
p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER);
if (p)
{
sig->keyid[0] = buf32_to_u32 (p);
sig->keyid[1] = buf32_to_u32 (p + 4);
}
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
&& opt.verbose)
log_info ("signature packet without keyid\n");
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIG_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
sig->expiredate = sig->timestamp + buf32_to_u32 (p);
if (sig->expiredate && sig->expiredate <= make_timestamp ())
sig->flags.expired = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, NULL);
if (p)
sig->flags.policy_url = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, NULL);
if (p)
sig->flags.pref_ks = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_SIGNERS_UID, &len);
if (p && len)
{
sig->signers_uid = try_make_printable_string (p, len, 0);
if (!sig->signers_uid)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_NOTATION, NULL);
if (p)
sig->flags.notation = 1;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_REVOCABLE, NULL);
if (p && *p == 0)
sig->flags.revocable = 0;
p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_TRUST, &len);
if (p && len == 2)
{
sig->trust_depth = p[0];
sig->trust_value = p[1];
/* Only look for a regexp if there is also a trust
subpacket. */
sig->trust_regexp =
parse_sig_subpkt (sig->hashed, SIGSUBPKT_REGEXP, &len);
/* If the regular expression is of 0 length, there is no
regular expression. */
if (len == 0)
sig->trust_regexp = NULL;
}
/* We accept the exportable subpacket from either the hashed or
unhashed areas as older versions of gpg put it in the
unhashed area. In theory, anyway, we should never see this
packet off of a local keyring. */
p = parse_sig_subpkt2 (sig, SIGSUBPKT_EXPORTABLE);
if (p && *p == 0)
sig->flags.exportable = 0;
/* Find all revocation keys. */
if (sig->sig_class == 0x1F)
parse_revkeys (sig);
}
if (list_mode)
{
es_fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n"
"\tversion %d, created %lu, md5len %d, sigclass 0x%02x\n"
"\tdigest algo %d, begin of digest %02x %02x\n",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
sig->version, (ulong) sig->timestamp, md5_len, sig->sig_class,
sig->digest_algo, sig->digest_start[0], sig->digest_start[1]);
if (is_v4)
{
parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL);
parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL);
}
}
ndata = pubkey_get_nsig (sig->pubkey_algo);
if (!ndata)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", sig->pubkey_algo);
unknown_pubkey_warning (sig->pubkey_algo);
/* We store the plain material in data[0], so that we are able
* to write it back with build_packet(). */
if (pktlen > (5 * MAX_EXTERN_MPI_BITS / 8))
{
/* We include a limit to avoid too trivial DoS attacks by
having gpg allocate too much memory. */
log_error ("signature packet: too much data\n");
rc = GPG_ERR_INV_PACKET;
}
else
{
sig->data[0] =
gcry_mpi_set_opaque (NULL, read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
}
}
else
{
for (i = 0; i < ndata; i++)
{
n = pktlen;
sig->data[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tdata: ");
mpi_print (listfp, sig->data[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!sig->data[i])
rc = GPG_ERR_INV_PACKET;
}
}
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
underflow:
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":signature packet: [too short]\n", listfp);
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
static int
parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
PKT_onepass_sig * ops)
{
int version;
int rc = 0;
if (pktlen < 13)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":onepass_sig packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
version = iobuf_get_noeof (inp);
pktlen--;
if (version != 3)
{
log_error ("onepass_sig with unknown version %d\n", version);
if (list_mode)
es_fputs (":onepass_sig packet: [unknown version]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ops->sig_class = iobuf_get_noeof (inp);
pktlen--;
ops->digest_algo = iobuf_get_noeof (inp);
pktlen--;
ops->pubkey_algo = iobuf_get_noeof (inp);
pktlen--;
ops->keyid[0] = read_32 (inp);
pktlen -= 4;
ops->keyid[1] = read_32 (inp);
pktlen -= 4;
ops->last = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp,
":onepass_sig packet: keyid %08lX%08lX\n"
"\tversion %d, sigclass 0x%02x, digest %d, pubkey %d, "
"last=%d\n",
(ulong) ops->keyid[0], (ulong) ops->keyid[1],
version, ops->sig_class,
ops->digest_algo, ops->pubkey_algo, ops->last);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return rc;
}
static int
parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
byte * hdr, int hdrlen, PACKET * pkt)
{
gpg_error_t err = 0;
int i, version, algorithm;
unsigned long timestamp, expiredate, max_expiredate;
int npkey, nskey;
u32 keyid[2];
PKT_public_key *pk;
(void) hdr;
pk = pkt->pkt.public_key; /* PK has been cleared. */
version = iobuf_get_noeof (inp);
pktlen--;
if (pkttype == PKT_PUBLIC_SUBKEY && version == '#')
{
/* Early versions of G10 used the old PGP comments packets;
* luckily all those comments are started by a hash. */
if (list_mode)
{
es_fprintf (listfp, ":rfc1991 comment packet: \"");
for (; pktlen; pktlen--)
{
int c;
c = iobuf_get (inp);
if (c == -1)
break; /* Ooops: shorter than indicated. */
if (c >= ' ' && c <= 'z')
es_putc (c, listfp);
else
es_fprintf (listfp, "\\x%02x", c);
}
es_fprintf (listfp, "\"\n");
}
iobuf_skip_rest (inp, pktlen, 0);
return 0;
}
else if (version == 4)
{
/* The only supported version. Use an older gpg
version (i.e. gpg 1.4) to parse v3 packets. */
}
else if (version == 2 || version == 3)
{
if (opt.verbose > 1)
log_info ("packet(%d) with obsolete version %d\n", pkttype, version);
if (list_mode)
es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version);
pk->version = version;
err = gpg_error (GPG_ERR_LEGACY_KEY);
goto leave;
}
else
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
es_fputs (":key packet: [unknown version]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
if (pktlen < 11)
{
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too short]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
else if (pktlen > MAX_KEY_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fputs (":key packet: [too larget]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
timestamp = read_32 (inp);
pktlen -= 4;
expiredate = 0; /* have to get it from the selfsignature */
max_expiredate = 0;
algorithm = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, ":%s key packet:\n"
"\tversion %d, algo %d, created %lu, expires %lu\n",
pkttype == PKT_PUBLIC_KEY ? "public" :
pkttype == PKT_SECRET_KEY ? "secret" :
pkttype == PKT_PUBLIC_SUBKEY ? "public sub" :
pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??",
version, algorithm, timestamp, expiredate);
pk->timestamp = timestamp;
pk->expiredate = expiredate;
pk->max_expiredate = max_expiredate;
pk->hdrbytes = hdrlen;
pk->version = version;
pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY);
pk->pubkey_algo = algorithm;
nskey = pubkey_get_nskey (algorithm);
npkey = pubkey_get_npkey (algorithm);
if (!npkey)
{
if (list_mode)
es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm);
unknown_pubkey_warning (algorithm);
}
if (!npkey)
{
/* Unknown algorithm - put data into an opaque MPI. */
pk->pkey[0] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen), pktlen * 8);
pktlen = 0;
goto leave;
}
else
{
for (i = 0; i < npkey; i++)
{
if ( (algorithm == PUBKEY_ALGO_ECDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_EDDSA && (i == 0))
|| (algorithm == PUBKEY_ALGO_ECDH && (i == 0 || i == 2)))
{
/* Read the OID (i==1) or the KDF params (i==2). */
size_t n;
err = read_size_body (inp, pktlen, &n, pk->pkey+i);
pktlen -= n;
}
else
{
unsigned int n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (list_mode)
{
es_fprintf (listfp, "\tpkey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
if ((algorithm == PUBKEY_ALGO_ECDSA
|| algorithm == PUBKEY_ALGO_EDDSA
|| algorithm == PUBKEY_ALGO_ECDH) && i==0)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
es_fprintf (listfp, " %s (%s)", name?name:"", curve);
xfree (curve);
}
es_putc ('\n', listfp);
}
}
}
if (list_mode)
keyid_from_pk (pk, keyid);
if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)
{
struct seckey_info *ski;
byte temp[16];
size_t snlen = 0;
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!pk->seckey_info)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->algo = iobuf_get_noeof (inp);
pktlen--;
if (ski->algo)
{
ski->is_protected = 1;
ski->s2k.count = 0;
if (ski->algo == 254 || ski->algo == 255)
{
if (pktlen < 3)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->sha1chk = (ski->algo == 254);
ski->algo = iobuf_get_noeof (inp);
pktlen--;
/* Note that a ski->algo > 110 is illegal, but I'm not
erroring on it here as otherwise there would be no
way to delete such a key. */
ski->s2k.mode = iobuf_get_noeof (inp);
pktlen--;
ski->s2k.hash_algo = iobuf_get_noeof (inp);
pktlen--;
/* Check for the special GNU extension. */
if (ski->s2k.mode == 101)
{
for (i = 0; i < 4 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 4 || memcmp (temp, "GNU", 3))
{
if (list_mode)
es_fprintf (listfp, "\tunknown S2K %d\n",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Here we know that it is a GNU extension. What
* follows is the GNU protection mode: All values
* have special meanings and they are mapped to MODE
* with a base of 1000. */
ski->s2k.mode = 1000 + temp[3];
}
/* Read the salt. */
switch (ski->s2k.mode)
{
case 1:
case 3:
for (i = 0; i < 8 && pktlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (i < 8)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
memcpy (ski->s2k.salt, temp, 8);
break;
}
/* Check the mode. */
switch (ski->s2k.mode)
{
case 0:
if (list_mode)
es_fprintf (listfp, "\tsimple S2K");
break;
case 1:
if (list_mode)
es_fprintf (listfp, "\tsalted S2K");
break;
case 3:
if (list_mode)
es_fprintf (listfp, "\titer+salt S2K");
break;
case 1001:
if (list_mode)
es_fprintf (listfp, "\tgnu-dummy S2K");
break;
case 1002:
if (list_mode)
es_fprintf (listfp, "\tgnu-divert-to-card S2K");
break;
default:
if (list_mode)
es_fprintf (listfp, "\tunknown %sS2K %d\n",
ski->s2k.mode < 1000 ? "" : "GNU ",
ski->s2k.mode);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Print some info. */
if (list_mode)
{
es_fprintf (listfp, ", algo: %d,%s hash: %d",
ski->algo,
ski->sha1chk ? " SHA1 protection,"
: " simple checksum,", ski->s2k.hash_algo);
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
{
es_fprintf (listfp, ", salt: ");
es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL);
}
es_putc ('\n', listfp);
}
/* Read remaining protection parameters. */
if (ski->s2k.mode == 3)
{
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->s2k.count = iobuf_get (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
(ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count),
(ulong) ski->s2k.count);
}
else if (ski->s2k.mode == 1002)
{
/* Read the serial number. */
if (pktlen < 1)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
snlen = iobuf_get (inp);
pktlen--;
if (pktlen < snlen || snlen == (size_t)(-1))
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
}
}
else /* Old version; no S2K, so we set mode to 0, hash MD5. */
{
/* Note that a ski->algo > 110 is illegal, but I'm not
erroring on it here as otherwise there would be no
way to delete such a key. */
ski->s2k.mode = 0;
ski->s2k.hash_algo = DIGEST_ALGO_MD5;
if (list_mode)
es_fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n",
ski->algo, ski->s2k.hash_algo);
}
/* It is really ugly that we don't know the size
* of the IV here in cases we are not aware of the algorithm.
* so a
* ski->ivlen = cipher_get_blocksize (ski->algo);
* won't work. The only solution I see is to hardwire it.
* NOTE: if you change the ivlen above 16, don't forget to
* enlarge temp. */
ski->ivlen = openpgp_cipher_blocklen (ski->algo);
log_assert (ski->ivlen <= sizeof (temp));
if (ski->s2k.mode == 1001)
ski->ivlen = 0;
else if (ski->s2k.mode == 1002)
ski->ivlen = snlen < 16 ? snlen : 16;
if (pktlen < ski->ivlen)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
for (i = 0; i < ski->ivlen; i++, pktlen--)
temp[i] = iobuf_get_noeof (inp);
if (list_mode)
{
es_fprintf (listfp,
ski->s2k.mode == 1002 ? "\tserial-number: "
: "\tprotect IV: ");
for (i = 0; i < ski->ivlen; i++)
es_fprintf (listfp, " %02x", temp[i]);
es_putc ('\n', listfp);
}
memcpy (ski->iv, temp, ski->ivlen);
}
/* It does not make sense to read it into secure memory.
* If the user is so careless, not to protect his secret key,
* we can assume, that he operates an open system :=(.
* So we put the key into secure memory when we unprotect it. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
/* Better set some dummy stuff here. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
xstrdup ("dummydata"),
10 * 8);
pktlen = 0;
}
else if (ski->is_protected)
{
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
/* Ugly: The length is encrypted too, so we read all stuff
* up to the end of the packet into the first SKEY
* element. */
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
read_rest (inp, pktlen),
pktlen * 8);
/* Mark that MPI as protected - we need this information for
importing a key. The OPAQUE flag can't be used because
we also store public EdDSA values in opaque MPIs. */
if (pk->pkey[npkey])
gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1);
pktlen = 0;
if (list_mode)
es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey);
}
else
{
/* Not encrypted. */
for (i = npkey; i < nskey; i++)
{
unsigned int n;
if (pktlen < 2) /* At least two bytes for the length. */
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
n = pktlen;
pk->pkey[i] = mpi_read (inp, &n, 0);
pktlen -= n;
if (list_mode)
{
es_fprintf (listfp, "\tskey[%d]: ", i);
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
es_putc ('\n', listfp);
}
if (!pk->pkey[i])
err = gpg_error (GPG_ERR_INV_PACKET);
}
if (err)
goto leave;
if (pktlen < 2)
{
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ski->csum = read_16 (inp);
pktlen -= 2;
if (list_mode)
es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum);
}
}
/* Note that KEYID below has been initialized above in list_mode. */
if (list_mode)
es_fprintf (listfp, "\tkeyid: %08lX%08lX\n",
(ulong) keyid[0], (ulong) keyid[1]);
leave:
iobuf_skip_rest (inp, pktlen, 0);
return err;
}
/* Attribute subpackets have the same format as v4 signature
subpackets. This is not part of OpenPGP, but is done in several
versions of PGP nevertheless. */
int
parse_attribute_subpkts (PKT_user_id * uid)
{
size_t n;
int count = 0;
struct user_attribute *attribs = NULL;
const byte *buffer = uid->attrib_data;
int buflen = uid->attrib_len;
byte type;
xfree (uid->attribs);
while (buflen)
{
n = *buffer++;
buflen--;
if (n == 255) /* 4 byte length header. */
{
if (buflen < 4)
goto too_short;
n = buf32_to_size_t (buffer);
buffer += 4;
buflen -= 4;
}
else if (n >= 192) /* 2 byte special encoded length header. */
{
if (buflen < 2)
goto too_short;
n = ((n - 192) << 8) + *buffer + 192;
buffer++;
buflen--;
}
if (buflen < n)
goto too_short;
if (!n)
{
/* Too short to encode the subpacket type. */
if (opt.verbose)
log_info ("attribute subpacket too short\n");
break;
}
attribs = xrealloc (attribs,
(count + 1) * sizeof (struct user_attribute));
memset (&attribs[count], 0, sizeof (struct user_attribute));
type = *buffer;
buffer++;
buflen--;
n--;
attribs[count].type = type;
attribs[count].data = buffer;
attribs[count].len = n;
buffer += n;
buflen -= n;
count++;
}
uid->attribs = attribs;
uid->numattribs = count;
return count;
too_short:
if (opt.verbose)
log_info ("buffer shorter than attribute subpacket\n");
uid->attribs = attribs;
uid->numattribs = count;
return count;
}
static int
parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap the size of a user ID at 2k: a value absurdly large enough
that there is no sane user ID string (which is printable text
as of RFC2440bis) that won't fit in it, but yet small enough to
avoid allocation problems. A large pktlen may not be
allocatable, and a very large pktlen could actually cause our
allocation to wrap around in xmalloc to a small number. */
if (pktlen > MAX_UID_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":user ID packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id + pktlen);
packet->pkt.user_id->len = pktlen;
packet->pkt.user_id->ref = 1;
p = packet->pkt.user_id->name;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
*p = 0;
if (list_mode)
{
int n = packet->pkt.user_id->len;
es_fprintf (listfp, ":user ID packet: \"");
/* fixme: Hey why don't we replace this with es_write_sanitized?? */
for (p = packet->pkt.user_id->name; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
void
make_attribute_uidname (PKT_user_id * uid, size_t max_namelen)
{
log_assert (max_namelen > 70);
if (uid->numattribs <= 0)
sprintf (uid->name, "[bad attribute packet of size %lu]",
uid->attrib_len);
else if (uid->numattribs > 1)
sprintf (uid->name, "[%d attributes of size %lu]",
uid->numattribs, uid->attrib_len);
else
{
/* Only one attribute, so list it as the "user id" */
if (uid->attribs->type == ATTRIB_IMAGE)
{
u32 len;
byte type;
if (parse_image_header (uid->attribs, &type, &len))
sprintf (uid->name, "[%.20s image of size %lu]",
image_type_to_string (type, 1), (ulong) len);
else
sprintf (uid->name, "[invalid image]");
}
else
sprintf (uid->name, "[unknown attribute of size %lu]",
(ulong) uid->attribs->len);
}
uid->len = strlen (uid->name);
}
static int
parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet)
{
byte *p;
(void) pkttype;
/* We better cap the size of an attribute packet to make DoS not too
easy. 16MB should be more then enough for one attribute packet
(ie. a photo). */
if (pktlen > MAX_ATTR_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":attribute packet: [too large]\n");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
#define EXTRA_UID_NAME_SPACE 71
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id
+ EXTRA_UID_NAME_SPACE);
packet->pkt.user_id->ref = 1;
packet->pkt.user_id->attrib_data = xmalloc (pktlen? pktlen:1);
packet->pkt.user_id->attrib_len = pktlen;
p = packet->pkt.user_id->attrib_data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
/* Now parse out the individual attribute subpackets. This is
somewhat pointless since there is only one currently defined
attribute type (jpeg), but it is correct by the spec. */
parse_attribute_subpkts (packet->pkt.user_id);
make_attribute_uidname (packet->pkt.user_id, EXTRA_UID_NAME_SPACE);
if (list_mode)
{
es_fprintf (listfp, ":attribute packet: %s\n", packet->pkt.user_id->name);
}
return 0;
}
static int
parse_comment (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
{
byte *p;
/* Cap comment packet at a reasonable value to avoid an integer
overflow in the malloc below. Comment packets are actually not
anymore define my OpenPGP and we even stopped to use our
private comment packet. */
if (pktlen > MAX_COMMENT_PACKET_LENGTH)
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
es_fprintf (listfp, ":%scomment packet: [too large]\n",
pkttype == PKT_OLD_COMMENT ? "OpenPGP draft " : "");
iobuf_skip_rest (inp, pktlen, 0);
return GPG_ERR_INV_PACKET;
}
packet->pkt.comment = xmalloc (sizeof *packet->pkt.comment + pktlen - 1);
packet->pkt.comment->len = pktlen;
p = packet->pkt.comment->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
if (list_mode)
{
int n = packet->pkt.comment->len;
es_fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT ?
"OpenPGP draft " : "");
for (p = packet->pkt.comment->data; n; p++, n--)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\"\n");
}
return 0;
}
/* Parse a ring trust packet RFC4880 (5.10).
*
* This parser is special in that the packet is not stored as a packet
* but its content is merged into the previous packet. */
static gpg_error_t
parse_ring_trust (parse_packet_ctx_t ctx, unsigned long pktlen)
{
gpg_error_t err;
iobuf_t inp = ctx->inp;
PKT_ring_trust rt = {0};
int c;
int not_gpg = 0;
if (!pktlen)
{
if (list_mode)
es_fprintf (listfp, ":trust packet: empty\n");
err = 0;
goto leave;
}
c = iobuf_get_noeof (inp);
pktlen--;
rt.trustval = c;
if (pktlen)
{
if (!c)
{
c = iobuf_get_noeof (inp);
/* We require that bit 7 of the sigcache is 0 (easier
* eof handling). */
if (!(c & 0x80))
rt.sigcache = c;
}
else
iobuf_get_noeof (inp); /* Dummy read. */
pktlen--;
}
/* Next is the optional subtype. */
if (pktlen > 3)
{
char tmp[4];
tmp[0] = iobuf_get_noeof (inp);
tmp[1] = iobuf_get_noeof (inp);
tmp[2] = iobuf_get_noeof (inp);
tmp[3] = iobuf_get_noeof (inp);
pktlen -= 4;
if (!memcmp (tmp, "gpg", 3))
rt.subtype = tmp[3];
else
not_gpg = 1;
}
/* If it is a key or uid subtype read the remaining data. */
if ((rt.subtype == RING_TRUST_KEY || rt.subtype == RING_TRUST_UID)
&& pktlen >= 6 )
{
int i;
unsigned int namelen;
rt.keyorg = iobuf_get_noeof (inp);
pktlen--;
rt.keyupdate = read_32 (inp);
pktlen -= 4;
namelen = iobuf_get_noeof (inp);
pktlen--;
if (namelen && pktlen)
{
rt.url = xtrymalloc (namelen + 1);
if (!rt.url)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (i = 0; pktlen && i < namelen; pktlen--, i++)
rt.url[i] = iobuf_get_noeof (inp);
rt.url[i] = 0;
}
}
if (list_mode)
{
if (rt.subtype == RING_TRUST_SIG)
es_fprintf (listfp, ":trust packet: sig flag=%02x sigcache=%02x\n",
rt.trustval, rt.sigcache);
else if (rt.subtype == RING_TRUST_UID || rt.subtype == RING_TRUST_KEY)
{
unsigned char *p;
es_fprintf (listfp, ":trust packet: %s upd=%lu src=%d%s",
(rt.subtype == RING_TRUST_UID? "uid" : "key"),
(unsigned long)rt.keyupdate,
rt.keyorg,
(rt.url? " url=":""));
if (rt.url)
{
for (p = rt.url; *p; p++)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
}
es_putc ('\n', listfp);
}
else if (not_gpg)
es_fprintf (listfp, ":trust packet: not created by gpg\n");
else
es_fprintf (listfp, ":trust packet: subtype=%02x\n",
rt.subtype);
}
/* Now transfer the data to the respective packet. Do not do this
* if SKIP_META is set. */
if (!ctx->last_pkt.pkt.generic || ctx->skip_meta)
;
else if (rt.subtype == RING_TRUST_SIG
&& ctx->last_pkt.pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = ctx->last_pkt.pkt.signature;
if ((rt.sigcache & 1))
{
sig->flags.checked = 1;
sig->flags.valid = !!(rt.sigcache & 2);
}
}
else if (rt.subtype == RING_TRUST_UID
&& (ctx->last_pkt.pkttype == PKT_USER_ID
|| ctx->last_pkt.pkttype == PKT_ATTRIBUTE))
{
PKT_user_id *uid = ctx->last_pkt.pkt.user_id;
uid->keyorg = rt.keyorg;
uid->keyupdate = rt.keyupdate;
uid->updateurl = rt.url;
rt.url = NULL;
}
else if (rt.subtype == RING_TRUST_KEY
&& (ctx->last_pkt.pkttype == PKT_PUBLIC_KEY
|| ctx->last_pkt.pkttype == PKT_SECRET_KEY))
{
PKT_public_key *pk = ctx->last_pkt.pkt.public_key;
pk->keyorg = rt.keyorg;
pk->keyupdate = rt.keyupdate;
pk->updateurl = rt.url;
rt.url = NULL;
}
err = 0;
leave:
xfree (rt.url);
free_packet (NULL, ctx); /* This sets ctx->last_pkt to NULL. */
iobuf_skip_rest (inp, pktlen, 0);
return err;
}
static int
parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
int mode, namelen;
PKT_plaintext *pt;
byte *p;
int c, i;
if (!partial && pktlen < 6)
{
log_error ("packet(%d) too short (%lu)\n", pkttype, (ulong) pktlen);
if (list_mode)
es_fputs (":literal data packet: [too short]\n", listfp);
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
mode = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
namelen = iobuf_get_noeof (inp);
if (pktlen)
pktlen--;
/* Note that namelen will never exceed 255 bytes. */
pt = pkt->pkt.plaintext =
xmalloc (sizeof *pkt->pkt.plaintext + namelen - 1);
pt->new_ctb = new_ctb;
pt->mode = mode;
pt->namelen = namelen;
pt->is_partial = partial;
if (pktlen)
{
for (i = 0; pktlen > 4 && i < namelen; pktlen--, i++)
pt->name[i] = iobuf_get_noeof (inp);
}
else
{
for (i = 0; i < namelen; i++)
if ((c = iobuf_get (inp)) == -1)
break;
else
pt->name[i] = c;
}
+ /* Fill up NAME so that a check with valgrind won't complain about
+ * reading from uninitalized memory. This case may be triggred by
+ * corrupted packets. */
+ for (; i < namelen; i++)
+ pt->name[i] = 0;
+
pt->timestamp = read_32 (inp);
if (pktlen)
pktlen -= 4;
pt->len = pktlen;
pt->buf = inp;
if (list_mode)
{
es_fprintf (listfp, ":literal data packet:\n"
"\tmode %c (%X), created %lu, name=\"",
mode >= ' ' && mode < 'z' ? mode : '?', mode,
(ulong) pt->timestamp);
for (p = pt->name, i = 0; i < namelen; p++, i++)
{
if (*p >= ' ' && *p <= 'z')
es_putc (*p, listfp);
else
es_fprintf (listfp, "\\x%02x", *p);
}
es_fprintf (listfp, "\",\n\traw data: ");
if (partial)
es_fprintf (listfp, "unknown length\n");
else
es_fprintf (listfp, "%lu bytes\n", (ulong) pt->len);
}
leave:
return rc;
}
static int
parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
PKT_compressed *zd;
/* PKTLEN is here 0, but data follows (this should be the last
object in a file or the compress algorithm should know the
length). */
(void) pkttype;
(void) pktlen;
zd = pkt->pkt.compressed = xmalloc (sizeof *pkt->pkt.compressed);
zd->algorithm = iobuf_get_noeof (inp);
zd->len = 0; /* not used */
zd->new_ctb = new_ctb;
zd->buf = inp;
if (list_mode)
es_fprintf (listfp, ":compressed packet: algo=%d\n", zd->algorithm);
return 0;
}
static int
parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb, int partial)
{
int rc = 0;
PKT_encrypted *ed;
unsigned long orig_pktlen = pktlen;
ed = pkt->pkt.encrypted = xmalloc (sizeof *pkt->pkt.encrypted);
/* ed->len is set below. */
ed->extralen = 0; /* Unknown here; only used in build_packet. */
ed->buf = NULL;
ed->new_ctb = new_ctb;
ed->is_partial = partial;
if (pkttype == PKT_ENCRYPTED_MDC)
{
/* Fixme: add some pktlen sanity checks. */
int version;
version = iobuf_get_noeof (inp);
if (orig_pktlen)
pktlen--;
if (version != 1)
{
log_error ("encrypted_mdc packet with unknown version %d\n",
version);
if (list_mode)
es_fputs (":encrypted data packet: [unknown version]\n", listfp);
/*skip_rest(inp, pktlen); should we really do this? */
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
ed->mdc_method = DIGEST_ALGO_SHA1;
}
else
ed->mdc_method = 0;
/* A basic sanity check. We need at least an 8 byte IV plus the 2
detection bytes. Note that we don't known the algorithm and thus
we may only check against the minimum blocksize. */
if (orig_pktlen && pktlen < 10)
{
/* Actually this is blocksize+2. */
log_error ("packet(%d) too short\n", pkttype);
if (list_mode)
es_fputs (":encrypted data packet: [too short]\n", listfp);
rc = GPG_ERR_INV_PACKET;
iobuf_skip_rest (inp, pktlen, partial);
goto leave;
}
/* Store the remaining length of the encrypted data (i.e. without
the MDC version number but with the IV etc.). This value is
required during decryption. */
ed->len = pktlen;
if (list_mode)
{
if (orig_pktlen)
es_fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n",
orig_pktlen);
else
es_fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n");
if (ed->mdc_method)
es_fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method);
}
ed->buf = inp;
leave:
return rc;
}
/* Note, that this code is not anymore used in real life because the
MDC checking is now done right after the decryption in
decrypt_data. */
static int
parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * pkt, int new_ctb)
{
int rc = 0;
PKT_mdc *mdc;
byte *p;
(void) pkttype;
mdc = pkt->pkt.mdc = xmalloc (sizeof *pkt->pkt.mdc);
if (list_mode)
es_fprintf (listfp, ":mdc packet: length=%lu\n", pktlen);
if (!new_ctb || pktlen != 20)
{
log_error ("mdc_packet with invalid encoding\n");
rc = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
p = mdc->hash;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
leave:
return rc;
}
/*
* This packet is internally generated by us (in armor.c) to transfer
* some information to the lower layer. To make sure that this packet
* is really a GPG faked one and not one coming from outside, we
* first check that there is a unique tag in it.
*
* The format of such a control packet is:
* n byte session marker
* 1 byte control type CTRLPKT_xxxxx
* m byte control data
*/
static int
parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
PACKET * packet, int partial)
{
byte *p;
const byte *sesmark;
size_t sesmarklen;
int i;
(void) pkttype;
if (list_mode)
es_fprintf (listfp, ":packet 63: length %lu ", pktlen);
sesmark = get_session_marker (&sesmarklen);
if (pktlen < sesmarklen + 1) /* 1 is for the control bytes */
goto skipit;
for (i = 0; i < sesmarklen; i++, pktlen--)
{
if (sesmark[i] != iobuf_get_noeof (inp))
goto skipit;
}
if (pktlen > 4096)
goto skipit; /* Definitely too large. We skip it to avoid an
overflow in the malloc. */
if (list_mode)
es_fputs ("- gpg control packet", listfp);
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
+ pktlen - 1);
packet->pkt.gpg_control->control = iobuf_get_noeof (inp);
pktlen--;
packet->pkt.gpg_control->datalen = pktlen;
p = packet->pkt.gpg_control->data;
for (; pktlen; pktlen--, p++)
*p = iobuf_get_noeof (inp);
return 0;
skipit:
if (list_mode)
{
int c;
i = 0;
es_fprintf (listfp, "- private (rest length %lu)\n", pktlen);
if (partial)
{
while ((c = iobuf_get (inp)) != -1)
dump_hex_line (c, &i);
}
else
{
for (; pktlen; pktlen--)
{
dump_hex_line ((c = iobuf_get (inp)), &i);
if (c == -1)
break;
}
}
es_putc ('\n', listfp);
}
iobuf_skip_rest (inp, pktlen, 0);
return gpg_error (GPG_ERR_INV_PACKET);
}
/* Create a GPG control packet to be used internally as a placeholder. */
PACKET *
create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen)
{
PACKET *packet;
byte *p;
packet = xmalloc (sizeof *packet);
init_packet (packet);
packet->pkttype = PKT_GPG_CONTROL;
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
+ datalen - 1);
packet->pkt.gpg_control->control = type;
packet->pkt.gpg_control->datalen = datalen;
p = packet->pkt.gpg_control->data;
for (; datalen; datalen--, p++)
*p = *data++;
return packet;
}